diff --git a/cura/ProfileWriter.py b/cura/ProfileWriter.py index 6c719205ec..276e2b80a3 100644 --- a/cura/ProfileWriter.py +++ b/cura/ProfileWriter.py @@ -18,8 +18,8 @@ class ProfileWriter(PluginObject): # The profile writer may write its own file format to the specified file. # # \param path \type{string} The file to output to. - # \param profile \type{Profile} The profile to write to the file. + # \param profiles \type{Profile} or \type{List} The profile(s) to write to the file. # \return \code True \endcode if the writing was successful, or \code # False \endcode if it wasn't. - def write(self, path, node): + def write(self, path, profiles): raise NotImplementedError("Profile writer plugin was not correctly implemented. No write was specified.") diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index ed7cf3c430..92e59eb19f 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -58,12 +58,10 @@ class CuraContainerRegistry(ContainerRegistry): ## Exports an profile to a file # - # \param instance_id \type{str} the ID of the profile to export. + # \param instance_ids \type{list} the IDs of the profiles to export. # \param file_name \type{str} the full path and filename to export to. # \param file_type \type{str} the file type with the format " (*.)" - def exportProfile(self, instance_id, file_name, file_type): - Logger.log('d', 'exportProfile instance_id: '+str(instance_id)) - + def exportProfile(self, instance_ids, file_name, file_type): # Parse the fileType to deduce what plugin can save the file format. # fileType has the format " (*.)" split = file_type.rfind(" (*.") # Find where the description ends and the extension starts. @@ -82,16 +80,16 @@ class CuraContainerRegistry(ContainerRegistry): catalog.i18nc("@label", "The file {0} already exists. Are you sure you want to overwrite it?").format(file_name)) if result == QMessageBox.No: return - - containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id) - if not containers: - return - container = containers[0] + found_containers = [] + for instance_id in instance_ids: + containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id) + if containers: + found_containers.append(containers[0]) profile_writer = self._findProfileWriter(extension, description) try: - success = profile_writer.write(file_name, container) + success = profile_writer.write(file_name, found_containers) except Exception as e: Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e)) m = Message(catalog.i18nc("@info:status", "Failed to export profile to {0}: {1}", file_name, str(e)), lifetime = 0) @@ -146,7 +144,10 @@ class CuraContainerRegistry(ContainerRegistry): return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) } else: for profile in profile_or_list: - self._configureProfile(profile, name_seed) + if profile.getId() != "": + ContainerRegistry.getInstance().addContainer(profile) + else: + self._configureProfile(profile, name_seed) if len(profile_or_list) == 1: return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())} @@ -160,7 +161,7 @@ class CuraContainerRegistry(ContainerRegistry): def _configureProfile(self, profile, name_seed): profile.setReadOnly(False) - new_name = self.createUniqueName("quality", "", name_seed, catalog.i18nc("@label", "Custom profile")) + new_name = self.createUniqueName("quality_changes", "", name_seed, catalog.i18nc("@label", "Custom profile")) profile.setName(new_name) profile._id = new_name diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index c9895be697..1359ab77b6 100644 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -55,6 +55,13 @@ class ExtruderManager(QObject): map[position] = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position].getId() return map + @pyqtSlot(str, result = str) + def getQualityChangesIdByExtruderStackId(self, id): + for position in self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]: + extruder = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position] + if extruder.getId() == id: + return extruder.findContainer(type = "quality_changes").getId() + ## The instance of the singleton pattern. # # It's None if the extruder manager hasn't been created yet. diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index 261e68a26b..8007a8e696 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -4,9 +4,11 @@ import os.path from UM.Logger import Logger -from UM.Settings.InstanceContainer import InstanceContainer #The new profile to make. +from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make. from cura.ProfileReader import ProfileReader +import zipfile + ## A plugin that reads profile data from Cura profile files. # # It reads a profile from a .curaprofile file, and returns it as a profile @@ -24,19 +26,19 @@ class CuraProfileReader(ProfileReader): # not be read or didn't contain a valid profile, \code None \endcode is # returned. def read(self, file_name): - # Create an empty profile. - profile = InstanceContainer(os.path.basename(os.path.splitext(file_name)[0])) - profile.addMetaDataEntry("type", "quality") - try: - with open(file_name) as f: # Open file for reading. + archive = zipfile.ZipFile(file_name, "r") + results = [] + for profile_id in archive.namelist(): + # Create an empty profile. + profile = InstanceContainer(profile_id) + profile.addMetaDataEntry("type", "quality_changes") + serialized = "" + with archive.open(profile_id) as f: serialized = f.read() - except IOError as e: - Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e)) - return None - - try: - profile.deserialize(serialized) - except Exception as e: # Parsing error. This is not a (valid) Cura profile then. - Logger.log("e", "Error while trying to parse profile: %s", str(e)) - return None - return profile \ No newline at end of file + try: + profile.deserialize(serialized.decode("utf-8") ) + except Exception as e: # Parsing error. This is not a (valid) Cura profile then. + Logger.log("e", "Error while trying to parse profile: %s", str(e)) + continue + results.append(profile) + return results \ No newline at end of file diff --git a/plugins/CuraProfileWriter/CuraProfileWriter.py b/plugins/CuraProfileWriter/CuraProfileWriter.py index 86b4f7dc89..cd3681b86f 100644 --- a/plugins/CuraProfileWriter/CuraProfileWriter.py +++ b/plugins/CuraProfileWriter/CuraProfileWriter.py @@ -5,22 +5,31 @@ from UM.Logger import Logger from UM.SaveFile import SaveFile from cura.ProfileWriter import ProfileWriter - +import zipfile ## Writes profiles to Cura's own profile format with config files. class CuraProfileWriter(ProfileWriter): ## Writes a profile to the specified file path. # # \param path \type{string} The file to output to. - # \param profile \type{Profile} The profile to write to that file. + # \param profiles \type{Profile} \type{List} The profile(s) to write to that file. # \return \code True \endcode if the writing was successful, or \code # False \endcode if it wasn't. - def write(self, path, profile): - serialized = profile.serialize() + def write(self, path, profiles): + if type(profiles) != list: + profiles = [profiles] + + stream = open(path, "wb") # Open file for writing in binary. + archive = zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_DEFLATED) try: - with SaveFile(path, "wt", -1, "utf-8") as f: # Open the specified file. - f.write(serialized) + # Open the specified file. + for profile in profiles: + serialized = profile.serialize() + profile_file = zipfile.ZipInfo(profile.getId()) + archive.writestr(profile_file, serialized) except Exception as e: Logger.log("e", "Failed to write profile to %s: %s", path, str(e)) return False + finally: + archive.close() return True diff --git a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py index 8b2580937b..f9dd0db85c 100644 --- a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py +++ b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py @@ -99,7 +99,7 @@ class RemovableDriveOutputDevice(OutputDevice): self._writing = False self.writeFinished.emit(self) if job.getResult(): - message = Message(catalog.i18nc("@info:status", "Saved to Removable Drive {0} as {1}").format(self.getName(), os.path.basename(job.getFileName())), lifetime = 0) + message = Message(catalog.i18nc("@info:status", "Saved to Removable Drive {0} as {1}").format(self.getName(), os.path.basename(job.getFileName()))) message.addAction("eject", catalog.i18nc("@action:button", "Eject"), "eject", catalog.i18nc("@action", "Eject removable device {0}").format(self.getName())) message.actionTriggered.connect(self._onActionTriggered) message.show() @@ -112,5 +112,5 @@ class RemovableDriveOutputDevice(OutputDevice): def _onActionTriggered(self, message, action): if action == "eject": - Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("RemovableDriveOutputDevice").ejectDevice(self) - + if Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("RemovableDriveOutputDevice").ejectDevice(self): + message.hide() \ No newline at end of file diff --git a/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py b/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py index 4e748230e9..37f4422a11 100644 --- a/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py +++ b/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py @@ -51,6 +51,7 @@ class RemovableDrivePlugin(OutputDevicePlugin): else: message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Maybe it is still in use?").format(device.getName())) message.show() + return result def performEjectDevice(self, device): raise NotImplementedError() diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index f84c7c62b9..53d83b9111 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -13,6 +13,8 @@ UM.ManagementPage id: base; title: catalog.i18nc("@title:tab", "Profiles"); + property var extrudersModel: Cura.ExtrudersModel{} + //Cura.ExtrudersModel { id: extrudersModel} model: UM.InstanceContainersModel { @@ -106,15 +108,13 @@ UM.ManagementPage text: catalog.i18nc("@action:button", "Import"); iconName: "document-import"; onClicked: importDialog.open(); - enabled: false }, Button { text: catalog.i18nc("@action:button", "Export") iconName: "document-export" onClicked: exportDialog.open() -// enabled: currentItem != null - enabled: false + enabled: currentItem != null } ] @@ -206,7 +206,7 @@ UM.ManagementPage Repeater { - model: Cura.ExtrudersModel { } + model: base.extrudersModel ProfileTab { @@ -299,7 +299,12 @@ UM.ManagementPage folder: CuraApplication.getDefaultPath("dialog_profile_path") onAccepted: { - var result = base.model.exportProfile(base.currentItem.id, fileUrl, selectedNameFilter) + var profiles_to_export = [base.currentItem.id] + for(var extruder_nr in base.extrudersModel.items) + { + profiles_to_export.push(ExtruderManager.getQualityChangesIdByExtruderStackId(base.extrudersModel.items[extruder_nr].id)) + } + var result = base.model.exportProfile(profiles_to_export, fileUrl, selectedNameFilter) if(result && result.status == "error") { messageDialog.icon = StandardIcon.Critical