diff --git a/.gitignore b/.gitignore index 7284597fa9..8d9ba4b2d2 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ plugins/cura-siemensnx-plugin plugins/CuraVariSlicePlugin plugins/CuraLiveScriptingPlugin plugins/CuraPrintProfileCreator +plugins/OctoPrintPlugin #Build stuff CMakeCache.txt diff --git a/README.md b/README.md index ba6a986093..90f17a5463 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Cura This is the new, shiny frontend for Cura. [daid/Cura](https://github.com/daid/Cura.git) is the old legacy Cura that everyone knows and loves/hates. -We re-worked the whole GUI code at Ultimaker, because the old code started to become a unmaintainable. +We re-worked the whole GUI code at Ultimaker, because the old code started to become unmaintainable. Logging Issues diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index fc72ea34de..50fa8ce7f6 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -56,11 +56,6 @@ class ConvexHullDecorator(SceneNodeDecorator): if self._node is None: return None - if getattr(self._node, "_non_printing_mesh", False): - # infill_mesh, cutting_mesh and anti_overhang_mesh do not need a convex hull - # node._non_printing_mesh is set in SettingOverrideDecorator - return None - hull = self._compute2DConvexHull() if self._global_stack and self._node: diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index cb0f121d47..81eca67a46 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -59,7 +59,7 @@ class CrashHandler: self.data = dict() self.data["time_stamp"] = time.time() - Logger.log("c", "An uncaught exception has occurred!") + Logger.log("c", "An uncaught error has occurred!") for line in traceback.format_exception(exception_type, value, tb): for part in line.rstrip("\n").split("\n"): Logger.log("c", part) @@ -90,7 +90,7 @@ class CrashHandler: def _messageWidget(self): label = QLabel() - label.setText(catalog.i18nc("@label crash message", """

A fatal exception has occurred. Please send us this Crash Report to fix the problem

+ label.setText(catalog.i18nc("@label crash message", """

A fatal error has occurred. Please send us this Crash Report to fix the problem

Please use the "Send report" button to post a bug report automatically to our servers

""")) @@ -143,7 +143,7 @@ class CrashHandler: def _exceptionInfoWidget(self): group = QGroupBox() - group.setTitle(catalog.i18nc("@title:groupbox", "Exception traceback")) + group.setTitle(catalog.i18nc("@title:groupbox", "Error traceback")) layout = QVBoxLayout() text_area = QTextEdit() diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index a5ae286b1e..c4e1c416dd 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -172,13 +172,14 @@ class CuraApplication(QtApplication): Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances") Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer) - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer) - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer) - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer) - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack) - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack) - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer) + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes") + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant") + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material") + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer, "user") + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train") + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack, "machine") + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") ## Initialise the version upgrade manager with Cura's storage paths. # Needs to be here to prevent circular dependencies. @@ -266,17 +267,17 @@ class CuraApplication(QtApplication): empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() empty_variant_container = copy.deepcopy(empty_container) - empty_variant_container._id = "empty_variant" + empty_variant_container.setMetaDataEntry("id", "empty_variant") empty_variant_container.addMetaDataEntry("type", "variant") ContainerRegistry.getInstance().addContainer(empty_variant_container) empty_material_container = copy.deepcopy(empty_container) - empty_material_container._id = "empty_material" + empty_material_container.setMetaDataEntry("id", "empty_material") empty_material_container.addMetaDataEntry("type", "material") ContainerRegistry.getInstance().addContainer(empty_material_container) empty_quality_container = copy.deepcopy(empty_container) - empty_quality_container._id = "empty_quality" + empty_quality_container.setMetaDataEntry("id", "empty_quality") empty_quality_container.setName("Not Supported") empty_quality_container.addMetaDataEntry("quality_type", "not_supported") empty_quality_container.addMetaDataEntry("type", "quality") @@ -284,12 +285,12 @@ class CuraApplication(QtApplication): ContainerRegistry.getInstance().addContainer(empty_quality_container) empty_quality_changes_container = copy.deepcopy(empty_container) - empty_quality_changes_container._id = "empty_quality_changes" + empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") ContainerRegistry.getInstance().addContainer(empty_quality_changes_container) with ContainerRegistry.getInstance().lockFile(): - ContainerRegistry.getInstance().load() + ContainerRegistry.getInstance().loadAllMetadata() # set the setting version for Preferences preferences = Preferences.getInstance() @@ -312,6 +313,7 @@ class CuraApplication(QtApplication): preferences.addPreference("cura/material_settings", "{}") preferences.addPreference("view/invert_zoom", False) + preferences.addPreference("cura/sidebar_collapse", False) self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement") @@ -469,69 +471,10 @@ class CuraApplication(QtApplication): if not self._started: # Do not do saving during application start return - # Lock file for "more" atomically loading and saving to/from config dir. - with ContainerRegistry.getInstance().lockFile(): - for instance in ContainerRegistry.getInstance().findInstanceContainers(): - if not instance.isDirty(): - continue - - try: - data = instance.serialize() - except NotImplementedError: - continue - except Exception: - Logger.logException("e", "An exception occurred when serializing container %s", instance.getId()) - continue - - mime_type = ContainerRegistry.getMimeTypeForContainer(type(instance)) - file_name = urllib.parse.quote_plus(instance.getId()) + "." + mime_type.preferredSuffix - instance_type = instance.getMetaDataEntry("type") - path = None - if instance_type == "material": - path = Resources.getStoragePath(self.ResourceTypes.MaterialInstanceContainer, file_name) - elif instance_type == "quality" or instance_type == "quality_changes": - path = Resources.getStoragePath(self.ResourceTypes.QualityInstanceContainer, file_name) - elif instance_type == "user": - path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name) - elif instance_type == "variant": - path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name) - elif instance_type == "definition_changes": - path = Resources.getStoragePath(self.ResourceTypes.DefinitionChangesContainer, file_name) - - if path: - instance.setPath(path) - with SaveFile(path, "wt") as f: - f.write(data) - - for stack in ContainerRegistry.getInstance().findContainerStacks(): - self.saveStack(stack) + ContainerRegistry.getInstance().saveDirtyContainers() def saveStack(self, stack): - if not stack.isDirty(): - return - try: - data = stack.serialize() - except NotImplementedError: - return - except Exception: - Logger.logException("e", "An exception occurred when serializing container %s", stack.getId()) - return - - mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack)) - file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix - - path = None - if isinstance(stack, GlobalStack): - path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name) - elif isinstance(stack, ExtruderStack): - path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name) - else: - path = Resources.getStoragePath(Resources.ContainerStacks, file_name) - - stack.setPath(path) - with SaveFile(path, "wt") as f: - f.write(data) - + ContainerRegistry.getInstance().saveContainer(stack) @pyqtSlot(str, result = QUrl) def getDefaultPath(self, key): @@ -733,7 +676,7 @@ class CuraApplication(QtApplication): self.exec_() - def getMachineManager(self, *args): + def getMachineManager(self, *args) -> MachineManager: if self._machine_manager is None: self._machine_manager = MachineManager.createMachineManager() return self._machine_manager diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index f0c29f5b81..ca5433f305 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -238,7 +238,7 @@ class PrintInformation(QObject): pass active_material_id = Application.getInstance().getMachineManager().activeMaterialId - active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id) + active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id = active_material_id) if active_material_containers: self._active_material_container = active_material_containers[0] diff --git a/cura/QualityManager.py b/cura/QualityManager.py index d2162c2449..76a0c86a5f 100644 --- a/cura/QualityManager.py +++ b/cura/QualityManager.py @@ -1,13 +1,12 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. # This collects a lot of quality and quality changes related code which was split between ContainerManager # and the MachineManager and really needs to usable from both. -from typing import List, Optional, Dict, TYPE_CHECKING +from typing import Any, Dict, List, Optional, TYPE_CHECKING from UM.Application import Application from UM.Settings.ContainerRegistry import ContainerRegistry -from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.InstanceContainer import InstanceContainer from cura.Settings.ExtruderManager import ExtruderManager @@ -33,16 +32,16 @@ class QualityManager: # \param quality_name # \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is # specified then the currently selected machine definition is used. - # \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then - # the current set of selected materials is used. + # \param material_containers_metadata If nothing is specified then the + # current set of selected materials is used. # \return the matching quality container \type{InstanceContainer} - def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None) -> Optional[InstanceContainer]: + def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None) -> Optional[InstanceContainer]: criteria = {"type": "quality", "name": quality_name} - result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria) + result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria) # Fall back to using generic materials and qualities if nothing could be found. - if not result and material_containers and len(material_containers) == 1: - basic_materials = self._getBasicMaterials(material_containers[0]) + if not result and material_containers_metadata and len(material_containers_metadata) == 1: + basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0]) result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) return result[0] if result else None @@ -108,18 +107,18 @@ class QualityManager: # \param quality_type \type{str} the name of the quality type to search for. # \param machine_definition (Optional) \type{InstanceContainer} If nothing is # specified then the currently selected machine definition is used. - # \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then - # the current set of selected materials is used. + # \param material_containers_metadata If nothing is specified then the + # current set of selected materials is used. # \return the matching quality container \type{InstanceContainer} - def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None, **kwargs) -> InstanceContainer: + def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs) -> InstanceContainer: criteria = kwargs criteria["type"] = "quality" if quality_type: criteria["quality_type"] = quality_type - result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria) + result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria) # Fall back to using generic materials and qualities if nothing could be found. - if not result and material_containers and len(material_containers) == 1: - basic_materials = self._getBasicMaterials(material_containers[0]) + if not result and material_containers_metadata and len(material_containers_metadata) == 1: + basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0]) if basic_materials: result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) return result[0] if result else None @@ -131,9 +130,9 @@ class QualityManager: # \return \type{List[InstanceContainer]} the list of suitable qualities. def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]: criteria = {"type": "quality"} - result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria) + result = self._getFilteredContainersForStack(machine_definition, [material_container.getMetaData()], **criteria) if not result: - basic_materials = self._getBasicMaterials(material_container) + basic_materials = self._getBasicMaterialMetadatas(material_container.getMetaData()) if basic_materials: result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) @@ -202,22 +201,34 @@ class QualityManager: ## Fetch more basic versions of a material. # # This tries to find a generic or basic version of the given material. - # \param material_container \type{InstanceContainer} the material - # \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found. - def _getBasicMaterials(self, material_container: InstanceContainer): - base_material = material_container.getMetaDataEntry("material") - material_container_definition = material_container.getDefinition() - if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"): - definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId()) - else: + # \param material_container \type{Dict[str, Any]} The metadata of a + # material to find the basic versions of. + # \return \type{List[Dict[str, Any]]} A list of the metadata of basic + # materials, or an empty list if none could be found. + def _getBasicMaterialMetadatas(self, material_container: Dict[str, Any]) -> List[Dict[str, Any]]: + if "definition" not in material_container: definition_id = "fdmprinter" + else: + material_container_definition = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = material_container["definition"]) + if not material_container_definition: + definition_id = "fdmprinter" + else: + material_container_definition = material_container_definition[0] + if "has_machine_quality" not in material_container_definition: + definition_id = "fdmprinter" + else: + definition_id = material_container_definition.get("quality_definition", material_container_definition["id"]) + + base_material = material_container.get("material") if base_material: # There is a basic material specified - criteria = { "type": "material", "name": base_material, "definition": definition_id } - containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria) - containers = [basic_material for basic_material in containers if - basic_material.getMetaDataEntry("variant") == material_container.getMetaDataEntry( - "variant")] + criteria = { + "type": "material", + "name": base_material, + "definition": definition_id, + "variant": material_container.get("variant") + } + containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria) return containers return [] @@ -225,29 +236,25 @@ class QualityManager: def _getFilteredContainers(self, **kwargs): return self._getFilteredContainersForStack(None, None, **kwargs) - def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_containers: List[InstanceContainer] = None, **kwargs): + def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs): # Fill in any default values. if machine_definition is None: machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() quality_definition_id = machine_definition.getMetaDataEntry("quality_definition") if quality_definition_id is not None: - machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0] + machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition_id)[0] - # for convenience - if material_containers is None: - material_containers = [] - - if not material_containers: + if not material_metadata: active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() if active_stacks: - material_containers = [stack.material for stack in active_stacks] + material_metadata = [stack.material.getMetaData() for stack in active_stacks] criteria = kwargs filter_by_material = False machine_definition = self.getParentMachineDefinition(machine_definition) criteria["definition"] = machine_definition.getId() - found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainers(**criteria) + found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria) whole_machine_definition = self.getWholeMachineDefinition(machine_definition) if whole_machine_definition.getMetaDataEntry("has_machine_quality"): definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId()) @@ -261,12 +268,12 @@ class QualityManager: # Stick the material IDs in a set material_ids = set() - for material_instance in material_containers: + for material_instance in material_metadata: if material_instance is not None: # Add the parent material too. - for basic_material in self._getBasicMaterials(material_instance): - material_ids.add(basic_material.getId()) - material_ids.add(material_instance.getId()) + for basic_material in self._getBasicMaterialMetadatas(material_instance): + material_ids.add(basic_material["id"]) + material_ids.add(material_instance["id"]) containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria) result = [] @@ -292,13 +299,13 @@ class QualityManager: # We have a normal (whole) machine defintion quality_definition = machine_definition.getMetaDataEntry("quality_definition") if quality_definition is not None: - parent_machine_definition = container_registry.findDefinitionContainers(id=quality_definition)[0] + parent_machine_definition = container_registry.findDefinitionContainers(id = quality_definition)[0] return self.getParentMachineDefinition(parent_machine_definition) else: return machine_definition else: # This looks like an extruder. Find the rest of the machine. - whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0] + whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0] parent_machine = self.getParentMachineDefinition(whole_machine) if whole_machine is parent_machine: # This extruder already belongs to a 'parent' machine def. @@ -307,7 +314,7 @@ class QualityManager: # Look up the corresponding extruder definition in the parent machine definition. extruder_position = machine_definition.getMetaDataEntry("position") parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position] - return container_registry.findDefinitionContainers(id=parent_extruder_id)[0] + return container_registry.findDefinitionContainers(id = parent_extruder_id)[0] ## Get the whole/global machine definition from an extruder definition. # @@ -321,5 +328,5 @@ class QualityManager: return machine_definition else: container_registry = ContainerRegistry.getInstance() - whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0] + whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0] return whole_machine diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 5857012e0e..bbabdadbde 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -1,10 +1,11 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import copy import os.path import urllib import uuid -from typing import Dict, Union +from typing import Any, Dict, List, Union from PyQt5.QtCore import QObject, QUrl, QVariant from UM.FlameProfiler import pyqtSlot @@ -55,7 +56,8 @@ class ContainerManager(QObject): # \return The ID of the new container, or an empty string if duplication failed. @pyqtSlot(str, result = str) def duplicateContainer(self, container_id): - containers = self._container_registry.findContainers(None, id = container_id) + #TODO: It should be able to duplicate a container of which only the metadata is known. + containers = self._container_registry.findContainers(id = container_id) if not containers: Logger.log("w", "Could duplicate container %s because it was not found.", container_id) return "" @@ -98,14 +100,14 @@ class ContainerManager(QObject): # \return True if successful, False if not. @pyqtSlot(str, str, str, result = bool) def renameContainer(self, container_id, new_id, new_name): - containers = self._container_registry.findContainers(None, id = container_id) + containers = self._container_registry.findContainers(id = container_id) if not containers: Logger.log("w", "Could rename container %s because it was not found.", container_id) return False container = containers[0] # First, remove the container from the registry. This will clean up any files related to the container. - self._container_registry.removeContainer(container) + self._container_registry.removeContainer(container_id) # Ensure we have a unique name for the container new_name = self._container_registry.uniqueName(new_name) @@ -126,9 +128,9 @@ class ContainerManager(QObject): # \return True if the container was successfully removed, False if not. @pyqtSlot(str, result = bool) def removeContainer(self, container_id): - containers = self._container_registry.findContainers(None, id = container_id) + containers = self._container_registry.findContainers(id = container_id) if not containers: - Logger.log("w", "Could remove container %s because it was not found.", container_id) + Logger.log("w", "Could not remove container %s because it was not found.", container_id) return False self._container_registry.removeContainer(containers[0].getId()) @@ -146,14 +148,14 @@ class ContainerManager(QObject): # \return True if successfully merged, False if not. @pyqtSlot(str, result = bool) def mergeContainers(self, merge_into_id, merge_id): - containers = self._container_registry.findContainers(None, id = merge_into_id) + containers = self._container_registry.findContainers(id = merge_into_id) if not containers: Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id) return False merge_into = containers[0] - containers = self._container_registry.findContainers(None, id = merge_id) + containers = self._container_registry.findContainers(id = merge_id) if not containers: Logger.log("w", "Could not merge container %s because it was not found", merge_id) return False @@ -175,13 +177,13 @@ class ContainerManager(QObject): # \return True if successful, False if not. @pyqtSlot(str, result = bool) def clearContainer(self, container_id): - containers = self._container_registry.findContainers(None, id = container_id) - if not containers: - Logger.log("w", "Could clear container %s because it was not found.", container_id) + if self._container_registry.isReadOnly(container_id): + Logger.log("w", "Cannot clear read-only container %s", container_id) return False - if containers[0].isReadOnly(): - Logger.log("w", "Cannot clear read-only container %s", container_id) + containers = self._container_registry.findContainers(id = container_id) + if not containers: + Logger.log("w", "Could clear container %s because it was not found.", container_id) return False containers[0].clear() @@ -190,16 +192,12 @@ class ContainerManager(QObject): @pyqtSlot(str, str, result=str) def getContainerMetaDataEntry(self, container_id, entry_name): - containers = self._container_registry.findContainers(None, id=container_id) - if not containers: + metadatas = self._container_registry.findContainersMetadata(id = container_id) + if not metadatas: Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id) return "" - result = containers[0].getMetaDataEntry(entry_name) - if result is not None: - return str(result) - else: - return "" + return str(metadatas[0].get(entry_name, "")) ## Set a metadata entry of the specified container. # @@ -215,17 +213,17 @@ class ContainerManager(QObject): # \return True if successful, False if not. @pyqtSlot(str, str, str, result = bool) def setContainerMetaDataEntry(self, container_id, entry_name, entry_value): - containers = self._container_registry.findContainers(None, id = container_id) + if self._container_registry.isReadOnly(container_id): + Logger.log("w", "Cannot set metadata of read-only container %s.", container_id) + return False + + containers = self._container_registry.findContainers(id = container_id) #We need the complete container, since we need to know whether the container is read-only or not. if not containers: Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id) return False container = containers[0] - if container.isReadOnly(): - Logger.log("w", "Cannot set metadata of read-only container %s.", container_id) - return False - entries = entry_name.split("/") entry_name = entries.pop() @@ -265,17 +263,17 @@ class ContainerManager(QObject): # \return True if successful, False if not. @pyqtSlot(str, str, str, str, result = bool) def setContainerProperty(self, container_id, setting_key, property_name, property_value): - containers = self._container_registry.findContainers(None, id = container_id) + if self._container_registry.isReadOnly(container_id): + Logger.log("w", "Cannot set properties of read-only container %s.", container_id) + return False + + containers = self._container_registry.findContainers(id = container_id) if not containers: Logger.log("w", "Could not set properties of container %s because it was not found.", container_id) return False container = containers[0] - if container.isReadOnly(): - Logger.log("w", "Cannot set properties of read-only container %s.", container_id) - return False - container.setProperty(setting_key, property_name, property_value) basefile = container.getMetaDataEntry("base_file", container_id) @@ -311,35 +309,30 @@ class ContainerManager(QObject): ## Set the name of the specified container. @pyqtSlot(str, str, result = bool) def setContainerName(self, container_id, new_name): - containers = self._container_registry.findContainers(None, id = container_id) + if self._container_registry.isReadOnly(container_id): + Logger.log("w", "Cannot set name of read-only container %s.", container_id) + return False + + containers = self._container_registry.findContainers(id = container_id) #We need to get the full container, not just metadata, since we need to know whether it's read-only. if not containers: Logger.log("w", "Could not set name of container %s because it was not found.", container_id) return False - container = containers[0] - - if container.isReadOnly(): - Logger.log("w", "Cannot set name of read-only container %s.", container_id) - return False - - container.setName(new_name) + containers[0].setName(new_name) return True ## Find instance containers matching certain criteria. # - # This effectively forwards to ContainerRegistry::findInstanceContainers. + # This effectively forwards to + # ContainerRegistry::findInstanceContainersMetadata. # # \param criteria A dict of key - value pairs to search for. # # \return A list of container IDs that match the given criteria. @pyqtSlot("QVariantMap", result = "QVariantList") def findInstanceContainers(self, criteria): - result = [] - for entry in self._container_registry.findInstanceContainers(**criteria): - result.append(entry.getId()) - - return result + return [entry["id"] for entry in self._container_registry.findInstanceContainersMetadata(**criteria)] @pyqtSlot(str, result = bool) def isContainerUsed(self, container_id): @@ -347,15 +340,17 @@ class ContainerManager(QObject): # check if this is a material container. If so, check if any material with the same base is being used by any # stacks. container_ids_to_check = [container_id] - container_results = self._container_registry.findInstanceContainers(id = container_id, type = "material") + container_results = self._container_registry.findInstanceContainersMetadata(id = container_id, type = "material") if container_results: this_container = container_results[0] - material_base_file = this_container.getMetaDataEntry("base_file", this_container.getId()) + material_base_file = this_container["id"] + if "base_file" in this_container: + material_base_file = this_container["base_file"] # check all material container IDs with the same base - material_containers = self._container_registry.findInstanceContainers(base_file = material_base_file, + material_containers = self._container_registry.findInstanceContainersMetadata(base_file = material_base_file, type = "material") if material_containers: - container_ids_to_check = [container.getId() for container in material_containers] + container_ids_to_check = [container["id"] for container in material_containers] all_stacks = self._container_registry.findContainerStacks() for stack in all_stacks: @@ -423,7 +418,7 @@ class ContainerManager(QObject): else: mime_type = self._container_name_filters[file_type]["mime"] - containers = self._container_registry.findContainers(None, id = container_id) + containers = self._container_registry.findContainers(id = container_id) if not containers: return { "status": "error", "message": "Container not found"} container = containers[0] @@ -627,9 +622,9 @@ class ContainerManager(QObject): elif activate_quality: definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId - containers = self._container_registry.findInstanceContainers(type = "quality", definition = definition_id, quality_type = activate_quality_type) + containers = self._container_registry.findInstanceContainersMetadata(type = "quality", definition = definition_id, quality_type = activate_quality_type) if containers: - self._machine_manager.setActiveQuality(containers[0].getId()) + self._machine_manager.setActiveQuality(containers[0]["id"]) self._machine_manager.activeQualityChanged.emit() return containers_found @@ -664,11 +659,13 @@ class ContainerManager(QObject): container_registry = self._container_registry - containers_to_rename = self._container_registry.findInstanceContainers(type = "quality_changes", name = quality_name) + containers_to_rename = self._container_registry.findInstanceContainersMetadata(type = "quality_changes", name = quality_name) for container in containers_to_rename: - stack_id = container.getMetaDataEntry("extruder", global_stack.getId()) - container_registry.renameContainer(container.getId(), new_name, self._createUniqueId(stack_id, new_name)) + stack_id = global_stack.getId() + if "extruder" in container: + stack_id = container["extruder"] + container_registry.renameContainer(container["id"], new_name, self._createUniqueId(stack_id, new_name)) if not containers_to_rename: Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name) @@ -693,27 +690,29 @@ class ContainerManager(QObject): machine_definition = global_stack.getBottom() active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() - material_containers = [stack.material for stack in active_stacks] + if active_stacks is None: + return "" + material_metadatas = [stack.material.getMetaData() for stack in active_stacks] result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name, QualityManager.getInstance().getParentMachineDefinition(machine_definition), - material_containers) + material_metadatas) return result[0].getName() if result else "" ## Duplicate a quality or quality changes profile specific to a machine type # - # \param quality_name \type{str} the name of the quality or quality changes container to duplicate. - # \param base_name \type{str} the desired name for the new container. - # \param machine_definition \type{DefinitionContainer} - # \param material_instances \type{List[InstanceContainer]} - # \return \type{str} the name of the newly created container. - def _duplicateQualityOrQualityChangesForMachineType(self, quality_name, base_name, machine_definition, material_instances): + # \param quality_name The name of the quality or quality changes container to duplicate. + # \param base_name The desired name for the new container. + # \param machine_definition The machine with the specific machine type. + # \param material_metadatas Metadata of materials + # \return List of duplicated quality profiles. + def _duplicateQualityOrQualityChangesForMachineType(self, quality_name: str, base_name: str, machine_definition: DefinitionContainer, material_metadatas: List[Dict[str, Any]]) -> List[InstanceContainer]: Logger.log("d", "Attempting to duplicate the quality %s", quality_name) if base_name is None: base_name = quality_name # Try to find a Quality with the name. - container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_instances) + container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_metadatas) if container: Logger.log("d", "We found a quality to duplicate.") return self._duplicateQualityForMachineType(container, base_name, machine_definition) @@ -722,7 +721,7 @@ class ContainerManager(QObject): return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition) # Duplicate a quality profile - def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition): + def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition) -> List[InstanceContainer]: if base_name is None: base_name = quality_container.getName() new_name = self._container_registry.uniqueName(base_name) @@ -746,7 +745,7 @@ class ContainerManager(QObject): return new_change_instances # Duplicate a quality changes container - def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition): + def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition) -> List[InstanceContainer]: new_change_instances = [] for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name, machine_definition): @@ -765,27 +764,57 @@ class ContainerManager(QObject): # \return \type{str} the id of the newly created container. @pyqtSlot(str, result = str) def duplicateMaterial(self, material_id: str) -> str: - containers = self._container_registry.findInstanceContainers(id=material_id) - if not containers: + original = self._container_registry.findContainersMetadata(id = material_id) + if not original: Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id) return "" + original = original[0] + + base_container_id = original.get("base_file") + base_container = self._container_registry.findContainers(id = base_container_id) + if not base_container: + Logger.log("d", "Unable to duplicate the material with id {material_id}, because base_file {base_container_id} doesn't exist.".format(material_id = material_id, base_container_id = base_container_id)) + return "" + base_container = base_container[0] + + #We'll copy all containers with the same base. + #This way the correct variant and machine still gets assigned when loading the copy of the material. + containers_to_copy = self._container_registry.findInstanceContainers(base_file = base_container_id) # Ensure all settings are saved. Application.getInstance().saveSettings() # Create a new ID & container to hold the data. - new_id = self._container_registry.uniqueName(material_id) - container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer - duplicated_container = container_type(new_id) + new_containers = [] + new_base_id = self._container_registry.uniqueName(base_container.getId()) + new_base_container = copy.deepcopy(base_container) + new_base_container.getMetaData()["id"] = new_base_id + new_base_container.getMetaData()["base_file"] = new_base_id + new_containers.append(new_base_container) - # Instead of duplicating we load the data from the basefile again. - # This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile - # are also correctly created. - with open(containers[0].getPath(), encoding="utf-8") as f: - duplicated_container.deserialize(f.read()) - duplicated_container.setDirty(True) - self._container_registry.addContainer(duplicated_container) - return self._getMaterialContainerIdForActiveMachine(new_id) + #Clone all of them. + clone_of_original = None #Keeping track of which one is the clone of the original material, since we need to return that. + for container_to_copy in containers_to_copy: + #Create unique IDs for every clone. + current_id = container_to_copy.getId() + new_id = new_base_id + if container_to_copy.getMetaDataEntry("definition") != "fdmprinter": + new_id += "_" + container_to_copy.getMetaDataEntry("definition") + if container_to_copy.getMetaDataEntry("variant"): + variant = self._container_registry.findContainers(id = container_to_copy.getMetaDataEntry("variant"))[0] + new_id += "_" + variant.getName().replace(" ", "_") + if current_id == material_id: + clone_of_original = new_id + + new_container = copy.deepcopy(container_to_copy) + new_container.getMetaData()["id"] = new_id + new_container.getMetaData()["base_file"] = new_base_id + new_containers.append(new_container) + + for container_to_add in new_containers: + container_to_add.setDirty(True) + ContainerRegistry.getInstance().addContainer(container_to_add) + return self._getMaterialContainerIdForActiveMachine(clone_of_original) ## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue # @@ -800,12 +829,12 @@ class ContainerManager(QObject): return "" approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value"))) - containers = self._container_registry.findInstanceContainers(id = "generic_pla*", approximate_diameter = approximate_diameter) + containers = self._container_registry.findInstanceContainersMetadata(id = "generic_pla*", approximate_diameter = approximate_diameter) if not containers: Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.") return "" - base_file = containers[0].getMetaDataEntry("base_file") + base_file = containers[0].get("base_file") containers = self._container_registry.findInstanceContainers(id = base_file) if not containers: Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.") @@ -846,14 +875,14 @@ class ContainerManager(QObject): has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False)) if has_machine_materials or has_variant_materials: if has_variants: - materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId) + materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId) else: - materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId()) + materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId()) if materials: - return materials[0].getId() + return materials[0]["id"] - Logger.log("w", "Unable to find a suitable container based on %s for the current machine .", base_file) + Logger.log("w", "Unable to find a suitable container based on %s for the current machine.", base_file) return "" # do not activate a new material if a container can not be found return base_file @@ -864,25 +893,25 @@ class ContainerManager(QObject): # \return \type{list} a list of names of materials with the same GUID @pyqtSlot(str, result = "QStringList") def getLinkedMaterials(self, material_id: str): - containers = self._container_registry.findInstanceContainers(id=material_id) + containers = self._container_registry.findInstanceContainersMetadata(id = material_id) if not containers: Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id) return [] material_container = containers[0] - material_base_file = material_container.getMetaDataEntry("base_file", "") - material_guid = material_container.getMetaDataEntry("GUID", "") + material_base_file = material_container.get("base_file", "") + material_guid = material_container.get("GUID", "") if not material_guid: Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id) return [] - containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_guid) + containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid) linked_material_names = [] for container in containers: - if container.getId() in [material_id, material_base_file] or container.getMetaDataEntry("base_file") != container.getId(): + if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]: continue - linked_material_names.append(container.getName()) + linked_material_names.append(container["name"]) return linked_material_names ## Unlink a material from all other materials by creating a new GUID @@ -968,14 +997,6 @@ class ContainerManager(QObject): name_filter = "{0} ({1})".format(mime_type.comment, suffix_list) self._container_name_filters[name_filter] = entry - ## Get containers filtered by machine type and material if required. - # - # \param kwargs Initial search criteria that the containers need to match. - # - # \return A list of containers matching the search criteria. - def _getFilteredContainers(self, **kwargs): - return QualityManager.getInstance()._getFilteredContainers(**kwargs) - ## Creates a unique ID for a container by prefixing the name with the stack ID. # # This method creates a unique ID for a container by prefixing it with a specified stack ID. @@ -1015,9 +1036,9 @@ class ContainerManager(QObject): # If the machine specifies qualities should be filtered, ensure we match the current criteria. if not machine_definition.getMetaDataEntry("has_machine_quality"): - quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0]) + quality_changes.setDefinition("fdmprinter") else: - quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition)) + quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition).getId()) from cura.CuraApplication import CuraApplication quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 096406fc3a..07eb357f39 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -44,7 +44,6 @@ class CuraContainerRegistry(ContainerRegistry): # Global stack based on metadata information. @override(ContainerRegistry) def addContainer(self, container): - # Note: Intentional check with type() because we want to ignore subclasses if type(container) == ContainerStack: container = self._convertContainerStack(container) @@ -89,8 +88,8 @@ class CuraContainerRegistry(ContainerRegistry): def _containerExists(self, container_type, container_name): container_class = ContainerStack if container_type == "machine" else InstanceContainer - return self.findContainers(container_class, id = container_name, type = container_type, ignore_case = True) or \ - self.findContainers(container_class, name = container_name, type = container_type) + return self.findContainersMetadata(id = container_name, type = container_type, ignore_case = True) or \ + self.findContainersMetadata(container_type = container_class, name = container_name, type = container_type) ## Exports an profile to a file # @@ -119,7 +118,7 @@ class CuraContainerRegistry(ContainerRegistry): found_containers = [] extruder_positions = [] for instance_id in instance_ids: - containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id) + containers = ContainerRegistry.getInstance().findInstanceContainers(id = instance_id) if containers: found_containers.append(containers[0]) @@ -129,9 +128,9 @@ class CuraContainerRegistry(ContainerRegistry): # Global stack extruder_positions.append(-1) else: - extruder_containers = ContainerRegistry.getInstance().findDefinitionContainers(id=extruder_id) + extruder_containers = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = extruder_id) if extruder_containers: - extruder_positions.append(int(extruder_containers[0].getMetaDataEntry("position", 0))) + extruder_positions.append(int(extruder_containers[0].get("position", 0))) else: extruder_positions.append(0) # Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...) @@ -274,7 +273,6 @@ class CuraContainerRegistry(ContainerRegistry): # # \return None if configuring was successful or an error message if an error occurred. def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]: - profile.setReadOnly(False) profile.setDirty(True) # Ensure the profiles are correctly saved new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile")) @@ -292,7 +290,7 @@ class CuraContainerRegistry(ContainerRegistry): quality_type_criteria = {"quality_type": quality_type} if self._machineHasOwnQualities(): - profile.setDefinition(self._activeQualityDefinition()) + profile.setDefinition(self._activeQualityDefinition().getId()) if self._machineHasOwnMaterials(): active_material_id = self._activeMaterialId() if active_material_id and active_material_id != "empty": # only update if there is an active material @@ -302,7 +300,7 @@ class CuraContainerRegistry(ContainerRegistry): quality_type_criteria["definition"] = profile.getDefinition().getId() else: - profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0]) + profile.setDefinition(fdmprinter) quality_type_criteria["definition"] = "fdmprinter" machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() @@ -349,7 +347,7 @@ class CuraContainerRegistry(ContainerRegistry): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom()) - definition = self.findDefinitionContainers(id=definition_id)[0] + definition = self.findDefinitionContainers(id = definition_id)[0] if definition: return definition @@ -534,13 +532,13 @@ class CuraContainerRegistry(ContainerRegistry): # set after upgrading, because the proper global stack was not yet loaded. This method # makes sure those extruders also get the right stack set. def _connectUpgradedExtruderStacksToMachines(self): - extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack) + extruder_stacks = self.findContainers(container_type = ExtruderStack.ExtruderStack) for extruder_stack in extruder_stacks: if extruder_stack.getNextStack(): # Has the right next stack, so ignore it. continue - machines = ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack.getMetaDataEntry("machine", "")) + machines = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack.getMetaDataEntry("machine", "")) if machines: extruder_stack.setNextStack(machines[0]) else: diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index f0cec3c450..61c28df570 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -14,7 +14,7 @@ from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackErro from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.ContainerRegistry import ContainerRegistry -from UM.Settings.Interfaces import ContainerInterface +from UM.Settings.Interfaces import ContainerInterface, DefinitionContainerInterface from . import Exceptions @@ -246,7 +246,7 @@ class CuraContainerStack(ContainerStack): ## Set the definition container. # # \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes". - def setDefinition(self, new_definition: DefinitionContainer) -> None: + def setDefinition(self, new_definition: DefinitionContainerInterface) -> None: self.replaceContainer(_ContainerIndexes.Definition, new_definition) ## Set the definition container by an ID. @@ -487,11 +487,17 @@ class CuraContainerStack(ContainerStack): search_criteria.pop("name", None) materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) - if materials: - return materials[0] + if not materials: + Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id) + return None + + for material in materials: + # Prefer a read-only material + if ContainerRegistry.getInstance().isReadOnly(material.getId()): + return material + + return materials[0] - Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id) - return None ## Find the quality that should be used as "default" quality. # @@ -502,7 +508,7 @@ class CuraContainerStack(ContainerStack): def findDefaultQuality(self) -> Optional[ContainerInterface]: definition = self._getMachineDefinition() registry = ContainerRegistry.getInstance() - material_container = self.material if self.material != self._empty_instance_container else None + material_container = self.material if self.material.getId() not in (self._empty_material.getId(), self._empty_instance_container.getId()) else None search_criteria = {"type": "quality"} @@ -546,7 +552,7 @@ class CuraContainerStack(ContainerStack): material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"} if definition.getMetaDataEntry("has_machine_quality"): if self.material != self._empty_instance_container: - material_search_criteria["definition"] = material_container.getDefinition().id + material_search_criteria["definition"] = material_container.getMetaDataEntry("definition") if definition.getMetaDataEntry("has_variants"): material_search_criteria["variant"] = material_container.getMetaDataEntry("variant") @@ -557,10 +563,10 @@ class CuraContainerStack(ContainerStack): material_search_criteria["variant"] = self.variant.id else: material_search_criteria["definition"] = "fdmprinter" - material_containers = registry.findInstanceContainers(**material_search_criteria) + material_containers = registry.findInstanceContainersMetadata(**material_search_criteria) # Try all materials to see if there is a quality profile available. for material_container in material_containers: - search_criteria["material"] = material_container.getId() + search_criteria["material"] = material_container["id"] containers = registry.findInstanceContainers(**search_criteria) if containers: diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index a661237722..8d2d66ea87 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -3,7 +3,7 @@ from UM.Logger import Logger -from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry @@ -34,7 +34,7 @@ class CuraStackBuilder: # Make sure the new name does not collide with any definition or (quality) profile # createUniqueName() only looks at other stacks, but not at definitions or quality profiles # Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true - if registry.findContainers(id = generated_name): + if registry.findContainersMetadata(id = generated_name): generated_name = registry.uniqueName(generated_name) new_global_stack = cls.createGlobalStack( @@ -55,12 +55,12 @@ class CuraStackBuilder: new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id) new_extruder = cls.createExtruderStack( new_extruder_id, - definition=extruder_definition, - machine_definition=machine_definition, - quality="default", - material="default", - variant="default", - next_stack=new_global_stack + definition = extruder_definition, + machine_definition_id = machine_definition.getId(), + quality = "default", + material = "default", + variant = "default", + next_stack = new_global_stack ) new_global_stack.addExtruder(new_extruder) else: @@ -74,7 +74,7 @@ class CuraStackBuilder: new_extruder = cls.createExtruderStack( new_extruder_id, definition = extruder_definition, - machine_definition = machine_definition, + machine_definition_id = machine_definition.getId(), quality = "default", material = "default", variant = "default", @@ -88,12 +88,13 @@ class CuraStackBuilder: # # \param new_stack_id The ID of the new stack. # \param definition The definition to base the new stack on. - # \param machine_definition The machine definition to use for the user container. + # \param machine_definition_id The ID of the machine definition to use for + # the user container. # \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm" # # \return A new Global stack instance with the specified parameters. @classmethod - def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainer, machine_definition: DefinitionContainer, **kwargs) -> ExtruderStack: + def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, machine_definition_id: str, **kwargs) -> ExtruderStack: stack = ExtruderStack(new_stack_id) stack.setName(definition.getName()) stack.setDefinition(definition) @@ -108,7 +109,7 @@ class CuraStackBuilder: user_container.addMetaDataEntry("extruder", new_stack_id) from cura.CuraApplication import CuraApplication user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - user_container.setDefinition(machine_definition) + user_container.setDefinition(machine_definition_id) stack.setUserChanges(user_container) @@ -148,7 +149,7 @@ class CuraStackBuilder: # # \return A new Global stack instance with the specified parameters. @classmethod - def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainer, **kwargs) -> GlobalStack: + def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, **kwargs) -> GlobalStack: stack = GlobalStack(new_stack_id) stack.setDefinition(definition) @@ -157,7 +158,7 @@ class CuraStackBuilder: user_container.addMetaDataEntry("machine", new_stack_id) from cura.CuraApplication import CuraApplication user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - user_container.setDefinition(definition) + user_container.setDefinition(definition.getId()) stack.setUserChanges(user_container) @@ -193,8 +194,7 @@ class CuraStackBuilder: unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name) definition_changes_container = InstanceContainer(unique_container_name) - definition = container_stack.getBottom() - definition_changes_container.setDefinition(definition) + definition_changes_container.setDefinition(container_stack.getBottom().getId()) definition_changes_container.addMetaDataEntry("type", "definition_changes") definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 9e365b6b1a..5139b9885d 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -130,6 +130,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self._active_machine_extruders = [] extruder_manager = Application.getInstance().getExtruderManager() for extruder in extruder_manager.getExtruderStacks(): + if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML. + continue extruder.containersChanged.connect(self._onExtruderStackContainersChanged) self._active_machine_extruders.append(extruder) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 2f27c4ab72..dae1c103b4 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -43,15 +43,11 @@ class GlobalStack(CuraContainerStack): def getLoadingPriority(cls) -> int: return 2 - def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]: - configuration_type = None - try: - parser = self._readAndValidateSerialized(serialized) - configuration_type = parser["metadata"].get("type") - if configuration_type == "machine": - configuration_type = "machine_stack" - except Exception as e: - Logger.log("e", "Could not get configuration type: %s", e) + @classmethod + def getConfigurationTypeFromSerialized(cls, serialized: str) -> Optional[str]: + configuration_type = super().getConfigurationTypeFromSerialized(serialized) + if configuration_type == "machine": + return "machine_stack" return configuration_type ## Add an extruder to the list of extruders of this stack. @@ -67,7 +63,7 @@ class GlobalStack(CuraContainerStack): return if any(item.getId() == extruder.id for item in self._extruders.values()): - Logger.log("w", "Extruder [%s] has already been added to this stack [%s]", extruder.id, self._id) + Logger.log("w", "Extruder [%s] has already been added to this stack [%s]", extruder.id, self.getId()) return self._extruders[position] = extruder diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 7920e89232..a54eb987ae 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -32,6 +32,7 @@ from .CuraStackBuilder import CuraStackBuilder from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") +from cura.Settings.ProfilesModel import ProfilesModel from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: @@ -60,9 +61,11 @@ class MachineManager(QObject): self._instance_container_timer = QTimer() self._instance_container_timer.setInterval(250) self._instance_container_timer.setSingleShot(True) - self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged) + self._instance_container_timer.timeout.connect(self.__emitChangedSignals) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) + Application.getInstance().getContainerRegistry().containerLoadComplete.connect(self._onInstanceContainersChanged) + self._connected_to_profiles_model = False ## When the global container is changed, active material probably needs to be updated. self.globalContainerChanged.connect(self.activeMaterialChanged) @@ -104,7 +107,7 @@ class MachineManager(QObject): # There might already be some output devices by the time the signal is connected self._onOutputDevicesChanged() - if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacks(id = active_machine_id): + if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id): # An active machine was saved, so restore it. self.setActiveMachine(active_machine_id) # Make sure _active_container_stack is properly initiated @@ -117,6 +120,10 @@ class MachineManager(QObject): "The selected material is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Incompatible Material")) + containers = ContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) + if containers: + containers[0].nameChanged.connect(self._onMaterialNameChanged) + globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value) activeMaterialChanged = pyqtSignal() activeVariantChanged = pyqtSignal() @@ -166,7 +173,7 @@ class MachineManager(QObject): if not self._global_container_stack: return - containers = ContainerRegistry.getInstance().findInstanceContainers(type="variant", definition=self._global_container_stack.getBottom().getId(), name=hotend_id) + containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "variant", definition = self._global_container_stack.definition.getId(), name = hotend_id) if containers: # New material ID is known extruder_manager = ExtruderManager.getInstance() machine_id = self.activeMachineId @@ -178,10 +185,10 @@ class MachineManager(QObject): break if matching_extruder and matching_extruder.variant.getName() != hotend_id: # Save the material that needs to be changed. Multiple changes will be handled by the callback. - self._auto_hotends_changed[str(index)] = containers[0].getId() + self._auto_hotends_changed[str(index)] = containers[0]["id"] self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback) else: - Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id)) + Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.definition.getId(), hotend_id)) def _onMaterialIdChanged(self, index: Union[str, int], material_id: str): if not self._global_container_stack: @@ -191,7 +198,7 @@ class MachineManager(QObject): if self._global_container_stack.getMetaDataEntry("has_machine_materials", False): definition_id = self.activeQualityDefinitionId extruder_manager = ExtruderManager.getInstance() - containers = ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id) + containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "material", definition = definition_id, GUID = material_id) if containers: # New material ID is known extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId)) matching_extruder = None @@ -202,15 +209,15 @@ class MachineManager(QObject): if matching_extruder and matching_extruder.material.getMetaDataEntry("GUID") != material_id: # Save the material that needs to be changed. Multiple changes will be handled by the callback. - if self._global_container_stack.getBottom().getMetaDataEntry("has_variants") and matching_extruder.variant: - variant_id = self.getQualityVariantId(self._global_container_stack.getBottom(), matching_extruder.variant) + if self._global_container_stack.definition.getMetaDataEntry("has_variants") and matching_extruder.variant: + variant_id = self.getQualityVariantId(self._global_container_stack.definition, matching_extruder.variant) for container in containers: - if container.getMetaDataEntry("variant") == variant_id: - self._auto_materials_changed[str(index)] = container.getId() + if container.get("variant") == variant_id: + self._auto_materials_changed[str(index)] = container["id"] break else: # Just use the first result we found. - self._auto_materials_changed[str(index)] = containers[0].getId() + self._auto_materials_changed[str(index)] = containers[0]["id"] self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback) else: Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id)) @@ -328,14 +335,24 @@ class MachineManager(QObject): # on _active_container_stack. If it changes, then the properties change. self.activeQualityChanged.emit() - def __onInstanceContainersChanged(self): + def __emitChangedSignals(self): self.activeQualityChanged.emit() self.activeVariantChanged.emit() self.activeMaterialChanged.emit() self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine self._error_check_timer.start() + def _onProfilesModelChanged(self, *args): + self.__emitChangedSignals() + def _onInstanceContainersChanged(self, container): + # This should not trigger the ProfilesModel to be created, or there will be an infinite recursion + if not self._connected_to_profiles_model and ProfilesModel.hasInstance(): + # This triggers updating the qualityModel in SidebarSimple whenever ProfilesModel is updated + Logger.log("d", "Connecting profiles model...") + ProfilesModel.getInstance().itemsChanged.connect(self._onProfilesModelChanged) + self._connected_to_profiles_model = True + self._instance_container_timer.start() def _onPropertyChanged(self, key: str, property_name: str): @@ -355,7 +372,7 @@ class MachineManager(QObject): if containers: Application.getInstance().setGlobalContainerStack(containers[0]) - self.__onInstanceContainersChanged() + self.__emitChangedSignals() @pyqtSlot(str, str) def addMachine(self, name: str, definition_id: str) -> None: @@ -703,10 +720,7 @@ class MachineManager(QObject): ## Check if a container is read_only @pyqtSlot(str, result = bool) def isReadOnly(self, container_id: str) -> bool: - containers = ContainerRegistry.getInstance().findInstanceContainers(id = container_id) - if not containers or not self._active_container_stack: - return True - return containers[0].isReadOnly() + return ContainerRegistry.getInstance().isReadOnly(container_id) ## Copy the value of the setting of the current extruder to all other extruders as well as the global container. @pyqtSlot(str) @@ -774,7 +788,7 @@ class MachineManager(QObject): if quality_type: candidate_quality = quality_manager.findQualityByQualityType(quality_type, quality_manager.getWholeMachineDefinition(global_stack.definition), - [material_container]) + [material_container.getMetaData()]) if not candidate_quality or candidate_quality.getId() == self._empty_quality_changes_container: Logger.log("d", "Attempting to find fallback quality") @@ -822,7 +836,7 @@ class MachineManager(QObject): preferred_material_name = None if old_material: preferred_material_name = old_material.getName() - preferred_material_id = self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id + preferred_material_id = self._updateMaterialContainer(self._global_container_stack.definition, self._global_container_stack, containers[0], preferred_material_name).id self.setActiveMaterial(preferred_material_id) else: Logger.log("w", "While trying to set the active variant, no variant was found to replace.") @@ -834,15 +848,15 @@ class MachineManager(QObject): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self.blurSettings.emit() - containers = ContainerRegistry.getInstance().findInstanceContainers(id = quality_id) + containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = quality_id) if not containers or not self._global_container_stack: return # Quality profile come in two flavours: type=quality and type=quality_changes # If we found a quality_changes profile then look up its parent quality profile. - container_type = containers[0].getMetaDataEntry("type") - quality_name = containers[0].getName() - quality_type = containers[0].getMetaDataEntry("quality_type") + container_type = containers[0].get("type") + quality_name = containers[0]["name"] + quality_type = containers[0].get("quality_type") # Get quality container and optionally the quality_changes container. if container_type == "quality": @@ -850,7 +864,7 @@ class MachineManager(QObject): elif container_type == "quality_changes": new_quality_settings_list = self._determineQualityAndQualityChangesForQualityChanges(quality_name) else: - Logger.log("e", "Tried to set quality to a container that is not of the right type") + Logger.log("e", "Tried to set quality to a container that is not of the right type: {container_id}".format(container_id = containers[0]["id"])) return # Check if it was at all possible to find new settings @@ -939,18 +953,18 @@ class MachineManager(QObject): if not global_container_stack: return [] - global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) + global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition) extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() # find qualities for extruders for extruder_stack in extruder_stacks: - material = extruder_stack.material + material_metadata = extruder_stack.material.getMetaData() # TODO: fix this if self._new_material_container and extruder_stack.getId() == self._active_container_stack.getId(): - material = self._new_material_container + material_metadata = self._new_material_container.getMetaData() - quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) + quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material_metadata]) if not quality: # No quality profile is found for this quality type. @@ -1000,12 +1014,6 @@ class MachineManager(QObject): Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name) return None - material = global_container_stack.material - - # find a quality type that matches both machine and materials - if self._new_material_container and self._active_container_stack.getId() == global_container_stack.getId(): - material = self._new_material_container - # For the global stack, find a quality which matches the quality_type in # the quality changes profile and also satisfies any material constraints. quality_type = global_quality_changes.getMetaDataEntry("quality_type") @@ -1025,12 +1033,12 @@ class MachineManager(QObject): if not quality_changes: quality_changes = self._empty_quality_changes_container - material = extruder_stack.material + material_metadata = extruder_stack.material.getMetaData() if self._new_material_container and self._active_container_stack.getId() == extruder_stack.getId(): - material = self._new_material_container + material_metadata = self._new_material_container.getMetaData() - quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) + quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material_metadata]) if not quality: # No quality profile found for this quality type. @@ -1043,7 +1051,7 @@ class MachineManager(QObject): }) # append the global quality changes - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True") + global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, global_quality = "True") # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482 if not global_quality and len(extruder_stacks) == 1: @@ -1097,18 +1105,14 @@ class MachineManager(QObject): @pyqtProperty(str, notify = globalContainerChanged) def activeDefinitionId(self) -> str: if self._global_container_stack: - definition = self._global_container_stack.getBottom() - if definition: - return definition.id + return self._global_container_stack.definition.id return "" @pyqtProperty(str, notify=globalContainerChanged) def activeDefinitionName(self) -> str: if self._global_container_stack: - definition = self._global_container_stack.getBottom() - if definition: - return definition.getName() + return self._global_container_stack.definition.getName() return "" @@ -1118,7 +1122,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = globalContainerChanged) def activeQualityDefinitionId(self) -> str: if self._global_container_stack: - return self.getQualityDefinitionId(self._global_container_stack.getBottom()) + return self.getQualityDefinitionId(self._global_container_stack.definition) return "" ## Get the Definition ID to use to select quality profiles for machines of the specified definition @@ -1136,7 +1140,7 @@ class MachineManager(QObject): if self._active_container_stack: variant = self._active_container_stack.variant if variant: - return self.getQualityVariantId(self._global_container_stack.getBottom(), variant) + return self.getQualityVariantId(self._global_container_stack.definition, variant) return "" ## Get the Variant ID to use to select quality profiles for variants of the specified definitions @@ -1160,7 +1164,7 @@ class MachineManager(QObject): def activeDefinitionVariantsName(self) -> str: fallback_title = catalog.i18nc("@label", "Nozzle") if self._global_container_stack: - return self._global_container_stack.getBottom().getMetaDataEntry("variants_name", fallback_title) + return self._global_container_stack.definition.getMetaDataEntry("variants_name", fallback_title) return fallback_title @@ -1169,7 +1173,7 @@ class MachineManager(QObject): container_registry = ContainerRegistry.getInstance() machine_stack = container_registry.findContainerStacks(id = machine_id) if machine_stack: - new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].getBottom().getName()) + new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].definition.getName()) machine_stack[0].setName(new_name) self.globalContainerChanged.emit() @@ -1180,15 +1184,15 @@ class MachineManager(QObject): # activate a new machine before removing a machine because this is safer if activate_new_machine: - machine_stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine") - other_machine_stacks = [s for s in machine_stacks if s.getId() != machine_id] + machine_stacks = ContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine") + other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id] if other_machine_stacks: - self.setActiveMachine(other_machine_stacks[0].getId()) + self.setActiveMachine(other_machine_stacks[0]["id"]) ExtruderManager.getInstance().removeMachineExtruders(machine_id) - containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id) + containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) for container in containers: - ContainerRegistry.getInstance().removeContainer(container.getId()) + ContainerRegistry.getInstance().removeContainer(container["id"]) ContainerRegistry.getInstance().removeContainer(machine_id) @pyqtProperty(bool, notify = globalContainerChanged) @@ -1225,9 +1229,9 @@ class MachineManager(QObject): # \returns DefinitionID (string) if found, None otherwise @pyqtSlot(str, result = str) def getDefinitionByMachineId(self, machine_id: str) -> str: - containers = ContainerRegistry.getInstance().findContainerStacks(id=machine_id) + containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) if containers: - return containers[0].getBottom().getId() + return containers[0].definition.getId() @staticmethod def createMachineManager(): diff --git a/cura/Settings/MaterialsModel.py b/cura/Settings/MaterialsModel.py index bab8929765..c4b0329336 100644 --- a/cura/Settings/MaterialsModel.py +++ b/cura/Settings/MaterialsModel.py @@ -1,6 +1,7 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import Any, List from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials. from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class. @@ -18,8 +19,19 @@ class MaterialsModel(InstanceContainersModel): # \param container The container whose metadata was changed. def _onContainerMetaDataChanged(self, container): if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed. - self._update() + self._container_change_timer.start() def _onContainerChanged(self, container): if container.getMetaDataEntry("type", "") == "material": super()._onContainerChanged(container) + + ## Group brand together + def _sortKey(self, item) -> List[Any]: + result = [] + result.append(item["metadata"]["brand"]) + result.append(item["metadata"]["material"]) + result.append(item["metadata"]["name"]) + result.append(item["metadata"]["color_name"]) + result.append(item["metadata"]["id"]) + result.extend(super()._sortKey(item)) + return result diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index 2b75cf1bd2..3b43e2740a 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -49,6 +49,10 @@ class ProfilesModel(InstanceContainersModel): ProfilesModel.__instance = cls() return ProfilesModel.__instance + @classmethod + def hasInstance(cls) -> bool: + return ProfilesModel.__instance is not None + __instance = None # type: "ProfilesModel" ## Fetch the list of containers to display. @@ -57,8 +61,7 @@ class ProfilesModel(InstanceContainersModel): def _fetchInstanceContainers(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: - return [] - + return {}, {} global_stack_definition = global_container_stack.definition # Get the list of extruders and place the selected extruder at the front of the list. @@ -88,11 +91,10 @@ class ProfilesModel(InstanceContainersModel): not_supported_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] result.append(not_supported_container) - return result + return {item.getId():item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet. ## Re-computes the items in this model, and adds the layer height role. def _recomputeItems(self): - # Some globals that we can re-use. global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: @@ -112,8 +114,12 @@ class ProfilesModel(InstanceContainersModel): # active machine and material, and later yield the right ones. tmp_all_quality_items = OrderedDict() for item in super()._recomputeItems(): - profile = container_registry.findContainers(id=item["id"]) - quality_type = profile[0].getMetaDataEntry("quality_type") if profile else "" + + profiles = container_registry.findContainersMetadata(id = item["id"]) + if not profiles or "quality_type" not in profiles[0]: + quality_type = "" + else: + quality_type = profiles[0]["quality_type"] if quality_type not in tmp_all_quality_items: tmp_all_quality_items[quality_type] = {"suitable_container": None, "all_containers": []} diff --git a/cura/Settings/QualityAndUserProfilesModel.py b/cura/Settings/QualityAndUserProfilesModel.py index 2e181c6031..bc81df976b 100644 --- a/cura/Settings/QualityAndUserProfilesModel.py +++ b/cura/Settings/QualityAndUserProfilesModel.py @@ -18,7 +18,7 @@ class QualityAndUserProfilesModel(ProfilesModel): def _fetchInstanceContainers(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if not global_container_stack: - return [] + return {}, {} # Fetch the list of quality changes. quality_manager = QualityManager.getInstance() @@ -35,10 +35,12 @@ class QualityAndUserProfilesModel(ProfilesModel): # Filter the quality_change by the list of available quality_types quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) - filtered_quality_changes = [qc for qc in quality_changes_list if + filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") is not None and (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or - qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] + qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())} - return quality_list + filtered_quality_changes + result = filtered_quality_changes + result.update({q.getId():q for q in quality_list}) + return result, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet. \ No newline at end of file diff --git a/cura/Settings/QualitySettingsModel.py b/cura/Settings/QualitySettingsModel.py index d0379dc510..243fd146dc 100644 --- a/cura/Settings/QualitySettingsModel.py +++ b/cura/Settings/QualitySettingsModel.py @@ -92,7 +92,6 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): items = [] - settings = collections.OrderedDict() definition_container = Application.getInstance().getGlobalContainerStack().getBottom() containers = self._container_registry.findInstanceContainers(id = self._quality_id) diff --git a/cura/Settings/UserProfilesModel.py b/cura/Settings/UserProfilesModel.py index 5ae9055759..e093c6c132 100644 --- a/cura/Settings/UserProfilesModel.py +++ b/cura/Settings/UserProfilesModel.py @@ -1,7 +1,7 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.Application import Application +from UM.Application import Application from cura.QualityManager import QualityManager from cura.Settings.ProfilesModel import ProfilesModel from cura.Settings.ExtruderManager import ExtruderManager @@ -12,13 +12,23 @@ class UserProfilesModel(ProfilesModel): def __init__(self, parent = None): super().__init__(parent) + #Need to connect to the metaDataChanged signal of the active materials. + self.__current_extruders = [] + self.__current_materials = [] + + Application.getInstance().getExtruderManager().extrudersChanged.connect(self.__onExtrudersChanged) + self.__onExtrudersChanged() + self.__current_materials = [extruder.material for extruder in self.__current_extruders] + for material in self.__current_materials: + material.metaDataChanged.connect(self._onContainerChanged) + ## Fetch the list of containers to display. # # See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers(). def _fetchInstanceContainers(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if not global_container_stack: - return [] + return {}, {} # Fetch the list of quality changes. quality_manager = QualityManager.getInstance() @@ -35,10 +45,36 @@ class UserProfilesModel(ProfilesModel): # Filter the quality_change by the list of available quality_types quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) - filtered_quality_changes = [qc for qc in quality_changes_list if + + filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") is not None and (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or - qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] + qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())} - return filtered_quality_changes + return filtered_quality_changes, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet. + + ## Called when a container changed on an extruder stack. + # + # If it's the material we need to connect to the metaDataChanged signal of + # that. + def __onContainerChanged(self, new_container): + #Careful not to update when a quality or quality changes profile changed! + #If you then update you're going to have an infinite recursion because the update may change the container. + if new_container.getMetaDataEntry("type") == "material": + for material in self.__current_materials: + material.metaDataChanged.disconnect(self._onContainerChanged) + self.__current_materials = [extruder.material for extruder in self.__current_extruders] + for material in self.__current_materials: + material.metaDataChanged.connect(self._onContainerChanged) + + ## Called when the current set of extruders change. + # + # This makes sure that we are listening to the signal for when the + # materials change. + def __onExtrudersChanged(self): + for extruder in self.__current_extruders: + extruder.containersChanged.disconnect(self.__onContainerChanged) + self.__current_extruders = Application.getInstance().getExtruderManager().getExtruderStacks() + for extruder in self.__current_extruders: + extruder.containersChanged.connect(self.__onContainerChanged) \ No newline at end of file diff --git a/cura_app.py b/cura_app.py index dd795e5aa4..7583dd054e 100755 --- a/cura_app.py +++ b/cura_app.py @@ -12,7 +12,8 @@ from UM.Platform import Platform #WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612 if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 - if platform.linux_distribution()[0] in ("debian", "Ubuntu", "LinuxMint"): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. + linux_distro_name = platform.linux_distribution()[0].lower() + if linux_distro_name in ("debian", "ubuntu", "linuxmint", "fedora"): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. import ctypes from ctypes.util import find_library libGL = find_library("GL") diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 786226ae61..89439454f8 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -117,7 +117,7 @@ class ThreeMFReader(MeshReader): # Get the definition & set it definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) - um_node.callDecoration("getStack").getTop().setDefinition(definition) + um_node.callDecoration("getStack").getTop().setDefinition(definition.getId()) setting_container = um_node.callDecoration("getStack").getTop() diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 129ed43244..16fd2ee93c 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -122,7 +122,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace") return WorkspaceReader.PreReadResult.failed - machine_name = "" machine_type = "" variant_type_name = i18n_catalog.i18nc("@label", "Nozzle") @@ -133,9 +132,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # A few lists of containers in this project files. # When loading the global stack file, it may be associated with those containers, which may or may not be # in Cura already, so we need to provide them as alternative search lists. - definition_container_list = [] instance_container_list = [] - material_container_list = [] resolve_strategy_keys = ["machine", "material", "quality_changes"] self._resolve_strategies = {k: None for k in resolve_strategy_keys} @@ -149,21 +146,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader): definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] for each_definition_container_file in definition_container_files: container_id = self._stripFileToId(each_definition_container_file) - definitions = self._container_registry.findDefinitionContainers(id=container_id) + definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id) if not definitions: definition_container = DefinitionContainer(container_id) - definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"), - file_name = each_definition_container_file) + definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"), file_name = each_definition_container_file) + definition_container = definition_container.getMetaData() else: definition_container = definitions[0] - definition_container_list.append(definition_container) - definition_container_type = definition_container.getMetaDataEntry("type") + definition_container_type = definition_container.get("type") if definition_container_type == "machine": - machine_type = definition_container.getName() - variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name) + machine_type = definition_container["name"] + variant_type_name = definition_container.get("variants_name", variant_type_name) machine_definition_container_count += 1 elif definition_container_type == "extruder": @@ -187,11 +183,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader): material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)] for material_container_file in material_container_files: container_id = self._stripFileToId(material_container_file) - materials = self._container_registry.findInstanceContainers(id=container_id) material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8"))) - if materials: + if self._container_registry.findContainersMetadata(id = container_id): #This material already exists. containers_found_dict["material"] = True - if not materials[0].isReadOnly(): # Only non readonly materials can be in conflict + if not self._container_registry.isReadOnly(container_id): # Only non readonly materials can be in conflict material_conflict = True Job.yieldThread() @@ -462,7 +457,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): extruder_stack_id_map = {} # new and old ExtruderStack IDs map if self._resolve_strategies["machine"] == "new": # We need a new id if the id already exists - if self._container_registry.findContainerStacks(id = global_stack_id_original): + if self._container_registry.findContainerStacksMetadata(id = global_stack_id_original): global_stack_id_new = self.getNewId(global_stack_id_original) global_stack_need_rename = True @@ -471,7 +466,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): for each_extruder_stack_file in extruder_stack_files: old_container_id = self._stripFileToId(each_extruder_stack_file) new_container_id = old_container_id - if self._container_registry.findContainerStacks(id = old_container_id): + if self._container_registry.findContainerStacksMetadata(id = old_container_id): # get a new name for this extruder new_container_id = self.getNewId(old_container_id) @@ -485,7 +480,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] for definition_container_file in definition_container_files: container_id = self._stripFileToId(definition_container_file) - definitions = self._container_registry.findDefinitionContainers(id = container_id) + definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"), @@ -512,7 +507,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): containers_to_add.append(material_container) else: material_container = materials[0] - if not material_container.isReadOnly(): # Only create new materials if they are not read only. + if not self._container_registry.isReadOnly(container_id): # Only create new materials if they are not read only. if self._resolve_strategies["material"] == "override": material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"), file_name = material_container_file) @@ -579,7 +574,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if old_extruder_id: new_extruder_id = extruder_stack_id_map[old_extruder_id] new_id = new_extruder_id + "_current_settings" - instance_container._id = new_id + instance_container.setMetaDataEntry("id", new_id) instance_container.setName(new_id) instance_container.setMetaDataEntry("extruder", new_extruder_id) containers_to_add.append(instance_container) @@ -588,7 +583,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if machine_id: new_machine_id = self.getNewId(machine_id) new_id = new_machine_id + "_current_settings" - instance_container._id = new_id + instance_container.setMetadataEntry("id", new_id) instance_container.setName(new_id) instance_container.setMetaDataEntry("machine", new_machine_id) containers_to_add.append(instance_container) @@ -644,7 +639,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): machine_extruder_count = definition_changes_extruder_count else: - existing_container = self._container_registry.findInstanceContainers(id = container_id) + existing_container = self._container_registry.findInstanceContainersMetadata(id = container_id) if not existing_container: containers_to_add.append(instance_container) if global_stack_need_rename: @@ -670,14 +665,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # HACK # There is a machine, check if it has authentication data. If so, keep that data. - network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id") - network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key") - container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"), - file_name = global_stack_file) + network_authentication_id = stack.getMetaDataEntry("network_authentication_id") + network_authentication_key = stack.getMetaDataEntry("network_authentication_key") + stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"), file_name = global_stack_file) if network_authentication_id: - container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id) + stack.addMetaDataEntry("network_authentication_id", network_authentication_id) if network_authentication_key: - container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key) + stack.addMetaDataEntry("network_authentication_key", network_authentication_key) elif self._resolve_strategies["machine"] == "new": # create a new global stack diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 4f1ff9494f..f07a37a25f 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -97,7 +97,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): file_in_archive.compress_type = zipfile.ZIP_DEFLATED # Do not include the network authentication keys - ignore_keys = ["network_authentication_id", "network_authentication_key"] + ignore_keys = {"network_authentication_id", "network_authentication_key"} serialized_data = container.serialize(ignored_metadata_keys = ignore_keys) archive.writestr(file_in_archive, serialized_data) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index d35df967b2..4581a78479 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -86,6 +86,7 @@ class CuraEngineBackend(QObject, Backend): # self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) + Application.getInstance().getExtruderManager().activeExtruderChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 876b4685cc..3c1998094c 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -131,12 +131,21 @@ class StartSliceJob(Job): Logger.log("w", "No objects suitable for one at a time found, or no correct order found") else: temp_list = [] + has_printing_mesh = False for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: - if not getattr(node, "_outside_buildarea", False) or getattr(node, "_non_printing_mesh", False): + _non_printing_mesh = getattr(node, "_non_printing_mesh", False) + if not getattr(node, "_outside_buildarea", False) or _non_printing_mesh: temp_list.append(node) + if not _non_printing_mesh: + has_printing_mesh = True Job.yieldThread() + #If the list doesn't have any model with suitable settings then clean the list + # otherwise CuraEngine will crash + if not has_printing_mesh: + temp_list.clear() + if temp_list: object_groups.append(temp_list) diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 3860590ef7..f30e2dd43a 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -74,12 +74,13 @@ class GCodeWriter(MeshWriter): ## Create a new container with container 2 as base and container 1 written over it. def _createFlattenedContainerInstance(self, instance_container1, instance_container2): flat_container = InstanceContainer(instance_container2.getName()) - if instance_container1.getDefinition(): - flat_container.setDefinition(instance_container1.getDefinition()) - else: - flat_container.setDefinition(instance_container2.getDefinition()) + + # The metadata includes id, name and definition flat_container.setMetaData(copy.deepcopy(instance_container2.getMetaData())) + if instance_container1.getDefinition(): + flat_container.setDefinition(instance_container1.getDefinition().getId()) + for key in instance_container2.getAllKeys(): flat_container.setProperty(key, "value", instance_container2.getProperty(key, "value")) diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index 369bdd6cf0..32cfb5d027 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -121,7 +121,7 @@ class LegacyProfileReader(ProfileReader): Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?") return None current_printer_definition = global_container_stack.getBottom() - profile.setDefinition(current_printer_definition) + profile.setDefinition(current_printer_definition.getId()) for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations. old_setting_expression = dict_of_doom["translation"][new_setting] compiled = compile(old_setting_expression, new_setting, "eval") diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.qml b/plugins/MachineSettingsAction/MachineSettingsAction.qml index f50dd9d206..b36fb989f0 100644 --- a/plugins/MachineSettingsAction/MachineSettingsAction.qml +++ b/plugins/MachineSettingsAction/MachineSettingsAction.qml @@ -22,7 +22,7 @@ Cura.MachineAction onModelChanged: { var extruderCount = base.extrudersModel.rowCount(); - base.extruderTabsCount = extruderCount > 1 ? extruderCount : 0; + base.extruderTabsCount = extruderCount; } } @@ -241,7 +241,6 @@ Cura.MachineAction UM.TooltipArea { - visible: manager.definedExtruderCount > 1 height: childrenRect.height width: childrenRect.width text: machineExtruderCountProvider.properties.description @@ -291,15 +290,6 @@ Cura.MachineAction property var afterOnEditingFinished: manager.updateMaterialForDiameter property string label: catalog.i18nc("@label", "Material diameter") } - Loader - { - id: nozzleSizeField - visible: !Cura.MachineManager.hasVariants && machineExtruderCountProvider.properties.value == 1 - sourceComponent: numericTextFieldWithUnit - property string settingKey: "machine_nozzle_size" - property string label: catalog.i18nc("@label", "Nozzle size") - property string unit: catalog.i18nc("@label", "mm") - } } } diff --git a/plugins/MonitorStage/MonitorMainView.qml b/plugins/MonitorStage/MonitorMainView.qml index fad76cba30..15b05bed0a 100644 --- a/plugins/MonitorStage/MonitorMainView.qml +++ b/plugins/MonitorStage/MonitorMainView.qml @@ -8,6 +8,9 @@ import Cura 1.0 as Cura Item { + width: parent.width + height: parent.height + // We show a nice overlay on the 3D viewer when the current output device has no monitor view Rectangle { @@ -29,6 +32,9 @@ Item { id: monitorViewComponent + width: parent.width + height: parent.height + property real maximumWidth: parent.width property real maximumHeight: parent.height diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py index bacd3f73d5..8c5a160ff4 100644 --- a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py +++ b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py @@ -179,7 +179,7 @@ class VersionUpgrade30to31(VersionUpgrade): if not os.path.isfile(file_path): continue - parser = configparser.ConfigParser() + parser = configparser.ConfigParser(interpolation = None) try: parser.read([file_path]) except: @@ -213,7 +213,7 @@ class VersionUpgrade30to31(VersionUpgrade): new_filename = machine_name + "_" + "fdmextruder" + suffix - extruder_quality_changes_parser = configparser.ConfigParser() + extruder_quality_changes_parser = configparser.ConfigParser(interpolation = None) extruder_quality_changes_parser.add_section("general") extruder_quality_changes_parser["general"]["version"] = str(2) extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"] diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 7ab4520aea..c31a60c778 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -3,7 +3,10 @@ import copy import io -from typing import List, Optional +import json #To parse the product-to-id mapping file. +import os.path #To find the product-to-id mapping. +import sys +from typing import Any, Dict, List, Optional import xml.etree.ElementTree as ET from UM.Resources import Resources @@ -14,7 +17,6 @@ import UM.Dictionary from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry - ## Handles serializing and deserializing material containers from an XML file class XmlMaterialProfile(InstanceContainer): CurrentFdmMaterialVersion = "1.3" @@ -42,25 +44,18 @@ class XmlMaterialProfile(InstanceContainer): def getInheritedFiles(self): return self._inherited_files - ## Overridden from InstanceContainer - def setReadOnly(self, read_only): - super().setReadOnly(read_only) - - basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile. - for container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): - container._read_only = read_only # prevent loop instead of calling setReadOnly - ## Overridden from InstanceContainer # set the meta data for all machine / variant combinations def setMetaDataEntry(self, key, value): - if self.isReadOnly(): + registry = ContainerRegistry.getInstance() + if registry.isReadOnly(self.getId()): return super().setMetaDataEntry(key, value) - basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile. + basefile = self.getMetaDataEntry("base_file", self.getId()) #if basefile is self.getId, this is a basefile. # Update all containers that share basefile - for container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): + for container in registry.findInstanceContainers(base_file = basefile): if container.getMetaDataEntry(key, None) != value: # Prevent recursion container.setMetaDataEntry(key, value) @@ -68,7 +63,8 @@ class XmlMaterialProfile(InstanceContainer): # without this function the setName would only set the name of the specific nozzle / material / machine combination container # The function is a bit tricky. It will not set the name of all containers if it has the correct name itself. def setName(self, new_name): - if self.isReadOnly(): + registry = ContainerRegistry.getInstance() + if registry.isReadOnly(self.getId()): return # Not only is this faster, it also prevents a major loop that causes a stack overflow. @@ -77,10 +73,10 @@ class XmlMaterialProfile(InstanceContainer): super().setName(new_name) - basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile. + basefile = self.getMetaDataEntry("base_file", self.getId()) # if basefile is self.getId, this is a basefile. # Update the basefile as well, this is actually what we're trying to do # Update all containers that share GUID and basefile - containers = ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile) + containers = registry.findInstanceContainers(base_file = basefile) for container in containers: container.setName(new_name) @@ -88,33 +84,20 @@ class XmlMaterialProfile(InstanceContainer): def setDirty(self, dirty): super().setDirty(dirty) base_file = self.getMetaDataEntry("base_file", None) - if base_file is not None and base_file != self._id: - containers = ContainerRegistry.getInstance().findContainers(id=base_file) + registry = ContainerRegistry.getInstance() + if base_file is not None and base_file != self.getId() and not registry.isReadOnly(base_file): + containers = registry.findContainers(id = base_file) if containers: - base_container = containers[0] - if not base_container.isReadOnly(): - base_container.setDirty(dirty) - - ## Overridden from InstanceContainer - # def setProperty(self, key, property_name, property_value, container = None): - # if self.isReadOnly(): - # return - # - # super().setProperty(key, property_name, property_value) - # - # basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile. - # for container in UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): - # if not container.isReadOnly(): - # container.setDirty(True) + containers[0].setDirty(dirty) ## Overridden from InstanceContainer # base file: common settings + supported machines # machine / variant combination: only changes for itself. - def serialize(self, ignored_metadata_keys: Optional[List] = None): + def serialize(self, ignored_metadata_keys: Optional[set] = None): registry = ContainerRegistry.getInstance() base_file = self.getMetaDataEntry("base_file", "") - if base_file and self.id != base_file: + if base_file and self.getId() != base_file: # Since we create an instance of XmlMaterialProfile for each machine and nozzle in the profile, # we should only serialize the "base" material definition, since that can then take care of # serializing the machine/nozzle specific profiles. @@ -132,8 +115,8 @@ class XmlMaterialProfile(InstanceContainer): metadata = copy.deepcopy(self.getMetaData()) # setting_version is derived from the "version" tag in the schema, so don't serialize it into a file if ignored_metadata_keys is None: - ignored_metadata_keys = [] - ignored_metadata_keys = ignored_metadata_keys + ["setting_version"] + ignored_metadata_keys = set() + ignored_metadata_keys |= {"setting_version"} # remove the keys that we want to ignore in the metadata for key in ignored_metadata_keys: if key in metadata: @@ -146,6 +129,9 @@ class XmlMaterialProfile(InstanceContainer): metadata.pop("type", "") metadata.pop("base_file", "") metadata.pop("approximate_diameter", "") + metadata.pop("id", "") + metadata.pop("container_type", "") + metadata.pop("name", "") ## Begin Name Block builder.start("name") @@ -163,7 +149,7 @@ class XmlMaterialProfile(InstanceContainer): builder.end("color") builder.start("label") - builder.data(self._name) + builder.data(self.getName()) builder.end("label") builder.end("name") @@ -195,16 +181,16 @@ class XmlMaterialProfile(InstanceContainer): ## Begin Settings Block builder.start("settings") - if self.getDefinition().id == "fdmprinter": + if self.getDefinition().getId() == "fdmprinter": for instance in self.findInstances(): self._addSettingElement(builder, instance) machine_container_map = {} machine_nozzle_map = {} - all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self._id) + all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId()) for container in all_containers: - definition_id = container.getDefinition().id + definition_id = container.getDefinition().getId() if definition_id == "fdmprinter": continue @@ -222,17 +208,16 @@ class XmlMaterialProfile(InstanceContainer): machine_container_map[definition_id] = container # Map machine human-readable names to IDs - product_id_map = {} - for container in registry.findDefinitionContainers(type = "machine"): - product_id_map[container.getName()] = container.getId() + product_id_map = self.getProductIdMap() for definition_id, container in machine_container_map.items(): definition = container.getDefinition() - try: - product = UM.Dictionary.findKey(product_id_map, definition_id) - except ValueError: - # An unknown product id; export it anyway - product = definition_id + + product = definition_id + for product_name, product_id_list in product_id_map.items(): + if definition_id in product_id_list: + product = product_name + break builder.start("machine") builder.start("machine_identifier", { @@ -242,7 +227,7 @@ class XmlMaterialProfile(InstanceContainer): builder.end("machine_identifier") for instance in container.findInstances(): - if self.getDefinition().id == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value: + if self.getDefinition().getId() == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value: # If the settings match that of the base profile, just skip since we inherit the base profile. continue @@ -250,11 +235,12 @@ class XmlMaterialProfile(InstanceContainer): # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile. for hotend_id, hotend in machine_nozzle_map[definition_id].items(): - variant_containers = registry.findInstanceContainers(id = hotend.getMetaDataEntry("variant")) + variant_containers = registry.findInstanceContainersMetadata(id = hotend.getMetaDataEntry("variant")) if not variant_containers: continue - builder.start("hotend", {"id": variant_containers[0].getName()}) + # The hotend identifier is not the containers name, but its "name". + builder.start("hotend", {"id": variant_containers[0]["name"]}) # Compatible is a special case, as it's added as a meta data entry (instead of an instance). compatible = hotend.getMetaDataEntry("compatible") @@ -397,15 +383,18 @@ class XmlMaterialProfile(InstanceContainer): first.append(element) def clearData(self): - self._metadata = {} - self._name = "" + self._metadata = { + "id": self.getId(), + "name": "" + } self._definition = None self._instances = {} self._read_only = False self._dirty = False self._path = "" - def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]: + @classmethod + def getConfigurationTypeFromSerialized(cls, serialized: str) -> Optional[str]: return "materials" @classmethod @@ -415,9 +404,9 @@ class XmlMaterialProfile(InstanceContainer): version = XmlMaterialProfile.Version # get setting version if "version" in data.attrib: - setting_version = XmlMaterialProfile.xmlVersionToSettingVersion(data.attrib["version"]) + setting_version = cls.xmlVersionToSettingVersion(data.attrib["version"]) else: - setting_version = XmlMaterialProfile.xmlVersionToSettingVersion("1.2") + setting_version = cls.xmlVersionToSettingVersion("1.2") return version * 1000000 + setting_version @@ -431,15 +420,18 @@ class XmlMaterialProfile(InstanceContainer): try: data = ET.fromstring(serialized) except: - Logger.logException("e", "An exception occured while parsing the material profile") + Logger.logException("e", "An exception occurred while parsing the material profile") return # Reset previous metadata + old_id = self.getId() self.clearData() # Ensure any previous data is gone. meta_data = {} meta_data["type"] = "material" - meta_data["base_file"] = self.id + meta_data["base_file"] = self.getId() meta_data["status"] = "unknown" # TODO: Add material verification + meta_data["id"] = old_id + meta_data["container_type"] = XmlMaterialProfile common_setting_values = {} @@ -454,8 +446,8 @@ class XmlMaterialProfile(InstanceContainer): else: meta_data["setting_version"] = self.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet. - metadata = data.iterfind("./um:metadata/*", self.__namespaces) - for entry in metadata: + meta_data["name"] = "Unknown Material" #In case the name tag is missing. + for entry in data.iterfind("./um:metadata/*", self.__namespaces): tag_name = _tag_without_namespace(entry) if tag_name == "name": @@ -465,9 +457,9 @@ class XmlMaterialProfile(InstanceContainer): label = entry.find("./um:label", self.__namespaces) if label is not None: - self._name = label.text + meta_data["name"] = label.text else: - self._name = self._profile_name(material.text, color.text) + meta_data["name"] = self._profile_name(material.text, color.text) meta_data["brand"] = brand.text meta_data["material"] = material.text meta_data["color_name"] = color.text @@ -499,8 +491,7 @@ class XmlMaterialProfile(InstanceContainer): meta_data["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm meta_data["properties"] = property_values - - self.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0]) + meta_data["definition"] = "fdmprinter" common_compatibility = True settings = data.iterfind("./um:settings/um:setting", self.__namespaces) @@ -518,9 +509,7 @@ class XmlMaterialProfile(InstanceContainer): self._dirty = False # Map machine human-readable names to IDs - product_id_map = {} - for container in ContainerRegistry.getInstance().findDefinitionContainers(type = "machine"): - product_id_map[container.getName()] = container.getId() + product_id_map = self.getProductIdMap() machines = data.iterfind("./um:settings/um:machine", self.__namespaces) for machine in machines: @@ -542,112 +531,285 @@ class XmlMaterialProfile(InstanceContainer): identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces) for identifier in identifiers: - machine_id = product_id_map.get(identifier.get("product"), None) - if machine_id is None: - # Lets try again with some naive heuristics. - machine_id = identifier.get("product").replace(" ", "").lower() + machine_id_list = product_id_map.get(identifier.get("product"), []) + if not machine_id_list: + machine_id_list = self.getPossibleDefinitionIDsFromName(identifier.get("product")) - definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id) - if not definitions: - # Logger.log("w", "No definition found for machine ID %s", machine_id) - continue - - definition = definitions[0] - - machine_manufacturer = identifier.get("manufacturer", definition.getMetaDataEntry("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition. - - if machine_compatibility: - new_material_id = self.id + "_" + machine_id - - # The child or derived material container may already exist. This can happen when a material in a - # project file and the a material in Cura have the same ID. - # In the case if a derived material already exists, override that material container because if - # the data in the parent material has been changed, the derived ones should be updated too. - found_materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id) - is_new_material = False - if found_materials: - new_material = found_materials[0] - else: - new_material = XmlMaterialProfile(new_material_id) - is_new_material = True - - # Update the private directly, as we want to prevent the lookup that is done when using setName - new_material._name = self.getName() - new_material.setMetaData(copy.deepcopy(self.getMetaData())) - new_material.setDefinition(definition) - # Don't use setMetadata, as that overrides it for all materials with same base file - new_material.getMetaData()["compatible"] = machine_compatibility - new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer - - new_material.setCachedValues(cached_machine_setting_properties) - - new_material._dirty = False - - if is_new_material: - containers_to_add.append(new_material) - - hotends = machine.iterfind("./um:hotend", self.__namespaces) - for hotend in hotends: - hotend_id = hotend.get("id") - if hotend_id is None: + for machine_id in machine_id_list: + definitions = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id) + if not definitions: + Logger.log("w", "No definition found for machine ID %s", machine_id) continue - variant_containers = ContainerRegistry.getInstance().findInstanceContainers(id = hotend_id) - if not variant_containers: - # It is not really properly defined what "ID" is so also search for variants by name. - variant_containers = ContainerRegistry.getInstance().findInstanceContainers(definition = definition.id, name = hotend_id) + Logger.log("d", "Found definition for machine ID %s", machine_id) + definition = definitions[0] - if not variant_containers: - #Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id) - continue + machine_manufacturer = identifier.get("manufacturer", definition.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition. - hotend_compatibility = machine_compatibility - hotend_setting_values = {} - settings = hotend.iterfind("./um:setting", self.__namespaces) - for entry in settings: - key = entry.get("key") - if key in self.__material_settings_setting_map: - hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text - elif key in self.__unmapped_settings: - if key == "hardware compatible": - hotend_compatibility = self._parseCompatibleValue(entry.text) + if machine_compatibility: + new_material_id = self.getId() + "_" + machine_id + + # The child or derived material container may already exist. This can happen when a material in a + # project file and the a material in Cura have the same ID. + # In the case if a derived material already exists, override that material container because if + # the data in the parent material has been changed, the derived ones should be updated too. + if ContainerRegistry.getInstance().isLoaded(new_material_id): + new_material = ContainerRegistry.getInstance().findContainers(id = new_material_id)[0] + is_new_material = False else: - Logger.log("d", "Unsupported material setting %s", key) + new_material = XmlMaterialProfile(new_material_id) + is_new_material = True - new_hotend_id = self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_") + new_material.setMetaData(copy.deepcopy(self.getMetaData())) + new_material.getMetaData()["id"] = new_material_id + new_material.getMetaData()["name"] = self.getName() + new_material.setDefinition(machine_id) + # Don't use setMetadata, as that overrides it for all materials with same base file + new_material.getMetaData()["compatible"] = machine_compatibility + new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer + new_material.getMetaData()["definition"] = machine_id - # Same as machine compatibility, keep the derived material containers consistent with the parent - # material - found_materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_hotend_id) - is_new_material = False - if found_materials: - new_hotend_material = found_materials[0] - else: - new_hotend_material = XmlMaterialProfile(new_hotend_id) - is_new_material = True + new_material.setCachedValues(cached_machine_setting_properties) - # Update the private directly, as we want to prevent the lookup that is done when using setName - new_hotend_material._name = self.getName() - new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData())) - new_hotend_material.setDefinition(definition) - new_hotend_material.addMetaDataEntry("variant", variant_containers[0].id) - # Don't use setMetadata, as that overrides it for all materials with same base file - new_hotend_material.getMetaData()["compatible"] = hotend_compatibility - new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer + new_material._dirty = False - cached_hotend_setting_properties = cached_machine_setting_properties.copy() - cached_hotend_setting_properties.update(hotend_setting_values) + if is_new_material: + containers_to_add.append(new_material) - new_hotend_material.setCachedValues(cached_hotend_setting_properties) + hotends = machine.iterfind("./um:hotend", self.__namespaces) + for hotend in hotends: + hotend_id = hotend.get("id") + if hotend_id is None: + continue - new_hotend_material._dirty = False + variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id) + if not variant_containers: + # It is not really properly defined what "ID" is so also search for variants by name. + variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id) - if is_new_material: - containers_to_add.append(new_hotend_material) + if not variant_containers: + continue + + hotend_compatibility = machine_compatibility + hotend_setting_values = {} + settings = hotend.iterfind("./um:setting", self.__namespaces) + for entry in settings: + key = entry.get("key") + if key in self.__material_settings_setting_map: + hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text + elif key in self.__unmapped_settings: + if key == "hardware compatible": + hotend_compatibility = self._parseCompatibleValue(entry.text) + else: + Logger.log("d", "Unsupported material setting %s", key) + + new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_") + + # Same as machine compatibility, keep the derived material containers consistent with the parent + # material + if ContainerRegistry.getInstance().isLoaded(new_hotend_id): + new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0] + is_new_material = False + else: + new_hotend_material = XmlMaterialProfile(new_hotend_id) + is_new_material = True + + new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData())) + new_hotend_material.getMetaData()["id"] = new_hotend_id + new_hotend_material.getMetaData()["name"] = self.getName() + new_hotend_material.getMetaData()["variant"] = variant_containers[0]["id"] + new_hotend_material.setDefinition(machine_id) + # Don't use setMetadata, as that overrides it for all materials with same base file + new_hotend_material.getMetaData()["compatible"] = hotend_compatibility + new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer + new_hotend_material.getMetaData()["definition"] = machine_id + + cached_hotend_setting_properties = cached_machine_setting_properties.copy() + cached_hotend_setting_properties.update(hotend_setting_values) + + new_hotend_material.setCachedValues(cached_hotend_setting_properties) + + new_hotend_material._dirty = False + + if is_new_material: + containers_to_add.append(new_hotend_material) + + # there is only one ID for a machine. Once we have reached here, it means we have already found + # a workable ID for that machine, so there is no need to continue + break for container_to_add in containers_to_add: ContainerRegistry.getInstance().addContainer(container_to_add) + @classmethod + def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]: + result_metadata = [] #All the metadata that we found except the base (because the base is returned). + + #Update the serialized data to the latest version. + serialized = cls._updateSerialized(serialized) + + base_metadata = { + "type": "material", + "status": "unknown", #TODO: Add material verification. + "container_type": XmlMaterialProfile, + "id": container_id, + "base_file": container_id + } + + try: + data = ET.fromstring(serialized) + except: + Logger.logException("e", "An exception occurred while parsing the material profile") + return [] + + #TODO: Implement the tag. It's unused at the moment though. + + if "version" in data.attrib: + base_metadata["setting_version"] = cls.xmlVersionToSettingVersion(data.attrib["version"]) + else: + base_metadata["setting_version"] = cls.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet. + + for entry in data.iterfind("./um:metadata/*", cls.__namespaces): + tag_name = _tag_without_namespace(entry) + + if tag_name == "name": + brand = entry.find("./um:brand", cls.__namespaces) + material = entry.find("./um:material", cls.__namespaces) + color = entry.find("./um:color", cls.__namespaces) + label = entry.find("./um:label", cls.__namespaces) + + if label is not None: + base_metadata["name"] = label.text + else: + base_metadata["name"] = cls._profile_name(material.text, color.text) + base_metadata["brand"] = brand.text + base_metadata["material"] = material.text + base_metadata["color_name"] = color.text + continue + + #Setting_version is derived from the "version" tag in the schema earlier, so don't set it here. + if tag_name == "setting_version": + continue + + base_metadata[tag_name] = entry.text + + if "description" not in base_metadata: + base_metadata["description"] = "" + if "adhesion_info" not in base_metadata: + base_metadata["adhesion_info"] = "" + + property_values = {} + properties = data.iterfind("./um:properties/*", cls.__namespaces) + for entry in properties: + tag_name = _tag_without_namespace(entry) + property_values[tag_name] = entry.text + + base_metadata["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm + base_metadata["properties"] = property_values + base_metadata["definition"] = "fdmprinter" + + compatible_entries = data.iterfind("./um:settings/um:setting[@key='hardware compatible']", cls.__namespaces) + try: + common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text) + except StopIteration: #No 'hardware compatible' setting. + common_compatibility = True + base_metadata["compatible"] = common_compatibility + result_metadata.append(base_metadata) + + # Map machine human-readable names to IDs + product_id_map = cls.getProductIdMap() + + for machine in data.iterfind("./um:settings/um:machine", cls.__namespaces): + machine_compatibility = common_compatibility + for entry in machine.iterfind("./um:setting", cls.__namespaces): + key = entry.get("key") + if key == "hardware compatible": + machine_compatibility = cls._parseCompatibleValue(entry.text) + + for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces): + machine_id_list = product_id_map.get(identifier.get("product"), []) + if not machine_id_list: + machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product")) + + for machine_id in machine_id_list: + definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id) + if not definition_metadata: + Logger.log("w", "No definition found for machine ID %s", machine_id) + continue + + Logger.log("d", "Found def for machine [%s]", machine_id) + definition_metadata = definition_metadata[0] + + machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition. + + if machine_compatibility: + new_material_id = container_id + "_" + machine_id + + # The child or derived material container may already exist. This can happen when a material in a + # project file and the a material in Cura have the same ID. + # In the case if a derived material already exists, override that material container because if + # the data in the parent material has been changed, the derived ones should be updated too. + found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_material_id) + if found_materials: + new_material_metadata = found_materials[0] + else: + new_material_metadata = {} + + new_material_metadata.update(base_metadata) + new_material_metadata["id"] = new_material_id + new_material_metadata["compatible"] = machine_compatibility + new_material_metadata["machine_manufacturer"] = machine_manufacturer + new_material_metadata["definition"] = machine_id + + if len(found_materials) == 0: #This is a new material. + result_metadata.append(new_material_metadata) + + for hotend in machine.iterfind("./um:hotend", cls.__namespaces): + hotend_id = hotend.get("id") + if hotend_id is None: + continue + + variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id) + if not variant_containers: + # It is not really properly defined what "ID" is so also search for variants by name. + variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id) + + hotend_compatibility = machine_compatibility + for entry in hotend.iterfind("./um:setting", cls.__namespaces): + key = entry.get("key") + if key == "hardware compatible": + hotend_compatibility = cls._parseCompatibleValue(entry.text) + + new_hotend_id = container_id + "_" + machine_id + "_" + hotend_id.replace(" ", "_") + + # Same as machine compatibility, keep the derived material containers consistent with the parent + # material + found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id) + if found_materials: + new_hotend_material_metadata = found_materials[0] + else: + new_hotend_material_metadata = {} + + new_hotend_material_metadata.update(base_metadata) + if variant_containers: + new_hotend_material_metadata["variant"] = variant_containers[0]["id"] + else: + new_hotend_material_metadata["variant"] = hotend_id + _with_missing_variants.append(new_hotend_material_metadata) + new_hotend_material_metadata["compatible"] = hotend_compatibility + new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer + new_hotend_material_metadata["id"] = new_hotend_id + new_hotend_material_metadata["definition"] = machine_id + + if len(found_materials) == 0: + result_metadata.append(new_hotend_material_metadata) + + # there is only one ID for a machine. Once we have reached here, it means we have already found + # a workable ID for that machine, so there is no need to continue + break + + return result_metadata + def _addSettingElement(self, builder, instance): try: key = UM.Dictionary.findKey(self.__material_settings_setting_map, instance.definition.key) @@ -658,16 +820,58 @@ class XmlMaterialProfile(InstanceContainer): builder.data(str(instance.value)) builder.end("setting") - def _profile_name(self, material_name, color_name): + @classmethod + def _profile_name(cls, material_name, color_name): if color_name != "Generic": return "%s %s" % (color_name, material_name) else: return material_name + @classmethod + def getPossibleDefinitionIDsFromName(cls, name): + name_parts = name.lower().split(" ") + merged_name_parts = [] + for part in name_parts: + if len(part) == 0: + continue + if len(merged_name_parts) == 0: + merged_name_parts.append(part) + continue + if part.isdigit(): + # for names with digit(s) such as Ultimaker 3 Extended, we generate an ID like + # "ultimaker3_extended", ignoring the space between "Ultimaker" and "3". + merged_name_parts[-1] = merged_name_parts[-1] + part + else: + merged_name_parts.append(part) + + id_list = [name.lower().replace(" ", ""), # simply removing all spaces + name.lower().replace(" ", "_"), # simply replacing all spaces with underscores + "_".join(merged_name_parts), + ] + + return id_list + + ## Gets a mapping from product names in the XML files to their definition + # IDs. + # + # This loads the mapping from a file. + @classmethod + def getProductIdMap(cls) -> Dict[str, List[str]]: + product_to_id_file = os.path.join(os.path.dirname(sys.modules[cls.__module__].__file__), "product_to_id.json") + with open(product_to_id_file) as f: + product_to_id_map = json.load(f) + product_to_id_map = {key: [value] for key, value in product_to_id_map.items()} + return product_to_id_map + ## Parse the value of the "material compatible" property. - def _parseCompatibleValue(self, value: str): + @classmethod + def _parseCompatibleValue(cls, value: str): return value in {"yes", "unknown"} + ## Small string representation for debugging. + def __str__(self): + return "".format(my_id = self.getId(), name = self.getName(), base_file = self.getMetaDataEntry("base_file")) + # Map XML file setting names to internal names __material_settings_setting_map = { "print temperature": "default_material_print_temperature", @@ -716,4 +920,22 @@ def _indent(elem, level = 0): # We are only interested in the actual tag name, so discard everything # before the last } def _tag_without_namespace(element): - return element.tag[element.tag.rfind("}") + 1:] \ No newline at end of file + return element.tag[element.tag.rfind("}") + 1:] + +#While loading XML profiles, some of these profiles don't know what variant +#they belong to. We'd like to search by the machine ID and the variant's +#name, but we don't know the variant's ID. Not all variants have been loaded +#yet so we can't run a filter on the name and machine. The ID is unknown +#so we can't lazily load the variant either. So we have to wait until all +#the rest is loaded properly and then assign the correct variant to the +#material files that were missing it. +_with_missing_variants = [] +def _fillMissingVariants(): + registry = ContainerRegistry.getInstance() + for variant_metadata in _with_missing_variants: + variants = registry.findContainersMetadata(definition = variant_metadata["definition"], name = variant_metadata["variant"]) + if not variants: + Logger.log("w", "Could not find variant for variant-specific material {material_id}.".format(material_id = variant_metadata["id"])) + continue + variant_metadata["variant"] = variants[0]["id"] +ContainerRegistry.allMetadataLoaded.connect(_fillMissingVariants) diff --git a/plugins/XmlMaterialProfile/product_to_id.json b/plugins/XmlMaterialProfile/product_to_id.json new file mode 100644 index 0000000000..d6b8f3bade --- /dev/null +++ b/plugins/XmlMaterialProfile/product_to_id.json @@ -0,0 +1,12 @@ +{ + "Ultimaker 2": "ultimaker2", + "Ultimaker 2 Extended": "ultimaker2_extended", + "Ultimaker 2 Extended+": "ultimaker2_extended_plus", + "Ultimaker 2 Go": "ultimaker2_go", + "Ultimaker 2+": "ultimaker2_plus", + "Ultimaker 3": "ultimaker3", + "Ultimaker 3 Extended": "ultimaker3_extended", + "Ultimaker Original": "ultimaker_original", + "Ultimaker Original+": "ultimaker_original_plus", + "IMADE3D JellyBOX": "imade3d_jellybox" +} \ No newline at end of file diff --git a/resources/definitions/101Hero.def.json b/resources/definitions/101Hero.def.json index aaea743b47..620fcfb519 100644 --- a/resources/definitions/101Hero.def.json +++ b/resources/definitions/101Hero.def.json @@ -1,5 +1,4 @@ { - "id": "101Hero", "version": 2, "name": "101Hero", "inherits": "fdmprinter", diff --git a/resources/definitions/3dator.def.json b/resources/definitions/3dator.def.json index 513ee8f0e1..2ec7119656 100644 --- a/resources/definitions/3dator.def.json +++ b/resources/definitions/3dator.def.json @@ -1,5 +1,4 @@ { - "id": "3Dator", "version": 2, "name": "3Dator", "inherits": "fdmprinter", diff --git a/resources/definitions/abax_pri3.def.json b/resources/definitions/abax_pri3.def.json index fa826e6f94..2fa648096f 100644 --- a/resources/definitions/abax_pri3.def.json +++ b/resources/definitions/abax_pri3.def.json @@ -1,5 +1,4 @@ { - "id": "PRi3", "name": "ABAX PRi3", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/abax_pri5.def.json b/resources/definitions/abax_pri5.def.json index b5588e5c96..cbebb576b0 100644 --- a/resources/definitions/abax_pri5.def.json +++ b/resources/definitions/abax_pri5.def.json @@ -1,5 +1,4 @@ { - "id": "PRi5", "name": "ABAX PRi5", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/abax_titan.def.json b/resources/definitions/abax_titan.def.json index 53b768c93f..5f7a99d64d 100644 --- a/resources/definitions/abax_titan.def.json +++ b/resources/definitions/abax_titan.def.json @@ -1,5 +1,4 @@ { - "id": "Titan", "name": "ABAX Titan", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/alya3dp.def.json b/resources/definitions/alya3dp.def.json index 5fa6630f51..2fda102249 100644 --- a/resources/definitions/alya3dp.def.json +++ b/resources/definitions/alya3dp.def.json @@ -1,5 +1,4 @@ { - "id": "alya3dp", "name": "ALYA", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/bfb.def.json b/resources/definitions/bfb.def.json index fddec169c5..7a76e231a8 100644 --- a/resources/definitions/bfb.def.json +++ b/resources/definitions/bfb.def.json @@ -1,5 +1,4 @@ { - "id": "bfb", "name": "BFB", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/bq_hephestos.def.json b/resources/definitions/bq_hephestos.def.json index 91eeecb500..0fb40a4741 100644 --- a/resources/definitions/bq_hephestos.def.json +++ b/resources/definitions/bq_hephestos.def.json @@ -1,5 +1,4 @@ { - "id": "bq_hephestos", "name": "BQ Prusa i3 Hephestos", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/bq_hephestos_2.def.json b/resources/definitions/bq_hephestos_2.def.json index 272e547af0..a3493334b6 100644 --- a/resources/definitions/bq_hephestos_2.def.json +++ b/resources/definitions/bq_hephestos_2.def.json @@ -1,5 +1,4 @@ { - "id": "bq_hephestos_2", "version": 2, "name": "BQ Hephestos 2", "inherits": "fdmprinter", diff --git a/resources/definitions/bq_hephestos_xl.def.json b/resources/definitions/bq_hephestos_xl.def.json index a5a183b1e9..75b756c71e 100644 --- a/resources/definitions/bq_hephestos_xl.def.json +++ b/resources/definitions/bq_hephestos_xl.def.json @@ -1,5 +1,4 @@ { - "id": "bq_hephestos_xl", "version": 2, "name": "BQ Prusa i3 Hephestos XL", "inherits": "fdmprinter", diff --git a/resources/definitions/bq_witbox.def.json b/resources/definitions/bq_witbox.def.json index ca14151e7a..ef4e12b704 100644 --- a/resources/definitions/bq_witbox.def.json +++ b/resources/definitions/bq_witbox.def.json @@ -1,5 +1,4 @@ { - "id": "bq_witbox", "version": 2, "name": "BQ Witbox", "inherits": "fdmprinter", diff --git a/resources/definitions/bq_witbox_2.def.json b/resources/definitions/bq_witbox_2.def.json index f634d9c9c8..ab3786ad1f 100644 --- a/resources/definitions/bq_witbox_2.def.json +++ b/resources/definitions/bq_witbox_2.def.json @@ -1,5 +1,4 @@ { - "id": "bq_witbox_2", "version": 2, "name": "BQ Witbox 2", "inherits": "fdmprinter", diff --git a/resources/definitions/cartesio.def.json b/resources/definitions/cartesio.def.json index 5d6a0fca16..44f3153015 100644 --- a/resources/definitions/cartesio.def.json +++ b/resources/definitions/cartesio.def.json @@ -1,5 +1,4 @@ { - "id": "cartesio", "name": "Cartesio", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/creality_cr10.def.json b/resources/definitions/creality_cr10.def.json index f913f27b6b..7a58adcd4d 100644 --- a/resources/definitions/creality_cr10.def.json +++ b/resources/definitions/creality_cr10.def.json @@ -1,5 +1,4 @@ { - "id": "creality_cr10", "name": "Creality CR-10", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/creality_cr10s4.def.json b/resources/definitions/creality_cr10s4.def.json index 13c2e53391..cebe255705 100644 --- a/resources/definitions/creality_cr10s4.def.json +++ b/resources/definitions/creality_cr10s4.def.json @@ -1,5 +1,4 @@ { - "id": "creality_cr10s4", "name": "Creality CR-10 S4", "version": 2, "inherits": "creality_cr10", diff --git a/resources/definitions/creality_cr10s5.def.json b/resources/definitions/creality_cr10s5.def.json index 15cf0b93d9..e6aaa88b04 100644 --- a/resources/definitions/creality_cr10s5.def.json +++ b/resources/definitions/creality_cr10s5.def.json @@ -1,5 +1,4 @@ { - "id": "creality_cr10s5", "name": "Creality CR-10 S5", "version": 2, "inherits": "creality_cr10", diff --git a/resources/definitions/custom.def.json b/resources/definitions/custom.def.json index 8f15f00a0f..e973a75bbf 100644 --- a/resources/definitions/custom.def.json +++ b/resources/definitions/custom.def.json @@ -1,5 +1,4 @@ { - "id": "custom", "version": 2, "name": "Custom FDM printer", "inherits": "fdmprinter", diff --git a/resources/definitions/dagoma_discoeasy200.def.json b/resources/definitions/dagoma_discoeasy200.def.json index 3b62ff042f..9bcc2402f2 100644 --- a/resources/definitions/dagoma_discoeasy200.def.json +++ b/resources/definitions/dagoma_discoeasy200.def.json @@ -1,5 +1,4 @@ { - "id": "Dagoma_discoeasy200", "name": "Dagoma DiscoEasy200", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/delta_go.def.json b/resources/definitions/delta_go.def.json index 36e0787039..a6d75b2983 100644 --- a/resources/definitions/delta_go.def.json +++ b/resources/definitions/delta_go.def.json @@ -1,5 +1,4 @@ { -"id": "Delta_Go", "name": "Delta Go", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/deltabot.def.json b/resources/definitions/deltabot.def.json index cee5ab1be1..e3b676d0ec 100644 --- a/resources/definitions/deltabot.def.json +++ b/resources/definitions/deltabot.def.json @@ -1,5 +1,4 @@ { - "id": "deltabot", "name": "DeltaBot", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/easyarts_ares.def.json b/resources/definitions/easyarts_ares.def.json index 8d3e5338b6..982496de4c 100644 --- a/resources/definitions/easyarts_ares.def.json +++ b/resources/definitions/easyarts_ares.def.json @@ -1,5 +1,4 @@ { - "id": "easyarts_ares", "name": "EasyArts Ares", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/fabtotum.def.json b/resources/definitions/fabtotum.def.json index eb33280401..87ce11a35c 100644 --- a/resources/definitions/fabtotum.def.json +++ b/resources/definitions/fabtotum.def.json @@ -1,5 +1,4 @@ { - "id": "fabtotum", "version": 2, "name": "FABtotum Personal Fabricator", "inherits": "fdmprinter", diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index ada35fafe0..2b314cd6a5 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -1,5 +1,4 @@ { - "id": "fdmextruder", "name": "Extruder", "version": 2, "metadata": diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index ed8bf59b97..24f7efe373 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1,5 +1,4 @@ { - "id": "fdmprinter", "name": "FDM Printer Base Description", "version": 2, "metadata": diff --git a/resources/definitions/grr_neo.def.json b/resources/definitions/grr_neo.def.json index 42c2e75319..847f638fbd 100644 --- a/resources/definitions/grr_neo.def.json +++ b/resources/definitions/grr_neo.def.json @@ -1,5 +1,4 @@ { - "id": "grr_neo", "version": 2, "name": "German RepRap Neo", "inherits": "fdmprinter", diff --git a/resources/definitions/helloBEEprusa.def.json b/resources/definitions/helloBEEprusa.def.json index 660e182187..2699a6c7d7 100644 --- a/resources/definitions/helloBEEprusa.def.json +++ b/resources/definitions/helloBEEprusa.def.json @@ -1,5 +1,4 @@ { - "id": "BEEVERYCREATIVE-helloBEEprusa", "version": 2, "name": "Hello BEE Prusa", "inherits": "fdmprinter", diff --git a/resources/definitions/imade3d_jellybox.def.json b/resources/definitions/imade3d_jellybox.def.json index 0c0f29c070..11df730408 100644 --- a/resources/definitions/imade3d_jellybox.def.json +++ b/resources/definitions/imade3d_jellybox.def.json @@ -1,5 +1,4 @@ { - "id": "imade3d_jellybox", "version": 2, "name": "IMADE3D JellyBOX", "inherits": "fdmprinter", diff --git a/resources/definitions/innovo_inventor.def.json b/resources/definitions/innovo_inventor.def.json index 5fc6c83ca2..84b6697d34 100644 --- a/resources/definitions/innovo_inventor.def.json +++ b/resources/definitions/innovo_inventor.def.json @@ -1,5 +1,4 @@ { - "id": "innovo-inventor", "version": 2, "name": "Innovo INVENTOR", "inherits": "fdmprinter", diff --git a/resources/definitions/julia.def.json b/resources/definitions/julia.def.json index fe10473596..7fdee30272 100644 --- a/resources/definitions/julia.def.json +++ b/resources/definitions/julia.def.json @@ -1,5 +1,4 @@ { - "id": "julia", "name": "Julia", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/kemiq_q2_beta.def.json b/resources/definitions/kemiq_q2_beta.def.json index d5bb8a895e..7946fbfad2 100644 --- a/resources/definitions/kemiq_q2_beta.def.json +++ b/resources/definitions/kemiq_q2_beta.def.json @@ -1,5 +1,4 @@ { - "id": "kemiq_q2_beta", "version": 2, "name": "Kemiq Q2 Beta", "inherits": "fdmprinter", diff --git a/resources/definitions/kemiq_q2_gama.def.json b/resources/definitions/kemiq_q2_gama.def.json index 1cb1b45c21..64051606f2 100644 --- a/resources/definitions/kemiq_q2_gama.def.json +++ b/resources/definitions/kemiq_q2_gama.def.json @@ -1,5 +1,4 @@ { - "id": "kemiq_q2_gama", "version": 2, "name": "Kemiq Q2 Gama", "inherits": "fdmprinter", diff --git a/resources/definitions/kossel_mini.def.json b/resources/definitions/kossel_mini.def.json index d915c148a3..df9c29b548 100644 --- a/resources/definitions/kossel_mini.def.json +++ b/resources/definitions/kossel_mini.def.json @@ -1,5 +1,4 @@ { - "id": "kossel_mini", "version": 2, "name": "Kossel Mini", "inherits": "fdmprinter", diff --git a/resources/definitions/kossel_pro.def.json b/resources/definitions/kossel_pro.def.json index 58f1c7f94e..fa0cb571fa 100644 --- a/resources/definitions/kossel_pro.def.json +++ b/resources/definitions/kossel_pro.def.json @@ -1,5 +1,4 @@ { - "id": "kossel_pro", "version": 2, "name": "Kossel Pro", "inherits": "fdmprinter", diff --git a/resources/definitions/kupido.def.json b/resources/definitions/kupido.def.json index 8dec63f6ec..b16ba62ccc 100644 --- a/resources/definitions/kupido.def.json +++ b/resources/definitions/kupido.def.json @@ -1,5 +1,4 @@ { - "id": "kupido", "name": "Kupido", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/m180.def.json b/resources/definitions/m180.def.json index 1e8ac1767b..71aa729b7e 100644 --- a/resources/definitions/m180.def.json +++ b/resources/definitions/m180.def.json @@ -1,5 +1,4 @@ { - "id": "m180", "version": 2, "name": "Malyan M180", "inherits": "fdmprinter", diff --git a/resources/definitions/makeR_pegasus.def.json b/resources/definitions/makeR_pegasus.def.json index b164b2983f..fb5a7dce75 100644 --- a/resources/definitions/makeR_pegasus.def.json +++ b/resources/definitions/makeR_pegasus.def.json @@ -1,5 +1,4 @@ { - "id": "makeR_pegasus", "version": 2, "name": "makeR Pegasus", "inherits": "fdmprinter", diff --git a/resources/definitions/makeR_prusa_tairona_i3.def.json b/resources/definitions/makeR_prusa_tairona_i3.def.json index caccb2ebe6..a82fffd254 100644 --- a/resources/definitions/makeR_prusa_tairona_i3.def.json +++ b/resources/definitions/makeR_prusa_tairona_i3.def.json @@ -1,5 +1,4 @@ { - "id": "makeR_prusa_tairona_i3", "version": 2, "name": "makeR Prusa Tairona i3", "inherits": "fdmprinter", diff --git a/resources/definitions/makeit_pro_l.def.json b/resources/definitions/makeit_pro_l.def.json index 36f7354b64..2a64a6729c 100644 --- a/resources/definitions/makeit_pro_l.def.json +++ b/resources/definitions/makeit_pro_l.def.json @@ -1,5 +1,4 @@ { - "id": "makeit_pro_l", "version": 2, "name": "MAKEiT Pro-L", "inherits": "fdmprinter", diff --git a/resources/definitions/makeit_pro_m.def.json b/resources/definitions/makeit_pro_m.def.json index f45e0b6635..43a5440165 100644 --- a/resources/definitions/makeit_pro_m.def.json +++ b/resources/definitions/makeit_pro_m.def.json @@ -1,5 +1,4 @@ { - "id": "makeit_pro_m", "version": 2, "name": "MAKEiT Pro-M", "inherits": "fdmprinter", diff --git a/resources/definitions/maker_starter.def.json b/resources/definitions/maker_starter.def.json index 8358ba0064..e7e6cb5dcd 100644 --- a/resources/definitions/maker_starter.def.json +++ b/resources/definitions/maker_starter.def.json @@ -1,5 +1,4 @@ { - "id": "maker_starter", "version": 2, "name": "3DMaker Starter", "inherits": "fdmprinter", diff --git a/resources/definitions/makerbotreplicator.def.json b/resources/definitions/makerbotreplicator.def.json index 7844976912..3d690990ce 100644 --- a/resources/definitions/makerbotreplicator.def.json +++ b/resources/definitions/makerbotreplicator.def.json @@ -1,5 +1,4 @@ { - "id": "makerbotreplicator", "name": "MakerBotReplicator", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/mankati_fullscale_xt_plus.def.json b/resources/definitions/mankati_fullscale_xt_plus.def.json index 6c3115b1dc..15ba889efc 100644 --- a/resources/definitions/mankati_fullscale_xt_plus.def.json +++ b/resources/definitions/mankati_fullscale_xt_plus.def.json @@ -1,5 +1,4 @@ { - "id": "mankati_fullscale_xt_plus", "version": 2, "name": "Mankati Fullscale XT Plus", "inherits": "fdmprinter", diff --git a/resources/definitions/mendel90.def.json b/resources/definitions/mendel90.def.json index 60f3307758..95f47d7aac 100644 --- a/resources/definitions/mendel90.def.json +++ b/resources/definitions/mendel90.def.json @@ -1,5 +1,4 @@ { - "id": "mendel90", "name": "Mendel90", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/ord.def.json b/resources/definitions/ord.def.json index d9909c4f1f..3238583ceb 100644 --- a/resources/definitions/ord.def.json +++ b/resources/definitions/ord.def.json @@ -1,5 +1,4 @@ { - "id": "ord", "name": "RoVa3D", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/peopoly_moai.def.json b/resources/definitions/peopoly_moai.def.json index 6b0e0ae547..78aca31dae 100644 --- a/resources/definitions/peopoly_moai.def.json +++ b/resources/definitions/peopoly_moai.def.json @@ -1,5 +1,4 @@ { - "id": "peopoly_moai", "version": 2, "name": "Peopoly Moai", "inherits": "fdmprinter", diff --git a/resources/definitions/printrbot_play.def.json b/resources/definitions/printrbot_play.def.json index 452b5e131f..36ef93e60c 100644 --- a/resources/definitions/printrbot_play.def.json +++ b/resources/definitions/printrbot_play.def.json @@ -1,5 +1,4 @@ { - "id": "printrbot_play", "version": 2, "name": "Printrbot Play", "inherits": "fdmprinter", diff --git a/resources/definitions/printrbot_play_heated.def.json b/resources/definitions/printrbot_play_heated.def.json index 02157a0913..9ca365a5c0 100644 --- a/resources/definitions/printrbot_play_heated.def.json +++ b/resources/definitions/printrbot_play_heated.def.json @@ -1,5 +1,4 @@ { - "id": "printrbot_play_heated", "version": 2, "name": "Printrbot Play (Heated Bed)", "inherits": "printrbot_play", diff --git a/resources/definitions/printrbot_simple.def.json b/resources/definitions/printrbot_simple.def.json index eba47c88ae..7c86617ce5 100644 --- a/resources/definitions/printrbot_simple.def.json +++ b/resources/definitions/printrbot_simple.def.json @@ -1,5 +1,4 @@ { - "id": "printrbot_simple", "version": 2, "name": "Printrbot Simple", "inherits": "fdmprinter", diff --git a/resources/definitions/printrbot_simple_extended.def.json b/resources/definitions/printrbot_simple_extended.def.json index b08e0c7d5d..95395efb23 100644 --- a/resources/definitions/printrbot_simple_extended.def.json +++ b/resources/definitions/printrbot_simple_extended.def.json @@ -1,5 +1,4 @@ { - "id": "printrbot_simple_extended", "version": 2, "name": "Printrbot Simple Metal Extended", "inherits": "fdmprinter", diff --git a/resources/definitions/prusa_i3.def.json b/resources/definitions/prusa_i3.def.json index 4f0f5b13d7..4599763397 100644 --- a/resources/definitions/prusa_i3.def.json +++ b/resources/definitions/prusa_i3.def.json @@ -1,5 +1,4 @@ { - "id": "prusa_i3", "version": 2, "name": "Prusa i3", "inherits": "fdmprinter", diff --git a/resources/definitions/prusa_i3_mk2.def.json b/resources/definitions/prusa_i3_mk2.def.json index c8c2cc1363..e58eb4d903 100644 --- a/resources/definitions/prusa_i3_mk2.def.json +++ b/resources/definitions/prusa_i3_mk2.def.json @@ -1,5 +1,4 @@ { - "id": "prusa_i3_mk2", "version": 2, "name": "Prusa i3 Mk2", "inherits": "fdmprinter", diff --git a/resources/definitions/prusa_i3_xl.def.json b/resources/definitions/prusa_i3_xl.def.json index e49838c95f..b27c460554 100644 --- a/resources/definitions/prusa_i3_xl.def.json +++ b/resources/definitions/prusa_i3_xl.def.json @@ -1,5 +1,4 @@ { - "id": "prusa_i3_xl", "version": 2, "name": "Prusa i3 xl", "inherits": "fdmprinter", diff --git a/resources/definitions/punchtec_connect_xl.def.json b/resources/definitions/punchtec_connect_xl.def.json index 16ba59dcb7..02d3562b41 100644 --- a/resources/definitions/punchtec_connect_xl.def.json +++ b/resources/definitions/punchtec_connect_xl.def.json @@ -1,5 +1,4 @@ { - "id": "punchtec_connect_xl", "name": "Punchtec Connect XL", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/raise3D_N2_dual.def.json b/resources/definitions/raise3D_N2_dual.def.json index d3d8f0e651..fe0a2bf3b8 100644 --- a/resources/definitions/raise3D_N2_dual.def.json +++ b/resources/definitions/raise3D_N2_dual.def.json @@ -1,5 +1,4 @@ { - "id": "raise3D_N2_dual", "version": 2, "name": "Raise3D N2 Dual", "inherits": "fdmprinter", @@ -33,7 +32,7 @@ }, "machine_heated_bed": { "default_value": true - }, + }, "machine_nozzle_size": { "default_value": 0.4 }, @@ -55,9 +54,6 @@ "machine_min_cool_heat_time_window": { "default_value": 3600 }, - "machine_nozzle_size": { - "default_value": 0.4 - }, "material_diameter": { "default_value": 1.75 }, diff --git a/resources/definitions/raise3D_N2_plus_dual.def.json b/resources/definitions/raise3D_N2_plus_dual.def.json index f49af40355..bddb2587e2 100644 --- a/resources/definitions/raise3D_N2_plus_dual.def.json +++ b/resources/definitions/raise3D_N2_plus_dual.def.json @@ -1,5 +1,4 @@ { - "id": "raise3D_N2_plus_dual", "version": 2, "name": "Raise3D N2 Plus Dual", "inherits": "fdmprinter", @@ -55,9 +54,6 @@ "machine_min_cool_heat_time_window": { "default_value": 3600 }, - "machine_nozzle_size": { - "default_value": 0.4 - }, "material_diameter": { "default_value": 1.75 }, diff --git a/resources/definitions/raise3D_N2_plus_single.def.json b/resources/definitions/raise3D_N2_plus_single.def.json index 5f10666528..ffcb723a27 100644 --- a/resources/definitions/raise3D_N2_plus_single.def.json +++ b/resources/definitions/raise3D_N2_plus_single.def.json @@ -1,5 +1,4 @@ { - "id": "raise3D_N2_plus_single", "version": 2, "name": "Raise3D N2 Plus Single", "inherits": "fdmprinter", @@ -50,9 +49,6 @@ "machine_min_cool_heat_time_window": { "default_value": 3600 }, - "machine_nozzle_size": { - "default_value": 0.4 - }, "material_diameter": { "default_value": 1.75 }, diff --git a/resources/definitions/raise3D_N2_single.def.json b/resources/definitions/raise3D_N2_single.def.json index 225794130c..1d01e479b1 100644 --- a/resources/definitions/raise3D_N2_single.def.json +++ b/resources/definitions/raise3D_N2_single.def.json @@ -1,5 +1,4 @@ { - "id": "raise3D_N2_single", "version": 2, "name": "Raise3D N2 Single", "inherits": "fdmprinter", @@ -50,9 +49,6 @@ "machine_min_cool_heat_time_window": { "default_value": 3600 }, - "machine_nozzle_size": { - "default_value": 0.4 - }, "material_diameter": { "default_value": 1.75 }, diff --git a/resources/definitions/renkforce_rf100.def.json b/resources/definitions/renkforce_rf100.def.json index 55e764800a..6f6de81643 100644 --- a/resources/definitions/renkforce_rf100.def.json +++ b/resources/definitions/renkforce_rf100.def.json @@ -1,5 +1,4 @@ { - "id": "RF100", "version": 2, "name": "Renkforce RF100", "inherits": "fdmprinter", diff --git a/resources/definitions/rigid3d.def.json b/resources/definitions/rigid3d.def.json index 75e435f880..97b0ebd276 100644 --- a/resources/definitions/rigid3d.def.json +++ b/resources/definitions/rigid3d.def.json @@ -1,5 +1,4 @@ { - "id": "rigid3d", "name": "Rigid3D", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/rigid3d_3rdgen.def.json b/resources/definitions/rigid3d_3rdgen.def.json index 3191817ecd..46c22bfa57 100644 --- a/resources/definitions/rigid3d_3rdgen.def.json +++ b/resources/definitions/rigid3d_3rdgen.def.json @@ -1,5 +1,4 @@ { - "id": "rigid3d_3rdgen", "name": "Rigid3D 3rdGen", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/rigid3d_hobby.def.json b/resources/definitions/rigid3d_hobby.def.json index 02e3cc514c..872cc3e6f4 100644 --- a/resources/definitions/rigid3d_hobby.def.json +++ b/resources/definitions/rigid3d_hobby.def.json @@ -1,5 +1,4 @@ { - "id": "rigid3d_hobby", "name": "Rigid3D Hobby", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/rigid3d_zero.def.json b/resources/definitions/rigid3d_zero.def.json index 7e99112621..56fb8284c0 100644 --- a/resources/definitions/rigid3d_zero.def.json +++ b/resources/definitions/rigid3d_zero.def.json @@ -1,5 +1,4 @@ { - "id": "rigid3d_zero", "name": "Rigid3D Zero", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/rigid3d_zero2.def.json b/resources/definitions/rigid3d_zero2.def.json index e2a77db895..27ceb87c29 100644 --- a/resources/definitions/rigid3d_zero2.def.json +++ b/resources/definitions/rigid3d_zero2.def.json @@ -1,5 +1,4 @@ { - "id": "rigid3d_zero2", "name": "Rigid3D Zero2", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/rigidbot.def.json b/resources/definitions/rigidbot.def.json index d183554947..bed10025d7 100644 --- a/resources/definitions/rigidbot.def.json +++ b/resources/definitions/rigidbot.def.json @@ -1,5 +1,4 @@ { - "id": "rigidbot", "version": 2, "name": "RigidBot", "inherits": "fdmprinter", diff --git a/resources/definitions/rigidbot_big.def.json b/resources/definitions/rigidbot_big.def.json index 33c4fd3d27..08cf3b2ab8 100644 --- a/resources/definitions/rigidbot_big.def.json +++ b/resources/definitions/rigidbot_big.def.json @@ -1,5 +1,4 @@ { - "id": "rigidbotbig", "version": 2, "name": "RigidBotBig", "inherits": "fdmprinter", diff --git a/resources/definitions/robo_3d_r1.def.json b/resources/definitions/robo_3d_r1.def.json index 88b3fba01f..b179779c59 100644 --- a/resources/definitions/robo_3d_r1.def.json +++ b/resources/definitions/robo_3d_r1.def.json @@ -1,5 +1,4 @@ { - "id": "robo_3d_r1", "name": "Robo 3D R1", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/tam.def.json b/resources/definitions/tam.def.json index 93ebe43ea6..20bc96358d 100644 --- a/resources/definitions/tam.def.json +++ b/resources/definitions/tam.def.json @@ -1,5 +1,4 @@ { - "id": "typeamachines", "version": 2, "name": "Type A Machines Series 1 2014", "inherits": "fdmprinter", diff --git a/resources/definitions/tevo_tarantula.def.json b/resources/definitions/tevo_tarantula.def.json index 29bfae77a4..097281aaeb 100644 --- a/resources/definitions/tevo_tarantula.def.json +++ b/resources/definitions/tevo_tarantula.def.json @@ -1,5 +1,4 @@ { - "id": "tevo_tarantula", "version": 2, "name": "Tevo Tarantula", "inherits": "fdmprinter", diff --git a/resources/definitions/ultimaker.def.json b/resources/definitions/ultimaker.def.json index 797a05e324..1ca7444d04 100644 --- a/resources/definitions/ultimaker.def.json +++ b/resources/definitions/ultimaker.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker_base", "version": 2, "name": "Ultimaker", "inherits": "fdmprinter", diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index 6974e61938..038071574f 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker2", "version": 2, "name": "Ultimaker 2", "inherits": "ultimaker", diff --git a/resources/definitions/ultimaker2_extended.def.json b/resources/definitions/ultimaker2_extended.def.json index 16f9e6f25e..94667b154d 100644 --- a/resources/definitions/ultimaker2_extended.def.json +++ b/resources/definitions/ultimaker2_extended.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker2_extended", "version": 2, "name": "Ultimaker 2 Extended", "inherits": "ultimaker2", diff --git a/resources/definitions/ultimaker2_extended_plus.def.json b/resources/definitions/ultimaker2_extended_plus.def.json index 525aaf5b72..adde89d858 100644 --- a/resources/definitions/ultimaker2_extended_plus.def.json +++ b/resources/definitions/ultimaker2_extended_plus.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker2_extended_plus", "version": 2, "name": "Ultimaker 2 Extended+", "inherits": "ultimaker2_plus", diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json index 9fb94022ce..e98381a7d7 100644 --- a/resources/definitions/ultimaker2_go.def.json +++ b/resources/definitions/ultimaker2_go.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker2_go", "version": 2, "name": "Ultimaker 2 Go", "inherits": "ultimaker2", diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json index 7214e6b7fe..58833904d2 100644 --- a/resources/definitions/ultimaker2_plus.def.json +++ b/resources/definitions/ultimaker2_plus.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker2_plus", "version": 2, "name": "Ultimaker 2+", "inherits": "ultimaker2", diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index 05e39e365c..e7b9b6255e 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker3", "version": 2, "name": "Ultimaker 3", "inherits": "ultimaker", diff --git a/resources/definitions/ultimaker3_extended.def.json b/resources/definitions/ultimaker3_extended.def.json index e47ccf4ba3..385199f4f1 100644 --- a/resources/definitions/ultimaker3_extended.def.json +++ b/resources/definitions/ultimaker3_extended.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker3_extended", "version": 2, "name": "Ultimaker 3 Extended", "inherits": "ultimaker3", diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json index ef2f5fae5c..a0a486429b 100644 --- a/resources/definitions/ultimaker_original.def.json +++ b/resources/definitions/ultimaker_original.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker_original", "version": 2, "name": "Ultimaker Original", "inherits": "ultimaker", diff --git a/resources/definitions/ultimaker_original_dual.def.json b/resources/definitions/ultimaker_original_dual.def.json index 8745434360..a1dbea6b6d 100644 --- a/resources/definitions/ultimaker_original_dual.def.json +++ b/resources/definitions/ultimaker_original_dual.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker_original_dual", "version": 2, "name": "Ultimaker Original Dual Extrusion", "inherits": "ultimaker", diff --git a/resources/definitions/ultimaker_original_plus.def.json b/resources/definitions/ultimaker_original_plus.def.json index 8e401d550c..115e1e2752 100644 --- a/resources/definitions/ultimaker_original_plus.def.json +++ b/resources/definitions/ultimaker_original_plus.def.json @@ -1,5 +1,4 @@ { - "id": "ultimaker_original_plus", "version": 2, "name": "Ultimaker Original+", "inherits": "ultimaker_original", diff --git a/resources/definitions/uniqbot_one.def.json b/resources/definitions/uniqbot_one.def.json index 410f7e57a6..ad14728269 100644 --- a/resources/definitions/uniqbot_one.def.json +++ b/resources/definitions/uniqbot_one.def.json @@ -1,5 +1,4 @@ { - "id": "uniqbot_one", "version": 2, "name": "Uniqbot", "inherits": "fdmprinter", diff --git a/resources/definitions/vertex_delta_k8800.def.json b/resources/definitions/vertex_delta_k8800.def.json index 0a89b61b36..736d17e17a 100644 --- a/resources/definitions/vertex_delta_k8800.def.json +++ b/resources/definitions/vertex_delta_k8800.def.json @@ -1,5 +1,4 @@ { - "id": "K8800", "name": "Vertex Delta K8800", "version": 2, "inherits": "fdmprinter", diff --git a/resources/definitions/vertex_k8400.def.json b/resources/definitions/vertex_k8400.def.json index a23e1fc893..8abf9acd7f 100644 --- a/resources/definitions/vertex_k8400.def.json +++ b/resources/definitions/vertex_k8400.def.json @@ -1,5 +1,4 @@ { - "id": "vertex_k8400", "version": 2, "name": "Vertex K8400", "inherits": "fdmprinter", diff --git a/resources/definitions/vertex_k8400_dual.def.json b/resources/definitions/vertex_k8400_dual.def.json index 9e24bab5d3..fc006a1d5f 100644 --- a/resources/definitions/vertex_k8400_dual.def.json +++ b/resources/definitions/vertex_k8400_dual.def.json @@ -1,5 +1,4 @@ { - "id": "vertex_k8400_dual", "version": 2, "name": "Vertex K8400 Dual", "inherits": "fdmprinter", diff --git a/resources/definitions/zone3d_printer.def.json b/resources/definitions/zone3d_printer.def.json index a1ed56c7ef..bac8968951 100644 --- a/resources/definitions/zone3d_printer.def.json +++ b/resources/definitions/zone3d_printer.def.json @@ -1,5 +1,4 @@ { - "id": "zone3d_printer", "name": "Zone3d Printer", "version": 2, "inherits": "fdmprinter", diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 054a6bed09..aa185f8615 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -18,6 +18,7 @@ Item property alias redo: redoAction; property alias homeCamera: homeCameraAction; + property alias expandSidebar: expandSidebarAction; property alias deleteSelection: deleteSelectionAction; property alias centerSelection: centerSelectionAction; @@ -389,4 +390,11 @@ Item text: catalog.i18nc("@action:menu", "Installed plugins..."); iconName: "plugins_configure" } + + Action + { + id: expandSidebarAction; + text: catalog.i18nc("@action:inmenu menubar:view","Expand/Collapse Sidebar"); + shortcut: "Ctrl+E"; + } } diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index aedf5d0fa8..91098bbb29 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -24,7 +24,7 @@ UM.MainWindow // It should be phased out in newer plugin versions. Connections { - target: Printer + target: CuraApplication onShowPrintMonitor: { if (show) { UM.Controller.setActiveStage("MonitorStage") @@ -34,6 +34,24 @@ UM.MainWindow } } + onWidthChanged: + { + // If slidebar is collapsed then it should be invisible + // otherwise after the main_window resize the sidebar will be fully re-drawn + if (sidebar.collapsed){ + if (sidebar.visible == true){ + sidebar.visible = false + sidebar.initialWidth = 0 + } + } + else{ + if (sidebar.visible == false){ + sidebar.visible = true + sidebar.initialWidth = UM.Theme.getSize("sidebar").width + } + } + } + Component.onCompleted: { CuraApplication.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size")) @@ -369,17 +387,59 @@ UM.MainWindow { id: sidebar - anchors - { - top: topbar.bottom - bottom: parent.bottom - right: parent.right + property bool collapsed: false; + property var initialWidth: UM.Theme.getSize("sidebar").width; + + function callExpandOrCollapse() { + if (collapsed) { + sidebar.visible = true; + sidebar.initialWidth = UM.Theme.getSize("sidebar").width; + viewportRect = Qt.rect(0, 0, (base.width - sidebar.width) / base.width, 1.0); + expandSidebarAnimation.start(); + } else { + viewportRect = Qt.rect(0, 0, 1, 1.0); + collapseSidebarAnimation.start(); + } + collapsed = !collapsed; + UM.Preferences.setValue("cura/sidebar_collapse", collapsed); } - width: UM.Theme.getSize("sidebar").width - z: 1 + anchors + { + top: topbar.top + bottom: parent.bottom + } + width: initialWidth + x: base.width - sidebar.width source: UM.Controller.activeStage.sidebarComponent + + NumberAnimation { + id: collapseSidebarAnimation + target: sidebar + properties: "x" + to: base.width + duration: 100 + } + + NumberAnimation { + id: expandSidebarAnimation + target: sidebar + properties: "x" + to: base.width - sidebar.width + duration: 100 + } + + Component.onCompleted: + { + var sidebarCollapsed = UM.Preferences.getValue("cura/sidebar_collapse"); + + if (sidebarCollapsed) { + sidebar.collapsed = true; + viewportRect = Qt.rect(0, 0, 1, 1.0) + collapseSidebarAnimation.start(); + } + } } Loader @@ -418,6 +478,13 @@ UM.MainWindow } } + // Expand or collapse sidebar + Connections + { + target: Cura.Actions.expandSidebar + onTriggered: sidebar.callExpandOrCollapse() + } + UM.PreferencesDialog { id: preferences @@ -819,7 +886,7 @@ UM.MainWindow Connections { - target: Printer + target: CuraApplication onShowMessageBox: { messageDialog.title = title @@ -865,7 +932,7 @@ UM.MainWindow Connections { - target: Printer + target: CuraApplication onRequestAddPrinter: { addMachineDialog.visible = true diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml index a6666b67f4..c1a1eea7a7 100644 --- a/resources/qml/Menus/MaterialMenu.qml +++ b/resources/qml/Menus/MaterialMenu.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Ultimaker B.V. +// Copyright (c) 2017 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -157,7 +157,7 @@ Menu } //: Model used to populate the brandModel - UM.InstanceContainersModel + Cura.MaterialsModel { id: materialsModel filter: materialFilter() diff --git a/resources/qml/Menus/ViewMenu.qml b/resources/qml/Menus/ViewMenu.qml index a610bb0009..c8243ad2ed 100644 --- a/resources/qml/Menus/ViewMenu.qml +++ b/resources/qml/Menus/ViewMenu.qml @@ -32,4 +32,5 @@ Menu MenuSeparator {} MenuItem { action: Cura.Actions.homeCamera; } + MenuItem { action: Cura.Actions.expandSidebar; } } diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml index dc978c0f7a..a8e25155e1 100644 --- a/resources/qml/Preferences/MachinesPage.qml +++ b/resources/qml/Preferences/MachinesPage.qml @@ -222,14 +222,19 @@ UM.ManagementPage } } + Component.onCompleted: { + addAdditionalComponents("machinesDetailPane") + } + Connections { - target: Printer - onAdditionalComponentsChanged: - { - if(areaId == "machinesDetailPane") { - for (var component in CuraApplication.additionalComponents["machinesDetailPane"]) { - CuraApplication.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn - } + target: CuraApplication + onAdditionalComponentsChanged: addAdditionalComponents + } + + function addAdditionalComponents (areaId) { + if(areaId == "machinesDetailPane") { + for (var component in CuraApplication.additionalComponents["machinesDetailPane"]) { + CuraApplication.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn } } } diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index 143f29c86e..311150c6b9 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -41,7 +41,7 @@ TabView Tab { - title: catalog.i18nc("@title","Information") + title: catalog.i18nc("@title", "Information") anchors.margins: UM.Theme.getSize("default_margin").width diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index cd04b79b20..c33cdbfc89 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -182,6 +182,7 @@ UM.ManagementPage { Cura.MachineManager.setActiveMaterial(material_id) } + // TODO: this doesn't work because the source is a bit delayed base.objectList.currentIndex = base.getIndexById(material_id); } }, @@ -292,10 +293,16 @@ UM.ManagementPage base_file = base.currentItem.id } var guid = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "GUID") + // remove base container first, it otherwise triggers loading the base file while removing other containers + var base_containers = Cura.ContainerManager.findInstanceContainers({"GUID": guid, "id": base_file, "base_file": base_file, "type": "material"}) + for(var i in base_containers) + { + Cura.ContainerManager.removeContainer(base_containers[i]); + } var containers = Cura.ContainerManager.findInstanceContainers({"GUID": guid, "base_file": base_file, "type": "material"}) for(var i in containers) { - Cura.ContainerManager.removeContainer(containers[i]) + Cura.ContainerManager.removeContainer(containers[i]); } if(base.objectList.currentIndex > 0) { diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index 390b868a9b..d53b43e459 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -12,11 +12,10 @@ Item { id: base; UM.I18nCatalog { id: catalog; name:"cura"} - property real progress: UM.Backend.progress; - property int backendState: UM.Backend.state; - - property var backend: CuraApplication.getBackend(); - property bool activity: CuraApplication.platformActivity; + property real progress: UM.Backend.progress + property int backendState: UM.Backend.state + property var backend: CuraApplication.getBackend() + property bool activity: CuraApplication.platformActivity property alias buttonRowWidth: saveRow.width @@ -28,6 +27,10 @@ Item { return catalog.i18nc("@label:PrintjobStatus", "Please load a 3D model"); } + if (base.backendState == "undefined") { + return "" + } + switch(base.backendState) { case 1: @@ -46,10 +49,12 @@ Item { } function sliceOrStopSlicing() { - if (backend != "undefined" && [1, 5].indexOf(UM.Backend.state) != -1) { - backend.forceSlice(); - } else { - backend.stopSlicing(); + if (base.backendState != "undefined" && backend !== "undefined") { + if ([1, 5].indexOf(base.backendState) != -1) { + backend.forceSlice(); + } else { + backend.stopSlicing(); + } } } @@ -81,7 +86,7 @@ Item { height: parent.height color: UM.Theme.getColor("progressbar_control") radius: UM.Theme.getSize("progressbar_radius").width - visible: base.backendState == 2 ? true : false + visible: (base.backendState != "undefined" && base.backendState == 2) ? true : false } } @@ -131,14 +136,19 @@ Item { spacing: UM.Theme.getSize("default_margin").width } + Component.onCompleted: { + addAdditionalComponents("saveButton") + } + Connections { - target: Printer - onAdditionalComponentsChanged: - { - if(areaId == "saveButton") { - for (var component in CuraApplication.additionalComponents["saveButton"]) { - CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow - } + target: CuraApplication + onAdditionalComponentsChanged: addAdditionalComponents + } + + function addAdditionalComponents (areaId) { + if(areaId == "saveButton") { + for (var component in CuraApplication.additionalComponents["saveButton"]) { + CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow } } } @@ -157,12 +167,10 @@ Item { Button { id: prepareButton - tooltip: [1, 5].indexOf(UM.Backend.state) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process") + tooltip: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process") // 1 = not started, 2 = Processing - enabled: (base.backendState == 1 || base.backendState == 2) && base.activity == true - visible: { - return !autoSlice && (base.backendState == 1 || base.backendState == 2) && base.activity == true; - } + enabled: base.backendState != "undefined" && (base.backendState == 1 || base.backendState == 2) && base.activity == true + visible: base.backendState != "undefined" && !autoSlice && (base.backendState == 1 || base.backendState == 2) && base.activity == true property bool autoSlice height: UM.Theme.getSize("save_button_save_to_button").height @@ -171,7 +179,7 @@ Item { anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width // 1 = not started, 5 = disabled - text: [1, 5].indexOf(UM.Backend.state) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel") + text: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel") onClicked: { sliceOrStopSlicing(); @@ -235,10 +243,8 @@ Item { tooltip: UM.OutputDeviceManager.activeDeviceDescription; // 3 = done, 5 = disabled - enabled: (base.backendState == 3 || base.backendState == 5) && base.activity == true - visible: { - return autoSlice || ((base.backendState == 3 || base.backendState == 5) && base.activity == true); - } + enabled: base.backendState != "undefined" && (base.backendState == 3 || base.backendState == 5) && base.activity == true + visible: base.backendState != "undefined" && autoSlice || ((base.backendState == 3 || base.backendState == 5) && base.activity == true) property bool autoSlice height: UM.Theme.getSize("save_button_save_to_button").height @@ -315,8 +321,8 @@ Item { width: UM.Theme.getSize("save_button_save_to_button").height height: UM.Theme.getSize("save_button_save_to_button").height // 3 = Done, 5 = Disabled - enabled: (base.backendState == 3 || base.backendState == 5) && base.activity == true - visible: (devicesModel.deviceCount > 1) && (base.backendState == 3 || base.backendState == 5) && base.activity == true + enabled: base.backendState != "undefined" && (base.backendState == 3 || base.backendState == 5) && base.activity == true + visible: base.backendState != "undefined" && (devicesModel.deviceCount > 1) && (base.backendState == 3 || base.backendState == 5) && base.activity == true style: ButtonStyle { diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index a459b481db..a45303aab8 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -87,10 +87,77 @@ Rectangle } } + ToolButton + { + id: machineSelection + text: Cura.MachineManager.activeMachineName + + width: base.width + height: UM.Theme.getSize("sidebar_header").height + tooltip: Cura.MachineManager.activeMachineName + + anchors.top: base.top + //anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + style: ButtonStyle + { + background: Rectangle + { + color: + { + if(control.pressed) + { + return UM.Theme.getColor("sidebar_header_active"); + } + else if(control.hovered) + { + return UM.Theme.getColor("sidebar_header_hover"); + } + else + { + return UM.Theme.getColor("sidebar_header_bar"); + } + } + Behavior on color { ColorAnimation { duration: 50; } } + + UM.RecolorImage + { + id: downArrow + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("default_margin").width + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + sourceSize.height: width + color: UM.Theme.getColor("text_emphasis") + source: UM.Theme.getIcon("arrow_bottom") + } + Label + { + id: sidebarComboBoxLabel + color: UM.Theme.getColor("sidebar_header_text_active") + text: control.text; + elide: Text.ElideRight; + anchors.left: parent.left; + anchors.leftMargin: UM.Theme.getSize("default_margin").width * 2 + anchors.right: downArrow.left; + anchors.rightMargin: control.rightMargin; + anchors.verticalCenter: parent.verticalCenter; + font: UM.Theme.getFont("large") + } + } + label: Label {} + } + + menu: PrinterMenu { } + } + SidebarHeader { id: header width: parent.width visible: machineExtruderCount.properties.value > 1 || Cura.MachineManager.hasMaterials || Cura.MachineManager.hasVariants + anchors.top: machineSelection.bottom onShowTooltip: base.showTooltip(item, location, text) onHideTooltip: base.hideTooltip() @@ -549,7 +616,6 @@ Rectangle visible: monitoringPrint } - SidebarTooltip { id: tooltip; diff --git a/resources/qml/Topbar.qml b/resources/qml/Topbar.qml index 0a475f8854..96473ae316 100644 --- a/resources/qml/Topbar.qml +++ b/resources/qml/Topbar.qml @@ -21,6 +21,22 @@ Rectangle property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands + property int rightMargin: UM.Theme.getSize("sidebar").width + UM.Theme.getSize("default_margin").width; + property int allItemsWidth: 0; + + function updateMarginsAndSizes() { + if (UM.Preferences.getValue("cura/sidebar_collapse")) { + rightMargin = UM.Theme.getSize("default_margin").width; + } else { + rightMargin = UM.Theme.getSize("sidebar").width + UM.Theme.getSize("default_margin").width; + } + allItemsWidth = ( + logo.width + UM.Theme.getSize("topbar_logo_right_margin").width + + UM.Theme.getSize("topbar_logo_right_margin").width + stagesMenuContainer.width + + UM.Theme.getSize("default_margin").width + viewModeButton.width + + rightMargin); + } + UM.I18nCatalog { id: catalog @@ -44,10 +60,9 @@ Rectangle Row { + id: stagesMenuContainer anchors.left: logo.right anchors.leftMargin: UM.Theme.getSize("topbar_logo_right_margin").width - anchors.right: machineSelection.left - anchors.rightMargin: UM.Theme.getSize("default_margin").width spacing: UM.Theme.getSize("default_margin").width // The topbar is dynamically filled with all available stages @@ -76,71 +91,6 @@ Rectangle ExclusiveGroup { id: topbarMenuGroup } } - ToolButton - { - id: machineSelection - text: Cura.MachineManager.activeMachineName - - width: UM.Theme.getSize("sidebar").width - height: UM.Theme.getSize("sidebar_header").height - tooltip: Cura.MachineManager.activeMachineName - - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - style: ButtonStyle - { - background: Rectangle - { - color: - { - if(control.pressed) - { - return UM.Theme.getColor("sidebar_header_active"); - } - else if(control.hovered) - { - return UM.Theme.getColor("sidebar_header_hover"); - } - else - { - return UM.Theme.getColor("sidebar_header_bar"); - } - } - Behavior on color { ColorAnimation { duration: 50; } } - - UM.RecolorImage - { - id: downArrow - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: UM.Theme.getSize("default_margin").width - width: UM.Theme.getSize("standard_arrow").width - height: UM.Theme.getSize("standard_arrow").height - sourceSize.width: width - sourceSize.height: width - color: UM.Theme.getColor("text_emphasis") - source: UM.Theme.getIcon("arrow_bottom") - } - Label - { - id: sidebarComboBoxLabel - color: UM.Theme.getColor("sidebar_header_text_active") - text: control.text; - elide: Text.ElideRight; - anchors.left: parent.left; - anchors.leftMargin: UM.Theme.getSize("default_margin").width * 2 - anchors.right: downArrow.left; - anchors.rightMargin: control.rightMargin; - anchors.verticalCenter: parent.verticalCenter; - font: UM.Theme.getFont("large") - } - } - label: Label {} - } - - menu: PrinterMenu { } - } - // View orientation Item Row { @@ -153,8 +103,8 @@ Rectangle anchors { verticalCenter: base.verticalCenter - right: viewModeButton.right - rightMargin: UM.Theme.getSize("default_margin").width + viewModeButton.width + right: viewModeButton.left + rightMargin: UM.Theme.getSize("default_margin").width } // #1 3d view @@ -166,7 +116,7 @@ Rectangle onClicked:{ UM.Controller.rotateView("3d", 0); } - visible: base.width > 1100 + visible: base.width - allItemsWidth - 4 * this.width > 0; } // #2 Front view @@ -178,7 +128,7 @@ Rectangle onClicked:{ UM.Controller.rotateView("home", 0); } - visible: base.width > 1130 + visible: base.width - allItemsWidth - 3 * this.width > 0; } // #3 Top view @@ -190,7 +140,7 @@ Rectangle onClicked:{ UM.Controller.rotateView("y", 90); } - visible: base.width > 1160 + visible: base.width - allItemsWidth - 2 * this.width > 0; } // #4 Left view @@ -202,7 +152,7 @@ Rectangle onClicked:{ UM.Controller.rotateView("x", 90); } - visible: base.width > 1190 + visible: base.width - allItemsWidth - 1 * this.width > 0; } // #5 Left view @@ -214,7 +164,7 @@ Rectangle onClicked:{ UM.Controller.rotateView("x", -90); } - visible: base.width > 1210 + visible: base.width - allItemsWidth > 0; } } @@ -225,7 +175,7 @@ Rectangle anchors { verticalCenter: parent.verticalCenter right: parent.right - rightMargin: UM.Theme.getSize("sidebar").width + UM.Theme.getSize("default_margin").width + rightMargin: rightMargin } style: UM.Theme.styles.combobox @@ -285,4 +235,16 @@ Rectangle source: UM.ActiveView.valid ? UM.ActiveView.activeViewPanel : ""; } + // Expand or collapse sidebar + Connections + { + target: Cura.Actions.expandSidebar + onTriggered: updateMarginsAndSizes() + } + + Component.onCompleted: + { + updateMarginsAndSizes(); + } + } diff --git a/tests/Settings/TestCuraContainerRegistry.py b/tests/Settings/TestCuraContainerRegistry.py index a6d33afc6d..e6dc6b99d8 100644 --- a/tests/Settings/TestCuraContainerRegistry.py +++ b/tests/Settings/TestCuraContainerRegistry.py @@ -74,7 +74,7 @@ def test_addContainerGoodSettingVersion(container_registry, definition_container instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance") instance.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - instance.setDefinition(definition_container) + instance.setDefinition(definition_container.getId()) mock_super_add_container = unittest.mock.MagicMock() #Take the role of the Uranium-ContainerRegistry where the resulting containers get registered. with unittest.mock.patch("UM.Settings.ContainerRegistry.ContainerRegistry.addContainer", mock_super_add_container): @@ -89,7 +89,7 @@ def test_addContainerNoSettingVersion(container_registry, definition_container): instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance") #Don't add setting_version metadata. - instance.setDefinition(definition_container) + instance.setDefinition(definition_container.getId()) mock_super_add_container = unittest.mock.MagicMock() #Take the role of the Uranium-ContainerRegistry where the resulting container should not get registered. with unittest.mock.patch("UM.Settings.ContainerRegistry.ContainerRegistry.addContainer", mock_super_add_container): @@ -104,7 +104,7 @@ def test_addContainerBadSettingVersion(container_registry, definition_container) instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance") instance.addMetaDataEntry("setting_version", 9001) #Wrong version! - instance.setDefinition(definition_container) + instance.setDefinition(definition_container.getId()) mock_super_add_container = unittest.mock.MagicMock() #Take the role of the Uranium-ContainerRegistry where the resulting container should not get registered. with unittest.mock.patch("UM.Settings.ContainerRegistry.ContainerRegistry.addContainer", mock_super_add_container): @@ -140,45 +140,9 @@ def test_loadTypes(filename, output_class, container_registry): #Check whether the resulting type was correct. stack_id = filename.split(".")[0] - for container in container_registry._containers: #Stupid ContainerRegistry class doesn't expose any way of getting at this except by prodding the privates. - if container.getId() == stack_id: #This is the one we're testing. + for container_id, container in container_registry._containers.items(): #Stupid ContainerRegistry class doesn't expose any way of getting at this except by prodding the privates. + if container_id == stack_id: #This is the one we're testing. assert type(container) == output_class break else: - assert False #Container stack with specified ID was not loaded. - -## Tests whether loading a legacy file moves the upgraded file properly. -def test_loadLegacyFileRenamed(container_registry): - #Create a temporary file for the registry to load. - stacks_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks") - temp_file = os.path.join(stacks_folder, "temporary.stack.cfg") - temp_file_source = os.path.join(stacks_folder, "MachineLegacy.stack.cfg") - shutil.copyfile(temp_file_source, temp_file) - - #Mock some dependencies. - UM.Settings.ContainerStack.setContainerRegistry(container_registry) - Resources.getAllResourcesOfType = unittest.mock.MagicMock(return_value = [temp_file]) #Return a temporary file that we'll make for this test. - - def findContainers(container_type = 0, id = None): - if id == "MachineLegacy": - return None - - container = UM.Settings.ContainerRegistry._EmptyInstanceContainer(id) - container.getNextStack = unittest.mock.MagicMock() - return [container] - - old_find_containers = container_registry.findContainers - container_registry.findContainers = findContainers - - with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"): - container_registry.load() - - container_registry.findContainers = old_find_containers - - container_registry.saveAll() - print("all containers in registry", container_registry._containers) - assert not os.path.isfile(temp_file) - mime_type = container_registry.getMimeTypeForContainer(GlobalStack) - file_name = urllib.parse.quote_plus("MachineLegacy") + "." + mime_type.preferredSuffix - path = Resources.getStoragePath(Resources.ContainerStacks, file_name) - assert os.path.isfile(path) + assert False #Container stack with specified ID was not loaded. \ No newline at end of file