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:
            definition_id = "fdmprinter"
        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")
            ]
            return containers

        return []
def test_renameContainer(container_registry):
    # Ensure that renaming an unknown container doesn't break
    container_registry.renameContainer("ContainerThatDoesntExist", "whatever")

    test_container = InstanceContainer("omgzomg")
    container_registry.addContainer(test_container)

    # Attempting a rename to the same name should not mark the container as dirty.
    container_registry.renameContainer("omgzomg", "omgzomg")
    assert "omgzomg" not in [
        container.getId()
        for container in container_registry.findDirtyContainers()
    ]

    container_registry.renameContainer("omgzomg", "BEEP")
    assert test_container.getMetaDataEntry("name") == "BEEP"
    # Ensure that the container is marked as dirty
    assert "omgzomg" in [
        container.getId()
        for container in container_registry.findDirtyContainers()
    ]

    # Rename the container and also try to give it a new ID
    container_registry.renameContainer("omgzomg", "BEEPz", "omgzomg2")
    assert "omgzomg2" in [
        container.getId()
        for container in container_registry.findDirtyContainers()
    ]
    # The old ID should not be in the list of dirty containers now.
    assert "omgzomg" not in [
        container.getId()
        for container in container_registry.findDirtyContainers()
    ]
Exemple #3
0
    def _loadProfile(self, serialized: str,
                     profile_id: str) -> Optional[InstanceContainer]:
        """Load a profile from a serialized string.

        :param serialized: The profile data to read.
        :param profile_id: The name of the profile.
        :return: The profile that was stored in the string.
        """

        # Create an empty profile.
        profile = InstanceContainer(profile_id)
        profile.setMetaDataEntry("type", "quality_changes")
        try:
            profile.deserialize(serialized, file_name=profile_id)
        except ContainerFormatError as e:
            Logger.log("e", "Error in the format of a container: %s", str(e))
            return None
        except Exception as e:
            Logger.log("e", "Error while trying to parse profile: %s", str(e))
            return None

        global_stack = CuraApplication.getInstance().getGlobalContainerStack()
        if global_stack is None:
            return None

        active_quality_definition = ContainerTree.getInstance().machines[
            global_stack.definition.getId()].quality_definition
        if profile.getMetaDataEntry("definition") != active_quality_definition:
            profile.setMetaDataEntry("definition", active_quality_definition)
        return profile
    def _onContainerChanged(self, container: InstanceContainer) -> None:
        container_type = container.getMetaDataEntry("type")
        if container_type not in ("quality", "quality_changes"):
            return

        # update the cache table
        self._update_timer.start()
Exemple #5
0
    def _onContainerChanged(self, container: InstanceContainer) -> None:
        container_type = container.getMetaDataEntry("type")
        if container_type not in ("quality", "quality_changes"):
            return

        # update the cache table
        self._update_timer.start()
    def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]:
        profile.setDirty(True)  # Ensure the profiles are correctly saved

        new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
        profile.setMetaDataEntry("id", new_id)
        profile.setName(new_name)

        # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
        # It also solves an issue with importing profiles from G-Codes
        profile.setMetaDataEntry("id", new_id)

        if "type" in profile.getMetaData():
            profile.setMetaDataEntry("type", "quality_changes")
        else:
            profile.addMetaDataEntry("type", "quality_changes")

        quality_type = profile.getMetaDataEntry("quality_type")
        if not quality_type:
            return catalog.i18nc("@info:status", "Profile is missing a quality type.")

        quality_type_criteria = {"quality_type": quality_type}
        if self._machineHasOwnQualities():
            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
                    profile.addMetaDataEntry("material", active_material_id)
                    quality_type_criteria["material"] = active_material_id

            quality_type_criteria["definition"] = profile.getDefinition().getId()

        else:
            profile.setDefinition("fdmprinter")
            quality_type_criteria["definition"] = "fdmprinter"

        machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
        del quality_type_criteria["definition"]

        # materials = None

        if "material" in quality_type_criteria:
            # materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"])
            del quality_type_criteria["material"]

        # Do not filter quality containers here with materials because we are trying to import a profile, so it should
        # NOT be restricted by the active materials on the current machine.
        materials = None

        # Check to make sure the imported profile actually makes sense in context of the current configuration.
        # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
        # successfully imported but then fail to show up.
        from cura.QualityManager import QualityManager
        qualities = QualityManager.getInstance()._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria)
        if not qualities:
            return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)

        ContainerRegistry.getInstance().addContainer(profile)

        return None
Exemple #7
0
    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:
            definition_id = "fdmprinter"
        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")]
            return containers

        return []
Exemple #8
0
    def _updateMetaData(self, container: InstanceContainer) -> None:
        index = self.find("id", container.id)

        if self._section_property:
            self.setProperty(index, "section", container.getMetaDataEntry(self._section_property, ""))

        self.setProperty(index, "metadata", container.getMetaData())
        self.setProperty(index, "name", container.getName())
        self.setProperty(index, "id", container.getId())
Exemple #9
0
    def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]:
        profile.setDirty(True)  # Ensure the profiles are correctly saved

        new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
        profile._id = new_id
        profile.setName(new_name)

        if "type" in profile.getMetaData():
            profile.setMetaDataEntry("type", "quality_changes")
        else:
            profile.addMetaDataEntry("type", "quality_changes")

        quality_type = profile.getMetaDataEntry("quality_type")
        if not quality_type:
            return catalog.i18nc("@info:status", "Profile is missing a quality type.")

        quality_type_criteria = {"quality_type": quality_type}
        if self._machineHasOwnQualities():
            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
                    profile.addMetaDataEntry("material", active_material_id)
                    quality_type_criteria["material"] = active_material_id

            quality_type_criteria["definition"] = profile.getDefinition().getId()

        else:
            profile.setDefinition(fdmprinter)
            quality_type_criteria["definition"] = "fdmprinter"

        machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
        del quality_type_criteria["definition"]

        # materials = None

        if "material" in quality_type_criteria:
            # materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"])
            del quality_type_criteria["material"]

        # Do not filter quality containers here with materials because we are trying to import a profile, so it should
        # NOT be restricted by the active materials on the current machine.
        materials = None

        # Check to make sure the imported profile actually makes sense in context of the current configuration.
        # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
        # successfully imported but then fail to show up.
        from cura.QualityManager import QualityManager
        qualities = QualityManager.getInstance()._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria)
        if not qualities:
            return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)

        ContainerRegistry.getInstance().addContainer(profile)

        return None
Exemple #10
0
    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"))
        profile._id = new_id
        profile.setName(new_name)

        if "type" in profile.getMetaData():
            profile.setMetaDataEntry("type", "quality_changes")
        else:
            profile.addMetaDataEntry("type", "quality_changes")

        quality_type = profile.getMetaDataEntry("quality_type")
        if not quality_type:
            return catalog.i18nc("@info:status",
                                 "Profile is missing a quality type.")

        quality_type_criteria = {"quality_type": quality_type}
        if self._machineHasOwnQualities():
            profile.setDefinition(self._activeQualityDefinition())
            if self._machineHasOwnMaterials():
                active_material_id = self._activeMaterialId()
                if active_material_id:  # only update if there is an active material
                    profile.addMetaDataEntry("material", active_material_id)
                    quality_type_criteria["material"] = active_material_id

            quality_type_criteria["definition"] = profile.getDefinition(
            ).getId()

        else:
            profile.setDefinition(
                ContainerRegistry.getInstance().findDefinitionContainers(
                    id="fdmprinter")[0])
            quality_type_criteria["definition"] = "fdmprinter"

        # Check to make sure the imported profile actually makes sense in context of the current configuration.
        # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
        # successfully imported but then fail to show up.
        qualities = self.findInstanceContainers(**quality_type_criteria)
        if not qualities:
            return catalog.i18nc(
                "@info:status",
                "Could not find a quality type {0} for the current configuration.",
                quality_type)

        ContainerRegistry.getInstance().addContainer(profile)

        return None
Exemple #11
0
    def _configureProfile(self, profile: InstanceContainer, id_seed: str,
                          new_name: str,
                          machine_definition_id: str) -> Optional[str]:
        profile.setDirty(True)  # Ensure the profiles are correctly saved

        new_id = self.createUniqueName(
            "quality_changes", "", id_seed,
            catalog.i18nc("@label", "Custom profile"))
        profile.setMetaDataEntry("id", new_id)
        profile.setName(new_name)

        # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
        # It also solves an issue with importing profiles from G-Codes
        profile.setMetaDataEntry("id", new_id)
        profile.setMetaDataEntry("definition", machine_definition_id)

        if "type" in profile.getMetaData():
            profile.setMetaDataEntry("type", "quality_changes")
        else:
            profile.setMetaDataEntry("type", "quality_changes")

        quality_type = profile.getMetaDataEntry("quality_type")
        if not quality_type:
            return catalog.i18nc("@info:status",
                                 "Profile is missing a quality type.")

        global_stack = Application.getInstance().getGlobalContainerStack()
        if global_stack is None:
            return None
        definition_id = getMachineDefinitionIDForQualitySearch(
            global_stack.definition)
        profile.setDefinition(definition_id)

        # Check to make sure the imported profile actually makes sense in context of the current configuration.
        # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
        # successfully imported but then fail to show up.
        quality_manager = cura.CuraApplication.CuraApplication.getInstance(
        )._quality_manager
        quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(
            global_stack)
        if quality_type not in quality_group_dict:
            return catalog.i18nc(
                "@info:status",
                "Could not find a quality type {0} for the current configuration.",
                quality_type)

        ContainerRegistry.getInstance().addContainer(profile)

        return None
Exemple #12
0
    def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str, machine_definition_id: str) -> Optional[str]:
        """Update an imported profile to match the current machine configuration.

        :param profile: The profile to configure.
        :param id_seed: The base ID for the profile. May be changed so it does not conflict with existing containers.
        :param new_name: The new name for the profile.

        :return: None if configuring was successful or an error message if an error occurred.
        """

        profile.setDirty(True)  # Ensure the profiles are correctly saved

        new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
        profile.setMetaDataEntry("id", new_id)
        profile.setName(new_name)

        # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
        # It also solves an issue with importing profiles from G-Codes
        profile.setMetaDataEntry("id", new_id)
        profile.setMetaDataEntry("definition", machine_definition_id)

        if "type" in profile.getMetaData():
            profile.setMetaDataEntry("type", "quality_changes")
        else:
            profile.setMetaDataEntry("type", "quality_changes")

        quality_type = profile.getMetaDataEntry("quality_type")
        if not quality_type:
            return catalog.i18nc("@info:status", "Profile is missing a quality type.")

        global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
        if global_stack is None:
            return None
        definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
        profile.setDefinition(definition_id)

        # Check to make sure the imported profile actually makes sense in context of the current configuration.
        # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
        # successfully imported but then fail to show up.
        quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups()
        # "not_supported" profiles can be imported.
        if quality_type != empty_quality_container.getMetaDataEntry("quality_type") and quality_type not in quality_group_dict:
            return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)

        ContainerRegistry.getInstance().addContainer(profile)

        return None
    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"))
        profile._id = new_id
        profile.setName(new_name)

        if "type" in profile.getMetaData():
            profile.setMetaDataEntry("type", "quality_changes")
        else:
            profile.addMetaDataEntry("type", "quality_changes")

        quality_type = profile.getMetaDataEntry("quality_type")
        if not quality_type:
            return catalog.i18nc("@info:status", "Profile is missing a quality type.")

        quality_type_criteria = {"quality_type": quality_type}
        if self._machineHasOwnQualities():
            profile.setDefinition(self._activeQualityDefinition())
            if self._machineHasOwnMaterials():
                active_material_id = self._activeMaterialId()
                if active_material_id:  # only update if there is an active material
                    profile.addMetaDataEntry("material", active_material_id)
                    quality_type_criteria["material"] = active_material_id

            quality_type_criteria["definition"] = profile.getDefinition().getId()

        else:
            profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
            quality_type_criteria["definition"] = "fdmprinter"

        # Check to make sure the imported profile actually makes sense in context of the current configuration.
        # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
        # successfully imported but then fail to show up.
        qualities = self.findInstanceContainers(**quality_type_criteria)
        if not qualities:
            return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)

        ContainerRegistry.getInstance().addContainer(profile)

        return None
Exemple #14
0
    def _loadProfile(self, serialized: str, profile_id: str) -> Optional[InstanceContainer]:
        # Create an empty profile.
        profile = InstanceContainer(profile_id)
        profile.setMetaDataEntry("type", "quality_changes")
        try:
            profile.deserialize(serialized, file_name = profile_id)
        except ContainerFormatError as e:
            Logger.log("e", "Error in the format of a container: %s", str(e))
            return None
        except Exception as e:
            Logger.log("e", "Error while trying to parse profile: %s", str(e))
            return None

        global_stack = CuraApplication.getInstance().getGlobalContainerStack()
        if global_stack is None:
            return None

        active_quality_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition)
        if profile.getMetaDataEntry("definition") != active_quality_definition:
            profile.setMetaDataEntry("definition", active_quality_definition)
        return profile
    def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str, machine_definition_id: str) -> Optional[str]:
        profile.setDirty(True)  # Ensure the profiles are correctly saved

        new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
        profile.setMetaDataEntry("id", new_id)
        profile.setName(new_name)

        # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
        # It also solves an issue with importing profiles from G-Codes
        profile.setMetaDataEntry("id", new_id)
        profile.setMetaDataEntry("definition", machine_definition_id)

        if "type" in profile.getMetaData():
            profile.setMetaDataEntry("type", "quality_changes")
        else:
            profile.setMetaDataEntry("type", "quality_changes")

        quality_type = profile.getMetaDataEntry("quality_type")
        if not quality_type:
            return catalog.i18nc("@info:status", "Profile is missing a quality type.")

        global_stack = Application.getInstance().getGlobalContainerStack()
        if global_stack is None:
            return None
        definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
        profile.setDefinition(definition_id)

        # Check to make sure the imported profile actually makes sense in context of the current configuration.
        # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
        # successfully imported but then fail to show up.
        quality_manager = cura.CuraApplication.CuraApplication.getInstance()._quality_manager
        quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
        if quality_type not in quality_group_dict:
            return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)

        ContainerRegistry.getInstance().addContainer(profile)

        return None
Exemple #16
0
    def importProfile(self, file_name: str) -> Dict[str, str]:
        Logger.log("d", "Attempting to import profile %s", file_name)
        if not file_name:
            return {
                "status":
                "error",
                "message":
                catalog.i18nc(
                    "@info:status Don't translate the XML tags <filename>!",
                    "Failed to import profile from <filename>{0}</filename>: {1}",
                    file_name, "Invalid path")
            }

        global_stack = Application.getInstance().getGlobalContainerStack()
        if not global_stack:
            return {
                "status":
                "error",
                "message":
                catalog.i18nc(
                    "@info:status Don't translate the XML tags <filename>!",
                    "Can't import profile from <filename>{0}</filename> before a printer is added.",
                    file_name)
            }

        machine_extruders = []
        for position in sorted(global_stack.extruders):
            machine_extruders.append(global_stack.extruders[position])

        plugin_registry = PluginRegistry.getInstance()
        extension = file_name.split(".")[-1]

        for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
            if meta_data["profile_reader"][0]["extension"] != extension:
                continue
            profile_reader = cast(ProfileReader,
                                  plugin_registry.getPluginObject(plugin_id))
            try:
                profile_or_list = profile_reader.read(
                    file_name)  # Try to open the file with the profile reader.
            except NoProfileException:
                return {
                    "status":
                    "ok",
                    "message":
                    catalog.i18nc(
                        "@info:status Don't translate the XML tags <filename>!",
                        "No custom profile to import in file <filename>{0}</filename>",
                        file_name)
                }
            except Exception as e:
                # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
                Logger.log(
                    "e",
                    "Failed to import profile from %s: %s while using profile reader. Got exception %s",
                    file_name, profile_reader.getPluginId(), str(e))
                return {
                    "status":
                    "error",
                    "message":
                    catalog.i18nc(
                        "@info:status Don't translate the XML tags <filename>!",
                        "Failed to import profile from <filename>{0}</filename>:",
                        file_name) + "\n<message>" + str(e) + "</message>"
                }

            if profile_or_list:
                # Ensure it is always a list of profiles
                if not isinstance(profile_or_list, list):
                    profile_or_list = [profile_or_list]

                # First check if this profile is suitable for this machine
                global_profile = None
                extruder_profiles = []
                if len(profile_or_list) == 1:
                    global_profile = profile_or_list[0]
                else:
                    for profile in profile_or_list:
                        if not profile.getMetaDataEntry("position"):
                            global_profile = profile
                        else:
                            extruder_profiles.append(profile)
                extruder_profiles = sorted(
                    extruder_profiles,
                    key=lambda x: int(x.getMetaDataEntry("position")))
                profile_or_list = [global_profile] + extruder_profiles

                if not global_profile:
                    Logger.log(
                        "e",
                        "Incorrect profile [%s]. Could not find global profile",
                        file_name)
                    return {
                        "status":
                        "error",
                        "message":
                        catalog.i18nc(
                            "@info:status Don't translate the XML tags <filename>!",
                            "This profile <filename>{0}</filename> contains incorrect data, could not import it.",
                            file_name)
                    }
                profile_definition = global_profile.getMetaDataEntry(
                    "definition")

                # Make sure we have a profile_definition in the file:
                if profile_definition is None:
                    break
                machine_definitions = self.findDefinitionContainers(
                    id=profile_definition)
                if not machine_definitions:
                    Logger.log(
                        "e",
                        "Incorrect profile [%s]. Unknown machine type [%s]",
                        file_name, profile_definition)
                    return {
                        "status":
                        "error",
                        "message":
                        catalog.i18nc(
                            "@info:status Don't translate the XML tags <filename>!",
                            "This profile <filename>{0}</filename> contains incorrect data, could not import it.",
                            file_name)
                    }
                machine_definition = machine_definitions[0]

                # Get the expected machine definition.
                # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
                profile_definition = getMachineDefinitionIDForQualitySearch(
                    machine_definition)
                expected_machine_definition = getMachineDefinitionIDForQualitySearch(
                    global_stack.definition)

                # And check if the profile_definition matches either one (showing error if not):
                if profile_definition != expected_machine_definition:
                    Logger.log(
                        "e",
                        "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile",
                        file_name, profile_definition,
                        expected_machine_definition)
                    return {
                        "status":
                        "error",
                        "message":
                        catalog.i18nc(
                            "@info:status Don't translate the XML tags <filename>!",
                            "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.",
                            file_name, profile_definition,
                            expected_machine_definition)
                    }

                # Fix the global quality profile's definition field in case it's not correct
                global_profile.setMetaDataEntry("definition",
                                                expected_machine_definition)
                quality_name = global_profile.getName()
                quality_type = global_profile.getMetaDataEntry("quality_type")

                name_seed = os.path.splitext(os.path.basename(file_name))[0]
                new_name = self.uniqueName(name_seed)

                # Ensure it is always a list of profiles
                if type(profile_or_list) is not list:
                    profile_or_list = [profile_or_list]

                # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack
                if len(profile_or_list) == 1:
                    global_profile = profile_or_list[0]
                    extruder_profiles = []
                    for idx, extruder in enumerate(
                            global_stack.extruders.values()):
                        profile_id = ContainerRegistry.getInstance(
                        ).uniqueName(global_stack.getId() + "_extruder_" +
                                     str(idx + 1))
                        profile = InstanceContainer(profile_id)
                        profile.setName(quality_name)
                        profile.setMetaDataEntry(
                            "setting_version", cura.CuraApplication.
                            CuraApplication.SettingVersion)
                        profile.setMetaDataEntry("type", "quality_changes")
                        profile.setMetaDataEntry("definition",
                                                 expected_machine_definition)
                        profile.setMetaDataEntry("quality_type", quality_type)
                        profile.setMetaDataEntry("position", "0")
                        profile.setDirty(True)
                        if idx == 0:
                            # Move all per-extruder settings to the first extruder's quality_changes
                            for qc_setting_key in global_profile.getAllKeys():
                                settable_per_extruder = global_stack.getProperty(
                                    qc_setting_key, "settable_per_extruder")
                                if settable_per_extruder:
                                    setting_value = global_profile.getProperty(
                                        qc_setting_key, "value")

                                    setting_definition = global_stack.getSettingDefinition(
                                        qc_setting_key)
                                    if setting_definition is not None:
                                        new_instance = SettingInstance(
                                            setting_definition, profile)
                                        new_instance.setProperty(
                                            "value", setting_value)
                                        new_instance.resetState(
                                        )  # Ensure that the state is not seen as a user state.
                                        profile.addInstance(new_instance)
                                        profile.setDirty(True)

                                    global_profile.removeInstance(
                                        qc_setting_key, postpone_emit=True)
                        extruder_profiles.append(profile)

                    for profile in extruder_profiles:
                        profile_or_list.append(profile)

                # Import all profiles
                for profile_index, profile in enumerate(profile_or_list):
                    if profile_index == 0:
                        # This is assumed to be the global profile
                        profile_id = (cast(ContainerInterface,
                                           global_stack.getBottom()).getId() +
                                      "_" + name_seed).lower().replace(
                                          " ", "_")

                    elif profile_index < len(machine_extruders) + 1:
                        # This is assumed to be an extruder profile
                        extruder_id = machine_extruders[profile_index -
                                                        1].definition.getId()
                        extruder_position = str(profile_index - 1)
                        if not profile.getMetaDataEntry("position"):
                            profile.setMetaDataEntry("position",
                                                     extruder_position)
                        else:
                            profile.setMetaDataEntry("position",
                                                     extruder_position)
                        profile_id = (extruder_id + "_" +
                                      name_seed).lower().replace(" ", "_")

                    else:  # More extruders in the imported file than in the machine.
                        continue  # Delete the additional profiles.

                    result = self._configureProfile(
                        profile, profile_id, new_name,
                        expected_machine_definition)
                    if result is not None:
                        return {
                            "status":
                            "error",
                            "message":
                            catalog.i18nc(
                                "@info:status Don't translate the XML tags <filename> or <message>!",
                                "Failed to import profile from <filename>{0}</filename>:",
                                file_name) + " <message>" + result +
                            "</message>"
                        }

                return {
                    "status":
                    "ok",
                    "message":
                    catalog.i18nc("@info:status",
                                  "Successfully imported profile {0}",
                                  profile_or_list[0].getName())
                }

            # This message is throw when the profile reader doesn't find any profile in the file
            return {
                "status":
                "error",
                "message":
                catalog.i18nc("@info:status",
                              "File {0} does not contain any valid profile.",
                              file_name)
            }

        # If it hasn't returned by now, none of the plugins loaded the profile successfully.
        return {
            "status":
            "error",
            "message":
            catalog.i18nc(
                "@info:status",
                "Profile {0} has an unknown file type or is corrupted.",
                file_name)
        }
    def preRead(self, file_name, show_dialog=True, *args, **kwargs):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            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")

        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]

        # 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 = []

        #
        # Read definition containers
        #
        machine_definition_container_count = 0
        extruder_definition_container_count = 0
        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)

            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"))

            else:
                definition_container = definitions[0]
            definition_container_list.append(definition_container)

            definition_container_type = definition_container.getMetaDataEntry("type")
            if definition_container_type == "machine":
                machine_type = definition_container.getName()
                variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)

                machine_definition_container_count += 1
            elif definition_container_type == "extruder":
                extruder_definition_container_count += 1
            else:
                Logger.log("w", "Unknown definition container type %s for %s",
                           definition_container_type, each_definition_container_file)
            Job.yieldThread()
        # sanity check
        if machine_definition_container_count != 1:
            msg = "Expecting one machine definition container but got %s" % machine_definition_container_count
            Logger.log("e", msg)
            raise RuntimeError(msg)

        material_labels = []
        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
        if xml_material_profile:
            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 and not materials[0].isReadOnly():  # Only non readonly materials can be in conflict
                    material_conflict = True
                Job.yieldThread()

        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        quality_name = ""
        quality_type = ""
        num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
        num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes
        num_user_settings = 0
        quality_changes_conflict = False
        definition_changes_conflict = False

        for each_instance_container_file in instance_container_files:
            container_id = self._stripFileToId(each_instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"))
            instance_container_list.append(instance_container)

            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                quality_name = instance_container.getName()
                num_settings_overriden_by_quality_changes += len(instance_container._instances)
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(id = container_id)
                if quality_changes:
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
            elif container_type == "definition_changes":
                definition_name = instance_container.getName()
                num_settings_overriden_by_definition_changes += len(instance_container._instances)
                definition_changes = self._container_registry.findDefinitionContainers(id = container_id)
                if definition_changes:
                    if definition_changes[0] != instance_container:
                        definition_changes_conflict = True
            elif container_type == "user":
                num_user_settings += len(instance_container._instances)
            elif container_type in self._ignored_instance_container_types:
                # Ignore certain instance container types
                Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
                continue

            Job.yieldThread()

        # Load ContainerStack files and ExtruderStack files
        global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
            file_name, cura_file_names)
        self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
        machine_conflict = False
        for container_stack_file in [global_stack_file] + extruder_stack_files:
            container_id = self._stripFileToId(container_stack_file)
            serialized = archive.open(container_stack_file).read().decode("utf-8")
            if machine_name == "":
                machine_name = self._getMachineNameFromSerializedStack(serialized)
            stacks = self._container_registry.findContainerStacks(id = container_id)
            if stacks:
                # Check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(serialized)
                for index, container_id in enumerate(id_list):
                    if stacks[0].getContainer(index).getId() != container_id:
                        machine_conflict = True
            Job.yieldThread()

        num_visible_settings = 0
        try:
            temp_preferences = Preferences()
            temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

            visible_settings_string = temp_preferences.getValue("general/visible_settings")
            if visible_settings_string is not None:
                num_visible_settings = len(visible_settings_string.split(";"))
            active_mode = temp_preferences.getValue("cura/active_mode")
            if not active_mode:
                active_mode = Preferences.getInstance().getValue("cura/active_mode")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
        if not show_dialog:
            return WorkspaceReader.PreReadResult.accepted

        # prepare data for the dialog
        num_extruders = extruder_definition_container_count
        if num_extruders == 0:
            num_extruders = 1  # No extruder stacks found, which means there is one extruder

        extruders = num_extruders * [""]

        # Show the dialog, informing the user what is about to happen.
        self._dialog.setMachineConflict(machine_conflict)
        self._dialog.setQualityChangesConflict(quality_changes_conflict)
        self._dialog.setDefinitionChangesConflict(definition_changes_conflict)
        self._dialog.setMaterialConflict(material_conflict)
        self._dialog.setNumVisibleSettings(num_visible_settings)
        self._dialog.setQualityName(quality_name)
        self._dialog.setQualityType(quality_type)
        self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
        self._dialog.setNumUserSettings(num_user_settings)
        self._dialog.setActiveMode(active_mode)
        self._dialog.setMachineName(machine_name)
        self._dialog.setMaterialLabels(material_labels)
        self._dialog.setMachineType(machine_type)
        self._dialog.setExtruders(extruders)
        self._dialog.setVariantType(variant_type_name)
        self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
        self._dialog.show()

        # Block until the dialog is closed.
        self._dialog.waitForClose()

        if self._dialog.getResult() == {}:
            return WorkspaceReader.PreReadResult.cancelled

        self._resolve_strategies = self._dialog.getResult()
        #
        # There can be 3 resolve strategies coming from the dialog:
        #  - new:       create a new container
        #  - override:  override the existing container
        #  - None:      There is no conflict, which means containers with the same IDs may or may not be there already.
        #               If they are there, there is no conflict between the them.
        #               In this case, you can either create a new one, or safely override the existing one.
        #
        # Default values
        for k, v in self._resolve_strategies.items():
            if v is None:
                self._resolve_strategies[k] = "new"

        return WorkspaceReader.PreReadResult.accepted
Exemple #18
0
    def preRead(self, file_name):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
            return WorkspaceReader.PreReadResult.failed
        machine_name = ""
        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
        container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
        self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
        machine_conflict = False
        quality_changes_conflict = False
        for container_stack_file in container_stack_files:
            container_id = self._stripFileToId(container_stack_file)
            serialized = archive.open(container_stack_file).read().decode("utf-8")
            if machine_name == "":
                machine_name = self._getMachineNameFromSerializedStack(serialized)
            stacks = self._container_registry.findContainerStacks(id=container_id)
            if stacks:
                # Check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(serialized)
                for index, container_id in enumerate(id_list):
                    if stacks[0].getContainer(index).getId() != container_id:
                        machine_conflict = True
            Job.yieldThread()

        material_labels = []
        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
        if xml_material_profile:
            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 and not materials[0].isReadOnly():  # Only non readonly materials can be in conflict
                    material_conflict = True
                Job.yieldThread()
        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        quality_name = ""
        quality_type = ""
        num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                quality_name = instance_container.getName()
                num_settings_overriden_by_quality_changes += len(instance_container._instances)
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(id = container_id)
                if quality_changes:
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
            elif container_type == "quality":
                # If the quality name is not set (either by quality or changes, set it now)
                # Quality changes should always override this (as they are "on top")
                if quality_name == "":
                    quality_name = instance_container.getName()
                quality_type = instance_container.getName()
            Job.yieldThread()
        num_visible_settings = 0
        try:
            temp_preferences = Preferences()
            temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

            visible_settings_string = temp_preferences.getValue("general/visible_settings")
            if visible_settings_string is not None:
                num_visible_settings = len(visible_settings_string.split(";"))
            active_mode = temp_preferences.getValue("cura/active_mode")
            if not active_mode:
                active_mode = Preferences.getInstance().getValue("cura/active_mode")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        # Show the dialog, informing the user what is about to happen.
        self._dialog.setMachineConflict(machine_conflict)
        self._dialog.setQualityChangesConflict(quality_changes_conflict)
        self._dialog.setMaterialConflict(material_conflict)
        self._dialog.setNumVisibleSettings(num_visible_settings)
        self._dialog.setQualityName(quality_name)
        self._dialog.setQualityType(quality_type)
        self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
        self._dialog.setActiveMode(active_mode)
        self._dialog.setMachineName(machine_name)
        self._dialog.setMaterialLabels(material_labels)
        self._dialog.setHasObjectsOnPlate(Application.getInstance().getPlatformActivity)
        self._dialog.show()

        # Block until the dialog is closed.
        self._dialog.waitForClose()

        if self._dialog.getResult() == {}:
            return WorkspaceReader.PreReadResult.cancelled

        self._resolve_strategies = self._dialog.getResult()

        return WorkspaceReader.PreReadResult.accepted
Exemple #19
0
    def importProfile(self, file_name):
        Logger.log("d", "Attempting to import profile %s", file_name)
        if not file_name:
            return {
                "status":
                "error",
                "message":
                catalog.i18nc(
                    "@info:status Don't translate the XML tags <filename> or <message>!",
                    "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>",
                    file_name, "Invalid path")
            }

        plugin_registry = PluginRegistry.getInstance()
        extension = file_name.split(".")[-1]

        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        if not global_container_stack:
            return

        machine_extruders = list(
            ExtruderManager.getInstance().getMachineExtruders(
                global_container_stack.getId()))
        machine_extruders.sort(key=lambda k: k.getMetaDataEntry("position"))

        for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
            if meta_data["profile_reader"][0]["extension"] != extension:
                continue
            profile_reader = plugin_registry.getPluginObject(plugin_id)
            try:
                profile_or_list = profile_reader.read(
                    file_name)  # Try to open the file with the profile reader.
            except Exception as e:
                # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
                Logger.log(
                    "e",
                    "Failed to import profile from %s: %s while using profile reader. Got exception %s",
                    file_name, profile_reader.getPluginId(), str(e))
                return {
                    "status":
                    "error",
                    "message":
                    catalog.i18nc(
                        "@info:status Don't translate the XML tags <filename> or <message>!",
                        "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>",
                        file_name, "\n" + str(e))
                }

            if profile_or_list:
                # Ensure it is always a list of profiles
                if not isinstance(profile_or_list, list):
                    profile_or_list = [profile_or_list]

                # First check if this profile is suitable for this machine
                global_profile = None
                if len(profile_or_list) == 1:
                    global_profile = profile_or_list[0]
                else:
                    for profile in profile_or_list:
                        if not profile.getMetaDataEntry("extruder"):
                            global_profile = profile
                            break
                if not global_profile:
                    Logger.log(
                        "e",
                        "Incorrect profile [%s]. Could not find global profile",
                        file_name)
                    return {
                        "status":
                        "error",
                        "message":
                        catalog.i18nc(
                            "@info:status Don't translate the XML tags <filename> or <message>!",
                            "This profile <filename>{0}</filename> contains incorrect data, could not import it.",
                            file_name)
                    }
                profile_definition = global_profile.getMetaDataEntry(
                    "definition")
                expected_machine_definition = "fdmprinter"
                if parseBool(
                        global_container_stack.getMetaDataEntry(
                            "has_machine_quality", "False")):
                    expected_machine_definition = global_container_stack.getMetaDataEntry(
                        "quality_definition")
                    if not expected_machine_definition:
                        expected_machine_definition = global_container_stack.definition.getId(
                        )
                if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition:
                    Logger.log(
                        "e",
                        "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile",
                        file_name, profile_definition,
                        expected_machine_definition)
                    return {
                        "status":
                        "error",
                        "message":
                        catalog.i18nc(
                            "@info:status Don't translate the XML tags <filename> or <message>!",
                            "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.",
                            file_name, profile_definition,
                            expected_machine_definition)
                    }

                name_seed = os.path.splitext(os.path.basename(file_name))[0]
                new_name = self.uniqueName(name_seed)

                # Ensure it is always a list of profiles
                if type(profile_or_list) is not list:
                    profile_or_list = [profile_or_list]

                # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack
                if len(profile_or_list) == 1:
                    global_profile = profile_or_list[0]
                    extruder_profiles = []
                    for idx, extruder in enumerate(
                            global_container_stack.extruders.values()):
                        profile_id = ContainerRegistry.getInstance(
                        ).uniqueName(global_container_stack.getId() +
                                     "_extruder_" + str(idx + 1))
                        profile = InstanceContainer(profile_id)
                        profile.setName(global_profile.getName())
                        profile.addMetaDataEntry(
                            "setting_version", CuraApplication.SettingVersion)
                        profile.addMetaDataEntry("type", "quality_changes")
                        profile.addMetaDataEntry(
                            "definition",
                            global_profile.getMetaDataEntry("definition"))
                        profile.addMetaDataEntry(
                            "quality_type",
                            global_profile.getMetaDataEntry("quality_type"))
                        profile.addMetaDataEntry("extruder", extruder.getId())
                        profile.setDirty(True)
                        if idx == 0:
                            # move all per-extruder settings to the first extruder's quality_changes
                            for qc_setting_key in global_profile.getAllKeys():
                                settable_per_extruder = global_container_stack.getProperty(
                                    qc_setting_key, "settable_per_extruder")
                                if settable_per_extruder:
                                    setting_value = global_profile.getProperty(
                                        qc_setting_key, "value")

                                    setting_definition = global_container_stack.getSettingDefinition(
                                        qc_setting_key)
                                    new_instance = SettingInstance(
                                        setting_definition, profile)
                                    new_instance.setProperty(
                                        "value", setting_value)
                                    new_instance.resetState(
                                    )  # Ensure that the state is not seen as a user state.
                                    profile.addInstance(new_instance)
                                    profile.setDirty(True)

                                    global_profile.removeInstance(
                                        qc_setting_key, postpone_emit=True)
                        extruder_profiles.append(profile)

                    for profile in extruder_profiles:
                        profile_or_list.append(profile)

                # Import all profiles
                for profile_index, profile in enumerate(profile_or_list):
                    if profile_index == 0:
                        # This is assumed to be the global profile
                        profile_id = (
                            global_container_stack.getBottom().getId() + "_" +
                            name_seed).lower().replace(" ", "_")

                    elif profile_index < len(machine_extruders) + 1:
                        # This is assumed to be an extruder profile
                        extruder_id = Application.getInstance(
                        ).getMachineManager().getQualityDefinitionId(
                            machine_extruders[profile_index - 1].getBottom())
                        if not profile.getMetaDataEntry("extruder"):
                            profile.addMetaDataEntry("extruder", extruder_id)
                        else:
                            profile.setMetaDataEntry("extruder", extruder_id)
                        profile_id = (extruder_id + "_" +
                                      name_seed).lower().replace(" ", "_")

                    else:  #More extruders in the imported file than in the machine.
                        continue  #Delete the additional profiles.

                    result = self._configureProfile(profile, profile_id,
                                                    new_name)
                    if result is not None:
                        return {
                            "status":
                            "error",
                            "message":
                            catalog.i18nc(
                                "@info:status Don't translate the XML tags <filename> or <message>!",
                                "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>",
                                file_name, result)
                        }

                return {
                    "status":
                    "ok",
                    "message":
                    catalog.i18nc("@info:status",
                                  "Successfully imported profile {0}",
                                  profile_or_list[0].getName())
                }

            # This message is throw when the profile reader doesn't find any profile in the file
            return {
                "status":
                "error",
                "message":
                catalog.i18nc("@info:status",
                              "File {0} does not contain any valid profile.",
                              file_name)
            }

        # If it hasn't returned by now, none of the plugins loaded the profile successfully.
        return {
            "status":
            "error",
            "message":
            catalog.i18nc(
                "@info:status",
                "Profile {0} has an unknown file type or is corrupted.",
                file_name)
        }
    def read(self, file_name):
        # Load all the nodes / meshdata of the workspace
        nodes = self._3mf_mesh_reader.read(file_name)
        if nodes is None:
            nodes = []

        archive = zipfile.ZipFile(file_name, "r")

        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]

        # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
        # parsing code.
        temp_preferences = Preferences()
        temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

        # Copy a number of settings from the temp preferences to the global
        global_preferences = Preferences.getInstance()
        global_preferences.setValue("general/visible_settings", temp_preferences.getValue("general/visible_settings"))
        global_preferences.setValue("cura/categories_expanded", temp_preferences.getValue("cura/categories_expanded"))
        Application.getInstance().expandedCategoriesChanged.emit()  # Notify the GUI of the change

        self._id_mapping = {}

        # We don't add containers right away, but wait right until right before the stack serialization.
        # We do this so that if something goes wrong, it's easier to clean up.
        containers_to_add = []

        # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
        # TODO: cases that the container loaded is the same (most notable in materials & definitions).
        # TODO: It might be possible that we need to add smarter checking in the future.
        Logger.log("d", "Workspace loading is checking definitions...")
        # Get all the definition files & check if they exist. If not, add them.
        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)
            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
                self._container_registry.addContainer(definition_container)

        Logger.log("d", "Workspace loading is checking materials...")
        material_containers = []
        # Get all the material files and check if they exist. If not, add them.
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
        if xml_material_profile:
            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)
                if not materials:
                    material_container = xml_material_profile(container_id)
                    material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                    containers_to_add.append(material_container)
                else:
                    if not materials[0].isReadOnly():  # Only create new materials if they are not read only.
                        if self._resolve_strategies["material"] == "override":
                            materials[0].deserialize(archive.open(material_container_file).read().decode("utf-8"))
                        elif self._resolve_strategies["material"] == "new":
                            # Note that we *must* deserialize it with a new ID, as multiple containers will be
                            # auto created & added.
                            material_container = xml_material_profile(self.getNewId(container_id))
                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                            containers_to_add.append(material_container)
                            material_containers.append(material_container)

        Logger.log("d", "Workspace loading is checking instance containers...")
        # Get quality_changes and user profiles saved in the workspace
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        user_instance_containers = []
        quality_changes_instance_containers = []
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "user":
                # Check if quality changes already exists.
                user_containers = self._container_registry.findInstanceContainers(id=container_id)
                if not user_containers:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies["machine"] == "override":
                        user_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                    elif self._resolve_strategies["machine"] == "new":
                        # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                        extruder_id = instance_container.getMetaDataEntry("extruder", None)
                        if extruder_id:
                            new_id = self.getNewId(extruder_id) + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry("extruder", self.getNewId(extruder_id))
                            containers_to_add.append(instance_container)

                        machine_id = instance_container.getMetaDataEntry("machine", None)
                        if machine_id:
                            new_id = self.getNewId(machine_id) + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry("machine", self.getNewId(machine_id))
                            containers_to_add.append(instance_container)
                user_instance_containers.append(instance_container)
            elif container_type == "quality_changes":
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(id = container_id)
                if not quality_changes:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies["quality_changes"] == "override":
                        quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                    elif self._resolve_strategies["quality_changes"] is None:
                        # The ID already exists, but nothing in the values changed, so do nothing.
                        pass
                quality_changes_instance_containers.append(instance_container)
            else:
                continue

        # Add all the containers right before we try to add / serialize the stack
        for container in containers_to_add:
            self._container_registry.addContainer(container)

        # Get the stack(s) saved in the workspace.
        Logger.log("d", "Workspace loading is checking stacks containers...")
        container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
        global_stack = None
        extruder_stacks = []
        container_stacks_added = []
        try:
            for container_stack_file in container_stack_files:
                container_id = self._stripFileToId(container_stack_file)

                # Check if a stack by this ID already exists;
                container_stacks = self._container_registry.findContainerStacks(id=container_id)
                if container_stacks:
                    stack = container_stacks[0]
                    if self._resolve_strategies["machine"] == "override":
                        container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8"))
                    elif self._resolve_strategies["machine"] == "new":
                        new_id = self.getNewId(container_id)
                        stack = ContainerStack(new_id)
                        stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))

                        # Ensure a unique ID and name
                        stack._id = new_id

                        # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
                        # bound machine also needs to change.
                        if stack.getMetaDataEntry("machine", None):
                            stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine")))

                        if stack.getMetaDataEntry("type") != "extruder_train":
                            # Only machines need a new name, stacks may be non-unique
                            stack.setName(self._container_registry.uniqueName(stack.getName()))
                        container_stacks_added.append(stack)
                        self._container_registry.addContainer(stack)
                    else:
                        Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
                else:
                    stack = ContainerStack(container_id)
                    # Deserialize stack by converting read data from bytes to string
                    stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
                    container_stacks_added.append(stack)
                    self._container_registry.addContainer(stack)

                if stack.getMetaDataEntry("type") == "extruder_train":
                    extruder_stacks.append(stack)
                else:
                    global_stack = stack
        except:
            Logger.log("W", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added. 
            for container in containers_to_add:
                self._container_registry.getInstance().removeContainer(container.getId())

            for container in container_stacks_added:
                self._container_registry.getInstance().removeContainer(container.getId())

            return None

        if self._resolve_strategies["machine"] == "new":
            # A new machine was made, but it was serialized with the wrong user container. Fix that now.
            for container in user_instance_containers:
                extruder_id = container.getMetaDataEntry("extruder", None)
                if extruder_id:
                    for extruder in extruder_stacks:
                        if extruder.getId() == extruder_id:
                            extruder.replaceContainer(0, container)
                            continue
                machine_id = container.getMetaDataEntry("machine", None)
                if machine_id:
                    if global_stack.getId() == machine_id:
                        global_stack.replaceContainer(0, container)
                        continue

        if self._resolve_strategies["quality_changes"] == "new":
            # Quality changes needs to get a new ID, added to registry and to the right stacks
            for container in quality_changes_instance_containers:
                old_id = container.getId()
                container.setName(self._container_registry.uniqueName(container.getName()))
                # We're not really supposed to change the ID in normal cases, but this is an exception.
                container._id = self.getNewId(container.getId())

                # The container was not added yet, as it didn't have an unique ID. It does now, so add it.
                self._container_registry.addContainer(container)

                # Replace the quality changes container
                old_container = global_stack.findContainer({"type": "quality_changes"})
                if old_container.getId() == old_id:
                    quality_changes_index = global_stack.getContainerIndex(old_container)
                    global_stack.replaceContainer(quality_changes_index, container)
                    continue

                for stack in extruder_stacks:
                    old_container = stack.findContainer({"type": "quality_changes"})
                    if old_container.getId() == old_id:
                        quality_changes_index = stack.getContainerIndex(old_container)
                        stack.replaceContainer(quality_changes_index, container)

        if self._resolve_strategies["material"] == "new":
            for material in material_containers:
                old_material = global_stack.findContainer({"type": "material"})
                if old_material.getId() in self._id_mapping:
                    material_index = global_stack.getContainerIndex(old_material)
                    global_stack.replaceContainer(material_index, material)
                    continue

                for stack in extruder_stacks:
                    old_material = stack.findContainer({"type": "material"})
                    if old_material.getId() in self._id_mapping:
                        material_index = stack.getContainerIndex(old_material)
                        stack.replaceContainer(material_index, material)
                        continue

        for stack in extruder_stacks:
            ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
        else:
            # Machine has no extruders, but it needs to be registered with the extruder manager.
            ExtruderManager.getInstance().registerExtruder(None, global_stack.getId())

        Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
        # Notify everything/one that is to notify about changes.
        for container in global_stack.getContainers():
            global_stack.containersChanged.emit(container)

        for stack in extruder_stacks:
            stack.setNextStack(global_stack)
            for container in stack.getContainers():
                stack.containersChanged.emit(container)

        # Actually change the active machine.
        Application.getInstance().setGlobalContainerStack(global_stack)
        return nodes
    def importProfile(self, file_name):
        Logger.log("d", "Attempting to import profile %s", file_name)
        if not file_name:
            return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")}

        plugin_registry = PluginRegistry.getInstance()
        extension = file_name.split(".")[-1]

        global_stack = Application.getInstance().getGlobalContainerStack()
        if not global_stack:
            return

        machine_extruders = []
        for position in sorted(global_stack.extruders):
            machine_extruders.append(global_stack.extruders[position])

        for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
            if meta_data["profile_reader"][0]["extension"] != extension:
                continue
            profile_reader = plugin_registry.getPluginObject(plugin_id)
            try:
                profile_or_list = profile_reader.read(file_name)  # Try to open the file with the profile reader.
            except NoProfileException:
                return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "No custom profile to import in file <filename>{0}</filename>", file_name)}
            except Exception as e:
                # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
                Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e))
                return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "\n" + str(e))}

            if profile_or_list:
                # Ensure it is always a list of profiles
                if not isinstance(profile_or_list, list):
                    profile_or_list = [profile_or_list]

                # First check if this profile is suitable for this machine
                global_profile = None
                extruder_profiles = []
                if len(profile_or_list) == 1:
                    global_profile = profile_or_list[0]
                else:
                    for profile in profile_or_list:
                        if not profile.getMetaDataEntry("position"):
                            global_profile = profile
                        else:
                            extruder_profiles.append(profile)
                extruder_profiles = sorted(extruder_profiles, key = lambda x: int(x.getMetaDataEntry("position")))
                profile_or_list = [global_profile] + extruder_profiles

                if not global_profile:
                    Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name)
                    return { "status": "error",
                             "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)}
                profile_definition = global_profile.getMetaDataEntry("definition")

                # Make sure we have a profile_definition in the file:
                if profile_definition is None:
                    break
                machine_definition = self.findDefinitionContainers(id = profile_definition)
                if not machine_definition:
                    Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition)
                    return {"status": "error",
                            "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)
                            }
                machine_definition = machine_definition[0]

                # Get the expected machine definition.
                # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
                profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition)
                expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition)

                # And check if the profile_definition matches either one (showing error if not):
                if profile_definition != expected_machine_definition:
                    Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition)
                    return { "status": "error",
                             "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)}

                # Fix the global quality profile's definition field in case it's not correct
                global_profile.setMetaDataEntry("definition", expected_machine_definition)
                quality_name = global_profile.getName()
                quality_type = global_profile.getMetaDataEntry("quality_type")

                name_seed = os.path.splitext(os.path.basename(file_name))[0]
                new_name = self.uniqueName(name_seed)

                # Ensure it is always a list of profiles
                if type(profile_or_list) is not list:
                    profile_or_list = [profile_or_list]

                # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack
                if len(profile_or_list) == 1:
                    global_profile = profile_or_list[0]
                    extruder_profiles = []
                    for idx, extruder in enumerate(global_stack.extruders.values()):
                        profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
                        profile = InstanceContainer(profile_id)
                        profile.setName(quality_name)
                        profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
                        profile.addMetaDataEntry("type", "quality_changes")
                        profile.addMetaDataEntry("definition", expected_machine_definition)
                        profile.addMetaDataEntry("quality_type", quality_type)
                        profile.addMetaDataEntry("position", "0")
                        profile.setDirty(True)
                        if idx == 0:
                            # move all per-extruder settings to the first extruder's quality_changes
                            for qc_setting_key in global_profile.getAllKeys():
                                settable_per_extruder = global_stack.getProperty(qc_setting_key,
                                                                                           "settable_per_extruder")
                                if settable_per_extruder:
                                    setting_value = global_profile.getProperty(qc_setting_key, "value")

                                    setting_definition = global_stack.getSettingDefinition(qc_setting_key)
                                    new_instance = SettingInstance(setting_definition, profile)
                                    new_instance.setProperty("value", setting_value)
                                    new_instance.resetState()  # Ensure that the state is not seen as a user state.
                                    profile.addInstance(new_instance)
                                    profile.setDirty(True)

                                    global_profile.removeInstance(qc_setting_key, postpone_emit=True)
                        extruder_profiles.append(profile)

                    for profile in extruder_profiles:
                        profile_or_list.append(profile)

                # Import all profiles
                for profile_index, profile in enumerate(profile_or_list):
                    if profile_index == 0:
                        # This is assumed to be the global profile
                        profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")

                    elif profile_index < len(machine_extruders) + 1:
                        # This is assumed to be an extruder profile
                        extruder_id = machine_extruders[profile_index - 1].definition.getId()
                        extruder_position = str(profile_index - 1)
                        if not profile.getMetaDataEntry("position"):
                            profile.addMetaDataEntry("position", extruder_position)
                        else:
                            profile.setMetaDataEntry("position", extruder_position)
                        profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_")

                    else: #More extruders in the imported file than in the machine.
                        continue #Delete the additional profiles.

                    result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition)
                    if result is not None:
                        return {"status": "error", "message": catalog.i18nc(
                            "@info:status Don't translate the XML tags <filename> or <message>!",
                            "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>",
                            file_name, result)}

                return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())}

            # This message is throw when the profile reader doesn't find any profile in the file
            return {"status": "error", "message": catalog.i18nc("@info:status", "File {0} does not contain any valid profile.", file_name)}

        # If it hasn't returned by now, none of the plugins loaded the profile successfully.
        return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
Exemple #22
0
    def preRead(self, file_name, show_dialog=True, *args, **kwargs):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            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")

        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]

        # 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}
        containers_found_dict = {k: False for k in resolve_strategy_keys}

        #
        # Read definition containers
        #
        machine_definition_container_count = 0
        extruder_definition_container_count = 0
        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)

            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"))

            else:
                definition_container = definitions[0]
            definition_container_list.append(definition_container)

            definition_container_type = definition_container.getMetaDataEntry("type")
            if definition_container_type == "machine":
                machine_type = definition_container.getName()
                variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)

                machine_definition_container_count += 1
            elif definition_container_type == "extruder":
                extruder_definition_container_count += 1
            else:
                Logger.log("w", "Unknown definition container type %s for %s",
                           definition_container_type, each_definition_container_file)
            Job.yieldThread()
        # sanity check
        if machine_definition_container_count != 1:
            msg = "Expecting one machine definition container but got %s" % machine_definition_container_count
            Logger.log("e", msg)
            raise RuntimeError(msg)

        material_labels = []
        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
        if xml_material_profile:
            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:
                    containers_found_dict["material"] = True
                    if not materials[0].isReadOnly():  # Only non readonly materials can be in conflict
                        material_conflict = True
                Job.yieldThread()

        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        quality_name = ""
        quality_type = ""
        num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
        num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes
        num_user_settings = 0
        quality_changes_conflict = False
        definition_changes_conflict = False

        for each_instance_container_file in instance_container_files:
            container_id = self._stripFileToId(each_instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"))
            instance_container_list.append(instance_container)

            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                quality_name = instance_container.getName()
                num_settings_overriden_by_quality_changes += len(instance_container._instances)
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(id = container_id)
                if quality_changes:
                    containers_found_dict["quality_changes"] = True
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
            elif container_type == "definition_changes":
                definition_name = instance_container.getName()
                num_settings_overriden_by_definition_changes += len(instance_container._instances)
                definition_changes = self._container_registry.findDefinitionContainers(id = container_id)
                if definition_changes:
                    if definition_changes[0] != instance_container:
                        definition_changes_conflict = True
            elif container_type == "user":
                num_user_settings += len(instance_container._instances)
            elif container_type in self._ignored_instance_container_types:
                # Ignore certain instance container types
                Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
                continue

            Job.yieldThread()

        # Load ContainerStack files and ExtruderStack files
        global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
            file_name, cura_file_names)
        machine_conflict = False
        # Because there can be cases as follows:
        #  - the global stack exists but some/all of the extruder stacks DON'T exist
        #  - the global stack DOESN'T exist but some/all of the extruder stacks exist
        # To simplify this, only check if the global stack exists or not
        container_id = self._stripFileToId(global_stack_file)
        serialized = archive.open(global_stack_file).read().decode("utf-8")
        machine_name = self._getMachineNameFromSerializedStack(serialized)
        stacks = self._container_registry.findContainerStacks(id = container_id)
        if stacks:
            global_stack = stacks[0]
            containers_found_dict["machine"] = True
            # Check if there are any changes at all in any of the container stacks.
            id_list = self._getContainerIdListFromSerialized(serialized)
            for index, container_id in enumerate(id_list):
                # take into account the old empty container IDs
                container_id = self._old_empty_profile_id_dict.get(container_id, container_id)
                if global_stack.getContainer(index).getId() != container_id:
                    machine_conflict = True
                    break
        Job.yieldThread()

        # if the global stack is found, we check if there are conflicts in the extruder stacks
        if containers_found_dict["machine"] and not machine_conflict:
            for extruder_stack_file in extruder_stack_files:
                container_id = self._stripFileToId(extruder_stack_file)
                serialized = archive.open(extruder_stack_file).read().decode("utf-8")
                parser = configparser.ConfigParser()
                parser.read_string(serialized)

                # The check should be done for the extruder stack that's associated with the existing global stack,
                # and those extruder stacks may have different IDs.
                # So we check according to the positions

                position = str(parser["metadata"]["position"])
                if position not in global_stack.extruders:
                    # The extruder position defined in the project doesn't exist in this global stack.
                    # We can say that it is a machine conflict, but it is very hard to override the machine in this
                    # case because we need to override the existing extruders and add the non-existing extruders.
                    #
                    # HACK:
                    # To make this simple, we simply say that there is no machine conflict and create a new machine
                    # by default.
                    machine_conflict = False
                    break

                existing_extruder_stack = global_stack.extruders[position]
                # check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(serialized)
                for index, container_id in enumerate(id_list):
                    # take into account the old empty container IDs
                    container_id = self._old_empty_profile_id_dict.get(container_id, container_id)
                    if existing_extruder_stack.getContainer(index).getId() != container_id:
                        machine_conflict = True
                        break

        num_visible_settings = 0
        try:
            temp_preferences = Preferences()
            temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

            visible_settings_string = temp_preferences.getValue("general/visible_settings")
            if visible_settings_string is not None:
                num_visible_settings = len(visible_settings_string.split(";"))
            active_mode = temp_preferences.getValue("cura/active_mode")
            if not active_mode:
                active_mode = Preferences.getInstance().getValue("cura/active_mode")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
        if not show_dialog:
            return WorkspaceReader.PreReadResult.accepted

        # prepare data for the dialog
        num_extruders = extruder_definition_container_count
        if num_extruders == 0:
            num_extruders = 1  # No extruder stacks found, which means there is one extruder

        extruders = num_extruders * [""]

        # Show the dialog, informing the user what is about to happen.
        self._dialog.setMachineConflict(machine_conflict)
        self._dialog.setQualityChangesConflict(quality_changes_conflict)
        self._dialog.setDefinitionChangesConflict(definition_changes_conflict)
        self._dialog.setMaterialConflict(material_conflict)
        self._dialog.setNumVisibleSettings(num_visible_settings)
        self._dialog.setQualityName(quality_name)
        self._dialog.setQualityType(quality_type)
        self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
        self._dialog.setNumUserSettings(num_user_settings)
        self._dialog.setActiveMode(active_mode)
        self._dialog.setMachineName(machine_name)
        self._dialog.setMaterialLabels(material_labels)
        self._dialog.setMachineType(machine_type)
        self._dialog.setExtruders(extruders)
        self._dialog.setVariantType(variant_type_name)
        self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
        self._dialog.show()

        # Block until the dialog is closed.
        self._dialog.waitForClose()

        if self._dialog.getResult() == {}:
            return WorkspaceReader.PreReadResult.cancelled

        self._resolve_strategies = self._dialog.getResult()
        #
        # There can be 3 resolve strategies coming from the dialog:
        #  - new:       create a new container
        #  - override:  override the existing container
        #  - None:      There is no conflict, which means containers with the same IDs may or may not be there already.
        #               If there is an existing container, there is no conflict between the them, and default to "override"
        #               If there is no existing container, default to "new"
        #
        # Default values
        for key, strategy in self._resolve_strategies.items():
            if key not in containers_found_dict or strategy is not None:
                continue
            self._resolve_strategies[key] = "override" if containers_found_dict[key] else "new"

        return WorkspaceReader.PreReadResult.accepted
    def preRead(self, file_name):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler(
        ).getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(
                file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            Logger.log(
                "w",
                "Could not find reader that was able to read the scene data for 3MF workspace"
            )
            return WorkspaceReader.PreReadResult.failed

        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [
            name for name in archive.namelist() if name.startswith("Cura/")
        ]
        container_stack_files = [
            name for name in cura_file_names
            if name.endswith(self._container_stack_suffix)
        ]
        self._resolve_strategies = {
            "machine": None,
            "quality_changes": None,
            "material": None
        }
        machine_conflict = False
        quality_changes_conflict = False
        for container_stack_file in container_stack_files:
            container_id = self._stripFileToId(container_stack_file)
            stacks = self._container_registry.findContainerStacks(
                id=container_id)
            if stacks:
                # Check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(
                    archive.open(container_stack_file).read().decode("utf-8"))
                for index, container_id in enumerate(id_list):
                    if stacks[0].getContainer(index).getId() != container_id:
                        machine_conflict = True
                        break
            Job.yieldThread()

        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(
                xml_material_profile).preferredSuffix
        if xml_material_profile:
            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)
                if materials and not materials[0].isReadOnly(
                ):  # Only non readonly materials can be in conflict
                    material_conflict = True
                Job.yieldThread()

        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [
            name for name in cura_file_names
            if name.endswith(self._instance_container_suffix)
        ]
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(
                archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(
                    id=container_id)
                if quality_changes:
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
                        break
            Job.yieldThread()
        try:
            archive.open("Cura/preferences.cfg")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        if machine_conflict or quality_changes_conflict or material_conflict:
            # There is a conflict; User should choose to either update the existing data, add everything as new data or abort
            self._dialog.setMachineConflict(machine_conflict)
            self._dialog.setQualityChangesConflict(quality_changes_conflict)
            self._dialog.setMaterialConflict(material_conflict)
            self._dialog.show()

            # Block until the dialog is closed.
            self._dialog.waitForClose()

            if self._dialog.getResult() == {}:
                return WorkspaceReader.PreReadResult.cancelled

            self._resolve_strategies = self._dialog.getResult()

        return WorkspaceReader.PreReadResult.accepted
    def read(self, file_name):
        archive = zipfile.ZipFile(file_name, "r")

        cura_file_names = [
            name for name in archive.namelist() if name.startswith("Cura/")
        ]

        # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
        # parsing code.
        temp_preferences = Preferences()
        temp_preferences.readFromFile(
            io.TextIOWrapper(archive.open("Cura/preferences.cfg"))
        )  # We need to wrap it, else the archive parser breaks.

        # Copy a number of settings from the temp preferences to the global
        global_preferences = Preferences.getInstance()

        visible_settings = temp_preferences.getValue(
            "general/visible_settings")
        if visible_settings is None:
            Logger.log(
                "w",
                "Workspace did not contain visible settings. Leaving visibility unchanged"
            )
        else:
            global_preferences.setValue("general/visible_settings",
                                        visible_settings)

        categories_expanded = temp_preferences.getValue(
            "cura/categories_expanded")
        if categories_expanded is None:
            Logger.log(
                "w",
                "Workspace did not contain expanded categories. Leaving them unchanged"
            )
        else:
            global_preferences.setValue("cura/categories_expanded",
                                        categories_expanded)

        Application.getInstance().expandedCategoriesChanged.emit(
        )  # Notify the GUI of the change

        self._id_mapping = {}

        # We don't add containers right away, but wait right until right before the stack serialization.
        # We do this so that if something goes wrong, it's easier to clean up.
        containers_to_add = []

        # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
        # TODO: cases that the container loaded is the same (most notable in materials & definitions).
        # TODO: It might be possible that we need to add smarter checking in the future.
        Logger.log("d", "Workspace loading is checking definitions...")
        # Get all the definition files & check if they exist. If not, add them.
        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)
            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(
                    archive.open(definition_container_file).read().decode(
                        "utf-8"))
                self._container_registry.addContainer(definition_container)
            Job.yieldThread()

        Logger.log("d", "Workspace loading is checking materials...")
        material_containers = []
        # Get all the material files and check if they exist. If not, add them.
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(
                xml_material_profile).suffixes[0]
        if xml_material_profile:
            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)
                if not materials:
                    material_container = xml_material_profile(container_id)
                    material_container.deserialize(
                        archive.open(material_container_file).read().decode(
                            "utf-8"))
                    containers_to_add.append(material_container)
                else:
                    if not materials[0].isReadOnly(
                    ):  # Only create new materials if they are not read only.
                        if self._resolve_strategies["material"] == "override":
                            materials[0].deserialize(
                                archive.open(material_container_file).read().
                                decode("utf-8"))
                        elif self._resolve_strategies["material"] == "new":
                            # Note that we *must* deserialize it with a new ID, as multiple containers will be
                            # auto created & added.
                            material_container = xml_material_profile(
                                self.getNewId(container_id))
                            material_container.deserialize(
                                archive.open(material_container_file).read().
                                decode("utf-8"))
                            containers_to_add.append(material_container)
                            material_containers.append(material_container)
                Job.yieldThread()

        Logger.log("d", "Workspace loading is checking instance containers...")
        # Get quality_changes and user profiles saved in the workspace
        instance_container_files = [
            name for name in cura_file_names
            if name.endswith(self._instance_container_suffix)
        ]
        user_instance_containers = []
        quality_changes_instance_containers = []
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(
                archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            Job.yieldThread()
            if container_type == "user":
                # Check if quality changes already exists.
                user_containers = self._container_registry.findInstanceContainers(
                    id=container_id)
                if not user_containers:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies[
                            "machine"] == "override" or self._resolve_strategies[
                                "machine"] is None:
                        user_containers[0].deserialize(
                            archive.open(instance_container_file).read().
                            decode("utf-8"))
                    elif self._resolve_strategies["machine"] == "new":
                        # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                        extruder_id = instance_container.getMetaDataEntry(
                            "extruder", None)
                        if extruder_id:
                            new_id = self.getNewId(
                                extruder_id) + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry(
                                "extruder", self.getNewId(extruder_id))
                            containers_to_add.append(instance_container)

                        machine_id = instance_container.getMetaDataEntry(
                            "machine", None)
                        if machine_id:
                            new_id = self.getNewId(
                                machine_id) + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry(
                                "machine", self.getNewId(machine_id))
                            containers_to_add.append(instance_container)
                user_instance_containers.append(instance_container)
            elif container_type == "quality_changes":
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(
                    id=container_id)
                if not quality_changes:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies[
                            "quality_changes"] == "override":
                        quality_changes[0].deserialize(
                            archive.open(instance_container_file).read().
                            decode("utf-8"))
                    elif self._resolve_strategies["quality_changes"] is None:
                        # The ID already exists, but nothing in the values changed, so do nothing.
                        pass
                quality_changes_instance_containers.append(instance_container)
            else:
                continue

        # Add all the containers right before we try to add / serialize the stack
        for container in containers_to_add:
            self._container_registry.addContainer(container)
            container.setDirty(True)

        # Get the stack(s) saved in the workspace.
        Logger.log("d", "Workspace loading is checking stacks containers...")
        container_stack_files = [
            name for name in cura_file_names
            if name.endswith(self._container_stack_suffix)
        ]
        global_stack = None
        extruder_stacks = []
        container_stacks_added = []
        try:
            for container_stack_file in container_stack_files:
                container_id = self._stripFileToId(container_stack_file)

                # Check if a stack by this ID already exists;
                container_stacks = self._container_registry.findContainerStacks(
                    id=container_id)
                if container_stacks:
                    stack = container_stacks[0]
                    if self._resolve_strategies["machine"] == "override":
                        # TODO: HACK
                        # There is a machine, check if it has authenticationd 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(container_stack_file).read().decode(
                                "utf-8"))
                        if network_authentication_id:
                            container_stacks[0].addMetaDataEntry(
                                "network_authentication_id",
                                network_authentication_id)
                        if network_authentication_key:
                            container_stacks[0].addMetaDataEntry(
                                "network_authentication_key",
                                network_authentication_key)
                    elif self._resolve_strategies["machine"] == "new":
                        new_id = self.getNewId(container_id)
                        stack = ContainerStack(new_id)
                        stack.deserialize(
                            archive.open(container_stack_file).read().decode(
                                "utf-8"))

                        # Ensure a unique ID and name
                        stack._id = new_id

                        # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
                        # bound machine also needs to change.
                        if stack.getMetaDataEntry("machine", None):
                            stack.setMetaDataEntry(
                                "machine",
                                self.getNewId(
                                    stack.getMetaDataEntry("machine")))

                        if stack.getMetaDataEntry("type") != "extruder_train":
                            # Only machines need a new name, stacks may be non-unique
                            stack.setName(
                                self._container_registry.uniqueName(
                                    stack.getName()))
                        container_stacks_added.append(stack)
                        self._container_registry.addContainer(stack)
                    else:
                        Logger.log(
                            "w",
                            "Resolve strategy of %s for machine is not supported",
                            self._resolve_strategies["machine"])
                else:
                    stack = ContainerStack(container_id)
                    # Deserialize stack by converting read data from bytes to string
                    stack.deserialize(
                        archive.open(container_stack_file).read().decode(
                            "utf-8"))
                    container_stacks_added.append(stack)
                    self._container_registry.addContainer(stack)

                if stack.getMetaDataEntry("type") == "extruder_train":
                    extruder_stacks.append(stack)
                else:
                    global_stack = stack
                Job.yieldThread()
        except:
            Logger.logException(
                "w", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added.
            for container in containers_to_add:
                self._container_registry.getInstance().removeContainer(
                    container.getId())

            for container in container_stacks_added:
                self._container_registry.getInstance().removeContainer(
                    container.getId())

            return None

        if self._resolve_strategies["machine"] == "new":
            # A new machine was made, but it was serialized with the wrong user container. Fix that now.
            for container in user_instance_containers:
                extruder_id = container.getMetaDataEntry("extruder", None)
                if extruder_id:
                    for extruder in extruder_stacks:
                        if extruder.getId() == extruder_id:
                            extruder.replaceContainer(0, container)
                            continue
                machine_id = container.getMetaDataEntry("machine", None)
                if machine_id:
                    if global_stack.getId() == machine_id:
                        global_stack.replaceContainer(0, container)
                        continue

        if self._resolve_strategies["quality_changes"] == "new":
            # Quality changes needs to get a new ID, added to registry and to the right stacks
            for container in quality_changes_instance_containers:
                old_id = container.getId()
                container.setName(
                    self._container_registry.uniqueName(container.getName()))
                # We're not really supposed to change the ID in normal cases, but this is an exception.
                container._id = self.getNewId(container.getId())

                # The container was not added yet, as it didn't have an unique ID. It does now, so add it.
                self._container_registry.addContainer(container)

                # Replace the quality changes container
                old_container = global_stack.findContainer(
                    {"type": "quality_changes"})
                if old_container.getId() == old_id:
                    quality_changes_index = global_stack.getContainerIndex(
                        old_container)
                    global_stack.replaceContainer(quality_changes_index,
                                                  container)
                    continue

                for stack in extruder_stacks:
                    old_container = stack.findContainer(
                        {"type": "quality_changes"})
                    if old_container.getId() == old_id:
                        quality_changes_index = stack.getContainerIndex(
                            old_container)
                        stack.replaceContainer(quality_changes_index,
                                               container)

        if self._resolve_strategies["material"] == "new":
            for material in material_containers:
                old_material = global_stack.findContainer({"type": "material"})
                if old_material.getId() in self._id_mapping:
                    material_index = global_stack.getContainerIndex(
                        old_material)
                    global_stack.replaceContainer(material_index, material)
                    continue

                for stack in extruder_stacks:
                    old_material = stack.findContainer({"type": "material"})
                    if old_material.getId() in self._id_mapping:
                        material_index = stack.getContainerIndex(old_material)
                        stack.replaceContainer(material_index, material)
                        continue

        for stack in extruder_stacks:
            ExtruderManager.getInstance().registerExtruder(
                stack, global_stack.getId())
        else:
            # Machine has no extruders, but it needs to be registered with the extruder manager.
            ExtruderManager.getInstance().registerExtruder(
                None, global_stack.getId())

        Logger.log(
            "d",
            "Workspace loading is notifying rest of the code of changes...")

        # Notify everything/one that is to notify about changes.
        global_stack.containersChanged.emit(global_stack.getTop())

        for stack in extruder_stacks:
            stack.setNextStack(global_stack)
            stack.containersChanged.emit(stack.getTop())

        # Actually change the active machine.
        Application.getInstance().setGlobalContainerStack(global_stack)

        # Load all the nodes / meshdata of the workspace
        nodes = self._3mf_mesh_reader.read(file_name)
        if nodes is None:
            nodes = []
        return nodes
Exemple #25
0
 def _onContainerChanged(self, container: InstanceContainer) -> None:
     container_type = container.getMetaDataEntry("type")
     if container_type not in ("quality", "quality_changes"):
         return
    def preRead(self, file_name, show_dialog=True, *args, **kwargs):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler(
        ).getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(
                file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            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")

        num_extruders = 0
        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [
            name for name in archive.namelist() if name.startswith("Cura/")
        ]
        container_stack_files = [
            name for name in cura_file_names
            if name.endswith(self._container_stack_suffix)
        ]
        self._resolve_strategies = {
            "machine": None,
            "quality_changes": None,
            "material": None
        }
        machine_conflict = False
        quality_changes_conflict = False
        for container_stack_file in container_stack_files:
            container_id = self._stripFileToId(container_stack_file)
            serialized = archive.open(container_stack_file).read().decode(
                "utf-8")
            if machine_name == "":
                machine_name = self._getMachineNameFromSerializedStack(
                    serialized)
            stacks = self._container_registry.findContainerStacks(
                id=container_id)
            if stacks:
                # Check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(serialized)
                for index, container_id in enumerate(id_list):
                    if stacks[0].getContainer(index).getId() != container_id:
                        machine_conflict = True
            Job.yieldThread()

        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)

            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(
                    archive.open(definition_container_file).read().decode(
                        "utf-8"))

            else:
                definition_container = definitions[0]

            if definition_container.getMetaDataEntry("type") != "extruder":
                machine_type = definition_container.getName()
                variant_type_name = definition_container.getMetaDataEntry(
                    "variants_name", variant_type_name)
            else:
                num_extruders += 1
            Job.yieldThread()

        if num_extruders == 0:
            num_extruders = 1  # No extruder stacks found, which means there is one extruder

        extruders = num_extruders * [""]

        material_labels = []
        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(
                xml_material_profile).preferredSuffix
        if xml_material_profile:
            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 and not materials[0].isReadOnly(
                ):  # Only non readonly materials can be in conflict
                    material_conflict = True
                Job.yieldThread()
        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [
            name for name in cura_file_names
            if name.endswith(self._instance_container_suffix)
        ]
        quality_name = ""
        quality_type = ""
        num_settings_overriden_by_quality_changes = 0  # How many settings are changed by the quality changes
        num_user_settings = 0
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(
                archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                quality_name = instance_container.getName()
                num_settings_overriden_by_quality_changes += len(
                    instance_container._instances)
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(
                    id=container_id)
                if quality_changes:
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
            elif container_type == "quality":
                # If the quality name is not set (either by quality or changes, set it now)
                # Quality changes should always override this (as they are "on top")
                if quality_name == "":
                    quality_name = instance_container.getName()
                quality_type = instance_container.getName()
            elif container_type == "user":
                num_user_settings += len(instance_container._instances)

            Job.yieldThread()
        num_visible_settings = 0
        try:
            temp_preferences = Preferences()
            temp_preferences.readFromFile(
                io.TextIOWrapper(archive.open("Cura/preferences.cfg"))
            )  # We need to wrap it, else the archive parser breaks.

            visible_settings_string = temp_preferences.getValue(
                "general/visible_settings")
            if visible_settings_string is not None:
                num_visible_settings = len(visible_settings_string.split(";"))
            active_mode = temp_preferences.getValue("cura/active_mode")
            if not active_mode:
                active_mode = Preferences.getInstance().getValue(
                    "cura/active_mode")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
        if not show_dialog:
            return WorkspaceReader.PreReadResult.accepted

        # Show the dialog, informing the user what is about to happen.
        self._dialog.setMachineConflict(machine_conflict)
        self._dialog.setQualityChangesConflict(quality_changes_conflict)
        self._dialog.setMaterialConflict(material_conflict)
        self._dialog.setNumVisibleSettings(num_visible_settings)
        self._dialog.setQualityName(quality_name)
        self._dialog.setQualityType(quality_type)
        self._dialog.setNumSettingsOverridenByQualityChanges(
            num_settings_overriden_by_quality_changes)
        self._dialog.setNumUserSettings(num_user_settings)
        self._dialog.setActiveMode(active_mode)
        self._dialog.setMachineName(machine_name)
        self._dialog.setMaterialLabels(material_labels)
        self._dialog.setMachineType(machine_type)
        self._dialog.setExtruders(extruders)
        self._dialog.setVariantType(variant_type_name)
        self._dialog.setHasObjectsOnPlate(
            Application.getInstance().platformActivity)
        self._dialog.show()

        # Block until the dialog is closed.
        self._dialog.waitForClose()

        if self._dialog.getResult() == {}:
            return WorkspaceReader.PreReadResult.cancelled

        self._resolve_strategies = self._dialog.getResult()

        return WorkspaceReader.PreReadResult.accepted
    def read(self, file_name):
        archive = zipfile.ZipFile(file_name, "r")

        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]

        # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
        # parsing code.
        temp_preferences = Preferences()
        temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

        # Copy a number of settings from the temp preferences to the global
        global_preferences = Preferences.getInstance()

        visible_settings = temp_preferences.getValue("general/visible_settings")
        if visible_settings is None:
            Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
        else:
            global_preferences.setValue("general/visible_settings", visible_settings)

        categories_expanded = temp_preferences.getValue("cura/categories_expanded")
        if categories_expanded is None:
            Logger.log("w", "Workspace did not contain expanded categories. Leaving them unchanged")
        else:
            global_preferences.setValue("cura/categories_expanded", categories_expanded)

        Application.getInstance().expandedCategoriesChanged.emit()  # Notify the GUI of the change

        self._id_mapping = {}

        # We don't add containers right away, but wait right until right before the stack serialization.
        # We do this so that if something goes wrong, it's easier to clean up.
        containers_to_add = []

        global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)

        global_stack = None
        extruder_stacks = []
        extruder_stacks_added = []
        container_stacks_added = []

        containers_added = []

        global_stack_id_original = self._stripFileToId(global_stack_file)
        global_stack_id_new = global_stack_id_original
        global_stack_need_rename = False

        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):
                global_stack_id_new = self.getNewId(global_stack_id_original)
                global_stack_need_rename = True

            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):
                    # get a new name for this extruder
                    new_container_id = self.getNewId(old_container_id)

                extruder_stack_id_map[old_container_id] = new_container_id

        # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
        # TODO: cases that the container loaded is the same (most notable in materials & definitions).
        # TODO: It might be possible that we need to add smarter checking in the future.
        Logger.log("d", "Workspace loading is checking definitions...")
        # Get all the definition files & check if they exist. If not, add them.
        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)
            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
                self._container_registry.addContainer(definition_container)
            Job.yieldThread()

        Logger.log("d", "Workspace loading is checking materials...")
        material_containers = []
        # Get all the material files and check if they exist. If not, add them.
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
        if xml_material_profile:
            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)

                if not materials:
                    material_container = xml_material_profile(container_id)
                    material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                    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 self._resolve_strategies["material"] == "override":
                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                        elif self._resolve_strategies["material"] == "new":
                            # Note that we *must* deserialize it with a new ID, as multiple containers will be
                            # auto created & added.
                            material_container = xml_material_profile(self.getNewId(container_id))
                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                            containers_to_add.append(material_container)

                material_containers.append(material_container)
                Job.yieldThread()

        Logger.log("d", "Workspace loading is checking instance containers...")
        # Get quality_changes and user profiles saved in the workspace
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        user_instance_containers = []
        quality_and_definition_changes_instance_containers = []
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            serialized = archive.open(instance_container_file).read().decode("utf-8")

            # HACK! we ignore "quality" and "variant" instance containers!
            parser = configparser.ConfigParser()
            parser.read_string(serialized)
            if not parser.has_option("metadata", "type"):
                Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
                continue
            if parser.get("metadata", "type") in self._ignored_instance_container_types:
                continue

            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(serialized)
            container_type = instance_container.getMetaDataEntry("type")
            Job.yieldThread()

            #
            # IMPORTANT:
            # If an instance container (or maybe other type of container) exists, and user chooses "Create New",
            # we need to rename this container and all references to it, and changing those references are VERY
            # HARD.
            #
            if container_type in self._ignored_instance_container_types:
                # Ignore certain instance container types
                Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
                continue
            elif container_type == "user":
                # Check if quality changes already exists.
                user_containers = self._container_registry.findInstanceContainers(id = container_id)
                if not user_containers:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
                        instance_container = user_containers[0]
                        instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                        instance_container.setDirty(True)
                    elif self._resolve_strategies["machine"] == "new":
                        # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                        old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
                        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.setName(new_id)
                            instance_container.setMetaDataEntry("extruder", new_extruder_id)
                            containers_to_add.append(instance_container)

                        machine_id = instance_container.getMetaDataEntry("machine", None)
                        if machine_id:
                            new_machine_id = self.getNewId(machine_id)
                            new_id = new_machine_id + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry("machine", new_machine_id)
                            containers_to_add.append(instance_container)
                user_instance_containers.append(instance_container)
            elif container_type in ("quality_changes", "definition_changes"):
                # Check if quality changes already exists.
                changes_containers = self._container_registry.findInstanceContainers(id = container_id)
                if not changes_containers:
                    # no existing containers with the same ID, so we can safely add the new one
                    containers_to_add.append(instance_container)
                else:
                    # we have found existing container with the same ID, so we need to resolve according to the
                    # selected strategy.
                    if self._resolve_strategies[container_type] == "override":
                        instance_container = changes_containers[0]
                        instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                        instance_container.setDirty(True)

                    elif self._resolve_strategies[container_type] == "new":
                        # TODO: how should we handle the case "new" for quality_changes and definition_changes?

                        instance_container.setName(self._container_registry.uniqueName(instance_container.getName()))
                        new_changes_container_id = self.getNewId(instance_container.getId())
                        instance_container._id = new_changes_container_id

                        # TODO: we don't know the following is correct or not, need to verify
                        #       AND REFACTOR!!!
                        if self._resolve_strategies["machine"] == "new":
                            # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                            old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
                            if old_extruder_id:
                                new_extruder_id = extruder_stack_id_map[old_extruder_id]
                                instance_container.setMetaDataEntry("extruder", new_extruder_id)

                            machine_id = instance_container.getMetaDataEntry("machine", None)
                            if machine_id:
                                new_machine_id = self.getNewId(machine_id)
                                instance_container.setMetaDataEntry("machine", new_machine_id)

                        containers_to_add.append(instance_container)

                    elif self._resolve_strategies[container_type] is None:
                        # The ID already exists, but nothing in the values changed, so do nothing.
                        pass
                quality_and_definition_changes_instance_containers.append(instance_container)
            else:
                existing_container = self._container_registry.findInstanceContainers(id = container_id)
                if not existing_container:
                    containers_to_add.append(instance_container)
            if global_stack_need_rename:
                if instance_container.getMetaDataEntry("machine"):
                    instance_container.setMetaDataEntry("machine", global_stack_id_new)

        # Add all the containers right before we try to add / serialize the stack
        for container in containers_to_add:
            self._container_registry.addContainer(container)
            container.setDirty(True)
            containers_added.append(container)

        # Get the stack(s) saved in the workspace.
        Logger.log("d", "Workspace loading is checking stacks containers...")

        # --
        # load global stack file
        try:
            # Check if a stack by this ID already exists;
            container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
            if container_stacks:
                stack = container_stacks[0]

                if self._resolve_strategies["machine"] == "override":
                    # TODO: 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"))
                    if network_authentication_id:
                        container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
                    if network_authentication_key:
                        container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
                elif self._resolve_strategies["machine"] == "new":
                    stack = GlobalStack(global_stack_id_new)
                    stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))

                    # Ensure a unique ID and name
                    stack._id = global_stack_id_new

                    # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
                    # bound machine also needs to change.
                    if stack.getMetaDataEntry("machine", None):
                        stack.setMetaDataEntry("machine", global_stack_id_new)

                    # Only machines need a new name, stacks may be non-unique
                    stack.setName(self._container_registry.uniqueName(stack.getName()))
                    container_stacks_added.append(stack)
                    self._container_registry.addContainer(stack)
                else:
                    Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
            else:
                # no existing container stack, so we create a new one
                stack = GlobalStack(global_stack_id_new)
                # Deserialize stack by converting read data from bytes to string
                stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
                container_stacks_added.append(stack)
                self._container_registry.addContainer(stack)
                containers_added.append(stack)

            global_stack = stack
            Job.yieldThread()
        except:
            Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added.
            for container in containers_added:
                self._container_registry.removeContainer(container.getId())
            return

        # --
        # load extruder stack files
        try:
            for index, extruder_stack_file in enumerate(extruder_stack_files):
                container_id = self._stripFileToId(extruder_stack_file)
                extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")

                container_stacks = self._container_registry.findContainerStacks(id = container_id)
                if container_stacks:
                    # this container stack already exists, try to resolve
                    stack = container_stacks[0]

                    if self._resolve_strategies["machine"] == "override":
                        # NOTE: This is the same code as those in the lower part
                        # deserialize new extruder stack over the current ones
                        stack = self._overrideExtruderStack(global_stack, extruder_file_content)

                    elif self._resolve_strategies["machine"] == "new":
                        # create a new extruder stack from this one
                        new_id = extruder_stack_id_map[container_id]
                        stack = ExtruderStack(new_id)

                        # HACK: the global stack can have a new name, so we need to make sure that this extruder stack
                        #       references to the new name instead of the old one. Normally, this can be done after
                        #       deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
                        #       also does addExtruder() to its machine stack, so we have to make sure that it's pointing
                        #       to the right machine BEFORE deserialization.
                        extruder_config = configparser.ConfigParser()
                        extruder_config.read_string(extruder_file_content)
                        extruder_config.set("metadata", "machine", global_stack_id_new)
                        tmp_string_io = io.StringIO()
                        extruder_config.write(tmp_string_io)
                        extruder_file_content = tmp_string_io.getvalue()

                        stack.deserialize(extruder_file_content)

                        # Ensure a unique ID and name
                        stack._id = new_id

                        self._container_registry.addContainer(stack)
                        extruder_stacks_added.append(stack)
                        containers_added.append(stack)
                else:
                    # No extruder stack with the same ID can be found
                    if self._resolve_strategies["machine"] == "override":
                        # deserialize new extruder stack over the current ones
                        stack = self._overrideExtruderStack(global_stack, extruder_file_content)

                    elif self._resolve_strategies["machine"] == "new":
                        # container not found, create a new one
                        stack = ExtruderStack(container_id)

                        # HACK: the global stack can have a new name, so we need to make sure that this extruder stack
                        #       references to the new name instead of the old one. Normally, this can be done after
                        #       deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
                        #       also does addExtruder() to its machine stack, so we have to make sure that it's pointing
                        #       to the right machine BEFORE deserialization.
                        extruder_config = configparser.ConfigParser()
                        extruder_config.read_string(extruder_file_content)
                        extruder_config.set("metadata", "machine", global_stack_id_new)
                        tmp_string_io = io.StringIO()
                        extruder_config.write(tmp_string_io)
                        extruder_file_content = tmp_string_io.getvalue()

                        stack.deserialize(extruder_file_content)
                        self._container_registry.addContainer(stack)
                        extruder_stacks_added.append(stack)
                        containers_added.append(stack)
                    else:
                        Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"]))

                extruder_stacks.append(stack)
        except:
            Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added. 
            for container in containers_added:
                self._container_registry.removeContainer(container.getId())
            return

        #
        # Replacing the old containers if resolve is "new".
        # When resolve is "new", some containers will get renamed, so all the other containers that reference to those
        # MUST get updated too.
        #
        if self._resolve_strategies["machine"] == "new":
            # A new machine was made, but it was serialized with the wrong user container. Fix that now.
            for container in user_instance_containers:
                # replacing the container ID for user instance containers for the extruders
                extruder_id = container.getMetaDataEntry("extruder", None)
                if extruder_id:
                    for extruder in extruder_stacks:
                        if extruder.getId() == extruder_id:
                            extruder.userChanges = container
                            continue

                # replacing the container ID for user instance containers for the machine
                machine_id = container.getMetaDataEntry("machine", None)
                if machine_id:
                    if global_stack.getId() == machine_id:
                        global_stack.userChanges = container
                        continue

        for changes_container_type in ("quality_changes", "definition_changes"):
            if self._resolve_strategies[changes_container_type] == "new":
                # Quality changes needs to get a new ID, added to registry and to the right stacks
                for each_changes_container in quality_and_definition_changes_instance_containers:
                    # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the
                    #       instance container loading part.
                    new_id = each_changes_container.getId()

                    # Find the old (current) changes container in the global stack
                    if changes_container_type == "quality_changes":
                        old_container = global_stack.qualityChanges
                    elif changes_container_type == "definition_changes":
                        old_container = global_stack.definitionChanges

                    # sanity checks
                    # NOTE: The following cases SHOULD NOT happen!!!!
                    if not old_container:
                        Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
                                   changes_container_type, global_stack.getId())

                    # Replace the quality/definition changes container if it's in the GlobalStack
                    # NOTE: we can get an empty container here, but the IDs will not match,
                    # so this comparison is fine.
                    if self._id_mapping.get(old_container.getId()) == new_id:
                        if changes_container_type == "quality_changes":
                            global_stack.qualityChanges = each_changes_container
                        elif changes_container_type == "definition_changes":
                            global_stack.definitionChanges = each_changes_container
                        continue

                    # Replace the quality/definition changes container if it's in one of the ExtruderStacks
                    for each_extruder_stack in extruder_stacks:
                        changes_container = None
                        if changes_container_type == "quality_changes":
                            changes_container = each_extruder_stack.qualityChanges
                        elif changes_container_type == "definition_changes":
                            changes_container = each_extruder_stack.definitionChanges

                        # sanity checks
                        # NOTE: The following cases SHOULD NOT happen!!!!
                        if not changes_container:
                            Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
                                       changes_container_type, each_extruder_stack.getId())

                        # NOTE: we can get an empty container here, but the IDs will not match,
                        # so this comparison is fine.
                        if self._id_mapping.get(changes_container.getId()) == new_id:
                            if changes_container_type == "quality_changes":
                                each_extruder_stack.qualityChanges = each_changes_container
                            elif changes_container_type == "definition_changes":
                                each_extruder_stack.definitionChanges = each_changes_container

        if self._resolve_strategies["material"] == "new":
            for each_material in material_containers:
                old_material = global_stack.material

                # check if the old material container has been renamed to this material container ID
                # if the container hasn't been renamed, we do nothing.
                new_id = self._id_mapping.get(old_material.getId())
                if new_id is None or new_id != each_material.getId():
                    continue

                if old_material.getId() in self._id_mapping:
                    global_stack.material = each_material

                for each_extruder_stack in extruder_stacks:
                    old_material = each_extruder_stack.material

                    # check if the old material container has been renamed to this material container ID
                    # if the container hasn't been renamed, we do nothing.
                    new_id = self._id_mapping.get(old_material.getId())
                    if new_id is None or new_id != each_material.getId():
                        continue

                    if old_material.getId() in self._id_mapping:
                        each_extruder_stack.material = each_material

        if extruder_stacks:
            for stack in extruder_stacks:
                ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
        else:
            # Machine has no extruders, but it needs to be registered with the extruder manager.
            ExtruderManager.getInstance().registerExtruder(None, global_stack.getId())

        Logger.log("d", "Workspace loading is notifying rest of the code of changes...")

        if self._resolve_strategies["machine"] == "new":
            for stack in extruder_stacks:
                stack.setNextStack(global_stack)
                stack.containersChanged.emit(stack.getTop())

        # Actually change the active machine.
        Application.getInstance().setGlobalContainerStack(global_stack)

        # Notify everything/one that is to notify about changes.
        global_stack.containersChanged.emit(global_stack.getTop())

        # Load all the nodes / meshdata of the workspace
        nodes = self._3mf_mesh_reader.read(file_name)
        if nodes is None:
            nodes = []
        return nodes
    def preRead(self, file_name):
        self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
        if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
            pass
        else:
            Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
            return WorkspaceReader.PreReadResult.failed

        # Check if there are any conflicts, so we can ask the user.
        archive = zipfile.ZipFile(file_name, "r")
        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
        container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
        self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
        machine_conflict = False
        quality_changes_conflict = False
        for container_stack_file in container_stack_files:
            container_id = self._stripFileToId(container_stack_file)
            stacks = self._container_registry.findContainerStacks(id=container_id)
            if stacks:
                # Check if there are any changes at all in any of the container stacks.
                id_list = self._getContainerIdListFromSerialized(archive.open(container_stack_file).read().decode("utf-8"))
                for index, container_id in enumerate(id_list):
                    if stacks[0].getContainer(index).getId() != container_id:
                        machine_conflict = True
                        break

        material_conflict = False
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
        if xml_material_profile:
            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)
                if materials and not materials[0].isReadOnly():  # Only non readonly materials can be in conflict
                    material_conflict = True

        # Check if any quality_changes instance container is in conflict.
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
            container_type = instance_container.getMetaDataEntry("type")
            if container_type == "quality_changes":
                # Check if quality changes already exists.
                quality_changes = self._container_registry.findInstanceContainers(id = container_id)
                if quality_changes:
                    # Check if there really is a conflict by comparing the values
                    if quality_changes[0] != instance_container:
                        quality_changes_conflict = True
                        break
        try:
            archive.open("Cura/preferences.cfg")
        except KeyError:
            # If there is no preferences file, it's not a workspace, so notify user of failure.
            Logger.log("w", "File %s is not a valid workspace.", file_name)
            return WorkspaceReader.PreReadResult.failed

        if machine_conflict or quality_changes_conflict or material_conflict:
            # There is a conflict; User should choose to either update the existing data, add everything as new data or abort
            self._dialog.setMachineConflict(machine_conflict)
            self._dialog.setQualityChangesConflict(quality_changes_conflict)
            self._dialog.setMaterialConflict(material_conflict)
            self._dialog.show()

            # Block until the dialog is closed.
            self._dialog.waitForClose()

            if self._dialog.getResult() == {}:
                return WorkspaceReader.PreReadResult.cancelled

            self._resolve_strategies = self._dialog.getResult()

        return WorkspaceReader.PreReadResult.accepted
Exemple #29
0
    def read(self, file_name):
        archive = zipfile.ZipFile(file_name, "r")

        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]

        # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
        # parsing code.
        temp_preferences = Preferences()
        temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg")))  # We need to wrap it, else the archive parser breaks.

        # Copy a number of settings from the temp preferences to the global
        global_preferences = Preferences.getInstance()

        visible_settings = temp_preferences.getValue("general/visible_settings")
        if visible_settings is None:
            Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
        else:
            global_preferences.setValue("general/visible_settings", visible_settings)

        categories_expanded = temp_preferences.getValue("cura/categories_expanded")
        if categories_expanded is None:
            Logger.log("w", "Workspace did not contain expanded categories. Leaving them unchanged")
        else:
            global_preferences.setValue("cura/categories_expanded", categories_expanded)

        Application.getInstance().expandedCategoriesChanged.emit()  # Notify the GUI of the change

        self._id_mapping = {}

        # We don't add containers right away, but wait right until right before the stack serialization.
        # We do this so that if something goes wrong, it's easier to clean up.
        containers_to_add = []

        global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)

        global_stack = None
        extruder_stacks = []
        extruder_stacks_added = []
        container_stacks_added = []

        containers_added = []

        global_stack_id_original = self._stripFileToId(global_stack_file)
        global_stack_id_new = global_stack_id_original
        global_stack_need_rename = False

        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):
                global_stack_id_new = self.getNewId(global_stack_id_original)
                global_stack_need_rename = True

            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):
                    # get a new name for this extruder
                    new_container_id = self.getNewId(old_container_id)

                extruder_stack_id_map[old_container_id] = new_container_id

        # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
        # TODO: cases that the container loaded is the same (most notable in materials & definitions).
        # TODO: It might be possible that we need to add smarter checking in the future.
        Logger.log("d", "Workspace loading is checking definitions...")
        # Get all the definition files & check if they exist. If not, add them.
        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)
            if not definitions:
                definition_container = DefinitionContainer(container_id)
                definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
                self._container_registry.addContainer(definition_container)
            Job.yieldThread()

        Logger.log("d", "Workspace loading is checking materials...")
        material_containers = []
        # Get all the material files and check if they exist. If not, add them.
        xml_material_profile = self._getXmlProfileClass()
        if self._material_container_suffix is None:
            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
        if xml_material_profile:
            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)

                if not materials:
                    material_container = xml_material_profile(container_id)
                    material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                    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 self._resolve_strategies["material"] == "override":
                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                        elif self._resolve_strategies["material"] == "new":
                            # Note that we *must* deserialize it with a new ID, as multiple containers will be
                            # auto created & added.
                            material_container = xml_material_profile(self.getNewId(container_id))
                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
                            containers_to_add.append(material_container)

                material_containers.append(material_container)
                Job.yieldThread()

        Logger.log("d", "Workspace loading is checking instance containers...")
        # Get quality_changes and user profiles saved in the workspace
        instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
        user_instance_containers = []
        quality_and_definition_changes_instance_containers = []
        for instance_container_file in instance_container_files:
            container_id = self._stripFileToId(instance_container_file)
            serialized = archive.open(instance_container_file).read().decode("utf-8")

            # HACK! we ignore "quality" and "variant" instance containers!
            parser = configparser.ConfigParser()
            parser.read_string(serialized)
            if not parser.has_option("metadata", "type"):
                Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
                continue
            if parser.get("metadata", "type") in self._ignored_instance_container_types:
                continue

            instance_container = InstanceContainer(container_id)

            # Deserialize InstanceContainer by converting read data from bytes to string
            instance_container.deserialize(serialized)
            container_type = instance_container.getMetaDataEntry("type")
            Job.yieldThread()

            #
            # IMPORTANT:
            # If an instance container (or maybe other type of container) exists, and user chooses "Create New",
            # we need to rename this container and all references to it, and changing those references are VERY
            # HARD.
            #
            if container_type in self._ignored_instance_container_types:
                # Ignore certain instance container types
                Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
                continue
            elif container_type == "user":
                # Check if quality changes already exists.
                user_containers = self._container_registry.findInstanceContainers(id = container_id)
                if not user_containers:
                    containers_to_add.append(instance_container)
                else:
                    if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
                        instance_container = user_containers[0]
                        instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                        instance_container.setDirty(True)
                    elif self._resolve_strategies["machine"] == "new":
                        # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                        old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
                        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.setName(new_id)
                            instance_container.setMetaDataEntry("extruder", new_extruder_id)
                            containers_to_add.append(instance_container)

                        machine_id = instance_container.getMetaDataEntry("machine", None)
                        if machine_id:
                            new_machine_id = self.getNewId(machine_id)
                            new_id = new_machine_id + "_current_settings"
                            instance_container._id = new_id
                            instance_container.setName(new_id)
                            instance_container.setMetaDataEntry("machine", new_machine_id)
                            containers_to_add.append(instance_container)
                user_instance_containers.append(instance_container)
            elif container_type in ("quality_changes", "definition_changes"):
                # Check if quality changes already exists.
                changes_containers = self._container_registry.findInstanceContainers(id = container_id)
                if not changes_containers:
                    # no existing containers with the same ID, so we can safely add the new one
                    containers_to_add.append(instance_container)
                else:
                    # we have found existing container with the same ID, so we need to resolve according to the
                    # selected strategy.
                    if self._resolve_strategies[container_type] == "override":
                        instance_container = changes_containers[0]
                        instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
                        instance_container.setDirty(True)

                    elif self._resolve_strategies[container_type] == "new":
                        # TODO: how should we handle the case "new" for quality_changes and definition_changes?

                        instance_container.setName(self._container_registry.uniqueName(instance_container.getName()))
                        new_changes_container_id = self.getNewId(instance_container.getId())
                        instance_container._id = new_changes_container_id

                        # TODO: we don't know the following is correct or not, need to verify
                        #       AND REFACTOR!!!
                        if self._resolve_strategies["machine"] == "new":
                            # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
                            old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
                            if old_extruder_id:
                                new_extruder_id = extruder_stack_id_map[old_extruder_id]
                                instance_container.setMetaDataEntry("extruder", new_extruder_id)

                            machine_id = instance_container.getMetaDataEntry("machine", None)
                            if machine_id:
                                new_machine_id = self.getNewId(machine_id)
                                instance_container.setMetaDataEntry("machine", new_machine_id)

                        containers_to_add.append(instance_container)

                    elif self._resolve_strategies[container_type] is None:
                        # The ID already exists, but nothing in the values changed, so do nothing.
                        pass
                quality_and_definition_changes_instance_containers.append(instance_container)
            else:
                existing_container = self._container_registry.findInstanceContainers(id = container_id)
                if not existing_container:
                    containers_to_add.append(instance_container)
            if global_stack_need_rename:
                if instance_container.getMetaDataEntry("machine"):
                    instance_container.setMetaDataEntry("machine", global_stack_id_new)

        # Add all the containers right before we try to add / serialize the stack
        for container in containers_to_add:
            self._container_registry.addContainer(container)
            container.setDirty(True)
            containers_added.append(container)

        # Get the stack(s) saved in the workspace.
        Logger.log("d", "Workspace loading is checking stacks containers...")

        # --
        # load global stack file
        try:
            if self._resolve_strategies["machine"] == "override":
                container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
                stack = container_stacks[0]

                # 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"))
                if network_authentication_id:
                    container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
                if network_authentication_key:
                    container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)

            elif self._resolve_strategies["machine"] == "new":
                # create a new global stack
                stack = GlobalStack(global_stack_id_new)
                # Deserialize stack by converting read data from bytes to string
                stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))

                # Ensure a unique ID and name
                stack._id = global_stack_id_new

                # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
                # bound machine also needs to change.
                if stack.getMetaDataEntry("machine", None):
                    stack.setMetaDataEntry("machine", global_stack_id_new)

                # Only machines need a new name, stacks may be non-unique
                stack.setName(global_stack_id_new)

                container_stacks_added.append(stack)
                self._container_registry.addContainer(stack)
                containers_added.append(stack)
            else:
                Logger.log("e", "Resolve strategy of %s for machine is not supported",
                           self._resolve_strategies["machine"])

            global_stack = stack
            Job.yieldThread()
        except:
            Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added.
            for container in containers_added:
                self._container_registry.removeContainer(container.getId())
            return

        # --
        # load extruder stack files
        try:
            for extruder_stack_file in extruder_stack_files:
                container_id = self._stripFileToId(extruder_stack_file)
                extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")

                if self._resolve_strategies["machine"] == "override":
                    # deserialize new extruder stack over the current ones
                    stack = self._overrideExtruderStack(global_stack, extruder_file_content)

                elif self._resolve_strategies["machine"] == "new":
                    new_id = extruder_stack_id_map[container_id]
                    stack = ExtruderStack(new_id)

                    # HACK: the global stack can have a new name, so we need to make sure that this extruder stack
                    #       references to the new name instead of the old one. Normally, this can be done after
                    #       deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
                    #       also does addExtruder() to its machine stack, so we have to make sure that it's pointing
                    #       to the right machine BEFORE deserialization.
                    extruder_config = configparser.ConfigParser()
                    extruder_config.read_string(extruder_file_content)
                    extruder_config.set("metadata", "machine", global_stack_id_new)
                    tmp_string_io = io.StringIO()
                    extruder_config.write(tmp_string_io)
                    extruder_file_content = tmp_string_io.getvalue()

                    stack.deserialize(extruder_file_content)

                    # Ensure a unique ID and name
                    stack._id = new_id

                    self._container_registry.addContainer(stack)
                    extruder_stacks_added.append(stack)
                    containers_added.append(stack)
                else:
                    Logger.log("w", "Unknown resolve strategy: %s", self._resolve_strategies["machine"])

                extruder_stacks.append(stack)
        except:
            Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
            # Something went really wrong. Try to remove any data that we added. 
            for container in containers_added:
                self._container_registry.removeContainer(container.getId())
            return

        #
        # Replacing the old containers if resolve is "new".
        # When resolve is "new", some containers will get renamed, so all the other containers that reference to those
        # MUST get updated too.
        #
        if self._resolve_strategies["machine"] == "new":
            # A new machine was made, but it was serialized with the wrong user container. Fix that now.
            for container in user_instance_containers:
                # replacing the container ID for user instance containers for the extruders
                extruder_id = container.getMetaDataEntry("extruder", None)
                if extruder_id:
                    for extruder in extruder_stacks:
                        if extruder.getId() == extruder_id:
                            extruder.userChanges = container
                            continue

                # replacing the container ID for user instance containers for the machine
                machine_id = container.getMetaDataEntry("machine", None)
                if machine_id:
                    if global_stack.getId() == machine_id:
                        global_stack.userChanges = container
                        continue

        for changes_container_type in ("quality_changes", "definition_changes"):
            if self._resolve_strategies[changes_container_type] == "new":
                # Quality changes needs to get a new ID, added to registry and to the right stacks
                for each_changes_container in quality_and_definition_changes_instance_containers:
                    # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the
                    #       instance container loading part.
                    new_id = each_changes_container.getId()

                    # Find the old (current) changes container in the global stack
                    if changes_container_type == "quality_changes":
                        old_container = global_stack.qualityChanges
                    elif changes_container_type == "definition_changes":
                        old_container = global_stack.definitionChanges

                    # sanity checks
                    # NOTE: The following cases SHOULD NOT happen!!!!
                    if not old_container:
                        Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
                                   changes_container_type, global_stack.getId())

                    # Replace the quality/definition changes container if it's in the GlobalStack
                    # NOTE: we can get an empty container here, but the IDs will not match,
                    # so this comparison is fine.
                    if self._id_mapping.get(old_container.getId()) == new_id:
                        if changes_container_type == "quality_changes":
                            global_stack.qualityChanges = each_changes_container
                        elif changes_container_type == "definition_changes":
                            global_stack.definitionChanges = each_changes_container
                        continue

                    # Replace the quality/definition changes container if it's in one of the ExtruderStacks
                    for each_extruder_stack in extruder_stacks:
                        changes_container = None
                        if changes_container_type == "quality_changes":
                            changes_container = each_extruder_stack.qualityChanges
                        elif changes_container_type == "definition_changes":
                            changes_container = each_extruder_stack.definitionChanges

                        # sanity checks
                        # NOTE: The following cases SHOULD NOT happen!!!!
                        if not changes_container:
                            Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
                                       changes_container_type, each_extruder_stack.getId())

                        # NOTE: we can get an empty container here, but the IDs will not match,
                        # so this comparison is fine.
                        if self._id_mapping.get(changes_container.getId()) == new_id:
                            if changes_container_type == "quality_changes":
                                each_extruder_stack.qualityChanges = each_changes_container
                            elif changes_container_type == "definition_changes":
                                each_extruder_stack.definitionChanges = each_changes_container

        if self._resolve_strategies["material"] == "new":
            for each_material in material_containers:
                old_material = global_stack.material

                # check if the old material container has been renamed to this material container ID
                # if the container hasn't been renamed, we do nothing.
                new_id = self._id_mapping.get(old_material.getId())
                if new_id is None or new_id != each_material.getId():
                    continue

                if old_material.getId() in self._id_mapping:
                    global_stack.material = each_material

                for each_extruder_stack in extruder_stacks:
                    old_material = each_extruder_stack.material

                    # check if the old material container has been renamed to this material container ID
                    # if the container hasn't been renamed, we do nothing.
                    new_id = self._id_mapping.get(old_material.getId())
                    if new_id is None or new_id != each_material.getId():
                        continue

                    if old_material.getId() in self._id_mapping:
                        each_extruder_stack.material = each_material

        if extruder_stacks:
            for stack in extruder_stacks:
                ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
        else:
            # Machine has no extruders, but it needs to be registered with the extruder manager.
            ExtruderManager.getInstance().registerExtruder(None, global_stack.getId())

        Logger.log("d", "Workspace loading is notifying rest of the code of changes...")

        if self._resolve_strategies["machine"] == "new":
            for stack in extruder_stacks:
                stack.setNextStack(global_stack)
                stack.containersChanged.emit(stack.getTop())

        # Actually change the active machine.
        Application.getInstance().setGlobalContainerStack(global_stack)

        # Notify everything/one that is to notify about changes.
        global_stack.containersChanged.emit(global_stack.getTop())

        # Load all the nodes / meshdata of the workspace
        nodes = self._3mf_mesh_reader.read(file_name)
        if nodes is None:
            nodes = []
        return nodes