Merge pull request #3579 from Ultimaker/feature_show_config_errors

Show configuration errors
This commit is contained in:
jack 2018-03-29 09:22:34 +02:00 committed by GitHub
commit 3108741b54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 135 additions and 164 deletions

View file

@ -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):

View file

@ -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

View file

@ -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":

View file

@ -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)

View file

@ -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,

View file

@ -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:

View file

@ -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))

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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]

View file

@ -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):

View file

@ -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