From 6d3fed8f52e4ef7e98fa9c894ea97a34002ac67e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 26 Mar 2018 15:48:03 +0200 Subject: [PATCH 01/23] Allow getContainer() to return None And in the rest of the locations we must then check if it's None and handle that gracefully. Here we assume that the getContainer message shows a message to the user if necessary. For now we'll just log it. Contributes to issue CURA-5045. --- cura/Machines/ContainerNode.py | 8 +++++--- cura/Machines/MaterialManager.py | 7 ++++++- cura/Machines/Models/QualitySettingsModel.py | 4 ++-- cura/Machines/QualityManager.py | 2 ++ cura/Settings/ContainerManager.py | 12 ++++++----- cura/Settings/CuraStackBuilder.py | 10 ++++++++-- cura/Settings/MachineManager.py | 20 ++++++++++++++----- plugins/3MFReader/ThreeMFWorkspaceReader.py | 6 +++--- .../XmlMaterialProfile/XmlMaterialProfile.py | 6 ++++-- 9 files changed, 52 insertions(+), 23 deletions(-) diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index 6a839fb921..125aaf56ad 100644 --- a/cura/Machines/ContainerNode.py +++ b/cura/Machines/ContainerNode.py @@ -30,9 +30,10 @@ class ContainerNode: def getChildNode(self, child_key: str) -> Optional["ContainerNode"]: return self.children_map.get(child_key) - def getContainer(self) -> "InstanceContainer": + def getContainer(self) -> Optional["InstanceContainer"]: if self.metadata is None: - raise RuntimeError("Cannot get container for a ContainerNode without metadata") + Logger.log("e", "Cannot get container for a ContainerNode without metadata.") + return None if self.container is None: container_id = self.metadata["id"] @@ -40,7 +41,8 @@ class ContainerNode: from UM.Settings.ContainerRegistry import ContainerRegistry container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id) if not container_list: - raise RuntimeError("Failed to lazy-load container [%s], cannot find it" % container_id) + Logger.log("e", "Failed to lazy-load container [{container_id}]. Cannot find it.".format(container_id = container_id)) + return None self.container = container_list[0] return self.container diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 24c7ccb8c0..5d78f589c6 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -423,7 +423,8 @@ class MaterialManager(QObject): return material_group = self.getMaterialGroup(root_material_id) - material_group.root_material_node.getContainer().setName(name) + if material_group: + material_group.root_material_node.getContainer().setName(name) # # Removes the given material. @@ -447,6 +448,8 @@ class MaterialManager(QObject): return None base_container = material_group.root_material_node.getContainer() + if not base_container: + return None # Ensure all settings are saved. self._application.saveSettings() @@ -466,6 +469,8 @@ class MaterialManager(QObject): # Clone all of them. for node in material_group.derived_material_node_list: container_to_copy = node.getContainer() + if not container_to_copy: + continue # Create unique IDs for every clone. new_id = new_base_id if container_to_copy.getMetaDataEntry("definition") != "fdmprinter": diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index b38f6f65c8..33eda2ec0d 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -90,7 +90,7 @@ class QualitySettingsModel(ListModel): quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position)) settings_keys = quality_group.getAllKeys() quality_containers = [] - if quality_node is not None: + if quality_node is not None and quality_node.getContainer() is not None: quality_containers.append(quality_node.getContainer()) # Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch @@ -100,7 +100,7 @@ class QualitySettingsModel(ListModel): quality_changes_node = quality_changes_group.node_for_global else: quality_changes_node = quality_changes_group.nodes_for_extruders.get(str(self._selected_position)) - if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime + if quality_changes_node is not None and quality_changes_node.getContainer() is not None: # it can be None if number of extruders are changed during runtime try: quality_containers.insert(0, quality_changes_node.getContainer()) except RuntimeError: diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 8d972c9192..f6b8e6e36c 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -393,6 +393,8 @@ class QualityManager(QObject): new_name = self._container_registry.uniqueName(quality_changes_name) for node in quality_changes_group.getAllNodes(): container = node.getContainer() + if not container: + continue new_id = self._container_registry.uniqueName(container.getId()) self._container_registry.addContainer(container.duplicate(new_id, new_name)) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 760a288b7b..f0eb2303f2 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -98,9 +98,10 @@ class ContainerManager(QObject): entry_value = root container = material_group.root_material_node.getContainer() - container.setMetaDataEntry(entry_name, entry_value) - if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed. - container.metaDataChanged.emit(container) + if container is not None: + container.setMetaDataEntry(entry_name, entry_value) + if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed. + container.metaDataChanged.emit(container) ## Set a setting property of the specified container. # @@ -377,7 +378,8 @@ class ContainerManager(QObject): # NOTE: We only need to set the root material container because XmlMaterialProfile.setMetaDataEntry() will # take care of the derived containers too container = material_group.root_material_node.getContainer() - container.setMetaDataEntry("GUID", new_guid) + if container is not None: + container.setMetaDataEntry("GUID", new_guid) ## Get the singleton instance for this class. @classmethod @@ -466,5 +468,5 @@ class ContainerManager(QObject): if not path: return - container_list = [n.getContainer() for n in quality_changes_group.getAllNodes()] + container_list = [n.getContainer() for n in quality_changes_group.getAllNodes() if n.getContainer() is not None] self._container_registry.exportQualityProfile(container_list, path, file_type) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index a8234b9de9..3c6ce59269 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -44,6 +44,8 @@ class CuraStackBuilder: global_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.BUILD_PLATE) if global_variant_node: global_variant_container = global_variant_node.getContainer() + if not global_variant_container: + global_variant_container = application.empty_variant_container # get variant container for extruders extruder_variant_container = application.empty_variant_container @@ -51,6 +53,8 @@ class CuraStackBuilder: extruder_variant_name = None if extruder_variant_node: extruder_variant_container = extruder_variant_node.getContainer() + if not extruder_variant_container: + extruder_variant_container = application.empty_variant_container extruder_variant_name = extruder_variant_container.getName() generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName()) @@ -72,7 +76,7 @@ class CuraStackBuilder: # get material container for extruders material_container = application.empty_material_container material_node = material_manager.getDefaultMaterial(new_global_stack, extruder_variant_name) - if material_node: + if material_node and material_node.getContainer(): material_container = material_node.getContainer() # Create ExtruderStacks @@ -107,8 +111,10 @@ class CuraStackBuilder: quality_group = quality_group_dict.get(preferred_quality_type) new_global_stack.quality = quality_group.node_for_global.getContainer() + if not new_global_stack.quality: + new_global_stack.quality = application.empty_quality_container for position, extruder_stack in new_global_stack.extruders.items(): - if position in quality_group.nodes_for_extruders: + if position in quality_group.nodes_for_extruders and quality_group.nodes_for_extruders[position].getContainer(): extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer() else: extruder_stack.quality = application.empty_quality_container diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index c25b58fbcf..6694bba0d6 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -987,6 +987,12 @@ class MachineManager(QObject): self.activeQualityChangesGroupChanged.emit() def _setQualityGroup(self, quality_group, empty_quality_changes = True): + if quality_group.node_for_global.getContainer() is None: + return + for node in quality_group.nodes_for_extruders.values(): + if node.getContainer() is None: + return + self._current_quality_group = quality_group if empty_quality_changes: self._current_quality_changes_group = None @@ -1012,9 +1018,9 @@ class MachineManager(QObject): quality_changes_container = self._empty_quality_changes_container quality_container = self._empty_quality_changes_container - if quality_changes_group.node_for_global: + if quality_changes_group.node_for_global and quality_changes_group.node_for_global.getContainer(): quality_changes_container = quality_changes_group.node_for_global.getContainer() - if quality_group.node_for_global: + if quality_group.node_for_global and quality_group.node_for_global.getContainer(): quality_container = quality_group.node_for_global.getContainer() self._global_container_stack.quality = quality_container @@ -1026,9 +1032,9 @@ class MachineManager(QObject): quality_changes_container = self._empty_quality_changes_container quality_container = self._empty_quality_container - if quality_changes_node: + if quality_changes_node and quality_changes_node.getContainer(): quality_changes_container = quality_changes_node.getContainer() - if quality_node: + if quality_node and quality_node.getContainer(): quality_container = quality_node.getContainer() extruder.quality = quality_container @@ -1040,14 +1046,18 @@ class MachineManager(QObject): self.activeQualityChangesGroupChanged.emit() def _setVariantNode(self, position, container_node): + if container_node.getContainer() is None: + return self._global_container_stack.extruders[position].variant = container_node.getContainer() self.activeVariantChanged.emit() def _setGlobalVariant(self, container_node): self._global_container_stack.variant = container_node.getContainer() + if not self._global_container_stack.variant: + self._global_container_stack.variant = Application.getInstance().empty_variant_container def _setMaterial(self, position, container_node = None): - if container_node: + if container_node and container_node.getContainer(): self._global_container_stack.extruders[position].material = container_node.getContainer() root_material_id = container_node.metadata["base_file"] else: diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 633142187c..6a16c2905e 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -895,7 +895,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): variant_type = VariantType.BUILD_PLATE node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) - if node is not None: + if node is not None and node.getContainer() is not None: global_stack.variant = node.getContainer() for position, extruder_stack in extruder_stack_dict.items(): @@ -911,7 +911,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): variant_type = VariantType.NOZZLE node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) - if node is not None: + if node is not None and node.getContainer() is not None: extruder_stack.variant = node.getContainer() def _applyMaterials(self, global_stack, extruder_stack_dict): @@ -937,7 +937,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): extruder_stack.variant.getName(), machine_material_diameter, root_material_id) - if material_node is not None: + if material_node is not None and material_node.getContainer() is not None: extruder_stack.material = material_node.getContainer() def _applyChangesToMachine(self, global_stack, extruder_stack_dict): diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 54caca855e..6dcd5a5ccc 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -71,12 +71,14 @@ class XmlMaterialProfile(InstanceContainer): # Update the root material container root_material_container = material_group.root_material_node.getContainer() - root_material_container.setMetaDataEntry(key, value, apply_to_all = False) + if root_material_container is not None: + root_material_container.setMetaDataEntry(key, value, apply_to_all = False) # Update all containers derived from it for node in material_group.derived_material_node_list: container = node.getContainer() - container.setMetaDataEntry(key, value, apply_to_all = False) + if container is not None: + container.setMetaDataEntry(key, value, apply_to_all = False) ## Overridden from InstanceContainer, similar to setMetaDataEntry. # without this function the setName would only set the name of the specific nozzle / material / machine combination container From bd4aba2572c75c6575df5a63d06dda6c9563af24 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 09:28:19 +0200 Subject: [PATCH 02/23] Return empty extruder list if no global stack Just about every call of this function (except 2) would break when this returns None. The signature also says it doesn't return None. Let's return an empty list instead. Contributes to issue CURA-5045. --- cura/Settings/ExtruderManager.py | 2 +- cura/Settings/MachineManager.py | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 2b422ec406..c2fe929957 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -366,7 +366,7 @@ class ExtruderManager(QObject): def getActiveExtruderStacks(self) -> List["ExtruderStack"]: global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: - return None + return [] result = [] if global_stack.getId() in self._extruder_trains: diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 6694bba0d6..8695252737 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -509,12 +509,11 @@ class MachineManager(QObject): result = {} active_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - if active_stacks is not None: # If we have extruder stacks - for stack in active_stacks: - material_container = stack.material - if not material_container: - continue - result[stack.getId()] = material_container.getId() + for stack in active_stacks: + material_container = stack.material + if not material_container: + continue + result[stack.getId()] = material_container.getId() return result @@ -961,12 +960,11 @@ class MachineManager(QObject): result = {} active_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - if active_stacks is not None: - for stack in active_stacks: - variant_container = stack.variant - position = stack.getMetaDataEntry("position") - if variant_container and variant_container != self._empty_variant_container: - result[position] = variant_container.getName() + for stack in active_stacks: + variant_container = stack.variant + position = stack.getMetaDataEntry("position") + if variant_container and variant_container != self._empty_variant_container: + result[position] = variant_container.getName() return result From d74c1e0d7a3d8f44b5cf69af5f985fe0c74f9ef4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 09:29:41 +0200 Subject: [PATCH 03/23] Don't crash if getting container changed signals before global stack Just don't get any signals. This function should be called again if the global container changed anyway. Contributes to issue CURA-5045. --- cura/Settings/MachineManager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 8695252737..2f8598d9a1 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -925,6 +925,8 @@ class MachineManager(QObject): self.activeQualityChanged.emit() def _getContainerChangedSignals(self) -> List[Signal]: + if self._global_container_stack is None: + return [] stacks = ExtruderManager.getInstance().getActiveExtruderStacks() stacks.append(self._global_container_stack) return [ s.containersChanged for s in stacks ] From 74a0da14bff202c31e241a0d688659c82bf326d7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 09:37:04 +0200 Subject: [PATCH 04/23] Don't crash if showing this before any machine is added There must be no quality profiles then. Contributes to issue CURA-5045. --- cura/Machines/Models/QualityManagementModel.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index 4d2b551805..93c996b98e 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -38,6 +38,9 @@ class QualityManagementModel(ListModel): Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) global_stack = self._machine_manager.activeMachine + if not global_stack: + self.setItems([]) + return quality_group_dict = self._quality_manager.getQualityGroups(global_stack) quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack) From 75e993eb60f809cb73d7d62317c68e572e6c0af9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 09:39:10 +0200 Subject: [PATCH 05/23] Don't crash when updating material list without global container There is nothing to update then. Contributes to issue CURA-5045. --- cura/Settings/MachineManager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 2f8598d9a1..1b493bf7de 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1115,6 +1115,8 @@ class MachineManager(QObject): self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True) def _updateMaterialWithVariant(self, position: Optional[str]): + if self._global_container_stack is None: + return if position is None: position_list = list(self._global_container_stack.extruders.keys()) else: From f2c8d8756e5a309c67e38bb5f16094c9402cd207 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 09:41:14 +0200 Subject: [PATCH 06/23] Don't emit changed signals if the global stack ID doesn't exist Because nothing will change then. This is equivalent to putting the call to self.__emitChangedSignals() in the if-statement. But I switched the condition of the if-statement around because it looks more like error handling to me. This is the main case. Contributes to issue CURA-5045. --- cura/Settings/MachineManager.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 1b493bf7de..1cda7711d3 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -326,14 +326,16 @@ class MachineManager(QObject): container_registry = ContainerRegistry.getInstance() containers = container_registry.findContainerStacks(id = stack_id) - if containers: - global_stack = containers[0] - ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder - self._global_container_stack = global_stack - Application.getInstance().setGlobalContainerStack(global_stack) - ExtruderManager.getInstance()._globalContainerStackChanged() - self._initMachineState(containers[0]) - self._onGlobalContainerChanged() + if not containers: + return + + global_stack = containers[0] + ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder + self._global_container_stack = global_stack + Application.getInstance().setGlobalContainerStack(global_stack) + ExtruderManager.getInstance()._globalContainerStackChanged() + self._initMachineState(containers[0]) + self._onGlobalContainerChanged() self.__emitChangedSignals() From 1aea5356b622a3562ace458396a6b3e76bd9da02 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 09:43:37 +0200 Subject: [PATCH 07/23] More early out if no global stack This prevents all sorts of crashes on start-up, instead causing no active printer to appear and the add printer wizard to show up. Contributes to issue CURA-5045. --- cura/Settings/MachineManager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 1cda7711d3..f868cfffec 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1014,6 +1014,8 @@ class MachineManager(QObject): self.activeQualityChangesGroupChanged.emit() def _setQualityChangesGroup(self, quality_changes_group): + if self._global_container_stack is None: + return #Can't change that. quality_type = quality_changes_group.quality_type quality_group_dict = self._quality_manager.getQualityGroups(self._global_container_stack) quality_group = quality_group_dict[quality_type] @@ -1082,6 +1084,8 @@ class MachineManager(QObject): ## Update current quality type and machine after setting material def _updateQualityWithMaterial(self, *args): + if self._global_container_stack is None: + return Logger.log("i", "Updating quality/quality_changes due to material change") current_quality_type = None if self._current_quality_group: @@ -1280,6 +1284,8 @@ class MachineManager(QObject): @pyqtSlot(str) def setQualityGroupByQualityType(self, quality_type): + if self._global_container_stack is None: + return # Get all the quality groups for this global stack and filter out by quality_type quality_group_dict = self._quality_manager.getQualityGroups(self._global_container_stack) quality_group = quality_group_dict[quality_type] From 593c80225d0edc727853da6238c52a4b4d942907 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 09:54:47 +0200 Subject: [PATCH 08/23] Show configuration error when failing to find container in ContainerNode Instead of only logging it. Contributes to issue CURA-5045. --- cura/Machines/ContainerNode.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index 125aaf56ad..44e2d6875d 100644 --- a/cura/Machines/ContainerNode.py +++ b/cura/Machines/ContainerNode.py @@ -5,6 +5,7 @@ from typing import Optional from collections import OrderedDict +from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Logger import Logger from UM.Settings.InstanceContainer import InstanceContainer @@ -37,11 +38,12 @@ class ContainerNode: if self.container is None: container_id = self.metadata["id"] - Logger.log("i", "Lazy-loading container [%s]", container_id) from UM.Settings.ContainerRegistry import ContainerRegistry container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id) if not container_list: Logger.log("e", "Failed to lazy-load container [{container_id}]. Cannot find it.".format(container_id = container_id)) + error_message = ConfigurationErrorMessage.getInstance() + error_message.addFaultyContainers(container_id) return None self.container = container_list[0] From 027f4b44bb4543c34280633683ec9197e9279c6e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 13:57:52 +0200 Subject: [PATCH 09/23] Don't crash when finding duplicated materials Only report it to the user. Contributes to issue CURA-5045. --- cura/Machines/MaterialManager.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 5d78f589c6..3a8634f605 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -9,6 +9,7 @@ from typing import Optional, TYPE_CHECKING from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot from UM.Application import Application +from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Logger import Logger from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.SettingFunction import SettingFunction @@ -205,12 +206,10 @@ class MaterialManager(QObject): machine_node.children_map[variant_name] = MaterialNode() variant_node = machine_node.children_map[variant_name] - if root_material_id not in variant_node.material_map: - variant_node.material_map[root_material_id] = MaterialNode(material_metadata) - else: - # Sanity check: make sure we don't have duplicated variant-specific materials for the same machine - raise RuntimeError("Found duplicate variant name [%s] for machine [%s] in material [%s]" % - (variant_name, definition, material_metadata["id"])) + if root_material_id in variant_node.material_map: #We shouldn't have duplicated variant-specific materials for the same machine. + ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) + continue + variant_node.material_map[root_material_id] = MaterialNode(material_metadata) self.materialsUpdated.emit() From 7af941541be39978ca1be916fa81f6a7e16a4b1b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:15:29 +0200 Subject: [PATCH 10/23] Check before adding quality changes node So you don't end up with half a node if we have to abort early. When this happens, show the configuration error message. Contributes to issue CURA-5045. --- cura/Machines/QualityChangesGroup.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cura/Machines/QualityChangesGroup.py b/cura/Machines/QualityChangesGroup.py index ad320a7006..5ff5af3657 100644 --- a/cura/Machines/QualityChangesGroup.py +++ b/cura/Machines/QualityChangesGroup.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.Application import Application +from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from .QualityGroup import QualityGroup @@ -13,14 +14,14 @@ class QualityChangesGroup(QualityGroup): def addNode(self, node: "QualityNode"): extruder_position = node.metadata.get("position") + + if extruder_position is None and self.node_for_global is not None or extruder_position in self.nodes_for_extruders: #We would be overwriting another node. + ConfigurationErrorMessage.getInstance().addFaultyContainers(node.metadata["id"]) + return + if extruder_position is None: #Then we're a global quality changes profile. - if self.node_for_global is not None: - raise RuntimeError("{group} tries to overwrite the existing node_for_global {original_global} with {new_global}".format(group = self, original_global = self.node_for_global, new_global = node)) self.node_for_global = node else: #This is an extruder's quality changes profile. - if extruder_position in self.nodes_for_extruders: - raise RuntimeError("%s tries to overwrite the existing nodes_for_extruders position [%s] %s with %s" % - (self, extruder_position, self.node_for_global, node)) self.nodes_for_extruders[extruder_position] = node def __str__(self) -> str: From 727a164722968aea6b5d06b00d97d67787bc0baf Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:19:36 +0200 Subject: [PATCH 11/23] Only show error when quality is global with material/nozzle Don't crash. Only show an error message that indicates that there is a problem with this one. Contributes to issue CURA-5045. --- cura/Machines/QualityManager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index f6b8e6e36c..9f343c8cf3 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Optional from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot from UM.Application import Application +from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Logger import Logger from UM.Util import parseBool from UM.Settings.InstanceContainer import InstanceContainer @@ -84,7 +85,8 @@ class QualityManager(QObject): # Sanity check: material+variant and is_global_quality cannot be present at the same time if is_global_quality and (root_material_id or variant_name): - raise RuntimeError("Quality profile [%s] contains invalid data: it is a global quality but contains 'material' and 'nozzle' info." % metadata["id"]) + ConfigurationErrorMessage.getInstance().addFaultyContainers(metadata["id"]) + continue if definition_id not in self._machine_variant_material_quality_type_to_quality_dict: self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode() From bc0a3e7fe2b1d3d0dfbb9223f14f6257b3f3bdfa Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:23:54 +0200 Subject: [PATCH 12/23] Show error when finding duplicated variant names Don't add the second variant we encounter then. Contributes to issue CURA-5045. --- cura/Machines/VariantManager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py index 4e033e054e..a1cb638555 100644 --- a/cura/Machines/VariantManager.py +++ b/cura/Machines/VariantManager.py @@ -5,6 +5,7 @@ from enum import Enum from collections import OrderedDict from typing import Optional, TYPE_CHECKING +from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Logger import Logger from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Util import parseBool @@ -78,8 +79,8 @@ class VariantManager: variant_dict = self._machine_to_variant_dict_map[variant_definition][variant_type] if variant_name in variant_dict: # ERROR: duplicated variant name. - raise RuntimeError("Found duplicated variant name [%s], type [%s] for machine [%s]" % - (variant_name, variant_type, variant_definition)) + ConfigurationErrorMessage.getInstance().addFaultyContainers(variant_metadata["id"]) + continue #Then ignore this variant. This now chooses one of the two variants arbitrarily and deletes the other one! No guarantees! variant_dict[variant_name] = ContainerNode(metadata = variant_metadata) From dd3b49e80d1dc762b7c2fbeda18e64f161d29fff Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:26:05 +0200 Subject: [PATCH 13/23] Remove check for if variant container is a variant container The filter on type=variant is right above it in the same function, 20 lines of code earlier. I think this check is superfluous then. Contributes to issue CURA-5045. --- cura/Machines/VariantManager.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py index a1cb638555..bab159007e 100644 --- a/cura/Machines/VariantManager.py +++ b/cura/Machines/VariantManager.py @@ -89,12 +89,8 @@ class VariantManager: if variant_definition not in self._machine_to_buildplate_dict_map: self._machine_to_buildplate_dict_map[variant_definition] = OrderedDict() - variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"]) - if not variant_container: - # ERROR: not variant container. This should never happen - raise RuntimeError("Not variant found [%s], type [%s] for machine [%s]" % - (variant_name, variant_type, variant_definition)) - buildplate_type = variant_container[0].getProperty("machine_buildplate_type", "value") + variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"])[0] + buildplate_type = variant_container.getProperty("machine_buildplate_type", "value") if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]: self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict() From 4f562211260a443f54a71655d0eb0788dc047c16 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:28:17 +0200 Subject: [PATCH 14/23] Remove catch of RuntimeError The error is no longer thrown and the function has its own error message. Contributes to issue CURA-5045. --- cura/Machines/Models/QualitySettingsModel.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index 33eda2ec0d..d8d3bd0179 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -101,12 +101,7 @@ class QualitySettingsModel(ListModel): else: quality_changes_node = quality_changes_group.nodes_for_extruders.get(str(self._selected_position)) if quality_changes_node is not None and quality_changes_node.getContainer() is not None: # it can be None if number of extruders are changed during runtime - try: - quality_containers.insert(0, quality_changes_node.getContainer()) - except RuntimeError: - # FIXME: This is to prevent incomplete update of QualityManager - Logger.logException("d", "Failed to get container for quality changes node %s", quality_changes_node) - return + quality_containers.insert(0, quality_changes_node.getContainer()) settings_keys.update(quality_changes_group.getAllKeys()) # We iterate over all definitions instead of settings in a quality/qualtiy_changes group is because in the GUI, From 544c2c69b4f7fbcf3d82fb333ee468b46fc8d258 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:30:48 +0200 Subject: [PATCH 15/23] Remove unused imports Contributes to issue CURA-5045. --- cura/Settings/CuraStackBuilder.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 3c6ce59269..c4445bff51 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -7,8 +7,6 @@ from UM.Logger import Logger from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry -from UM.Settings.SettingFunction import SettingFunction -from UM.Util import parseBool from cura.Machines.VariantManager import VariantType from .GlobalStack import GlobalStack @@ -34,6 +32,7 @@ class CuraStackBuilder: definitions = registry.findDefinitionContainers(id = definition_id) if not definitions: + Logger.log("w", "Definition {definition} was not found!", definition = definition_id) return None From 013bb04a7dd16436987093d93a2d9a14296e10ef Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:32:13 +0200 Subject: [PATCH 16/23] Show configuration error if finding definition to add wasn't found It must've gotten this ID from somewhere. Contributes to issue CURA-5045. --- cura/Settings/CuraStackBuilder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index c4445bff51..b646abd669 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -3,6 +3,7 @@ from typing import Optional +from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Logger import Logger from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.InstanceContainer import InstanceContainer @@ -32,7 +33,7 @@ class CuraStackBuilder: definitions = registry.findDefinitionContainers(id = definition_id) if not definitions: - + ConfigurationErrorMessage.getInstance().addFaultyContainers(definition_id) Logger.log("w", "Definition {definition} was not found!", definition = definition_id) return None From d6205d5d858bfb3f7806520d7aac942c332656cd Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:38:15 +0200 Subject: [PATCH 17/23] Show configuration error when extruder positions don't match When any extruder position doesn't match when adding a container, don't add any of the extruders. Don't add faulty half-data to the registry! Contributes to issue CURA-5045. --- cura/Settings/CuraStackBuilder.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index b646abd669..640489adb3 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -88,8 +88,8 @@ class CuraStackBuilder: extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0] position_in_extruder_def = extruder_definition.getMetaDataEntry("position") if position_in_extruder_def != position: - raise RuntimeError("Extruder position [%s] defined in extruder definition [%s] is not the same as in machine definition [%s] position [%s]" % - (position_in_extruder_def, extruder_definition_id, definition_id, position)) + ConfigurationErrorMessage.getInstance().addFaultyContainers(extruder_definition_id) + return None #Don't return any container stack then, not the rest of the extruders either. new_extruder_id = registry.uniqueName(extruder_definition_id) new_extruder = cls.createExtruderStack( @@ -104,6 +104,8 @@ class CuraStackBuilder: ) new_extruder.setNextStack(new_global_stack) new_global_stack.addExtruder(new_extruder) + + for new_extruder in new_global_stack.extruders.values(): #Only register the extruders if we're sure that all of them are correct. registry.addContainer(new_extruder) preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type") From d9e8d64e3fd985a442baaf04b5970134cf4d9af0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:46:21 +0200 Subject: [PATCH 18/23] Add typing for _determineGlobalAndExtruderStackFiles Contributes to issue CURA-5045. --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 6a16c2905e..20ec3ca775 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -1,10 +1,11 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from configparser import ConfigParser import zipfile import os import threading +from typing import List, Tuple import xml.etree.ElementTree as ET @@ -160,7 +161,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # # In old versions, extruder stack files have the same suffix as container stack files ".stack.cfg". # - def _determineGlobalAndExtruderStackFiles(self, project_file_name, file_list): + def _determineGlobalAndExtruderStackFiles(self, project_file_name: str, file_list: List[str]) -> Tuple[str, List[str]]: archive = zipfile.ZipFile(project_file_name, "r") global_stack_file_list = [name for name in file_list if name.endswith(self._global_stack_suffix)] From 8b75a0e92b138ed886a1305aeb5f28fe5512f1fd Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:51:34 +0200 Subject: [PATCH 19/23] Better error handling if there is no global stack It reports that the workspace failed to load instead of crashing the entire application then. Contributes to issue CURA-5045. --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 20ec3ca775..78e3532656 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -192,8 +192,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Logger.log("w", "Unknown container stack type '%s' from %s in %s", stack_type, file_name, project_file_name) - if len(global_stack_file_list) != 1: - raise RuntimeError("More than one global stack file found: [%s]" % str(global_stack_file_list)) + if len(global_stack_file_list) > 1: + Logger.log("e", "More than one global stack file found: [{file_list}]".format(file_list = global_stack_file_list)) + #But we can recover by just getting the first global stack file. + if len(global_stack_file_list) == 0: + Logger.log("e", "No global stack file found!") + raise FileNotFoundError("No global stack file found!") return global_stack_file_list[0], extruder_stack_file_list @@ -347,8 +351,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._machine_info.quality_changes_info = None # Load ContainerStack files and ExtruderStack files - global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles( - file_name, cura_file_names) + try: + global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles( + file_name, cura_file_names) + except FileNotFoundError: + return WorkspaceReader.PreReadResult.failed machine_conflict = False # Because there can be cases as follows: # - the global stack exists but some/all of the extruder stacks DON'T exist From e174a015ce214750367c2bafbb3faff0f7d84430 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 14:53:19 +0200 Subject: [PATCH 20/23] Remove unused function It's not used anywhere so please let us not maintain this code. Contributes to issue CURA-5045. --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 22 --------------------- 1 file changed, 22 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 78e3532656..74bc56c3b9 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -555,28 +555,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return WorkspaceReader.PreReadResult.accepted - ## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack. - def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file): - # Get extruder position first - extruder_config = ConfigParser(interpolation = None) - extruder_config.read_string(extruder_file_content) - if not extruder_config.has_option("metadata", "position"): - msg = "Could not find 'metadata/position' in extruder stack file" - Logger.log("e", "Could not find 'metadata/position' in extruder stack file") - raise RuntimeError(msg) - extruder_position = extruder_config.get("metadata", "position") - try: - extruder_stack = global_stack.extruders[extruder_position] - except KeyError: - Logger.log("w", "Could not find the matching extruder stack to override for position %s", extruder_position) - return None - - # Override the given extruder stack - extruder_stack.deserialize(extruder_file_content, file_name = extruder_stack_file) - - # return the new ExtruderStack - return extruder_stack - ## Read the project file # Add all the definitions / materials / quality changes that do not exist yet. Then it loads # all the stacks into the container registry. In some cases it will reuse the container for the global stack. From da0d7a25c3d039b17fc4ee6960d192ca70e55328 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 16:02:43 +0200 Subject: [PATCH 21/23] Remove semicolon What is this? Java??? Contributes to issue CURA-5045. --- cura/CrashHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 7700ee2e71..1108ff0bf0 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -190,7 +190,7 @@ class CrashHandler: self.early_crash_dialog.close() def _showConfigurationFolder(self): - path = Resources.getConfigStoragePath(); + path = Resources.getConfigStoragePath() QDesktopServices.openUrl(QUrl.fromLocalFile( path )) def _showDetailedReport(self): From 2214a5f40a079845d61244b3be40423b45c683a0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Mar 2018 16:30:10 +0200 Subject: [PATCH 22/23] Remove duplicate imports Contributes to issue CURA-5045. --- cura/CrashHandler.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 1108ff0bf0..4c955fe59f 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -18,7 +18,6 @@ from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QUrl from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton from PyQt5.QtGui import QDesktopServices -from UM.Resources import Resources from UM.Application import Application from UM.Logger import Logger from UM.View.GL.OpenGL import OpenGL @@ -131,10 +130,9 @@ class CrashHandler: self._sendCrashReport() os._exit(1) + ## Backup the current resource directories and create clean ones. def _backupAndStartClean(self): - # backup the current cura directories and create clean ones from cura.CuraVersion import CuraVersion - from UM.Resources import Resources # The early crash may happen before those information is set in Resources, so we need to set them here to # make sure that Resources can find the correct place. Resources.ApplicationIdentifier = "cura" From fce703536bad4600ed38f35a99199d4f876a34d6 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Mar 2018 08:43:46 +0200 Subject: [PATCH 23/23] Move factory reset to Uranium This way it can be called from other crash handling routines there too, such as ConfigurationErrorMessage which may be triggered by arbitrary configuration errors in Uranium. Contributes to issue CURA-5045. --- cura/CrashHandler.py | 54 +------------------------------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 4c955fe59f..46544ca0ef 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -132,59 +132,7 @@ class CrashHandler: ## Backup the current resource directories and create clean ones. def _backupAndStartClean(self): - from cura.CuraVersion import CuraVersion - # The early crash may happen before those information is set in Resources, so we need to set them here to - # make sure that Resources can find the correct place. - Resources.ApplicationIdentifier = "cura" - Resources.ApplicationVersion = CuraVersion - config_path = Resources.getConfigStoragePath() - data_path = Resources.getDataStoragePath() - cache_path = Resources.getCacheStoragePath() - - folders_to_backup = [] - folders_to_remove = [] # only cache folder needs to be removed - - folders_to_backup.append(config_path) - if data_path != config_path: - folders_to_backup.append(data_path) - - # Only remove the cache folder if it's not the same as data or config - if cache_path not in (config_path, data_path): - folders_to_remove.append(cache_path) - - for folder in folders_to_remove: - shutil.rmtree(folder, ignore_errors = True) - for folder in folders_to_backup: - base_name = os.path.basename(folder) - root_dir = os.path.dirname(folder) - - import datetime - date_now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") - idx = 0 - file_name = base_name + "_" + date_now - zip_file_path = os.path.join(root_dir, file_name + ".zip") - while os.path.exists(zip_file_path): - idx += 1 - file_name = base_name + "_" + date_now + "_" + idx - zip_file_path = os.path.join(root_dir, file_name + ".zip") - try: - # only create the zip backup when the folder exists - if os.path.exists(folder): - # remove the .zip extension because make_archive() adds it - zip_file_path = zip_file_path[:-4] - shutil.make_archive(zip_file_path, "zip", root_dir = root_dir, base_dir = base_name) - - # remove the folder only when the backup is successful - shutil.rmtree(folder, ignore_errors = True) - - # create an empty folder so Resources will not try to copy the old ones - os.makedirs(folder, 0o0755, exist_ok=True) - - except Exception as e: - Logger.logException("e", "Failed to backup [%s] to file [%s]", folder, zip_file_path) - if not self.has_started: - print("Failed to backup [%s] to file [%s]: %s", folder, zip_file_path, e) - + Resources.factoryReset() self.early_crash_dialog.close() def _showConfigurationFolder(self):