mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Pre-read now checks for conflicts and asks the user what strategy for resolvement to use
CURA-1263
This commit is contained in:
parent
99e753d3be
commit
cbcc48ff33
3 changed files with 221 additions and 41 deletions
|
@ -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
|
||||
|
|
79
plugins/3MFReader/WorkspaceDialog.py
Normal file
79
plugins/3MFReader/WorkspaceDialog.py
Normal file
|
@ -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()
|
52
plugins/3MFReader/WorkspaceDialog.qml
Normal file
52
plugins/3MFReader/WorkspaceDialog.qml
Normal file
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue