diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 95e5f9e8bf..264f7bbd8b 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -287,7 +287,8 @@ class MaterialManager(QObject): if root_material_id is not None: self.removeMaterialByRootId(root_material_id) - def duplicateMaterialByRootId(self, root_material_id, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]: + def duplicateMaterialByRootId(self, root_material_id: str, new_base_id: Optional[str] = None, + new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]: container_registry = CuraContainerRegistry.getInstance() results = container_registry.findContainers(id=root_material_id) @@ -346,12 +347,14 @@ class MaterialManager(QObject): # Returns the root material ID of the duplicated material if successful. # @pyqtSlot("QVariant", result = str) - def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]: + def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, + new_metadata: Optional[Dict[str, Any]] = None) -> str: if material_node.container is None: Logger.log("e", "Material node {0} doesn't have container.".format(material_node.container_id)) return "ERROR" root_material_id = cast(str, material_node.container.getMetaDataEntry("base_file", "")) - return self.duplicateMaterialByRootId(root_material_id, new_base_id, new_metadata) + new_material_id = self.duplicateMaterialByRootId(root_material_id, new_base_id, new_metadata) + return new_material_id if new_material_id is not None else "ERROR" # Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID. # Returns the ID of the newly created material. diff --git a/cura/Machines/Models/IntentModel.py b/cura/Machines/Models/IntentModel.py index e4b297093f..8773a12fd3 100644 --- a/cura/Machines/Models/IntentModel.py +++ b/cura/Machines/Models/IntentModel.py @@ -74,6 +74,8 @@ class IntentModel(ListModel): active_material_node = active_variant_node.materials[active_extruder.material.getMetaDataEntry("base_file")] layer_heights_added = [] for quality_id, quality_node in active_material_node.qualities.items(): + if quality_node.quality_type not in quality_groups: # Don't add the empty quality type (or anything else that would crash, defensively). + continue quality_group = quality_groups[quality_node.quality_type] layer_height = self._fetchLayerHeight(quality_group) diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py index 7cd9f39c7d..2311e80689 100644 --- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py @@ -12,7 +12,7 @@ import cura.CuraApplication # Imported this way to prevent circular dependencie from cura.Machines.ContainerTree import ContainerTree if TYPE_CHECKING: - from cura.Machines import QualityGroup + from cura.Machines.QualityGroup import QualityGroup # diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 0688a2222d..140412076e 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -87,14 +87,15 @@ class ContainerManager(QObject): Logger.log("w", "Container node {0} doesn't have a container.".format(container_node.container_id)) return False root_material_id = container_node.container.getMetaDataEntry("base_file", "") - if cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().isReadOnly(root_material_id): + container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() + if container_registry.isReadOnly(root_material_id): Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id) return False - - material_group = MaterialManager.getInstance().getMaterialGroup(root_material_id) - if material_group is None: - Logger.log("w", "Unable to find material group for: %s.", root_material_id) + root_material_query = container_registry.findContainers(id = root_material_id) + if not root_material_query: + Logger.log("w", "Unable to find root material: {root_material}.".format(root_material = root_material_id)) return False + root_material = root_material_query[0] entries = entry_name.split("/") entry_name = entries.pop() @@ -102,7 +103,7 @@ class ContainerManager(QObject): sub_item_changed = False if entries: root_name = entries.pop(0) - root = material_group.root_material_node.container.getMetaDataEntry(root_name) + root = root_material.getMetaDataEntry(root_name) item = root for _ in range(len(entries)): @@ -115,11 +116,9 @@ class ContainerManager(QObject): entry_name = root_name entry_value = root - container = material_group.root_material_node.container - if container is not None: - container.setMetaDataEntry(entry_name, entry_value) - if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed. - container.metaDataChanged.emit(container) + root_material.setMetaDataEntry(entry_name, entry_value) + if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed. + root_material.metaDataChanged.emit(root_material) return True @pyqtSlot(str, result = str) @@ -355,11 +354,11 @@ class ContainerManager(QObject): if material_node.container is None: Logger.log("w", "Material node {0} doesn't have a container.".format(material_node.container_id)) return - material_group = MaterialManager.getInstance().getMaterialGroup(material_node.container.getMetaDataEntry("base_file", "")) - - if material_group is None: + root_material_query = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().findInstanceContainers(id = material_node.getMetaDataEntry("base_file", "")) + if not root_material_query: Logger.log("w", "Unable to find material group for %s", material_node) return + root_material = root_material_query[0] # Generate a new GUID new_guid = str(uuid.uuid4()) @@ -367,9 +366,7 @@ class ContainerManager(QObject): # Update the GUID # NOTE: We only need to set the root material container because XmlMaterialProfile.setMetaDataEntry() will # take care of the derived containers too - container = material_group.root_material_node.container - if container is not None: - container.setMetaDataEntry("GUID", new_guid) + root_material.setMetaDataEntry("GUID", new_guid) def _performMerge(self, merge_into: InstanceContainer, merge: InstanceContainer, clear_settings: bool = True) -> None: if merge == merge_into: diff --git a/cura/Settings/cura_empty_instance_containers.py b/cura/Settings/cura_empty_instance_containers.py index a09537272d..b142c53c11 100644 --- a/cura/Settings/cura_empty_instance_containers.py +++ b/cura/Settings/cura_empty_instance_containers.py @@ -25,6 +25,9 @@ EMPTY_MATERIAL_CONTAINER_ID = "empty_material" empty_material_container = copy.deepcopy(empty_container) empty_material_container.setMetaDataEntry("id", EMPTY_MATERIAL_CONTAINER_ID) empty_material_container.setMetaDataEntry("type", "material") +empty_material_container.setMetaDataEntry("base_file", "empty_material") +empty_material_container.setMetaDataEntry("GUID", "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") +empty_material_container.setMetaDataEntry("material", "empty") # Empty quality EMPTY_QUALITY_CONTAINER_ID = "empty_quality" diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 37be9cfe2d..0d1118ed64 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -942,7 +942,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): extruder_info = self._machine_info.extruder_info_dict[position] if extruder_info.variant_info is None: # If there is no variant_info, try to use the default variant. Otherwise, leave it be. - node = variant_manager.getDefaultVariantNode(global_stack.definition, VariantType.NOZZLE, global_stack) + machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] + node = machine_node.variants[machine_node.preferred_variant_name] if node is not None and node.container is not None: extruder_stack.variant = node.container continue @@ -951,7 +952,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): variant_name = parser["general"]["name"] variant_type = VariantType.NOZZLE - node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) + node = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[variant_name] if node is not None and node.container is not None: extruder_stack.variant = node.container diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index 1e6b2e80cc..d698cbfab7 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -1,4 +1,4 @@ -#Copyright (c) 2018 Ultimaker B.V. +#Copyright (c) 2019 Ultimaker B.V. #Cura is released under the terms of the LGPLv3 or higher. from typing import cast @@ -7,13 +7,13 @@ from Charon.VirtualFile import VirtualFile #To open UFP files. from Charon.OpenMode import OpenMode #To indicate that we want to write to UFP files. from io import StringIO #For converting g-code to bytes. -from UM.Application import Application from UM.Logger import Logger from UM.Mesh.MeshWriter import MeshWriter #The writer we need to implement. from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.PluginRegistry import PluginRegistry #To get the g-code writer. from PyQt5.QtCore import QBuffer +from cura.CuraApplication import CuraApplication from cura.Snapshot import Snapshot from cura.Utils.Threading import call_on_qt_thread @@ -79,9 +79,9 @@ class UFPWriter(MeshWriter): Logger.log("d", "Thumbnail not created, cannot save it") # Store the material. - application = Application.getInstance() + application = CuraApplication.getInstance() machine_manager = application.getMachineManager() - material_manager = application.getMaterialManager() + container_registry = application.getContainerRegistry() global_stack = machine_manager.activeMachine material_extension = "xml.fdm_material" @@ -107,12 +107,12 @@ class UFPWriter(MeshWriter): continue material_root_id = material.getMetaDataEntry("base_file") - material_group = material_manager.getMaterialGroup(material_root_id) - if material_group is None: - Logger.log("e", "Cannot find material container with root id [%s]", material_root_id) + material_root_query = container_registry.findContainers(id = material_root_id) + if not material_root_query: + Logger.log("e", "Cannot find material container with root id {root_id}".format(root_id = material_root_id)) return False + material_container = material_root_query[0] - material_container = material_group.root_material_node.container try: serialized_material = material_container.serialize() except NotImplementedError: diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py deleted file mode 100644 index 999cb1d35a..0000000000 --- a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Uranium is released under the terms of the LGPLv3 or higher. - -from PyQt5.QtCore import pyqtSignal, pyqtProperty - -from UM.Settings.ContainerRegistry import ContainerRegistry -from UM.i18n import i18nCatalog -from UM.Application import Application -from UM.Util import parseBool - -from cura.MachineAction import MachineAction - -catalog = i18nCatalog("cura") - - -## The Ultimaker 2 can have a few revisions & upgrades. -class UM2UpgradeSelection(MachineAction): - def __init__(self): - super().__init__("UM2UpgradeSelection", catalog.i18nc("@action", "Select upgrades")) - self._qml_url = "UM2UpgradeSelectionMachineAction.qml" - - self._container_registry = ContainerRegistry.getInstance() - - self._current_global_stack = None - - Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) - self._reset() - - def _reset(self): - self.hasVariantsChanged.emit() - - def _onGlobalStackChanged(self): - if self._current_global_stack: - self._current_global_stack.metaDataChanged.disconnect(self._onGlobalStackMetaDataChanged) - - self._current_global_stack = Application.getInstance().getGlobalContainerStack() - if self._current_global_stack: - self._current_global_stack.metaDataChanged.connect(self._onGlobalStackMetaDataChanged) - self._reset() - - def _onGlobalStackMetaDataChanged(self): - self._reset() - - hasVariantsChanged = pyqtSignal() - - def setHasVariants(self, has_variants = True): - global_container_stack = Application.getInstance().getGlobalContainerStack() - if global_container_stack: - variant_container = global_container_stack.extruders["0"].variant - - if has_variants: - global_container_stack.setMetaDataEntry("has_variants", True) - - # Set the variant container to a sane default - empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() - if type(variant_container) == type(empty_container): - search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" } - containers = self._container_registry.findInstanceContainers(**search_criteria) - if containers: - global_container_stack.extruders["0"].variant = containers[0] - else: - # The metadata entry is stored in an ini, and ini files are parsed as strings only. - # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. - if "has_variants" in global_container_stack.getMetaData(): - global_container_stack.removeMetaDataEntry("has_variants") - - # Set the variant container to an empty variant - global_container_stack.extruders["0"].variant = ContainerRegistry.getInstance().getEmptyInstanceContainer() - - Application.getInstance().globalContainerStackChanged.emit() - self._reset() - - @pyqtProperty(bool, fset = setHasVariants, notify = hasVariantsChanged) - def hasVariants(self): - if self._current_global_stack: - return parseBool(self._current_global_stack.getMetaDataEntry("has_variants", "false")) diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml deleted file mode 100644 index 13525f6eb3..0000000000 --- a/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2019 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.10 -import QtQuick.Controls 2.3 - -import UM 1.3 as UM -import Cura 1.1 as Cura - - -Cura.MachineAction -{ - UM.I18nCatalog { id: catalog; name: "cura"; } - anchors.fill: parent - - Item - { - id: upgradeSelectionMachineAction - anchors.fill: parent - anchors.topMargin: UM.Theme.getSize("default_margin").width * 5 - anchors.leftMargin: UM.Theme.getSize("default_margin").width * 4 - - Label - { - id: pageDescription - anchors.top: parent.top - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Please select any upgrades made to this Ultimaker 2.") - font: UM.Theme.getFont("medium") - color: UM.Theme.getColor("text") - renderType: Text.NativeRendering - } - - Cura.CheckBox - { - id: olssonBlockCheckBox - anchors.top: pageDescription.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - - height: UM.Theme.getSize("setting_control").height - - text: catalog.i18nc("@label", "Olsson Block") - checked: manager.hasVariants - onClicked: manager.hasVariants = checked - - Connections - { - target: manager - onHasVariantsChanged: olssonBlockCheckBox.checked = manager.hasVariants - } - } - } -} diff --git a/plugins/UltimakerMachineActions/__init__.py b/plugins/UltimakerMachineActions/__init__.py index e87949580a..aecb3b0ad6 100644 --- a/plugins/UltimakerMachineActions/__init__.py +++ b/plugins/UltimakerMachineActions/__init__.py @@ -1,9 +1,8 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from . import BedLevelMachineAction from . import UMOUpgradeSelection -from . import UM2UpgradeSelection def getMetaData(): return {} @@ -11,6 +10,5 @@ def getMetaData(): def register(app): return { "machine_action": [ BedLevelMachineAction.BedLevelMachineAction(), - UMOUpgradeSelection.UMOUpgradeSelection(), - UM2UpgradeSelection.UM2UpgradeSelection() + UMOUpgradeSelection.UMOUpgradeSelection() ]} diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 5a1abf0dfb..7dfa6483d2 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -75,37 +75,20 @@ class XmlMaterialProfile(InstanceContainer): if k in self.__material_properties_setting_map: new_setting_values_dict[self.__material_properties_setting_map[k]] = v - # Prevent recursion - if not apply_to_all: - super().setMetaDataEntry(key, value) + if not apply_to_all: # Historical: If you only want to modify THIS container. We only used that to prevent recursion but with the below code that's no longer necessary. + container_query = registry.findContainers(id = self.getId()) + else: + container_query = registry.findContainers(base_file = self.getMetaDataEntry("base_file")) + + for container in container_query: + if key not in container.getMetaData() or container.getMetaData()[key] != value: + container.getMetaData()[key] = value + container.setDirty(True) + container.metaDataChanged.emit(container) for k, v in new_setting_values_dict.items(): self.setProperty(k, "value", v) return - # Get the MaterialGroup - material_manager = CuraApplication.getInstance().getMaterialManager() - root_material_id = self.getMetaDataEntry("base_file") #if basefile is self.getId, this is a basefile. - material_group = material_manager.getMaterialGroup(root_material_id) - if not material_group: #If the profile is not registered in the registry but loose/temporary, it will not have a base file tree. - super().setMetaDataEntry(key, value) - for k, v in new_setting_values_dict.items(): - self.setProperty(k, "value", v) - return - # Update the root material container - root_material_container = material_group.root_material_node.container - if root_material_container is not None: - root_material_container.setMetaDataEntry(key, value, apply_to_all = False) - for k, v in new_setting_values_dict.items(): - root_material_container.setProperty(k, "value", v) - - # Update all containers derived from it - for node in material_group.derived_material_node_list: - container = node.container - if container is not None: - container.setMetaDataEntry(key, value, apply_to_all = False) - for k, v in new_setting_values_dict.items(): - container.setProperty(k, "value", v) - ## Overridden from InstanceContainer, similar to setMetaDataEntry. # 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. diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index 285f5bed08..bb1ab13196 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -14,8 +14,6 @@ "has_materials": false, "has_machine_quality": true, "preferred_variant_name": "0.4 mm", - "first_start_actions": ["UM2UpgradeSelection"], - "supported_actions":["UM2UpgradeSelection"], "machine_extruder_trains": { "0": "ultimaker2_extruder_0" diff --git a/resources/definitions/ultimaker2_extended_olsson.def.json b/resources/definitions/ultimaker2_extended_olsson.def.json new file mode 100644 index 0000000000..d2eb7f9a5d --- /dev/null +++ b/resources/definitions/ultimaker2_extended_olsson.def.json @@ -0,0 +1,12 @@ +{ + "version": 2, + "name": "Ultimaker 2 Extended with Olsson", + "inherits": "ultimaker2_extended", + "metadata": { + "has_variants": true + }, + + "overrides": { + "machine_name": { "default_value": "Ultimaker 2 Extended with Olsson" } + } +} diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json index 9e374b2c88..774d215bef 100644 --- a/resources/definitions/ultimaker2_go.def.json +++ b/resources/definitions/ultimaker2_go.def.json @@ -11,7 +11,6 @@ "platform": "ultimaker2go_platform.obj", "platform_texture": "Ultimaker2Gobackplate.png", "platform_offset": [0, 0, 0], - "first_start_actions": [], "machine_extruder_trains": { "0": "ultimaker2_go_extruder_0" diff --git a/resources/definitions/ultimaker2_olsson.def.json b/resources/definitions/ultimaker2_olsson.def.json new file mode 100644 index 0000000000..2f8b877942 --- /dev/null +++ b/resources/definitions/ultimaker2_olsson.def.json @@ -0,0 +1,13 @@ +{ + "version": 2, + "name": "Ultimaker 2 with Olsson Block", + "inherits": "ultimaker2", + "metadata": { + "has_variants": true, + "quality_definition": "ultimaker2" + }, + + "overrides": { + "machine_name": { "default_value": "Ultimaker 2 with Olsson Block" } + } +} diff --git a/resources/variants/ultimaker2_0.25.inst.cfg b/resources/variants/ultimaker2_olsson_0.25.inst.cfg similarity index 86% rename from resources/variants/ultimaker2_0.25.inst.cfg rename to resources/variants/ultimaker2_olsson_0.25.inst.cfg index 6e2446a0ea..dd33048059 100644 --- a/resources/variants/ultimaker2_0.25.inst.cfg +++ b/resources/variants/ultimaker2_olsson_0.25.inst.cfg @@ -1,7 +1,7 @@ [general] name = 0.25 mm version = 4 -definition = ultimaker2 +definition = ultimaker2_olsson [metadata] setting_version = 10 diff --git a/resources/variants/ultimaker2_0.4.inst.cfg b/resources/variants/ultimaker2_olsson_0.4.inst.cfg similarity index 85% rename from resources/variants/ultimaker2_0.4.inst.cfg rename to resources/variants/ultimaker2_olsson_0.4.inst.cfg index d44af57513..77e1f523e0 100644 --- a/resources/variants/ultimaker2_0.4.inst.cfg +++ b/resources/variants/ultimaker2_olsson_0.4.inst.cfg @@ -1,7 +1,7 @@ [general] name = 0.4 mm version = 4 -definition = ultimaker2 +definition = ultimaker2_olsson [metadata] setting_version = 10 diff --git a/resources/variants/ultimaker2_0.6.inst.cfg b/resources/variants/ultimaker2_olsson_0.6.inst.cfg similarity index 85% rename from resources/variants/ultimaker2_0.6.inst.cfg rename to resources/variants/ultimaker2_olsson_0.6.inst.cfg index 3d76193a81..57a30eab82 100644 --- a/resources/variants/ultimaker2_0.6.inst.cfg +++ b/resources/variants/ultimaker2_olsson_0.6.inst.cfg @@ -1,7 +1,7 @@ [general] name = 0.6 mm version = 4 -definition = ultimaker2 +definition = ultimaker2_olsson [metadata] setting_version = 10 diff --git a/resources/variants/ultimaker2_0.8.inst.cfg b/resources/variants/ultimaker2_olsson_0.8.inst.cfg similarity index 85% rename from resources/variants/ultimaker2_0.8.inst.cfg rename to resources/variants/ultimaker2_olsson_0.8.inst.cfg index 43491d656c..2e47ebb5cf 100644 --- a/resources/variants/ultimaker2_0.8.inst.cfg +++ b/resources/variants/ultimaker2_olsson_0.8.inst.cfg @@ -1,7 +1,7 @@ [general] name = 0.8 mm version = 4 -definition = ultimaker2 +definition = ultimaker2_olsson [metadata] setting_version = 10