From 29fcbf7f7453a0db2c589d6e7d092a758c256b87 Mon Sep 17 00:00:00 2001 From: GregValiant <64202104+GregValiant@users.noreply.github.com> Date: Sun, 4 Feb 2024 08:57:08 -0500 Subject: [PATCH 01/18] Update LimitXYAccelJerk.py --- .../scripts/LimitXYAccelJerk.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/PostProcessingPlugin/scripts/LimitXYAccelJerk.py b/plugins/PostProcessingPlugin/scripts/LimitXYAccelJerk.py index 17d17c9e24..9dac57e218 100644 --- a/plugins/PostProcessingPlugin/scripts/LimitXYAccelJerk.py +++ b/plugins/PostProcessingPlugin/scripts/LimitXYAccelJerk.py @@ -9,6 +9,7 @@ # When setting an accel limit on multi-extruder printers ALL extruders are effected. # This post does not distinguish between Print Accel and Travel Accel. The limit is the limit for all regardless. Example: Skin Accel = 1000 and Outer Wall accel = 500. If the limit is set to 300 then both Skin and Outer Wall will be Accel = 300. # 9/15/2023 added support for RepRap M566 command for Jerk in mm/min +# 2/4/2024 Added a block so the script doesn't run unless Accel Control is enabled in Cura. This should keep users from increasing the Accel Limits. from ..Script import Script from cura.CuraApplication import CuraApplication @@ -45,6 +46,10 @@ class LimitXYAccelJerk(Script): # Warn the user if the printer is multi-extruder------------------ if ext_count > 1: Message(text = " 'Limit the X-Y Accel/Jerk': The post processor treats all extruders the same. If you have multiple extruders they will all be subject to the same Accel and Jerk limits imposed. If you have different Travel and Print Accel they will also be subject to the same limits. If that is not acceptable then you should not use this Post Processor.").show() + + # Warn the user if Accel Control is not enabled in Cura. This keeps the script from being able to increase the Accel limits----------- + if not bool(extruder[0].getProperty("acceleration_enabled", "value")): + Message(title = "[Limit the X-Y Accel/Jerk]", text = "You must have 'Enable Acceleration Control' checked in Cura or the script will exit.").show() def getSettingDataString(self): return """{ @@ -169,6 +174,13 @@ class LimitXYAccelJerk(Script): extruder = mycura.extruderList machine_name = str(mycura.getProperty("machine_name", "value")) print_sequence = str(mycura.getProperty("print_sequence", "value")) + acceleration_enabled = bool(extruder[0].getProperty("acceleration_enabled", "value")) + + # Exit if acceleration control is not enabled---------------- + if not acceleration_enabled: + Message(title = "[Limit the X-Y Accel/Jerk]", text = "DID NOT RUN. You must have 'Enable Acceleration Control' checked in Cura.").show() + data[0] += "; [LimitXYAccelJerk] DID NOT RUN because 'Enable Acceleration Control' is not checked in Cura.\n" + return data # Exit if 'one_at_a_time' is enabled------------------------- if print_sequence == "one_at_a_time": @@ -183,12 +195,8 @@ class LimitXYAccelJerk(Script): return data type_of_change = str(self.getSettingValueByKey("type_of_change")) - accel_print_enabled = bool(extruder[0].getProperty("acceleration_enabled", "value")) - accel_travel_enabled = bool(extruder[0].getProperty("acceleration_travel_enabled", "value")) accel_print = extruder[0].getProperty("acceleration_print", "value") accel_travel = extruder[0].getProperty("acceleration_travel", "value") - jerk_print_enabled = str(extruder[0].getProperty("jerk_enabled", "value")) - jerk_travel_enabled = str(extruder[0].getProperty("jerk_travel_enabled", "value")) jerk_print_old = extruder[0].getProperty("jerk_print", "value") jerk_travel_old = extruder[0].getProperty("jerk_travel", "value") if int(accel_print) >= int(accel_travel): From f3c49e494ebb74163c4a2c502d1b981bdcc5d6e3 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Thu, 29 Feb 2024 15:45:13 +0100 Subject: [PATCH 02/18] 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 03/18] 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 04/18] 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 05/18] 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 06/18] 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 07/18] 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 08/18] 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 09/18] 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 10/18] 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 11/18] 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 12/18] 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 13/18] 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 14/18] 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 15/18] 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 16/18] =?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 17/18] 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 18/18] 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]: