diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 581424fa77..38e32cf99a 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -25,6 +25,7 @@ from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.Validator import Validator from UM.Message import Message from UM.i18n import i18nCatalog +from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation @@ -1260,3 +1261,21 @@ class CuraApplication(QtApplication): def addNonSliceableExtension(self, extension): self._non_sliceable_extensions.append(extension) + + @pyqtSlot(str, result=bool) + def checkIsValidProjectFile(self, file_url): + """ + Checks if the given file URL is a valid project file. + """ + file_url_prefix = 'file:///' + + file_name = file_url + if file_name.startswith(file_url_prefix): + file_name = file_name[len(file_url_prefix):] + + workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_name) + if workspace_reader is None: + return False # non-project files won't get a reader + + result = workspace_reader.preRead(file_name, show_dialog=False) + return result == WorkspaceReader.PreReadResult.accepted diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 707238ab26..a0ce679464 100644 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -47,7 +47,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._id_mapping[old_id] = self._container_registry.uniqueName(old_id) return self._id_mapping[old_id] - def preRead(self, 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 @@ -167,6 +167,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader): 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) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 9d70dde8e1..04dce9f439 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -21,7 +21,7 @@ class ImageReader(MeshReader): self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"] self._ui = ImageReaderUI(self) - def preRead(self, file_name): + def preRead(self, file_name, *args, **kwargs): img = QImage(file_name) if img.isNull(): diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index f586f82a90..94140ea876 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -12,7 +12,6 @@ Item { property alias newProject: newProjectAction; property alias open: openAction; - property alias loadWorkspace: loadWorkspaceAction; property alias quit: quitAction; property alias undo: undoAction; @@ -284,7 +283,7 @@ Item Action { id: openAction; - text: catalog.i18nc("@action:inmenu menubar:file","&Open File..."); + text: catalog.i18nc("@action:inmenu menubar:file","&Open File(s)..."); iconName: "document-open"; shortcut: StandardKey.Open; } @@ -296,12 +295,6 @@ Item shortcut: StandardKey.New } - Action - { - id: loadWorkspaceAction - text: catalog.i18nc("@action:inmenu menubar:file","&Open Project..."); - } - Action { id: showEngineLogAction; diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 31ccd3906f..e6b1b636f5 100755 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -78,11 +78,6 @@ UM.MainWindow RecentFilesMenu { } - MenuItem - { - action: Cura.Actions.loadWorkspace - } - MenuSeparator { } MenuItem @@ -744,27 +739,43 @@ UM.MainWindow id: openDialog; //: File open dialog title - title: catalog.i18nc("@title:window","Open file") + title: catalog.i18nc("@title:window","Open file(s)") modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal; selectMultiple: true nameFilters: UM.MeshFileHandler.supportedReadFileTypes; folder: CuraApplication.getDefaultPath("dialog_load_path") onAccepted: { - //Because several implementations of the file dialog only update the folder - //when it is explicitly set. + // Because several implementations of the file dialog only update the folder + // when it is explicitly set. var f = folder; folder = f; CuraApplication.setDefaultPath("dialog_load_path", folder); - for(var i in fileUrls) + // look for valid project files + var projectFileUrlList = []; + for (var i in fileUrls) { - Printer.readLocalFile(fileUrls[i]) + if (CuraApplication.checkIsValidProjectFile(fileUrls[i])) + projectFileUrlList.push(fileUrls[i]); } - var meshName = backgroundItem.getMeshName(fileUrls[0].toString()) - backgroundItem.hasMesh(decodeURIComponent(meshName)) + // we only allow opening one project file + var selectedMultipleFiles = fileUrls.length > 1; + var hasProjectFile = projectFileUrlList.length > 0; + var selectedMultipleWithProjectFile = hasProjectFile && selectedMultipleFiles; + if (selectedMultipleWithProjectFile) + { + askOpenProjectOrModelsDialog.fileUrls = fileUrls; + askOpenProjectOrModelsDialog.show(); + return; + } + + if (hasProjectFile) + loadProjectFile(projectFileUrlList[0]); + else + loadModelFiles(fileUrls); } } @@ -774,38 +785,101 @@ UM.MainWindow onTriggered: openDialog.open() } - FileDialog + function loadProjectFile(projectFile) { - id: openWorkspaceDialog; + UM.WorkspaceFileHandler.readLocalFile(projectFile); - //: File open dialog title - title: catalog.i18nc("@title:window","Open workspace") - modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal; - selectMultiple: false - nameFilters: UM.WorkspaceFileHandler.supportedReadFileTypes; - folder: CuraApplication.getDefaultPath("dialog_load_path") - onAccepted: - { - //Because several implementations of the file dialog only update the folder - //when it is explicitly set. - var f = folder; - folder = f; - - CuraApplication.setDefaultPath("dialog_load_path", folder); - - for(var i in fileUrls) - { - UM.WorkspaceFileHandler.readLocalFile(fileUrls[i]) - } - var meshName = backgroundItem.getMeshName(fileUrls[0].toString()) - backgroundItem.hasMesh(decodeURIComponent(meshName)) - } + var meshName = backgroundItem.getMeshName(projectFile.toString()); + backgroundItem.hasMesh(decodeURIComponent(meshName)); } - Connections + function loadModelFiles(fileUrls) { - target: Cura.Actions.loadWorkspace - onTriggered: openWorkspaceDialog.open() + for (var i in fileUrls) + Printer.readLocalFile(fileUrls[i]); + + var meshName = backgroundItem.getMeshName(fileUrls[0].toString()); + backgroundItem.hasMesh(decodeURIComponent(meshName)); + } + + UM.Dialog + { + // This dialog asks the user whether he/she wants to open the project file we have detected or the model files. + id: askOpenProjectOrModelsDialog + + title: catalog.i18nc("@title:window", "Open file(s)") + width: 620 + height: 250 + + maximumHeight: height + maximumWidth: width + minimumHeight: height + minimumWidth: width + + modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal; + + property var fileUrls: [] + property int spacerHeight: 10 + + Column + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + anchors.left: parent.left + anchors.right: parent.right + spacing: UM.Theme.getSize("default_margin").width + + Text + { + id: askOpenProjectOrModelsDialogText + text: catalog.i18nc("@text:window", "We have found multiple project file(s) within the files you have\nselected. You can open only one project file at a time. We\nsuggest to only import models from those files. Would you like\nto proceed?") + anchors.margins: UM.Theme.getSize("default_margin").width + font: UM.Theme.getFont("default") + wrapMode: Text.WordWrap + } + + Item // Spacer + { + height: askOpenProjectOrModelsDialog.spacerHeight + width: height + } + + // Buttons + Item + { + anchors.right: parent.right + anchors.left: parent.left + height: childrenRect.height + + Button + { + id: cancelButton + text: catalog.i18nc("@action:button", "Cancel"); + anchors.right: importAllAsModelsButton.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + onClicked: + { + // cancel + askOpenProjectOrModelsDialog.hide(); + } + } + + Button + { + id: importAllAsModelsButton + text: catalog.i18nc("@action:button", "Import all as models"); + anchors.right: parent.right + isDefault: true + onClicked: + { + // load models from all selected file + loadModelFiles(askOpenProjectOrModelsDialog.fileUrls); + + askOpenProjectOrModelsDialog.hide(); + } + } + } + } } EngineLog @@ -846,7 +920,7 @@ UM.MainWindow function start(id) { - var actions = Cura.MachineActionManager.getFirstStartActions(id) + var actions = Cura.MachineActionManager.getFirstStartActions(id) resetPages() // Remove previous pages for (var i = 0; i < actions.length; i++) @@ -905,7 +979,6 @@ UM.MainWindow { discardOrKeepProfileChangesDialog.show() } - } Connections @@ -955,4 +1028,3 @@ UM.MainWindow } } } -