Browse Source

adding option of opening model as UCP or normal project file

CURA-11403
Saumya Jain 1 year ago
parent
commit
f3c49e494e

+ 9 - 1
cura/CuraActions.py

@@ -1,6 +1,6 @@
 # Copyright (c) 2023 UltiMaker
 # Cura is released under the terms of the LGPLv3 or higher.
-
+import zipfile
 from typing import List, cast
 
 from PyQt6.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
@@ -33,6 +33,7 @@ from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOper
 from UM.Logger import Logger
 from UM.Scene.SceneNode import SceneNode
 
+USER_SETTINGS_PATH = "Cura/user-settings.json"
 
 class CuraActions(QObject):
     def __init__(self, parent: QObject = None) -> None:
@@ -195,6 +196,13 @@ class CuraActions(QObject):
             operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
         operation.push()
 
+    @pyqtSlot(str, result = bool)
+    def isProjectUcp(self, file_url) -> bool:
+        file_name = QUrl(file_url).toLocalFile()
+        archive = zipfile.ZipFile(file_name, "r")
+        cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
+        return USER_SETTINGS_PATH in cura_file_names
+
     @pyqtSlot(int)
     def setBuildPlateForSelection(self, build_plate_nr: int) -> None:
         Logger.log("d", "Setting build plate number... %d" % build_plate_nr)

+ 12 - 0
cura/CuraApplication.py

@@ -1979,6 +1979,18 @@ class CuraApplication(QtApplication):
 
     openProjectFile = pyqtSignal(QUrl, bool, arguments = ["project_file", "add_to_recent_files"])  # Emitted when a project file is about to open.
 
+    @pyqtSlot(QUrl, bool)
+    def readLocalUcpFile(self, file: QUrl, add_to_recent_files: bool = True):
+
+        file_name = QUrl(file).toLocalFile()
+        workspace_reader = self.getWorkspaceFileHandler()
+        if workspace_reader is None:
+            Logger.log("w", "Workspace reader not found")
+            return
+
+        workspace_reader.getReaderForFile(file_name).setOpenAsUcp(True)
+        workspace_reader.readLocalFile(file, add_to_recent_files)
+
     @pyqtSlot(QUrl, str, bool)
     @pyqtSlot(QUrl, str)
     @pyqtSlot(QUrl)

+ 10 - 6
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -117,6 +117,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._supported_extensions = [".3mf"]
         self._dialog = WorkspaceDialog()
         self._3mf_mesh_reader = None
+        self._is_ucp = False
         self._container_registry = ContainerRegistry.getInstance()
 
         # suffixes registered with the MimeTypes don't start with a dot '.'
@@ -153,6 +154,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._load_profile = False
         self._user_settings = {}
 
+    def setOpenAsUcp(self, openAsUcp: bool):
+        self._is_ucp =  openAsUcp
+
     def getNewId(self, old_id: str):
         """Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
 
@@ -242,7 +246,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         # Read definition containers
         #
         machine_definition_id = None
-        updatable_machines = None if is_ucp else []
+        updatable_machines = None if self._is_ucp else []
         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)]
@@ -609,7 +613,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
 
         # Load the user specifically exported settings
         self._dialog.exportedSettingModel.clear()
-        if is_ucp:
+        if self._is_ucp:
             try:
                 self._user_settings = json.loads(archive.open("Cura/user-settings.json").read().decode("utf-8"))
                 any_extruder_stack = ExtruderManager.getInstance().getExtruderStack(0)
@@ -658,8 +662,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._dialog.setVariantType(variant_type_name)
         self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
         self._dialog.setMissingPackagesMetadata(missing_package_metadata)
-        self._dialog.setHasVisibleSelectSameProfileChanged(is_ucp)
-        self._dialog.setAllowCreatemachine(not is_ucp)
+        self._dialog.setHasVisibleSelectSameProfileChanged(self._is_ucp)
+        self._dialog.setAllowCreatemachine(not self._is_ucp)
         self._dialog.show()
 
 
@@ -701,7 +705,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         if self._dialog.getResult() == {}:
             return WorkspaceReader.PreReadResult.cancelled
 
-        self._load_profile = not is_ucp or (self._dialog.selectSameProfileChecked and self._dialog.isCompatibleMachine)
+        self._load_profile = not self._is_ucp
 
         self._resolve_strategies = self._dialog.getResult()
         #
@@ -717,7 +721,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             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"
-
+        self._is_ucp = False
         return WorkspaceReader.PreReadResult.accepted
 
     @call_on_qt_thread

+ 7 - 10
plugins/3MFWriter/ThreeMFWriter.py

@@ -135,16 +135,13 @@ class ThreeMFWriter(MeshWriter):
         stack = um_node.callDecoration("getStack")
         if stack is not None:
             changed_setting_keys = stack.getTop().getAllKeys()
-
-            if exported_settings is None:
-                # Ensure that we save the extruder used for this object in a multi-extrusion setup
-                if stack.getProperty("machine_extruder_count", "value") > 1:
-                    changed_setting_keys.add("extruder_nr")
-
-                # Get values for all changed settings & save them.
-                for key in changed_setting_keys:
-                    savitar_node.setSetting("cura:" + key, str(stack.getProperty(key, "value")))
-            else:
+            # Ensure that we save the extruder used for this object in a multi-extrusion setup
+            if stack.getProperty("machine_extruder_count", "value") > 1:
+                changed_setting_keys.add("extruder_nr")
+            # Get values for all changed settings & save them.
+            for key in changed_setting_keys:
+                savitar_node.setSetting("cura:" + key, str(stack.getProperty(key, "value")))
+            if exported_settings is not None:
                  # We want to export only the specified settings
                 if um_node.getName() in exported_settings:
                     model_exported_settings = exported_settings[um_node.getName()]

+ 45 - 18
resources/qml/Cura.qml

@@ -701,24 +701,34 @@ UM.MainWindow
 
             if (hasProjectFile)
             {
-                var projectFile = projectFileUrlList[0];
-
-                // check preference
-                var choice = UM.Preferences.getValue("cura/choice_on_open_project");
-                if (choice == "open_as_project")
-                {
-                    openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
-                }
-                else if (choice == "open_as_model")
+                var projectFile = projectFileUrlList[0]
+                var is_ucp = CuraActions.isProjectUcp(projectFile);
+                print("this file is ucp", is_ucp);
+                if (is_ucp)
                 {
-                    openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice());
+                    askOpenAsProjectOrUcpOrImportModelsDialog.fileUrl = projectFile;
+                    askOpenAsProjectOrUcpOrImportModelsDialog.addToRecent = true;
+                    askOpenAsProjectOrUcpOrImportModelsDialog.show();
                 }
-                else    // always ask
+                else
                 {
-                    // ask whether to open as project or as models
-                    askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
-                    askOpenAsProjectOrModelsDialog.addToRecent = true;
-                    askOpenAsProjectOrModelsDialog.show();
+                    // check preference
+                    var choice = UM.Preferences.getValue("cura/choice_on_open_project");
+                    if (choice == "open_as_project")
+                    {
+                        openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
+                    }
+                    else if (choice == "open_as_model")
+                    {
+                        openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice());
+                    }
+                    else    // always ask
+                    {
+                        // ask whether to open as project or as models
+                        askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
+                        askOpenAsProjectOrModelsDialog.addToRecent = true;
+                        askOpenAsProjectOrModelsDialog.show();
+                    }
                 }
             }
             else
@@ -769,14 +779,31 @@ UM.MainWindow
         id: askOpenAsProjectOrModelsDialog
     }
 
+    AskOpenAsProjectOrUcpOrImportModel
+    {
+        id: askOpenAsProjectOrUcpOrImportModelsDialog
+    }
+
     Connections
     {
         target: CuraApplication
         function onOpenProjectFile(project_file, add_to_recent_files)
         {
-            askOpenAsProjectOrModelsDialog.fileUrl = project_file;
-            askOpenAsProjectOrModelsDialog.addToRecent = add_to_recent_files;
-            askOpenAsProjectOrModelsDialog.show();
+            var is_ucp = CuraActions.isProjectUcp(project_file);
+            print("this file is ucp", is_ucp);
+            if (is_ucp)
+            {
+
+                askOpenAsProjectOrUcpOrImportModelsDialog.fileUrl = project_file;
+                askOpenAsProjectOrUcpOrImportModelsDialog.addToRecent = add_to_recent_files;
+                askOpenAsProjectOrUcpOrImportModelsDialog.show();
+            }
+            else
+            {
+                askOpenAsProjectOrModelsDialog.fileUrl = project_file;
+                askOpenAsProjectOrModelsDialog.addToRecent = add_to_recent_files;
+                askOpenAsProjectOrModelsDialog.show();
+            }
         }
     }
 

+ 104 - 0
resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml

@@ -0,0 +1,104 @@
+// Copyright (c) 2022 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.1
+
+import UM 1.5 as UM
+import Cura 1.0 as Cura
+
+
+UM.Dialog
+{
+    // This dialog asks the user whether he/she wants to open a project file as a project or import models.
+    id: base
+
+    title: catalog.i18nc("@title:window", "Open Universal Cura Project (UCP) file")
+    width: UM.Theme.getSize("small_popup_dialog").width
+    height: UM.Theme.getSize("small_popup_dialog").height
+    backgroundColor: UM.Theme.getColor("main_background")
+
+    maximumHeight: height
+    maximumWidth: width
+    minimumHeight: maximumHeight
+    minimumWidth: maximumWidth
+
+    modality: Qt.WindowModal
+
+    property var fileUrl
+    property var addToRecent: true //Whether to add this file to the recent files list after reading it.
+
+    // load the entire project
+    function loadProjectFile() {
+
+        UM.WorkspaceFileHandler.readLocalFile(base.fileUrl, base.addToRecent);
+
+        base.hide()
+    }
+
+    // load the project file as separated models
+    function loadModelFiles() {
+        CuraApplication.readLocalFile(base.fileUrl, "open_as_model", base.addToRecent)
+
+        base.hide()
+    }
+
+    // load the project file as Universal cura project
+    function loadUcpFiles() {
+        CuraApplication.readLocalUcpFile(base.fileUrl, base.addToRecent)
+
+        base.hide()
+    }
+
+    // override UM.Dialog accept
+    function accept () {
+
+        // when hitting 'enter', we always open as project unless open_as_model was explicitly stored as preference
+        if (openAsPreference == "open_as_model") {
+            loadModelFiles()
+        } else if (openAsPreference == "open_as_ucp"){
+            loadUcpFiles()
+        }else {
+            loadProjectFile()
+        }
+    }
+
+    Column
+    {
+        anchors.fill: parent
+        spacing: UM.Theme.getSize("default_margin").height
+
+        UM.Label
+        {
+            id: questionText
+            width: parent.width
+            text: catalog.i18nc("@text:window", "This is a Cura Universal project file. Would you like to open it as a Cura project or Cura Universal Project or import the models from it?")
+            wrapMode: Text.WordWrap
+        }
+    }
+
+    onAccepted: loadProjectFile()
+    onRejected: loadModelFiles()
+
+    buttonSpacing: UM.Theme.getSize("thin_margin").width
+
+    rightButtons:
+    [
+        Cura.SecondaryButton
+        {
+            text: catalog.i18nc("@action:button", "Open as project")
+            onClicked: loadProjectFile()
+        },
+        Cura.PrimaryButton
+        {
+            text: catalog.i18nc("@action:button", "Open as UCP")
+            onClicked: loadUcpFiles()
+        },
+        Cura.SecondaryButton
+        {
+            text: catalog.i18nc("@action:button", "Import models")
+            onClicked: loadModelFiles()
+        }
+    ]
+}