From f3c49e494ebb74163c4a2c502d1b981bdcc5d6e3 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Thu, 29 Feb 2024 15:45:13 +0100 Subject: [PATCH 01/17] adding option of opening model as UCP or normal project file CURA-11403 --- cura/CuraActions.py | 10 +- cura/CuraApplication.py | 12 ++ plugins/3MFReader/ThreeMFWorkspaceReader.py | 16 ++- plugins/3MFWriter/ThreeMFWriter.py | 17 ++- resources/qml/Cura.qml | 63 ++++++++--- .../AskOpenAsProjectOrUcpOrImportModel.qml | 104 ++++++++++++++++++ 6 files changed, 187 insertions(+), 35 deletions(-) create mode 100644 resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml diff --git a/cura/CuraActions.py b/cura/CuraActions.py index e33ce8123d..9612e473b8 100644 --- a/cura/CuraActions.py +++ b/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) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c32017371f..00e6304c0a 100755 --- a/cura/CuraApplication.py +++ b/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) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 25e2afa8bd..0e527590f5 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/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 diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 5583059a2f..3389941ed8 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/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()] diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 4983363946..b01cd192c3 100644 --- a/resources/qml/Cura.qml +++ b/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") + var projectFile = projectFileUrlList[0] + var is_ucp = CuraActions.isProjectUcp(projectFile); + print("this file is ucp", is_ucp); + if (is_ucp) { - openFilesIncludingProjectsDialog.loadProjectFile(projectFile); + askOpenAsProjectOrUcpOrImportModelsDialog.fileUrl = projectFile; + askOpenAsProjectOrUcpOrImportModelsDialog.addToRecent = true; + askOpenAsProjectOrUcpOrImportModelsDialog.show(); } - else if (choice == "open_as_model") + else { - openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice()); - } - else // always ask - { - // 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(); + } } } diff --git a/resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml b/resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml new file mode 100644 index 0000000000..9791a3e451 --- /dev/null +++ b/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() + } + ] +} From f678ff2de25a207dcdeda084e89ce413fb0869fd Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Thu, 29 Feb 2024 18:51:48 +0100 Subject: [PATCH 02/17] icon additon in open SVG CURA-11403 --- plugins/3MFWriter/SettingSelection.qml | 2 +- plugins/3MFWriter/ThreeMFWriter.py | 17 +++++---- resources/qml/Cura.qml | 2 - .../AskOpenAsProjectOrUcpOrImportModel.qml | 19 ++-------- .../icons/default/CuraShareIcon.svg | 37 +++++++++++++++++++ 5 files changed, 51 insertions(+), 26 deletions(-) create mode 100644 resources/themes/cura-light/icons/default/CuraShareIcon.svg diff --git a/plugins/3MFWriter/SettingSelection.qml b/plugins/3MFWriter/SettingSelection.qml index 478c2d393c..a50c02f11c 100644 --- a/plugins/3MFWriter/SettingSelection.qml +++ b/plugins/3MFWriter/SettingSelection.qml @@ -19,7 +19,7 @@ RowLayout Layout.preferredWidth: UM.Theme.getSize("setting").width checked: modelData.selected onClicked: modelData.selected = checked - enabled: modelData.selectable + } UM.Label diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 3389941ed8..5583059a2f 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -135,13 +135,16 @@ class ThreeMFWriter(MeshWriter): stack = um_node.callDecoration("getStack") if stack is not None: changed_setting_keys = stack.getTop().getAllKeys() - # 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: + + 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: # We want to export only the specified settings if um_node.getName() in exported_settings: model_exported_settings = exported_settings[um_node.getName()] diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index b01cd192c3..a07bb598d8 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -703,7 +703,6 @@ UM.MainWindow { var projectFile = projectFileUrlList[0] var is_ucp = CuraActions.isProjectUcp(projectFile); - print("this file is ucp", is_ucp); if (is_ucp) { askOpenAsProjectOrUcpOrImportModelsDialog.fileUrl = projectFile; @@ -790,7 +789,6 @@ UM.MainWindow function onOpenProjectFile(project_file, add_to_recent_files) { var is_ucp = CuraActions.isProjectUcp(project_file); - print("this file is ucp", is_ucp); if (is_ucp) { diff --git a/resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml b/resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml index 9791a3e451..a68c48b4a6 100644 --- a/resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml +++ b/resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml @@ -29,13 +29,6 @@ UM.Dialog 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() { @@ -57,10 +50,8 @@ UM.Dialog // 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"){ + } else{ loadUcpFiles() - }else { - loadProjectFile() } } @@ -78,21 +69,17 @@ UM.Dialog } } - onAccepted: loadProjectFile() + onAccepted: loadUcpFile() 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") + iconSource: UM.Theme.getIcon("CuraShareIcon") onClicked: loadUcpFiles() }, Cura.SecondaryButton diff --git a/resources/themes/cura-light/icons/default/CuraShareIcon.svg b/resources/themes/cura-light/icons/default/CuraShareIcon.svg new file mode 100644 index 0000000000..fb9a6b922c --- /dev/null +++ b/resources/themes/cura-light/icons/default/CuraShareIcon.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9633a8e9d431ea421d01e08cf948cbacb535a52f Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 1 Mar 2024 11:53:05 +0100 Subject: [PATCH 03/17] adding warning to settings not in whitelist CURA-11403 --- plugins/3MFWriter/SettingSelection.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/3MFWriter/SettingSelection.qml b/plugins/3MFWriter/SettingSelection.qml index a50c02f11c..d33f2ef8f0 100644 --- a/plugins/3MFWriter/SettingSelection.qml +++ b/plugins/3MFWriter/SettingSelection.qml @@ -19,7 +19,7 @@ RowLayout Layout.preferredWidth: UM.Theme.getSize("setting").width checked: modelData.selected onClicked: modelData.selected = checked - + tooltip: modelData.selectable ? "" :catalog.i18nc("@tooltip", "This setting may not perform well while exporting in UCP. Users are asked to add it at their own risk") } UM.Label @@ -30,9 +30,10 @@ RowLayout UM.HelpIcon { UM.I18nCatalog { id: catalog; name: "cura" } - text: catalog.i18nc("@tooltip", - "This setting can't be exported because it depends on the used printer capacities") + "This setting may not perform well while exporting in UCP, Users are asked to add it at their own risk") visible: !modelData.selectable } + + } From db88a48982383c88007d3491940b21a3eece9a6a Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 1 Mar 2024 13:02:18 +0100 Subject: [PATCH 04/17] modifier meshes supported in case of per model setting CURA-11403 --- plugins/3MFWriter/SettingsExportModel.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/plugins/3MFWriter/SettingsExportModel.py b/plugins/3MFWriter/SettingsExportModel.py index 3b034236c8..62c445f34e 100644 --- a/plugins/3MFWriter/SettingsExportModel.py +++ b/plugins/3MFWriter/SettingsExportModel.py @@ -59,7 +59,17 @@ class SettingsExportModel(QObject): 'skin_edge_support_thickness', 'alternate_carve_order', 'top_skin_preshrink', - 'interlocking_enable'} + 'interlocking_enable', + 'infill_mesh', + 'cutting_mesh'} + + PER_MODEL_EXPORTABLE_SETTINGS_KEYS = { 'top_bottom_thickness', + 'top_thickness', + 'bottom_thickness', + 'top_layers', + 'bottom_layers', + 'wall_thickness', + 'wall_line_count'} def __init__(self, parent = None): super().__init__(parent) @@ -107,15 +117,20 @@ class SettingsExportModel(QObject): def _exportSettings(settings_stack): user_settings_container = settings_stack.userChanges user_keys = user_settings_container.getAllKeys() - + exportable_settings = SettingsExportModel.EXPORTABLE_SETTINGS settings_export = [] + # in case of modify mesh settings we add spme extra settings to the exportable settings + if 'infill_mesh' in user_keys: + exportable_settings = exportable_settings.union(SettingsExportModel.PER_MODEL_EXPORTABLE_SETTINGS_KEYS) for setting_to_export in user_keys: label = settings_stack.getProperty(setting_to_export, "label") value = settings_stack.getProperty(setting_to_export, "value") unit = settings_stack.getProperty(setting_to_export, "unit") setting_type = settings_stack.getProperty(setting_to_export, "type") + + is_exportable = True if setting_to_export in exportable_settings else False if setting_type is not None: # This is not very good looking, but will do for now value = f"{str(SettingDefinition.settingValueToString(setting_type, value))} {unit}" @@ -125,6 +140,6 @@ class SettingsExportModel(QObject): settings_export.append(SettingExport(setting_to_export, label, value, - setting_to_export in SettingsExportModel.EXPORTABLE_SETTINGS)) + is_exportable)) return settings_export From f67d2ed5febdbc3812ab7c2f70a0fb9b3dfedd88 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 1 Mar 2024 15:25:24 +0100 Subject: [PATCH 05/17] Remove setting visibility for Ucp show settings expanded by default CURA-11403 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 8 ++++++-- plugins/3MFReader/WorkspaceDialog.py | 18 +++++++++--------- plugins/3MFReader/WorkspaceDialog.qml | 13 +++++++------ 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 0e527590f5..6cbf054e4b 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -212,6 +212,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return global_stack_file_list[0], extruder_stack_file_list def preRead(self, file_name, show_dialog=True, *args, **kwargs): + result = self._preRead(file_name, show_dialog) + self._is_ucp = False + return result + + def _preRead(self, file_name, show_dialog=True): """Read some info so we can make decisions :param file_name: @@ -662,8 +667,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(self._is_ucp) self._dialog.setAllowCreatemachine(not self._is_ucp) + self._dialog.setIsUcp(self._is_ucp) self._dialog.show() @@ -721,7 +726,6 @@ 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 diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 1fafcf59f5..0f7095ce0a 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -74,10 +74,10 @@ class WorkspaceDialog(QObject): self._is_abstract_machine = False self._is_networked_machine = False self._is_compatible_machine = False - self._has_visible_select_same_profile = False self._select_same_profile_checked = True self._allow_create_machine = True self._exported_settings_model = SpecificSettingsModel() + self._is_ucp = False machineConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal() @@ -102,7 +102,7 @@ class WorkspaceDialog(QObject): isPrinterGroupChanged = pyqtSignal() missingPackagesChanged = pyqtSignal() isCompatibleMachineChanged = pyqtSignal() - hasVisibleSelectSameProfileChanged = pyqtSignal() + isUcpChanged = pyqtSignal() selectSameProfileCheckedChanged = pyqtSignal() @pyqtProperty(bool, notify = isPrinterGroupChanged) @@ -318,14 +318,14 @@ class WorkspaceDialog(QObject): def isCompatibleMachine(self) -> bool: return self._is_compatible_machine - def setHasVisibleSelectSameProfileChanged(self, has_visible_select_same_profile): - if has_visible_select_same_profile != self._has_visible_select_same_profile: - self._has_visible_select_same_profile = has_visible_select_same_profile - self.hasVisibleSelectSameProfileChanged.emit() + def setIsUcp(self, isUcp: bool) -> None: + if isUcp != self._is_ucp: + self._is_ucp = isUcp + self.isUcpChanged.emit() - @pyqtProperty(bool, notify = hasVisibleSelectSameProfileChanged) - def hasVisibleSelectSameProfile(self): - return self._has_visible_select_same_profile + @pyqtProperty(bool, notify=isUcpChanged) + def isUcp(self): + return self._is_ucp def setSelectSameProfileChecked(self, select_same_profile_checked): if select_same_profile_checked != self._select_same_profile_checked: diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 8d06b32e14..5212c799df 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -185,14 +185,14 @@ UM.Dialog { leftLabelText: catalog.i18nc("@action:label", "Not in profile") rightLabelText: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings) - visible: manager.numUserSettings != 0 && manager.selectSameProfileChecked && manager.isCompatibleMachine + visible: manager.numUserSettings != 0 && manager.isCompatibleMachine } WorkspaceRow { leftLabelText: catalog.i18nc("@action:label", "Derivative from") rightLabelText: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges) - visible: manager.numSettingsOverridenByQualityChanges != 0 && manager.selectSameProfileChecked && manager.isCompatibleMachine + visible: manager.numSettingsOverridenByQualityChanges != 0 && manager.isCompatibleMachine } WorkspaceRow @@ -200,7 +200,7 @@ UM.Dialog leftLabelText: catalog.i18nc("@action:label", "Specific settings") rightLabelText: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.exportedSettingModel.rowCount()).arg(manager.exportedSettingModel.rowCount()) buttonText: tableViewSpecificSettings.shouldBeVisible ? catalog.i18nc("@action:button", "Hide settings") : catalog.i18nc("@action:button", "Show settings") - visible: !manager.selectSameProfileChecked || !manager.isCompatibleMachine + visible: manager.isUcp onButtonClicked: tableViewSpecificSettings.shouldBeVisible = !tableViewSpecificSettings.shouldBeVisible } @@ -209,8 +209,8 @@ UM.Dialog id: tableViewSpecificSettings width: parent.width - parent.leftPadding - UM.Theme.getSize("default_margin").width height: UM.Theme.getSize("card").height - visible: shouldBeVisible && (!manager.selectSameProfileChecked || !manager.isCompatibleMachine) - property bool shouldBeVisible: false + visible: shouldBeVisible && manager.isUcp + property bool shouldBeVisible: true columnHeaders: [ @@ -232,7 +232,7 @@ UM.Dialog text: catalog.i18nc("@action:checkbox", "Select the same profile") onEnabledChanged: manager.selectSameProfileChecked = enabled tooltip: enabled ? "" : catalog.i18nc("@tooltip", "You can use the same profile only if you have the same printer as the project was published with") - visible: manager.hasVisibleSelectSameProfile && manager.isCompatibleMachine + visible: manager.isUcp checked: manager.selectSameProfileChecked onCheckedChanged: manager.selectSameProfileChecked = checked @@ -330,6 +330,7 @@ UM.Dialog id: visibilitySection title: catalog.i18nc("@action:label", "Setting visibility") iconSource: UM.Theme.getIcon("Eye") + visible : !manager.isUcp content: Column { spacing: UM.Theme.getSize("default_margin").height From b1b966065161d509508a3cdd0e162330f5413041 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 1 Mar 2024 15:43:37 +0100 Subject: [PATCH 06/17] removing select same profile checkbox CURA-11403 --- plugins/3MFReader/SpecificSettingsModel.py | 2 +- plugins/3MFReader/ThreeMFWorkspaceReader.py | 1 - plugins/3MFReader/WorkspaceDialog.py | 11 ----------- plugins/3MFReader/WorkspaceDialog.qml | 11 ----------- 4 files changed, 1 insertion(+), 24 deletions(-) diff --git a/plugins/3MFReader/SpecificSettingsModel.py b/plugins/3MFReader/SpecificSettingsModel.py index fd5719d6b3..d541e50b7f 100644 --- a/plugins/3MFReader/SpecificSettingsModel.py +++ b/plugins/3MFReader/SpecificSettingsModel.py @@ -27,7 +27,7 @@ class SpecificSettingsModel(ListModel): setting_type = stack.getProperty(setting, "type") if setting_type is not None: # This is not very good looking, but will do for now - value = SettingDefinition.settingValueToString(setting_type, value) + " " + unit + value = str(SettingDefinition.settingValueToString(setting_type, value)) + " " + str(unit) else: value = str(value) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 6cbf054e4b..108ba3f09b 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -702,7 +702,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._dialog.setIsAbstractMachine(is_abstract_machine) self._dialog.setMachineName(machine_name) self._dialog.updateCompatibleMachine() - self._dialog.setSelectSameProfileChecked(self._dialog.isCompatibleMachine) # Block until the dialog is closed. self._dialog.waitForClose() diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 0f7095ce0a..13e2650971 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -74,7 +74,6 @@ class WorkspaceDialog(QObject): self._is_abstract_machine = False self._is_networked_machine = False self._is_compatible_machine = False - self._select_same_profile_checked = True self._allow_create_machine = True self._exported_settings_model = SpecificSettingsModel() self._is_ucp = False @@ -103,7 +102,6 @@ class WorkspaceDialog(QObject): missingPackagesChanged = pyqtSignal() isCompatibleMachineChanged = pyqtSignal() isUcpChanged = pyqtSignal() - selectSameProfileCheckedChanged = pyqtSignal() @pyqtProperty(bool, notify = isPrinterGroupChanged) def isPrinterGroup(self) -> bool: @@ -327,15 +325,6 @@ class WorkspaceDialog(QObject): def isUcp(self): return self._is_ucp - def setSelectSameProfileChecked(self, select_same_profile_checked): - if select_same_profile_checked != self._select_same_profile_checked: - self._select_same_profile_checked = select_same_profile_checked - self.selectSameProfileCheckedChanged.emit() - - @pyqtProperty(bool, notify = selectSameProfileCheckedChanged, fset = setSelectSameProfileChecked) - def selectSameProfileChecked(self): - return self._select_same_profile_checked - def setAllowCreatemachine(self, allow_create_machine): self._allow_create_machine = allow_create_machine diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 5212c799df..90b15378c5 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -226,17 +226,6 @@ UM.Dialog rows: manager.exportedSettingModel.items } } - - UM.CheckBox - { - text: catalog.i18nc("@action:checkbox", "Select the same profile") - onEnabledChanged: manager.selectSameProfileChecked = enabled - tooltip: enabled ? "" : catalog.i18nc("@tooltip", "You can use the same profile only if you have the same printer as the project was published with") - visible: manager.isUcp - - checked: manager.selectSameProfileChecked - onCheckedChanged: manager.selectSameProfileChecked = checked - } } comboboxVisible: manager.qualityChangesConflict From 72f65406270ba80b89d7f4b3928f282f2046a793 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 1 Mar 2024 17:18:15 +0100 Subject: [PATCH 07/17] material and profile as suggested CURA-11403 --- plugins/3MFReader/WorkspaceDialog.qml | 28 ++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 90b15378c5..58d2cfaa49 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -12,7 +12,7 @@ import Cura 1.1 as Cura UM.Dialog { id: workspaceDialog - title: catalog.i18nc("@title:window", "Open Project") + title: manager.isUcp? catalog.i18nc("@title:window", "Open Universal Cura Project (UCP)"): catalog.i18nc("@title:window", "Open Project") margin: UM.Theme.getSize("default_margin").width minimumWidth: UM.Theme.getSize("modal_window_minimum").width @@ -28,7 +28,7 @@ UM.Dialog UM.Label { id: titleLabel - text: catalog.i18nc("@action:title", "Summary - Cura Project") + text: manager.isUcp? catalog.i18nc("@action:title", "Summary - Open Universal Cura Project (UCP)"): catalog.i18nc("@action:title", "Summary - Cura Project") font: UM.Theme.getFont("large") anchors.top: parent.top anchors.left: parent.left @@ -159,7 +159,7 @@ UM.Dialog WorkspaceSection { id: profileSection - title: catalog.i18nc("@action:label", "Profile settings") + title: manager.isUcp? catalog.i18nc("@action:label", "Suggested Profile settings"):catalog.i18nc("@action:label", "Profile settings") iconSource: UM.Theme.getIcon("Sliders") content: Column { @@ -194,13 +194,26 @@ UM.Dialog rightLabelText: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges) visible: manager.numSettingsOverridenByQualityChanges != 0 && manager.isCompatibleMachine } + } + } + WorkspaceSection + { + id: ucpProfileSection + visible: manager.isUcp + title: catalog.i18nc("@action:label", "Settings Loaded from UCP file") + iconSource: UM.Theme.getIcon("Settings") + + content: Column + { + id: ucpProfileSettingsValuesTable + spacing: UM.Theme.getSize("default_margin").height + leftPadding: UM.Theme.getSize("medium_button_icon").width + UM.Theme.getSize("default_margin").width WorkspaceRow { - leftLabelText: catalog.i18nc("@action:label", "Specific settings") + leftLabelText: catalog.i18nc("@action:label", "Settings Loaded from UCP file") rightLabelText: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.exportedSettingModel.rowCount()).arg(manager.exportedSettingModel.rowCount()) buttonText: tableViewSpecificSettings.shouldBeVisible ? catalog.i18nc("@action:button", "Hide settings") : catalog.i18nc("@action:button", "Show settings") - visible: manager.isUcp onButtonClicked: tableViewSpecificSettings.shouldBeVisible = !tableViewSpecificSettings.shouldBeVisible } @@ -263,7 +276,7 @@ UM.Dialog WorkspaceSection { id: materialSection - title: catalog.i18nc("@action:label", "Material settings") + title: manager.isUcp? catalog.i18nc("@action:label", "Suggested Material settings"): catalog.i18nc("@action:label", "Material settings") iconSource: UM.Theme.getIcon("Spool") content: Column { @@ -457,12 +470,13 @@ UM.Dialog { if (visible) { - // Force relead the comboboxes + // Force reload the comboboxes // Since this dialog is only created once the first time you open it, these comboxes need to be reloaded // each time it is shown after the first time so that the indexes will update correctly. materialSection.reloadValues() profileSection.reloadValues() printerSection.reloadValues() + ucpProfileSection.reloadValues() } } } From f19320cad872fca8a901b955b285cdf3bceef609 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Mon, 4 Mar 2024 11:58:21 +0100 Subject: [PATCH 08/17] update the setting table while loading ucp the seond time CURA-11403 --- plugins/3MFReader/SpecificSettingsModel.py | 8 ++++++++ plugins/3MFReader/WorkspaceDialog.py | 2 +- plugins/3MFReader/WorkspaceDialog.qml | 8 +++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/3MFReader/SpecificSettingsModel.py b/plugins/3MFReader/SpecificSettingsModel.py index d541e50b7f..1a4e02b1b2 100644 --- a/plugins/3MFReader/SpecificSettingsModel.py +++ b/plugins/3MFReader/SpecificSettingsModel.py @@ -3,6 +3,7 @@ from PyQt6.QtCore import Qt +from UM.Logger import Logger from UM.Settings.SettingDefinition import SettingDefinition from UM.Qt.ListModel import ListModel @@ -19,6 +20,8 @@ class SpecificSettingsModel(ListModel): self.addRoleName(self.ValueRole, "value") self._i18n_catalog = None + self._update() + def addSettingsFromStack(self, stack, category, settings): for setting, value in settings.items(): @@ -36,3 +39,8 @@ class SpecificSettingsModel(ListModel): "label": stack.getProperty(setting, "label"), "value": value }) + + def _update(self): + Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) + self.setItems([]) + return diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 13e2650971..6a6642331b 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -332,7 +332,7 @@ class WorkspaceDialog(QObject): def allowCreateMachine(self): return self._allow_create_machine - @pyqtProperty(QObject, constant = True) + @pyqtProperty(QObject) def exportedSettingModel(self): return self._exported_settings_model diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 58d2cfaa49..700f15ab5b 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -216,7 +216,6 @@ UM.Dialog buttonText: tableViewSpecificSettings.shouldBeVisible ? catalog.i18nc("@action:button", "Hide settings") : catalog.i18nc("@action:button", "Show settings") onButtonClicked: tableViewSpecificSettings.shouldBeVisible = !tableViewSpecificSettings.shouldBeVisible } - Cura.TableView { id: tableViewSpecificSettings @@ -239,6 +238,13 @@ UM.Dialog rows: manager.exportedSettingModel.items } } + + property var modelRows: manager.exportedSettingModel.items + onModelRowsChanged: + { + tableModel.clear() + tableModel.rows = modelRows + } } comboboxVisible: manager.qualityChangesConflict From c879809836cb6085915f80fe8c42ebd14e515ef5 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Mon, 4 Mar 2024 16:47:09 +0100 Subject: [PATCH 09/17] selected printer is active printer in UCP CURA-11403 --- cura/Machines/Models/MachineListModel.py | 7 +++++- plugins/3MFReader/ThreeMFWorkspaceReader.py | 28 +++++++++------------ plugins/3MFReader/WorkspaceDialog.py | 26 ++++++++++++++++++- plugins/3MFReader/WorkspaceDialog.qml | 2 +- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/cura/Machines/Models/MachineListModel.py b/cura/Machines/Models/MachineListModel.py index 69a3c53d03..de78928687 100644 --- a/cura/Machines/Models/MachineListModel.py +++ b/cura/Machines/Models/MachineListModel.py @@ -5,7 +5,7 @@ # online cloud connected printers are represented within this ListModel. Additional information such as the number of # connected printers for each printer type is gathered. -from typing import Optional, List, cast +from typing import Optional, List, cast, Dict, Any from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSlot, pyqtProperty, pyqtSignal @@ -159,3 +159,8 @@ class MachineListModel(ListModel): "machineCount": machine_count, "catergory": "connected" if is_online else "other", }) + + def getItems(self) -> Dict[str, Any]: + if self.count > 0: + return self.items + return {} \ No newline at end of file diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 108ba3f09b..dd011c43f6 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -144,14 +144,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._old_new_materials: Dict[str, str] = {} self._machine_info = None - self._load_profile = False self._user_settings: Dict[str, Dict[str, Any]] = {} def _clearState(self): self._id_mapping = {} self._old_new_materials = {} self._machine_info = None - self._load_profile = False self._user_settings = {} def setOpenAsUcp(self, openAsUcp: bool): @@ -212,11 +210,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return global_stack_file_list[0], extruder_stack_file_list def preRead(self, file_name, show_dialog=True, *args, **kwargs): - result = self._preRead(file_name, show_dialog) - self._is_ucp = False - return result - - def _preRead(self, file_name, show_dialog=True): """Read some info so we can make decisions :param file_name: @@ -618,11 +611,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Load the user specifically exported settings self._dialog.exportedSettingModel.clear() + self._dialog.setCurrentMachineName("") 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) actual_global_stack = CuraApplication.getInstance().getGlobalContainerStack() + self._dialog.setCurrentMachineName(actual_global_stack.id) for stack_name, settings in self._user_settings.items(): if stack_name == 'global': @@ -675,7 +670,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Choosing the initially selected printer in MachineSelector is_networked_machine = False is_abstract_machine = False - if global_stack and isinstance(global_stack, GlobalStack): + if global_stack and isinstance(global_stack, GlobalStack) and not self._is_ucp: # The machine included in the project file exists locally already, no need to change selected printers. is_networked_machine = global_stack.hasNetworkedConnection() is_abstract_machine = parseBool(existing_global_stack.getMetaDataEntry("is_abstract_machine", False)) @@ -684,7 +679,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): elif self._dialog.updatableMachinesModel.count > 0: # The machine included in the project file does not exist. There is another machine of the same type. # This will always default to an abstract machine first. - machine = self._dialog.updatableMachinesModel.getItem(0) + machine = self._dialog.updatableMachinesModel.getItem(self._dialog.currentMachinePositionIndex) machine_name = machine["name"] is_networked_machine = machine["isNetworked"] is_abstract_machine = machine["isAbstractMachine"] @@ -709,8 +704,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if self._dialog.getResult() == {}: return WorkspaceReader.PreReadResult.cancelled - self._load_profile = not self._is_ucp - self._resolve_strategies = self._dialog.getResult() # # There can be 3 resolve strategies coming from the dialog: @@ -832,7 +825,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): for stack in extruder_stacks: stack.setNextStack(global_stack, connect_signals = False) - if self._load_profile: + if not self._is_ucp: Logger.log("d", "Workspace loading is checking definitions...") # Get all the definition files & check if they exist. If not, add them. definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] @@ -906,7 +899,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): QCoreApplication.processEvents() # Ensure that the GUI does not freeze. if global_stack: - if self._load_profile: + if not self._is_ucp: # Handle quality changes if any self._processQualityChanges(global_stack) @@ -914,7 +907,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._applyChangesToMachine(global_stack, extruder_stack_dict) else: # Just clear the settings now, so that we can change the active machine without conflicts - self._clearMachineSettings(global_stack, extruder_stack_dict) + self._clearMachineSettings(global_stack, {}) + Logger.log("d", "Workspace loading is notifying rest of the code of changes...") # Actually change the active machine. @@ -924,9 +918,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # function is running on the main thread (Qt thread), although those "changed" signals have been emitted, but # they won't take effect until this function is done. # To solve this, we schedule _updateActiveMachine() for later so it will have the latest data. + self._updateActiveMachine(global_stack) - if not self._load_profile: + if self._is_ucp: # Now we have switched, apply the user settings self._applyUserSettings(global_stack, extruder_stack_dict, self._user_settings) @@ -938,6 +933,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): base_file_name = os.path.basename(file_name) self.setWorkspaceName(base_file_name) + self._is_ucp = False return nodes, self._loadMetadata(file_name) @staticmethod @@ -1320,7 +1316,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if key not in global_stack.getMetaData() and key not in _ignored_machine_network_metadata: global_stack.setMetaDataEntry(key, value) - if self._quality_changes_to_apply: + if self._quality_changes_to_apply !=None: quality_changes_group_list = container_tree.getCurrentQualityChangesGroups() quality_changes_group = next((qcg for qcg in quality_changes_group_list if qcg.name == self._quality_changes_to_apply), None) if not quality_changes_group: diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 6a6642331b..fdb63c6a11 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -63,6 +63,7 @@ class WorkspaceDialog(QObject): self._machine_name = "" self._machine_type = "" self._variant_type = "" + self._current_machine_name = "" self._material_labels = [] self._extruders = [] self._objects_on_plate = False @@ -76,6 +77,7 @@ class WorkspaceDialog(QObject): self._is_compatible_machine = False self._allow_create_machine = True self._exported_settings_model = SpecificSettingsModel() + self._current_machine_pos_index = 0 self._is_ucp = False machineConflictChanged = pyqtSignal() @@ -174,11 +176,33 @@ class WorkspaceDialog(QObject): self._machine_name = machine_name self.machineNameChanged.emit() + def setCurrentMachineName(self, machine: str) -> None: + self._current_machine_name = machine + + @pyqtProperty(str, notify = machineNameChanged) + def currentMachineName(self) -> str: + return self._current_machine_name + + @staticmethod + def getIndexOfCurrentMachine(list_of_dicts, key, value): + for i, d in enumerate(list_of_dicts): + if d.get(key) == value: # found the dictionary + return i; + return 0 + + @pyqtProperty(int, notify = machineNameChanged) + def currentMachinePositionIndex(self): + return self._current_machine_pos_index + @pyqtProperty(QObject, notify = updatableMachinesChanged) def updatableMachinesModel(self) -> MachineListModel: + if self._current_machine_name != "": + self._current_machine_pos_index = self.getIndexOfCurrentMachine(self._updatable_machines_model.getItems(), "id", self._current_machine_name) + else: + self._current_machine_pos_index = 0 return cast(MachineListModel, self._updatable_machines_model) - def setUpdatableMachines(self, updatable_machines: List[GlobalStack]) -> None: + def setUpdatableMachines(self, updatable_machines: List[GlobalStack], current_machine=None) -> None: self._updatable_machines_model.set_machines_filter(updatable_machines) self.updatableMachinesChanged.emit() diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 700f15ab5b..5f13af933c 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -96,7 +96,7 @@ UM.Dialog WorkspaceRow { leftLabelText: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name") - rightLabelText: manager.machineName == catalog.i18nc("@button", "Create new") ? "" : manager.machineName + rightLabelText: manager.isUcp? manager.machineType: manager.machineName == catalog.i18nc("@button", "Create new") ? "" : manager.machineName } } From 68669794de6e22824348c3797dfb9bd259d35374 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Mon, 4 Mar 2024 18:03:11 +0100 Subject: [PATCH 10/17] selected printer is active printer also in case it is a cloud printer CURA-11403 --- cura/Machines/Models/MachineListModel.py | 4 ++-- plugins/3MFReader/WorkspaceDialog.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/Machines/Models/MachineListModel.py b/cura/Machines/Models/MachineListModel.py index de78928687..cac52a0e65 100644 --- a/cura/Machines/Models/MachineListModel.py +++ b/cura/Machines/Models/MachineListModel.py @@ -30,10 +30,10 @@ class MachineListModel(ListModel): ComponentTypeRole = Qt.ItemDataRole.UserRole + 8 IsNetworkedMachineRole = Qt.ItemDataRole.UserRole + 9 - def __init__(self, parent: Optional[QObject] = None, machines_filter: List[GlobalStack] = None, listenToChanges: bool = True) -> None: + def __init__(self, parent: Optional[QObject] = None, machines_filter: List[GlobalStack] = None, listenToChanges: bool = True, showCloudPrinters: bool = False) -> None: super().__init__(parent) - self._show_cloud_printers = False + self._show_cloud_printers = showCloudPrinters self._machines_filter = machines_filter self._catalog = i18nCatalog("cura") diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index fdb63c6a11..c04062f686 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -68,7 +68,7 @@ class WorkspaceDialog(QObject): self._extruders = [] self._objects_on_plate = False self._is_printer_group = False - self._updatable_machines_model = MachineListModel(self, listenToChanges=False) + self._updatable_machines_model = MachineListModel(self, listenToChanges = False, showCloudPrinters = True) self._missing_package_metadata: List[Dict[str, str]] = [] self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() self._install_missing_package_dialog: Optional[QObject] = None @@ -202,7 +202,7 @@ class WorkspaceDialog(QObject): self._current_machine_pos_index = 0 return cast(MachineListModel, self._updatable_machines_model) - def setUpdatableMachines(self, updatable_machines: List[GlobalStack], current_machine=None) -> None: + def setUpdatableMachines(self, updatable_machines: List[GlobalStack]) -> None: self._updatable_machines_model.set_machines_filter(updatable_machines) self.updatableMachinesChanged.emit() From 3e23ce1c3725750b14bf343919d87774fe3e2b51 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Tue, 5 Mar 2024 14:08:39 +0100 Subject: [PATCH 11/17] code review fixed for PAP CURA-11403 --- cura/CuraApplication.py | 2 +- plugins/3MFReader/SpecificSettingsModel.py | 2 +- plugins/3MFReader/WorkspaceDialog.py | 8 ++++---- plugins/3MFWriter/SettingSelection.qml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 00e6304c0a..0879f88841 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1985,7 +1985,7 @@ class CuraApplication(QtApplication): file_name = QUrl(file).toLocalFile() workspace_reader = self.getWorkspaceFileHandler() if workspace_reader is None: - Logger.log("w", "Workspace reader not found") + Logger.warning(f"Workspace reader not found, cannot read file {file_name}.") return workspace_reader.getReaderForFile(file_name).setOpenAsUcp(True) diff --git a/plugins/3MFReader/SpecificSettingsModel.py b/plugins/3MFReader/SpecificSettingsModel.py index 1a4e02b1b2..ac8e7af3ef 100644 --- a/plugins/3MFReader/SpecificSettingsModel.py +++ b/plugins/3MFReader/SpecificSettingsModel.py @@ -41,6 +41,6 @@ class SpecificSettingsModel(ListModel): }) def _update(self): - Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) + Logger.debug(f"Updating {self.__class__.__name__}") self.setItems([]) return diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index c04062f686..4b9f1eaa6f 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -184,11 +184,11 @@ class WorkspaceDialog(QObject): return self._current_machine_name @staticmethod - def getIndexOfCurrentMachine(list_of_dicts, key, value): + def getIndexOfCurrentMachine(list_of_dicts, key, value, defaultIndex): for i, d in enumerate(list_of_dicts): if d.get(key) == value: # found the dictionary - return i; - return 0 + return i + return defaultIndex @pyqtProperty(int, notify = machineNameChanged) def currentMachinePositionIndex(self): @@ -197,7 +197,7 @@ class WorkspaceDialog(QObject): @pyqtProperty(QObject, notify = updatableMachinesChanged) def updatableMachinesModel(self) -> MachineListModel: if self._current_machine_name != "": - self._current_machine_pos_index = self.getIndexOfCurrentMachine(self._updatable_machines_model.getItems(), "id", self._current_machine_name) + self._current_machine_pos_index = self.getIndexOfCurrentMachine(self._updatable_machines_model.getItems(), "id", self._current_machine_name, defaultIndex = 0) else: self._current_machine_pos_index = 0 return cast(MachineListModel, self._updatable_machines_model) diff --git a/plugins/3MFWriter/SettingSelection.qml b/plugins/3MFWriter/SettingSelection.qml index d33f2ef8f0..794a5aacf6 100644 --- a/plugins/3MFWriter/SettingSelection.qml +++ b/plugins/3MFWriter/SettingSelection.qml @@ -19,7 +19,7 @@ RowLayout Layout.preferredWidth: UM.Theme.getSize("setting").width checked: modelData.selected onClicked: modelData.selected = checked - tooltip: modelData.selectable ? "" :catalog.i18nc("@tooltip", "This setting may not perform well while exporting in UCP. Users are asked to add it at their own risk") + tooltip: modelData.selectable ? "" :catalog.i18nc("@tooltip", "This setting may not perform well while exporting to UCP. Users are asked to add it at their own risk.") } UM.Label @@ -31,7 +31,7 @@ RowLayout { UM.I18nCatalog { id: catalog; name: "cura" } text: catalog.i18nc("@tooltip", - "This setting may not perform well while exporting in UCP, Users are asked to add it at their own risk") + "This setting may not perform well while exporting to UCP, Users are asked to add it at their own risk.") visible: !modelData.selectable } From 0e70b8044655cad72b0f20e138744b31e8a5b134 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Tue, 5 Mar 2024 14:56:09 +0100 Subject: [PATCH 12/17] removal of preference to not open the save dialog for ucp again CURA-11403 --- cura/CuraApplication.py | 2 -- plugins/3MFWriter/ThreeMFWriter.py | 39 ++--------------------- plugins/3MFWriter/UCPDialog.qml | 28 +--------------- resources/qml/Preferences/GeneralPage.qml | 14 -------- 4 files changed, 3 insertions(+), 80 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 0879f88841..39446d0f96 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -601,9 +601,7 @@ class CuraApplication(QtApplication): preferences.addPreference("mesh/scale_to_fit", False) preferences.addPreference("mesh/scale_tiny_meshes", True) preferences.addPreference("cura/dialog_on_project_save", True) - preferences.addPreference("cura/dialog_on_ucp_project_save", True) preferences.addPreference("cura/asked_dialog_on_project_save", False) - preferences.addPreference("cura/asked_dialog_on_ucp_project_save", False) preferences.addPreference("cura/choice_on_profile_override", "always_ask") preferences.addPreference("cura/choice_on_open_project", "always_ask") preferences.addPreference("cura/use_multi_build_plate", False) diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 5583059a2f..1c14c37cfd 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -462,40 +462,5 @@ class ThreeMFWriter(MeshWriter): return extra_settings def exportUcp(self): - preferences = CuraApplication.getInstance().getPreferences() - if preferences.getValue("cura/dialog_on_ucp_project_save"): - self._config_dialog = UCPDialog() - self._config_dialog.show() - else: - application = CuraApplication.getInstance() - workspace_handler = application.getInstance().getWorkspaceFileHandler() - - # Set the model to the workspace writer - mesh_writer = workspace_handler.getWriter("3MFWriter") - mesh_writer.setExportModel(SettingsExportModel()) - - # Open file dialog and write the file - device = application.getOutputDeviceManager().getOutputDevice("local_file") - nodes = [application.getController().getScene().getRoot()] - - file_name = CuraApplication.getInstance().getPrintInformation().baseName - - try: - device.requestWrite( - nodes, - file_name, - ["application/vnd.ms-package.3dmanufacturing-3dmodel+xml"], - workspace_handler, - preferred_mimetype_list="application/vnd.ms-package.3dmanufacturing-3dmodel+xml" - ) - except OutputDeviceError.UserCanceledError: - self._onRejected() - except Exception as e: - message = Message( - catalog.i18nc("@info:error", "Unable to write to file: {0}", file_name), - title=catalog.i18nc("@info:title", "Error"), - message_type=Message.MessageType.ERROR - ) - message.show() - Logger.logException("e", "Unable to write to file %s: %s", file_name, e) - self._onRejected() + self._config_dialog = UCPDialog() + self._config_dialog.show() diff --git a/plugins/3MFWriter/UCPDialog.qml b/plugins/3MFWriter/UCPDialog.qml index 3a0e6bf842..5d094f9187 100644 --- a/plugins/3MFWriter/UCPDialog.qml +++ b/plugins/3MFWriter/UCPDialog.qml @@ -19,21 +19,6 @@ UM.Dialog minimumHeight: UM.Theme.getSize("modal_window_minimum").height backgroundColor: UM.Theme.getColor("detail_background") - property bool dontShowAgain: false - - function storeDontShowAgain() - { - UM.Preferences.setValue("cura/dialog_on_ucp_project_save", !dontShowAgainCheckbox.checked) - UM.Preferences.setValue("cura/asked_dialog_on_ucp_project_save", false) - } - - onVisibleChanged: - { - if(visible && UM.Preferences.getValue("cura/asked_dialog_on_ucp_project_save")) - { - dontShowAgain = !UM.Preferences.getValue("cura/dialog_on_ucp_project_save") - } - } headerComponent: Rectangle { @@ -90,15 +75,7 @@ UM.Dialog delegate: SettingsSelectionGroup { Layout.margins: 0 } } } - leftButtons: - [ - UM.CheckBox - { - id: dontShowAgainCheckbox - text: catalog.i18nc("@action:label", "Don't show project summary on save again") - checked: dontShowAgain - } - ] + rightButtons: [ Cura.TertiaryButton @@ -117,9 +94,6 @@ UM.Dialog onClosing: { - storeDontShowAgain() manager.notifyClosed() } - onRejected: storeDontShowAgain() - onAccepted: storeDontShowAgain() } diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 0f50f169ef..b753d0e48a 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -784,20 +784,6 @@ UM.PreferencesPage } } - UM.TooltipArea - { - width: childrenRect.width - height: childrenRect.height - text: catalog.i18nc("@info:tooltip", "Should a summary be shown when saving a UCP project file?") - - UM.CheckBox - { - text: catalog.i18nc("@option:check", "Show summary dialog when saving a UCP project") - checked: boolCheck(UM.Preferences.getValue("cura/dialog_on_ucp_project_save")) - onCheckedChanged: UM.Preferences.setValue("cura/dialog_on_ucp_project_save", checked) - } - } - UM.TooltipArea { width: childrenRect.width From b119a010ca7ecfec0add86cc8e47c33049053a81 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Tue, 5 Mar 2024 16:34:41 +0100 Subject: [PATCH 13/17] Moved calculating UCP only to Preread and getting value here CURA-11403 --- cura/CuraActions.py | 10 ---------- cura/CuraApplication.py | 6 ++++++ resources/qml/Cura.qml | 4 ++-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 9612e473b8..835c46bba8 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -1,6 +1,5 @@ # 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,8 +32,6 @@ 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: super().__init__(parent) @@ -196,13 +193,6 @@ 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) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 39446d0f96..5de74e4714 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -2194,6 +2194,12 @@ class CuraApplication(QtApplication): def addNonSliceableExtension(self, extension): self._non_sliceable_extensions.append(extension) + @pyqtSlot(str, result = bool) + def isProjectUcp(self, file_url) -> bool: + file_path = QUrl(file_url).toLocalFile() + workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path) + return workspace_reader.getIsProjectUcp() + @pyqtSlot(str, result=bool) def checkIsValidProjectFile(self, file_url): """Checks if the given file URL is a valid project file. """ diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index a07bb598d8..776417e15d 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -702,7 +702,7 @@ UM.MainWindow if (hasProjectFile) { var projectFile = projectFileUrlList[0] - var is_ucp = CuraActions.isProjectUcp(projectFile); + var is_ucp = CuraApplication.isProjectUcp(projectFile); if (is_ucp) { askOpenAsProjectOrUcpOrImportModelsDialog.fileUrl = projectFile; @@ -788,7 +788,7 @@ UM.MainWindow target: CuraApplication function onOpenProjectFile(project_file, add_to_recent_files) { - var is_ucp = CuraActions.isProjectUcp(project_file); + var is_ucp = CuraApplication.isProjectUcp(project_file); if (is_ucp) { From 8ef7b65710ee990ebf74116736ee1c1f3a27e21b Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Tue, 5 Mar 2024 16:37:17 +0100 Subject: [PATCH 14/17] removing update existing/ create new in case of UCP also, making sure post processing scripts are not loaded. CURA-11403 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 76 ++++++++++++--------- plugins/3MFReader/WorkspaceDialog.qml | 4 +- plugins/3MFReader/WorkspaceSection.qml | 3 +- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index dd011c43f6..75b04db9e3 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -117,7 +117,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._supported_extensions = [".3mf"] self._dialog = WorkspaceDialog() self._3mf_mesh_reader = None - self._is_ucp = False + self._is_ucp = None self._container_registry = ContainerRegistry.getInstance() # suffixes registered with the MimeTypes don't start with a dot '.' @@ -208,6 +208,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader): raise FileNotFoundError("No global stack file found!") return global_stack_file_list[0], extruder_stack_file_list + def _isProjectUcp(self, file_name) -> bool: + if self._is_ucp == None: + archive = zipfile.ZipFile(file_name, "r") + cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] + self._is_ucp =True if USER_SETTINGS_PATH in cura_file_names else False + + def getIsProjectUcp(self) -> bool: + return self._is_ucp + def preRead(self, file_name, show_dialog=True, *args, **kwargs): """Read some info so we can make decisions @@ -217,7 +226,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): we don't want to show a dialog. """ self._clearState() - + self._isProjectUcp(file_name) 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 @@ -933,7 +942,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): base_file_name = os.path.basename(file_name) self.setWorkspaceName(base_file_name) - self._is_ucp = False + self._is_ucp = None return nodes, self._loadMetadata(file_name) @staticmethod @@ -1312,39 +1321,40 @@ class ThreeMFWorkspaceReader(WorkspaceReader): machine_manager.setActiveMachine(global_stack.getId()) # Set metadata fields that are missing from the global stack - for key, value in self._machine_info.metadata_dict.items(): - if key not in global_stack.getMetaData() and key not in _ignored_machine_network_metadata: - global_stack.setMetaDataEntry(key, value) + if not self._is_ucp: + for key, value in self._machine_info.metadata_dict.items(): + if key not in global_stack.getMetaData() and key not in _ignored_machine_network_metadata: + global_stack.setMetaDataEntry(key, value) - if self._quality_changes_to_apply !=None: - quality_changes_group_list = container_tree.getCurrentQualityChangesGroups() - quality_changes_group = next((qcg for qcg in quality_changes_group_list if qcg.name == self._quality_changes_to_apply), None) - if not quality_changes_group: - Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply) - return - machine_manager.setQualityChangesGroup(quality_changes_group, no_dialog = True) - else: - self._quality_type_to_apply = self._quality_type_to_apply.lower() if self._quality_type_to_apply else None - quality_group_dict = container_tree.getCurrentQualityGroups() - if self._quality_type_to_apply in quality_group_dict: - quality_group = quality_group_dict[self._quality_type_to_apply] + if self._quality_changes_to_apply !=None: + quality_changes_group_list = container_tree.getCurrentQualityChangesGroups() + quality_changes_group = next((qcg for qcg in quality_changes_group_list if qcg.name == self._quality_changes_to_apply), None) + if not quality_changes_group: + Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply) + return + machine_manager.setQualityChangesGroup(quality_changes_group, no_dialog = True) else: - Logger.log("i", "Could not find quality type [%s], switch to default", self._quality_type_to_apply) - preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type") - quality_group = quality_group_dict.get(preferred_quality_type) - if quality_group is None: - Logger.log("e", "Could not get preferred quality type [%s]", preferred_quality_type) - - if quality_group is not None: - machine_manager.setQualityGroup(quality_group, no_dialog = True) - - # Also apply intent if available - available_intent_category_list = IntentManager.getInstance().currentAvailableIntentCategories() - if self._intent_category_to_apply is not None and self._intent_category_to_apply in available_intent_category_list: - machine_manager.setIntentByCategory(self._intent_category_to_apply) + self._quality_type_to_apply = self._quality_type_to_apply.lower() if self._quality_type_to_apply else None + quality_group_dict = container_tree.getCurrentQualityGroups() + if self._quality_type_to_apply in quality_group_dict: + quality_group = quality_group_dict[self._quality_type_to_apply] else: - # if no intent is provided, reset to the default (balanced) intent - machine_manager.resetIntents() + Logger.log("i", "Could not find quality type [%s], switch to default", self._quality_type_to_apply) + preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type") + quality_group = quality_group_dict.get(preferred_quality_type) + if quality_group is None: + Logger.log("e", "Could not get preferred quality type [%s]", preferred_quality_type) + + if quality_group is not None: + machine_manager.setQualityGroup(quality_group, no_dialog = True) + + # Also apply intent if available + available_intent_category_list = IntentManager.getInstance().currentAvailableIntentCategories() + if self._intent_category_to_apply is not None and self._intent_category_to_apply in available_intent_category_list: + machine_manager.setIntentByCategory(self._intent_category_to_apply) + else: + # if no intent is provided, reset to the default (balanced) intent + machine_manager.resetIntents() # Notify everything/one that is to notify about changes. global_stack.containersChanged.emit(global_stack.getTop()) diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 5f13af933c..c8d53a1154 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -253,7 +253,7 @@ UM.Dialog id: qualityChangesResolveComboBox model: resolveStrategiesModel textRole: "label" - visible: manager.qualityChangesConflict + visible: manager.qualityChangesConflict && !manager.isUcp contentLeftPadding: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width textFont: UM.Theme.getFont("medium") @@ -307,7 +307,7 @@ UM.Dialog id: materialResolveComboBox model: resolveStrategiesModel textRole: "label" - visible: manager.materialConflict + visible: manager.materialConflict && !manager.isUcp contentLeftPadding: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width textFont: UM.Theme.getFont("medium") diff --git a/plugins/3MFReader/WorkspaceSection.qml b/plugins/3MFReader/WorkspaceSection.qml index 63b5e89b41..7c8b01be7a 100644 --- a/plugins/3MFReader/WorkspaceSection.qml +++ b/plugins/3MFReader/WorkspaceSection.qml @@ -84,7 +84,8 @@ Item { anchors.right: parent.right anchors.verticalCenter: comboboxLabel.verticalCenter - + color: UM.Theme.getColor("small_button_text") + icon: UM.Theme.getIcon("Information") text: comboboxTooltipText visible: comboboxTooltipText != "" } From a63cf954d420aaaed618a35bfc7863206282d3d6 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 6 Mar 2024 10:28:09 +0100 Subject: [PATCH 15/17] =?UTF-8?q?modify-settings-for-overlap=E2=80=99,=20'?= =?UTF-8?q?support-blocker'=20and=20'support-mesh'=20saved=20as=20their=20?= =?UTF-8?q?respective=20types=20along=20with=20their=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CURA-11403 --- plugins/3MFWriter/SettingsExportModel.py | 25 ++++++++---------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/plugins/3MFWriter/SettingsExportModel.py b/plugins/3MFWriter/SettingsExportModel.py index 62c445f34e..401bf21e90 100644 --- a/plugins/3MFWriter/SettingsExportModel.py +++ b/plugins/3MFWriter/SettingsExportModel.py @@ -59,17 +59,12 @@ class SettingsExportModel(QObject): 'skin_edge_support_thickness', 'alternate_carve_order', 'top_skin_preshrink', - 'interlocking_enable', - 'infill_mesh', - 'cutting_mesh'} + 'interlocking_enable'} - PER_MODEL_EXPORTABLE_SETTINGS_KEYS = { 'top_bottom_thickness', - 'top_thickness', - 'bottom_thickness', - 'top_layers', - 'bottom_layers', - 'wall_thickness', - 'wall_line_count'} + PER_MODEL_EXPORTABLE_SETTINGS_KEYS = {"anti_overhang_mesh", + "infill_mesh", + "cutting_mesh", + "support_mesh"} def __init__(self, parent = None): super().__init__(parent) @@ -119,20 +114,16 @@ class SettingsExportModel(QObject): user_keys = user_settings_container.getAllKeys() exportable_settings = SettingsExportModel.EXPORTABLE_SETTINGS settings_export = [] + # Check whether any of the user keys exist in PER_MODEL_EXPORTABLE_SETTINGS_KEYS + is_exportable = any(key in SettingsExportModel.PER_MODEL_EXPORTABLE_SETTINGS_KEYS for key in user_keys) - # in case of modify mesh settings we add spme extra settings to the exportable settings - if 'infill_mesh' in user_keys: - exportable_settings = exportable_settings.union(SettingsExportModel.PER_MODEL_EXPORTABLE_SETTINGS_KEYS) for setting_to_export in user_keys: label = settings_stack.getProperty(setting_to_export, "label") value = settings_stack.getProperty(setting_to_export, "value") unit = settings_stack.getProperty(setting_to_export, "unit") setting_type = settings_stack.getProperty(setting_to_export, "type") - - is_exportable = True if setting_to_export in exportable_settings else False if setting_type is not None: - # This is not very good looking, but will do for now value = f"{str(SettingDefinition.settingValueToString(setting_type, value))} {unit}" else: value = str(value) @@ -140,6 +131,6 @@ class SettingsExportModel(QObject): settings_export.append(SettingExport(setting_to_export, label, value, - is_exportable)) + is_exportable or setting_to_export in exportable_settings)) return settings_export From 831a1d4876fc5d12c786c2b93a0ebe9c2fbf3728 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 6 Mar 2024 12:32:05 +0100 Subject: [PATCH 16/17] Reset openAsUcp at the start of preread CURA-11403 --- cura/CuraApplication.py | 3 ++- plugins/3MFReader/ThreeMFWorkspaceReader.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 5de74e4714..86bb53126c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1986,7 +1986,6 @@ class CuraApplication(QtApplication): Logger.warning(f"Workspace reader not found, cannot read file {file_name}.") return - workspace_reader.getReaderForFile(file_name).setOpenAsUcp(True) workspace_reader.readLocalFile(file, add_to_recent_files) @pyqtSlot(QUrl, str, bool) @@ -2209,6 +2208,8 @@ class CuraApplication(QtApplication): if workspace_reader is None: return False # non-project files won't get a reader try: + if workspace_reader.getPluginId() == "3MFReader": + workspace_reader.clearOpenAsUcp() result = workspace_reader.preRead(file_path, show_dialog=False) return result == WorkspaceReader.PreReadResult.accepted except: diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 75b04db9e3..e6992611c1 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -152,8 +152,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._machine_info = None self._user_settings = {} - def setOpenAsUcp(self, openAsUcp: bool): - self._is_ucp = openAsUcp + def clearOpenAsUcp(self): + self._is_ucp = None 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. @@ -208,6 +208,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): raise FileNotFoundError("No global stack file found!") return global_stack_file_list[0], extruder_stack_file_list + def _isProjectUcp(self, file_name) -> bool: if self._is_ucp == None: archive = zipfile.ZipFile(file_name, "r") From dbf722e034bf1d14b05a39d30ef9d450e80ec34a Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 6 Mar 2024 13:38:33 +0100 Subject: [PATCH 17/17] added settings for modifier meshes and grouped meshed CURA-11403 --- plugins/3MFWriter/SettingsExportModel.py | 53 ++++++++++++------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/plugins/3MFWriter/SettingsExportModel.py b/plugins/3MFWriter/SettingsExportModel.py index 401bf21e90..99ffad4bac 100644 --- a/plugins/3MFWriter/SettingsExportModel.py +++ b/plugins/3MFWriter/SettingsExportModel.py @@ -66,43 +66,42 @@ class SettingsExportModel(QObject): "cutting_mesh", "support_mesh"} - def __init__(self, parent = None): + def __init__(self, parent=None): super().__init__(parent) self._settings_groups = [] application = CuraApplication.getInstance() - # Display global settings - global_stack = application.getGlobalContainerStack() - self._settings_groups.append(SettingsExportGroup(global_stack, - "Global settings", - SettingsExportGroup.Category.Global, - self._exportSettings(global_stack))) + self._appendGlobalSettings(application) + self._appendExtruderSettings(application) + self._appendModelSettings(application) - # Display per-extruder settings + def _appendGlobalSettings(self, application): + global_stack = application.getGlobalContainerStack() + self._settings_groups.append(SettingsExportGroup( + global_stack, "Global settings", SettingsExportGroup.Category.Global, self._exportSettings(global_stack))) + + def _appendExtruderSettings(self, application): extruders_stacks = ExtruderManager.getInstance().getUsedExtruderStacks() for extruder_stack in extruders_stacks: - color = "" - if extruder_stack.material: - color = extruder_stack.material.getMetaDataEntry("color_code") + color = extruder_stack.material.getMetaDataEntry("color_code") if extruder_stack.material else "" + self._settings_groups.append(SettingsExportGroup( + extruder_stack, "Extruder settings", SettingsExportGroup.Category.Extruder, + self._exportSettings(extruder_stack), extruder_index=extruder_stack.position, extruder_color=color)) - self._settings_groups.append(SettingsExportGroup(extruder_stack, - "Extruder settings", - SettingsExportGroup.Category.Extruder, - self._exportSettings(extruder_stack), - extruder_index=extruder_stack.position, - extruder_color=color)) + def _appendModelSettings(self, application): + scene = application.getController().getScene() + for scene_node in scene.getRoot().getChildren(): + self._appendNodeSettings(scene_node, "Model settings", SettingsExportGroup.Category.Model) + + def _appendNodeSettings(self, node, title_prefix, category): + stack = node.callDecoration("getStack") + if stack: + self._settings_groups.append(SettingsExportGroup( + stack, f"{title_prefix}", category, self._exportSettings(stack), node.getName())) + for child in node.getChildren(): + self._appendNodeSettings(child, f"Children of {node.getName()}", SettingsExportGroup.Category.Model) - # Display per-model settings - scene_root = application.getController().getScene().getRoot() - for scene_node in scene_root.getChildren(): - per_model_stack = scene_node.callDecoration("getStack") - if per_model_stack is not None: - self._settings_groups.append(SettingsExportGroup(per_model_stack, - "Model settings", - SettingsExportGroup.Category.Model, - self._exportSettings(per_model_stack), - scene_node.getName())) @pyqtProperty(list, constant=True) def settingsGroups(self) -> List[SettingsExportGroup]: