diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 7700ee2e71..46544ca0ef 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,66 +130,13 @@ 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" - 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): - path = Resources.getConfigStoragePath(); + path = Resources.getConfigStoragePath() QDesktopServices.openUrl(QUrl.fromLocalFile( path )) def _showDetailedReport(self): diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index 6a839fb921..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 @@ -30,17 +31,20 @@ 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"] - 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: - 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)) + error_message = ConfigurationErrorMessage.getInstance() + error_message.addFaultyContainers(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..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() @@ -423,7 +422,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 +447,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 +468,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/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) diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index b38f6f65c8..d8d3bd0179 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,13 +100,8 @@ 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 - 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 + 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 + 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, 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: diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 8d972c9192..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() @@ -393,6 +395,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/Machines/VariantManager.py b/cura/Machines/VariantManager.py index 4e033e054e..bab159007e 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) @@ -88,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() 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..640489adb3 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -3,12 +3,11 @@ 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 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 +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 @@ -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 @@ -84,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( @@ -100,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") @@ -107,8 +113,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/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 2a845c2737..e304a14d67 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -331,14 +331,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() @@ -518,12 +520,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 @@ -935,6 +936,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 ] @@ -970,12 +973,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 @@ -996,6 +998,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 @@ -1015,6 +1023,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 # A custom quality can be created based on "not supported". # In that case, do not set quality containers to empty. @@ -1025,12 +1035,11 @@ class MachineManager(QObject): quality_group = quality_group_dict[quality_type] quality_changes_container = self._empty_quality_changes_container - if quality_changes_group.node_for_global: - quality_changes_container = quality_changes_group.node_for_global.getContainer() quality_container = self._empty_quality_container - if quality_group is not None: - if quality_group.node_for_global: - quality_container = quality_group.node_for_global.getContainer() + 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 is not None and 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 self._global_container_stack.qualityChanges = quality_changes_container @@ -1042,10 +1051,10 @@ class MachineManager(QObject): quality_node = quality_group.nodes_for_extruders.get(position) quality_changes_container = self._empty_quality_changes_container - if quality_changes_node: - quality_changes_container = quality_changes_node.getContainer() quality_container = self._empty_quality_container - if quality_node: + if quality_changes_node and quality_changes_node.getContainer(): + quality_changes_container = quality_changes_node.getContainer() + if quality_node and quality_node.getContainer(): quality_container = quality_node.getContainer() extruder.quality = quality_container @@ -1057,14 +1066,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: @@ -1087,6 +1100,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: @@ -1122,6 +1137,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: @@ -1283,6 +1300,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] diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index b3dbad79ac..847f71c6aa 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)] @@ -191,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 @@ -346,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 @@ -549,28 +557,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. @@ -897,7 +883,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(): @@ -913,7 +899,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): @@ -939,7 +925,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