From cbcc48ff3342f63d9f2e175e3227923adf095114 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 11 Nov 2016 17:17:23 +0100 Subject: [PATCH] Pre-read now checks for conflicts and asks the user what strategy for resolvement to use CURA-1263 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 131 ++++++++++++++------ plugins/3MFReader/WorkspaceDialog.py | 79 ++++++++++++ plugins/3MFReader/WorkspaceDialog.qml | 52 ++++++++ 3 files changed, 221 insertions(+), 41 deletions(-) create mode 100644 plugins/3MFReader/WorkspaceDialog.py create mode 100644 plugins/3MFReader/WorkspaceDialog.qml diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 6f31115c24..e4a2c574ac 100644 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -2,23 +2,34 @@ from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Application import Application from UM.Logger import Logger +from UM.i18n import i18nCatalog from UM.Settings.ContainerStack import ContainerStack from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Preferences import Preferences - +from .WorkspaceDialog import WorkspaceDialog import zipfile import io +i18n_catalog = i18nCatalog("cura") + + ## Base implementation for reading 3MF workspace files. class ThreeMFWorkspaceReader(WorkspaceReader): def __init__(self): super().__init__() self._supported_extensions = [".3mf"] - + self._dialog = WorkspaceDialog() self._3mf_mesh_reader = None + self._container_registry = ContainerRegistry.getInstance() + self._definition_container_suffix = ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).suffixes[0] + self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it + self._instance_container_suffix = ContainerRegistry.getMimeTypeForContainer(InstanceContainer).suffixes[0] + self._container_stack_suffix = ContainerRegistry.getMimeTypeForContainer(ContainerStack).suffixes[0] + + self._resolvement_strategy = None def preRead(self, file_name): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name) @@ -27,7 +38,45 @@ class ThreeMFWorkspaceReader(WorkspaceReader): else: Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace") return WorkspaceReader.PreReadResult.failed - # TODO: Ask user if it's okay for the scene to be cleared + + # 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)] + + 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: + conflict = True + break + + # Check if any quality_changes instance container is in conflict. + if not 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: + conflict = True + if conflict: + # There is a conflict; User should choose to either update the existing data, add everything as new data or abort + self._resolvement_strategy = None + self._dialog.show() + self._dialog.waitForClose() + if self._dialog.getResult() == "cancel": + return WorkspaceReader.PreReadResult.cancelled + + self._resolvement_strategy = self._dialog.getResult() + pass return WorkspaceReader.PreReadResult.accepted def read(self, file_name): @@ -36,7 +85,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if nodes is None: nodes = [] - container_registry = ContainerRegistry.getInstance() archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] @@ -57,83 +105,84 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # TODO: It might be possible that we need to add smarter checking in the future. # Get all the definition files & check if they exist. If not, add them. - definition_container_suffix = ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).suffixes[0] - definition_container_files = [name for name in cura_file_names if name.endswith(definition_container_suffix)] + 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 = definition_container_file.replace("Cura/", "") - container_id = container_id.replace(".%s" % definition_container_suffix, "") - definitions = container_registry.findDefinitionContainers(id=container_id) + 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")) - container_registry.addContainer(definition_container) + self._container_registry.addContainer(definition_container) # Get all the material files and check if they exist. If not, add them. - xml_material_profile = None - for type_name, container_type in container_registry.getContainerTypes(): - if type_name == "XmlMaterialProfile": - xml_material_profile = container_type - break - + 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_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0] - material_container_files = [name for name in cura_file_names if name.endswith(material_container_suffix)] + 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 = material_container_file.replace("Cura/", "") - container_id = container_id.replace(".%s" % material_container_suffix, "") - materials = container_registry.findInstanceContainers(id=container_id) + 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")) - container_registry.addContainer(material_container) + self._container_registry.addContainer(material_container) # Get quality_changes and user profiles saved in the workspace - instance_container_suffix = ContainerRegistry.getMimeTypeForContainer(InstanceContainer).suffixes[0] - instance_container_files = [name for name in cura_file_names if name.endswith(instance_container_suffix)] + 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 = instance_container_file.replace("Cura/", "") - container_id = container_id.replace(".%s" % instance_container_suffix, "") + 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: + self._container_registry.addContainer(instance_container) user_instance_containers.append(instance_container) elif container_type == "quality_changes": # Check if quality changes already exists. - quality_changes = container_registry.findInstanceContainers(id = container_id) + quality_changes = self._container_registry.findInstanceContainers(id = container_id) if not quality_changes: - container_registry.addContainer(instance_container) + self._container_registry.addContainer(instance_container) quality_changes_instance_containers.append(instance_container) else: continue - # Get the stack(s) saved in the workspace. - '''container_stack_suffix = ContainerRegistry.getMimeTypeForContainer(ContainerStack).suffixes[0] - container_stack_files = [name for name in cura_file_names if name.endswith(container_stack_suffix)] + container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)] global_stack = None extruder_stacks = [] + for container_stack_file in container_stack_files: - container_id = container_stack_file.replace("Cura/", "") - container_id = container_id.replace(".%s" % container_stack_suffix, "") - - # Check if a stack by this ID already exists; - container_stacks = container_registry.findContainerStacks(id = container_id) - if container_stacks: - print("CONTAINER ALREADY EXISTSSS") - - #stack = ContainerStack(container_id) + container_id = self._stripFileToId(container_stack_file) + 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")) + # Check if a stack by this ID already exists; + container_stacks = self._container_registry.findContainerStacks(id = container_id) + if container_stacks: + print("CONTAINER ALREADY EXISTSSS") + if stack.getMetaDataEntry("type") == "extruder_train": extruder_stacks.append(stack) else: - global_stack = stack''' + global_stack = stack return nodes + + def _stripFileToId(self, file): + return file.replace("Cura/", "").split(".")[0] + + def _getXmlProfileClass(self): + for type_name, container_type in self._container_registry.getContainerTypes(): + print(type_name, container_type) + if type_name == "XmlMaterialProfile": + return container_type diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py new file mode 100644 index 0000000000..ae1280b4bd --- /dev/null +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -0,0 +1,79 @@ +from PyQt5.QtCore import Qt, QUrl, pyqtSignal, pyqtSlot, QObject +from PyQt5.QtQml import QQmlComponent, QQmlContext +from UM.PluginRegistry import PluginRegistry +from UM.Application import Application + +import os +import threading + +class WorkspaceDialog(QObject): + showDialogSignal = pyqtSignal() + + def __init__(self, parent = None): + super().__init__(parent) + self._component = None + self._context = None + self._view = None + self._qml_url = "WorkspaceDialog.qml" + self._lock = threading.Lock() + self._result = None # What option did the user pick? + self._visible = False + self.showDialogSignal.connect(self.__show) + + def getResult(self): + return self._result + + def _createViewFromQML(self): + path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url)) + self._component = QQmlComponent(Application.getInstance()._engine, path) + self._context = QQmlContext(Application.getInstance()._engine.rootContext()) + self._context.setContextProperty("manager", self) + self._view = self._component.create(self._context) + + def show(self): + # Emit signal so the right thread actually shows the view. + self._lock.acquire() + self._result = None + self._visible = True + self.showDialogSignal.emit() + + @pyqtSlot() + ## Used to notify the dialog so the lock can be released. + def notifyClosed(self): + if self._result is None: + self._result = "cancel" + self._lock.release() + + def hide(self): + self._visible = False + self._lock.release() + self._view.hide() + + @pyqtSlot() + def onOverrideButtonClicked(self): + self._view.hide() + self.hide() + self._result = "override" + + @pyqtSlot() + def onNewButtonClicked(self): + self._view.hide() + self.hide() + self._result = "new" + + @pyqtSlot() + def onCancelButtonClicked(self): + self._view.hide() + self.hide() + self._result = "cancel" + + ## Block thread until the dialog is closed. + def waitForClose(self): + if self._visible: + self._lock.acquire() + self._lock.release() + + def __show(self): + if self._view is None: + self._createViewFromQML() + self._view.show() diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml new file mode 100644 index 0000000000..0c56dbcb6c --- /dev/null +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.1 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.1 as UM + +UM.Dialog +{ + title: catalog.i18nc("@title:window", "Conflict") + + width: 350 * Screen.devicePixelRatio; + minimumWidth: 350 * Screen.devicePixelRatio; + maximumWidth: 350 * Screen.devicePixelRatio; + + height: 250 * Screen.devicePixelRatio; + minimumHeight: 250 * Screen.devicePixelRatio; + maximumHeight: 250 * Screen.devicePixelRatio; + + onClosing: manager.notifyClosed() + + Item + { + UM.I18nCatalog { id: catalog; name: "cura"; } + } + rightButtons: [ + Button + { + id: override_button + text: catalog.i18nc("@action:button","Override"); + onClicked: { manager.onOverrideButtonClicked() } + enabled: true + }, + Button + { + id: create_new + text: catalog.i18nc("@action:button","Create new"); + onClicked: { manager.onNewButtonClicked() } + enabled: true + }, + Button + { + id: cancel_button + text: catalog.i18nc("@action:button","Cancel"); + onClicked: { manager.onCancelButtonClicked() } + enabled: true + } + ] +} \ No newline at end of file