From 330dfd8be0dc4ea312273df3e73654ca51618242 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 10 Jan 2024 15:13:42 +0100 Subject: [PATCH 01/11] Add retry to OAuth token refresh failure CURA-11406 --- cura/OAuth2/AuthorizationHelpers.py | 11 ++++++++--- cura/OAuth2/AuthorizationService.py | 26 +++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/cura/OAuth2/AuthorizationHelpers.py b/cura/OAuth2/AuthorizationHelpers.py index a654ee4bdb..1bc0d545f4 100644 --- a/cura/OAuth2/AuthorizationHelpers.py +++ b/cura/OAuth2/AuthorizationHelpers.py @@ -16,6 +16,7 @@ from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To downlo catalog = i18nCatalog("cura") TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" +REQUEST_TIMEOUT = 5 # Seconds class AuthorizationHelpers: @@ -52,7 +53,8 @@ class AuthorizationHelpers: data = urllib.parse.urlencode(data).encode("UTF-8"), headers_dict = headers, callback = lambda response: self.parseTokenResponse(response, callback), - error_callback = lambda response, _: self.parseTokenResponse(response, callback) + error_callback = lambda response, _: self.parseTokenResponse(response, callback), + timeout = REQUEST_TIMEOUT ) def getAccessTokenUsingRefreshToken(self, refresh_token: str, callback: Callable[[AuthenticationResponse], None]) -> None: @@ -75,7 +77,9 @@ class AuthorizationHelpers: data = urllib.parse.urlencode(data).encode("UTF-8"), headers_dict = headers, callback = lambda response: self.parseTokenResponse(response, callback), - error_callback = lambda response, _: self.parseTokenResponse(response, callback) + error_callback = lambda response, _: self.parseTokenResponse(response, callback), + urgent = True, + timeout = REQUEST_TIMEOUT ) def parseTokenResponse(self, token_response: QNetworkReply, callback: Callable[[AuthenticationResponse], None]) -> None: @@ -120,7 +124,8 @@ class AuthorizationHelpers: check_token_url, headers_dict = headers, callback = lambda reply: self._parseUserProfile(reply, success_callback, failed_callback), - error_callback = lambda _, _2: failed_callback() if failed_callback is not None else None + error_callback = lambda _, _2: failed_callback() if failed_callback is not None else None, + timeout = REQUEST_TIMEOUT ) def _parseUserProfile(self, reply: QNetworkReply, success_callback: Optional[Callable[[UserProfile], None]], failed_callback: Optional[Callable[[], None]] = None) -> None: diff --git a/cura/OAuth2/AuthorizationService.py b/cura/OAuth2/AuthorizationService.py index 62bf31982a..7a1bfb75f7 100644 --- a/cura/OAuth2/AuthorizationService.py +++ b/cura/OAuth2/AuthorizationService.py @@ -6,13 +6,14 @@ from datetime import datetime, timedelta from typing import Callable, Dict, Optional, TYPE_CHECKING, Union from urllib.parse import urlencode, quote_plus -from PyQt6.QtCore import QUrl +from PyQt6.QtCore import QUrl, QTimer from PyQt6.QtGui import QDesktopServices from UM.Logger import Logger from UM.Message import Message from UM.Signal import Signal from UM.i18n import i18nCatalog +from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To download log-in tokens. from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer from cura.OAuth2.Models import AuthenticationResponse, BaseModel @@ -53,6 +54,12 @@ class AuthorizationService: self.onAuthStateChanged.connect(self._authChanged) + self._refresh_token_retries = 0 + self._refresh_token_retry_timer = QTimer() + self._refresh_token_retry_timer.setInterval(1000) + self._refresh_token_retry_timer.setSingleShot(True) + self._refresh_token_retry_timer.timeout.connect(self.refreshAccessToken) + def _authChanged(self, logged_in): if logged_in and self._unable_to_get_data_message is not None: self._unable_to_get_data_message.hide() @@ -163,16 +170,29 @@ class AuthorizationService: return def process_auth_data(response: AuthenticationResponse) -> None: + self._currently_refreshing_token = False + if response.success: + self._refresh_token_retries = 0 self._storeAuthData(response) + HttpRequestManager.getInstance().setDelayRequests(False) self.onAuthStateChanged.emit(logged_in = True) else: - Logger.warning("Failed to get a new access token from the server.") - self.onAuthStateChanged.emit(logged_in = False) + if self._refresh_token_retries >= 15: + self._refresh_token_retries = 0 + Logger.warning("Failed to get a new access token from the server, giving up.") + HttpRequestManager.getInstance().setDelayRequests(False) + self.onAuthStateChanged.emit(logged_in = False) + else: + # Retry a bit later, network may be offline right now and will hopefully be back soon + Logger.warning("Failed to get a new access token from the server, retrying later.") + self._refresh_token_retries += 1 + self._refresh_token_retry_timer.start() if self._currently_refreshing_token: Logger.debug("Was already busy refreshing token. Do not start a new request.") return + HttpRequestManager.getInstance().setDelayRequests(True) self._currently_refreshing_token = True self._auth_helpers.getAccessTokenUsingRefreshToken(self._auth_data.refresh_token, process_auth_data) From e8bdca3dd95a9ba3b1efacc224732b73e5d2d0e4 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Fri, 9 Feb 2024 10:32:39 +0100 Subject: [PATCH 02/11] introducing drop to buildplate per model CURA-10542 --- cura/PlatformPhysics.py | 15 ++++-- resources/qml/Preferences/GeneralPage.qml | 66 +++++++++++++++++++++++ resources/qml/Toolbar.qml | 2 +- 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index 1ef39de80d..98e9e823c6 100755 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -38,7 +38,7 @@ class PlatformPhysics: self._minimum_gap = 2 # It is a minimum distance (in mm) between two models, applicable for small models Application.getInstance().getPreferences().addPreference("physics/automatic_push_free", False) - Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", True) + Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down_per_model", "never") def _onSceneChanged(self, source): if not source.callDecoration("isSliceable"): @@ -71,6 +71,15 @@ class PlatformPhysics: # We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A. # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve. random.shuffle(nodes) + default_value = False + if app_automatic_drop_down == "always": + default_value = True + if app_automatic_drop_down == "never": + default_value = False + if app_automatic_drop_down == "always_ask": + # ask_during_loading_model + pass + for node in nodes: if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None: continue @@ -80,10 +89,10 @@ class PlatformPhysics: # Move it downwards if bottom is above platform move_vector = Vector() - if node.getSetting(SceneNodeSettings.AutoDropDown, app_automatic_drop_down) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down + + if node.getSetting(SceneNodeSettings.AutoDropDown, default_value) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y = -bbox.bottom + z_offset) - # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator) and not node.callDecoration("isNonPrintingMesh") and node.callDecoration("getLayerData") is None: node.addDecorator(ConvexHullDecorator()) diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index c313ffbd94..963f95da4b 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -63,6 +63,18 @@ UM.PreferencesPage } } + function setDefaultDropDown(code) + { + for (var i = 0; i < choiceOnDropDown.model.count; ++i) + { + if (choiceOnDropDown.model.get(i).code == code) + { + choiceOnDropDown.currentIndex = i + break; + } + } + } + function reset() { UM.Preferences.resetPreference("general/language") @@ -513,6 +525,60 @@ UM.PreferencesPage } } + UM.TooltipArea + { + width: childrenRect.width + height: childrenRect.height + text: catalog.i18nc("@info:tooltip", "This setting will set a default (or not) for the Per Model Drop to buildplate feature (Either Always, Never, decide every time") + + Column + { + spacing: UM.Theme.getSize("narrow_margin").height + + UM.Label + { + text: catalog.i18nc("@window:text", " Default per model setting for drop to build plate when importing a model: ") + } + + Cura.ComboBox + { + id: choiceOnDropDown + width: UM.Theme.getSize("combobox").width + height: UM.Theme.getSize("combobox").height + + model: ListModel + { + id: dropDownOptions + + Component.onCompleted: + { + append({ text: catalog.i18nc("@option:openProject", "Always"), code: "always" }) + append({ text: catalog.i18nc("@option:openProject", "Never"), code: "never" }) + append({ text: catalog.i18nc("@option:openProject", "Always ask me this"), code: "always_ask" }) + } + } + textRole: "text" + + currentIndex: + { + var index = 0; + var currentChoice = UM.Preferences.getValue("physics/automatic_drop_down_per_model"); + for (var i = 0; i < model.count; ++i) + { + if (model.get(i).code == currentChoice) + { + index = i; + break; + } + } + return index; + } + + onActivated: UM.Preferences.setValue("physics/automatic_drop_down_per_model", model.get(index).code) + } + } + } + UM.TooltipArea { diff --git a/resources/qml/Toolbar.qml b/resources/qml/Toolbar.qml index 1af4e958f4..81f6c5d682 100644 --- a/resources/qml/Toolbar.qml +++ b/resources/qml/Toolbar.qml @@ -222,7 +222,7 @@ Item UM.Label { id: toolHint - text: UM.Controller.properties.getValue("ToolHint") != undefined ? UM.ActiveTool.properties.getValue("ToolHint") : "" + text: UM.Controller.properties.getValue("ToolHint") != undefined ? UM.Controller.properties.getValue("ToolHint") : "" color: UM.Theme.getColor("tooltip_text") anchors.horizontalCenter: parent.horizontalCenter } From 91a84674f022b0baf0b1915fadef1d171c9cbddd Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Fri, 9 Feb 2024 15:11:56 +0100 Subject: [PATCH 03/11] Adding the preference drop to buildplate while opening Cura project CURA-10542 --- cura/PlatformPhysics.py | 14 +---- plugins/3MFReader/WorkspaceDialog.qml | 14 +++++ resources/qml/Preferences/GeneralPage.qml | 67 ----------------------- 3 files changed, 16 insertions(+), 79 deletions(-) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index 98e9e823c6..7add1e24e7 100755 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -38,7 +38,7 @@ class PlatformPhysics: self._minimum_gap = 2 # It is a minimum distance (in mm) between two models, applicable for small models Application.getInstance().getPreferences().addPreference("physics/automatic_push_free", False) - Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down_per_model", "never") + Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", False) def _onSceneChanged(self, source): if not source.callDecoration("isSliceable"): @@ -71,14 +71,6 @@ class PlatformPhysics: # We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A. # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve. random.shuffle(nodes) - default_value = False - if app_automatic_drop_down == "always": - default_value = True - if app_automatic_drop_down == "never": - default_value = False - if app_automatic_drop_down == "always_ask": - # ask_during_loading_model - pass for node in nodes: if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None: @@ -88,9 +80,7 @@ class PlatformPhysics: # Move it downwards if bottom is above platform move_vector = Vector() - - - if node.getSetting(SceneNodeSettings.AutoDropDown, default_value) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down + if node.getSetting(SceneNodeSettings.AutoDropDown, app_automatic_drop_down) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y = -bbox.bottom + z_offset) # If there is no convex hull for the node, start calculating it and continue. diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index d5f9b1817d..874ca0aab8 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -299,6 +299,20 @@ UM.Dialog } } } + Row + { + id: dropToBuildPlate + width: parent.width + height: childrenRect.height + spacing: UM.Theme.getSize("default_margin").width + UM.CheckBox + { + id: rememberChoiceCheckBox + text: catalog.i18nc("@text:window", "Drop models to buildplate") + checked: UM.Preferences.getValue("physics/automatic_drop_down") == True + onCheckedChanged: UM.Preferences.setValue("physics/automatic_drop_down", checked) + } + } Row { diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 963f95da4b..af63743931 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -63,18 +63,6 @@ UM.PreferencesPage } } - function setDefaultDropDown(code) - { - for (var i = 0; i < choiceOnDropDown.model.count; ++i) - { - if (choiceOnDropDown.model.get(i).code == code) - { - choiceOnDropDown.currentIndex = i - break; - } - } - } - function reset() { UM.Preferences.resetPreference("general/language") @@ -525,61 +513,6 @@ UM.PreferencesPage } } - UM.TooltipArea - { - width: childrenRect.width - height: childrenRect.height - text: catalog.i18nc("@info:tooltip", "This setting will set a default (or not) for the Per Model Drop to buildplate feature (Either Always, Never, decide every time") - - Column - { - spacing: UM.Theme.getSize("narrow_margin").height - - UM.Label - { - text: catalog.i18nc("@window:text", " Default per model setting for drop to build plate when importing a model: ") - } - - Cura.ComboBox - { - id: choiceOnDropDown - width: UM.Theme.getSize("combobox").width - height: UM.Theme.getSize("combobox").height - - model: ListModel - { - id: dropDownOptions - - Component.onCompleted: - { - append({ text: catalog.i18nc("@option:openProject", "Always"), code: "always" }) - append({ text: catalog.i18nc("@option:openProject", "Never"), code: "never" }) - append({ text: catalog.i18nc("@option:openProject", "Always ask me this"), code: "always_ask" }) - } - } - textRole: "text" - - currentIndex: - { - var index = 0; - var currentChoice = UM.Preferences.getValue("physics/automatic_drop_down_per_model"); - for (var i = 0; i < model.count; ++i) - { - if (model.get(i).code == currentChoice) - { - index = i; - break; - } - } - return index; - } - - onActivated: UM.Preferences.setValue("physics/automatic_drop_down_per_model", model.get(index).code) - } - } - } - - UM.TooltipArea { width: childrenRect.width; From aa2abfbd2e1a86a5e10f6a3e3c56e0092d8cb589 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Fri, 9 Feb 2024 15:15:44 +0100 Subject: [PATCH 04/11] removing id of checkbox CURA-10542 --- cura/PlatformPhysics.py | 2 ++ plugins/3MFReader/WorkspaceDialog.qml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index 7add1e24e7..d204bee185 100755 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -80,9 +80,11 @@ class PlatformPhysics: # Move it downwards if bottom is above platform move_vector = Vector() + if node.getSetting(SceneNodeSettings.AutoDropDown, app_automatic_drop_down) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y = -bbox.bottom + z_offset) + # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator) and not node.callDecoration("isNonPrintingMesh") and node.callDecoration("getLayerData") is None: node.addDecorator(ConvexHullDecorator()) diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 874ca0aab8..8b4b2341d9 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -299,6 +299,7 @@ UM.Dialog } } } + Row { id: dropToBuildPlate @@ -307,7 +308,6 @@ UM.Dialog spacing: UM.Theme.getSize("default_margin").width UM.CheckBox { - id: rememberChoiceCheckBox text: catalog.i18nc("@text:window", "Drop models to buildplate") checked: UM.Preferences.getValue("physics/automatic_drop_down") == True onCheckedChanged: UM.Preferences.setValue("physics/automatic_drop_down", checked) From 32d9e6b45c56f65beea413248849b8599e62c30a Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Wed, 14 Feb 2024 12:59:41 +0100 Subject: [PATCH 05/11] preference does not change after user changes drop to buildplate for a model CURA-10542 --- cura/PlatformPhysics.py | 12 +++++++++++- plugins/3MFReader/WorkspaceDialog.qml | 10 ++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index d204bee185..b7fe549601 100755 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -39,6 +39,7 @@ class PlatformPhysics: Application.getInstance().getPreferences().addPreference("physics/automatic_push_free", False) Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", False) + Application.getInstance().getPreferences().addPreference("physics/per_model_drop", False) def _onSceneChanged(self, source): if not source.callDecoration("isSliceable"): @@ -54,6 +55,9 @@ class PlatformPhysics: app_preferences = app_instance.getPreferences() app_automatic_drop_down = app_preferences.getValue("physics/automatic_drop_down") app_automatic_push_free = app_preferences.getValue("physics/automatic_push_free") + # silent preference setting to mimic preference automatic_drop_down only different when user changes it explicitely while opening model + app_per_model_drop = app_preferences.getValue("physics/per_model_drop") + root = self._controller.getScene().getRoot() build_volume = app_instance.getBuildVolume() @@ -81,7 +85,11 @@ class PlatformPhysics: # Move it downwards if bottom is above platform move_vector = Vector() - if node.getSetting(SceneNodeSettings.AutoDropDown, app_automatic_drop_down) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down + # if per model drop is different then app_automatic_drop + # in case of 3mf loading when user changes this setting for that model + if (app_per_model_drop != app_automatic_drop_down): + node.setSetting(SceneNodeSettings.AutoDropDown, app_per_model_drop) + if node.getSetting(SceneNodeSettings.AutoDropDown, app_per_model_drop) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y = -bbox.bottom + z_offset) @@ -169,6 +177,8 @@ class PlatformPhysics: op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push() + # setting this silent preference same as app_automatic_drop_down + app_preferences.setValue("physics/per_model_drop", app_automatic_drop_down) # After moving, we have to evaluate the boundary checks for nodes build_volume.updateNodeBoundaryCheck() diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index 8b4b2341d9..eefa0f713a 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -308,9 +308,14 @@ UM.Dialog spacing: UM.Theme.getSize("default_margin").width UM.CheckBox { + id: checkDropModels text: catalog.i18nc("@text:window", "Drop models to buildplate") - checked: UM.Preferences.getValue("physics/automatic_drop_down") == True - onCheckedChanged: UM.Preferences.setValue("physics/automatic_drop_down", checked) + checked: UM.Preferences.getValue("physics/automatic_drop_down") + onCheckedChanged: UM.Preferences.setValue("physics/per_model_drop", checked) + } + function reloadValue() + { + checkDropModels.checked = UM.Preferences.getValue("physics/automatic_drop_down") } } @@ -436,6 +441,7 @@ UM.Dialog materialSection.reloadValues() profileSection.reloadValues() printerSection.reloadValues() + dropToBuildPlate.reloadValue() } } } From 831af362cb9acb61192f76421c33f884c88b2f2b Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 14 Feb 2024 15:46:40 +0100 Subject: [PATCH 06/11] Constantify magic values. part of CURA-11406 --- cura/OAuth2/AuthorizationService.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cura/OAuth2/AuthorizationService.py b/cura/OAuth2/AuthorizationService.py index 1832ff37c2..4e8d28173a 100644 --- a/cura/OAuth2/AuthorizationService.py +++ b/cura/OAuth2/AuthorizationService.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2024 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. import json @@ -26,6 +26,8 @@ if TYPE_CHECKING: MYCLOUD_LOGOFF_URL = "https://account.ultimaker.com/logoff?utm_source=cura&utm_medium=software&utm_campaign=change-account-before-adding-printers" +REFRESH_TOKEN_MAX_RETRIES = 15 +REFRESH_TOKEN_RETRY_INTERVAL = 1000 class AuthorizationService: """The authorization service is responsible for handling the login flow, storing user credentials and providing @@ -60,7 +62,7 @@ class AuthorizationService: self._refresh_token_retries = 0 self._refresh_token_retry_timer = QTimer() - self._refresh_token_retry_timer.setInterval(1000) + self._refresh_token_retry_timer.setInterval(REFRESH_TOKEN_RETRY_INTERVAL) self._refresh_token_retry_timer.setSingleShot(True) self._refresh_token_retry_timer.timeout.connect(self.refreshAccessToken) @@ -182,7 +184,7 @@ class AuthorizationService: HttpRequestManager.getInstance().setDelayRequests(False) self.onAuthStateChanged.emit(logged_in = True) else: - if self._refresh_token_retries >= 15: + if self._refresh_token_retries >= REFRESH_TOKEN_MAX_RETRIES: self._refresh_token_retries = 0 Logger.warning("Failed to get a new access token from the server, giving up.") HttpRequestManager.getInstance().setDelayRequests(False) From f54d6099cd97c995b3a01beb177969a4a9a2a244 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Thu, 15 Feb 2024 11:23:34 +0100 Subject: [PATCH 07/11] removed the preference made it a member of class platformphysics CURA-10542 --- cura/CuraApplication.py | 4 ++++ cura/PlatformPhysics.py | 24 ++++++++++++----------- plugins/3MFReader/WorkspaceDialog.py | 5 +++++ plugins/3MFReader/WorkspaceDialog.qml | 2 +- resources/qml/Preferences/GeneralPage.qml | 6 +++++- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 76e3e4b400..153788b867 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1082,6 +1082,10 @@ class CuraApplication(QtApplication): def getTextManager(self, *args) -> "TextManager": return self._text_manager + @pyqtSlot(bool) + def getLocalDropToBuildplate(self, drop_to_build_plate: bool) ->None: + return self._physics.setAppPerModelDropDown(drop_to_build_plate) + def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions": if self._cura_formula_functions is None: self._cura_formula_functions = CuraFormulaFunctions(self) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index b7fe549601..6a26190a56 100755 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -39,7 +39,13 @@ class PlatformPhysics: Application.getInstance().getPreferences().addPreference("physics/automatic_push_free", False) Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", False) - Application.getInstance().getPreferences().addPreference("physics/per_model_drop", False) + self._app_per_model_drop = Application.getInstance().getPreferences().getValue("physics/automatic_drop_down") + + def getAppPerModelDropDown(self): + return self._app_per_model_drop + + def setAppPerModelDropDown(self, drop_to_buildplate): + self._app_per_model_drop = drop_to_buildplate def _onSceneChanged(self, source): if not source.callDecoration("isSliceable"): @@ -55,9 +61,6 @@ class PlatformPhysics: app_preferences = app_instance.getPreferences() app_automatic_drop_down = app_preferences.getValue("physics/automatic_drop_down") app_automatic_push_free = app_preferences.getValue("physics/automatic_push_free") - # silent preference setting to mimic preference automatic_drop_down only different when user changes it explicitely while opening model - app_per_model_drop = app_preferences.getValue("physics/per_model_drop") - root = self._controller.getScene().getRoot() build_volume = app_instance.getBuildVolume() @@ -85,11 +88,10 @@ class PlatformPhysics: # Move it downwards if bottom is above platform move_vector = Vector() - # if per model drop is different then app_automatic_drop - # in case of 3mf loading when user changes this setting for that model - if (app_per_model_drop != app_automatic_drop_down): - node.setSetting(SceneNodeSettings.AutoDropDown, app_per_model_drop) - if node.getSetting(SceneNodeSettings.AutoDropDown, app_per_model_drop) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down + # if per model drop is different then app_automatic_drop, in case of 3mf loading when user changes this setting for that model + if (self._app_per_model_drop != app_automatic_drop_down): + node.setSetting(SceneNodeSettings.AutoDropDown, self._app_per_model_drop) + if node.getSetting(SceneNodeSettings.AutoDropDown, self._app_per_model_drop) and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y = -bbox.bottom + z_offset) @@ -177,8 +179,8 @@ class PlatformPhysics: op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push() - # setting this silent preference same as app_automatic_drop_down - app_preferences.setValue("physics/per_model_drop", app_automatic_drop_down) + # setting this drop to model same as app_automatic_drop_down + self._app_per_model_drop = app_automatic_drop_down # After moving, we have to evaluate the boundary checks for nodes build_volume.updateNodeBoundaryCheck() diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 0203fc92b5..5b8ddeb2f9 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -299,6 +299,11 @@ class WorkspaceDialog(QObject): Application.getInstance().getBackend().close() + @pyqtSlot(bool) + def setDropToBuildPlateForModel(self, drop_to_buildplate: bool) -> None: + CuraApplication.getInstance().getLocalDropToBuildplate(drop_to_buildplate) + + def setMaterialConflict(self, material_conflict: bool) -> None: if self._has_material_conflict != material_conflict: self._has_material_conflict = material_conflict diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index eefa0f713a..cb1664a2c0 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -311,7 +311,7 @@ UM.Dialog id: checkDropModels text: catalog.i18nc("@text:window", "Drop models to buildplate") checked: UM.Preferences.getValue("physics/automatic_drop_down") - onCheckedChanged: UM.Preferences.setValue("physics/per_model_drop", checked) + onCheckedChanged: manager.setDropToBuildPlateForModel(checked) } function reloadValue() { diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index af63743931..0fbc319fb9 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -509,7 +509,11 @@ UM.PreferencesPage id: dropDownCheckbox text: catalog.i18nc("@option:check", "Automatically drop models to the build plate") checked: boolCheck(UM.Preferences.getValue("physics/automatic_drop_down")) - onCheckedChanged: UM.Preferences.setValue("physics/automatic_drop_down", checked) + onCheckedChanged: + { + UM.Preferences.setValue("physics/automatic_drop_down", checked) + CuraApplication.getLocalDropToBuildplate(checked) + } } } From d68a454fa9170eea4659f4dee2fc84fc71d15d3d Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Thu, 15 Feb 2024 11:35:09 +0100 Subject: [PATCH 08/11] function name change to getWorkplaceDropToBuildplate CURA-10542 --- cura/CuraApplication.py | 2 +- plugins/3MFReader/WorkspaceDialog.py | 2 +- resources/qml/Preferences/GeneralPage.qml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 153788b867..20e54fa57c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1083,7 +1083,7 @@ class CuraApplication(QtApplication): return self._text_manager @pyqtSlot(bool) - def getLocalDropToBuildplate(self, drop_to_build_plate: bool) ->None: + def getWorkplaceDropToBuildplate(self, drop_to_build_plate: bool) ->None: return self._physics.setAppPerModelDropDown(drop_to_build_plate) def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions": diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 5b8ddeb2f9..02e71f2185 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -301,7 +301,7 @@ class WorkspaceDialog(QObject): @pyqtSlot(bool) def setDropToBuildPlateForModel(self, drop_to_buildplate: bool) -> None: - CuraApplication.getInstance().getLocalDropToBuildplate(drop_to_buildplate) + CuraApplication.getInstance().getWorkplaceDropToBuildplate(drop_to_buildplate) def setMaterialConflict(self, material_conflict: bool) -> None: diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 0fbc319fb9..69607a3f6b 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -512,7 +512,7 @@ UM.PreferencesPage onCheckedChanged: { UM.Preferences.setValue("physics/automatic_drop_down", checked) - CuraApplication.getLocalDropToBuildplate(checked) + CuraApplication.getWorkplaceDropToBuildplate(checked) } } } From 831c72dbe5d64b468eb2089ecdd3cce24a584f59 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 15 Feb 2024 15:03:13 +0100 Subject: [PATCH 09/11] Fix unit-tests. related to internal ticket CURA-11406 --- tests/TestOAuth2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestOAuth2.py b/tests/TestOAuth2.py index 09fa555af4..82d138a71e 100644 --- a/tests/TestOAuth2.py +++ b/tests/TestOAuth2.py @@ -97,7 +97,7 @@ def test__parseJWTNoRefreshToken(): mock_reply = Mock() # The user profile that the service should respond with. mock_reply.error = Mock(return_value = QNetworkReply.NetworkError.NoError) http_mock = Mock() - http_mock.get = lambda url, headers_dict, callback, error_callback: callback(mock_reply) + http_mock.get = lambda url, headers_dict, callback, error_callback, timeout: callback(mock_reply) http_mock.readJSON = Mock(return_value = {"data": {"user_id": "id_ego_or_superego", "username": "Ghostkeeper"}}) with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.getInstance", MagicMock(return_value = http_mock)): From 58f8eae80cd84cf0112a4d851a842f3ff36cc430 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 15 Feb 2024 15:45:49 +0100 Subject: [PATCH 10/11] Fix more unit-tests. related to internal ticket CURA-11406 --- tests/TestOAuth2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/TestOAuth2.py b/tests/TestOAuth2.py index 82d138a71e..c7985e39f3 100644 --- a/tests/TestOAuth2.py +++ b/tests/TestOAuth2.py @@ -119,8 +119,8 @@ def test__parseJWTFailOnRefresh(): mock_reply = Mock() # The response that the request should give, containing an error about it failing to authenticate. mock_reply.error = Mock(return_value = QNetworkReply.NetworkError.AuthenticationRequiredError) # The reply is 403: Authentication required, meaning the server responded with a "Can't do that, Dave". http_mock = Mock() - http_mock.get = lambda url, headers_dict, callback, error_callback: callback(mock_reply) - http_mock.post = lambda url, data, headers_dict, callback, error_callback: callback(mock_reply) + http_mock.get = lambda url, headers_dict, callback, error_callback, timeout: callback(mock_reply) + http_mock.post = lambda url, data, headers_dict, callback, error_callback, timeout: callback(mock_reply) with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.readJSON", Mock(return_value = {"error_description": "Mock a failed request!"})): with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.getInstance", MagicMock(return_value = http_mock)): @@ -142,7 +142,7 @@ def test__parseJWTSucceedOnRefresh(): mock_reply_failure = Mock() mock_reply_failure.error = Mock(return_value = QNetworkReply.NetworkError.AuthenticationRequiredError) http_mock = Mock() - def mock_get(url, headers_dict, callback, error_callback): + def mock_get(url, headers_dict, callback, error_callback, timeout): if(headers_dict == {"Authorization": "Bearer beep"}): callback(mock_reply_success) else: @@ -181,8 +181,8 @@ def test_refreshAccessTokenFailed(): mock_reply = Mock() # The response that the request should give, containing an error about it failing to authenticate. mock_reply.error = Mock(return_value = QNetworkReply.NetworkError.AuthenticationRequiredError) # The reply is 403: Authentication required, meaning the server responded with a "Can't do that, Dave". http_mock = Mock() - http_mock.get = lambda url, headers_dict, callback, error_callback: callback(mock_reply) - http_mock.post = lambda url, data, headers_dict, callback, error_callback: callback(mock_reply) + http_mock.get = lambda url, headers_dict, callback, error_callback, timeout: callback(mock_reply) + http_mock.post = lambda url, data, headers_dict, callback, error_callback, timeout: callback(mock_reply) with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.readJSON", Mock(return_value = {"error_description": "Mock a failed request!"})): with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.getInstance", MagicMock(return_value = http_mock)): @@ -263,7 +263,7 @@ def test_loginAndLogout() -> None: mock_reply = Mock() # The user profile that the service should respond with. mock_reply.error = Mock(return_value = QNetworkReply.NetworkError.NoError) http_mock = Mock() - http_mock.get = lambda url, headers_dict, callback, error_callback: callback(mock_reply) + http_mock.get = lambda url, headers_dict, callback, error_callback, timeout: callback(mock_reply) http_mock.readJSON = Mock(return_value = {"data": {"user_id": "di_resu", "username": "Emanresu"}}) # Let the service think there was a successful response From 77823a9c122331e24496c75be34c6365b2bf37b0 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 15 Feb 2024 16:01:32 +0100 Subject: [PATCH 11/11] Fix more unit-tests II. related to internal ticket CURA-11406 --- tests/TestOAuth2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/TestOAuth2.py b/tests/TestOAuth2.py index c7985e39f3..41edaebbf7 100644 --- a/tests/TestOAuth2.py +++ b/tests/TestOAuth2.py @@ -120,7 +120,7 @@ def test__parseJWTFailOnRefresh(): mock_reply.error = Mock(return_value = QNetworkReply.NetworkError.AuthenticationRequiredError) # The reply is 403: Authentication required, meaning the server responded with a "Can't do that, Dave". http_mock = Mock() http_mock.get = lambda url, headers_dict, callback, error_callback, timeout: callback(mock_reply) - http_mock.post = lambda url, data, headers_dict, callback, error_callback, timeout: callback(mock_reply) + http_mock.post = lambda url, data, headers_dict, callback, error_callback, urgent, timeout: callback(mock_reply) with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.readJSON", Mock(return_value = {"error_description": "Mock a failed request!"})): with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.getInstance", MagicMock(return_value = http_mock)): @@ -182,7 +182,7 @@ def test_refreshAccessTokenFailed(): mock_reply.error = Mock(return_value = QNetworkReply.NetworkError.AuthenticationRequiredError) # The reply is 403: Authentication required, meaning the server responded with a "Can't do that, Dave". http_mock = Mock() http_mock.get = lambda url, headers_dict, callback, error_callback, timeout: callback(mock_reply) - http_mock.post = lambda url, data, headers_dict, callback, error_callback, timeout: callback(mock_reply) + http_mock.post = lambda url, data, headers_dict, callback, error_callback, urgent, timeout: callback(mock_reply) with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.readJSON", Mock(return_value = {"error_description": "Mock a failed request!"})): with patch("UM.TaskManagement.HttpRequestManager.HttpRequestManager.getInstance", MagicMock(return_value = http_mock)):