diff --git a/cura/ApplicationMetadata.py b/cura/ApplicationMetadata.py index 73eb9bb288..eeb283a72b 100644 --- a/cura/ApplicationMetadata.py +++ b/cura/ApplicationMetadata.py @@ -9,7 +9,7 @@ DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura" DEFAULT_CURA_VERSION = "master" DEFAULT_CURA_BUILD_TYPE = "" DEFAULT_CURA_DEBUG_MODE = False -DEFAULT_CURA_SDK_VERSION = "6.1.0" +DEFAULT_CURA_SDK_VERSION = "6.2.0" try: from cura.CuraVersion import CuraAppName # type: ignore @@ -45,4 +45,4 @@ except ImportError: # Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for # example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the # CuraVersion.py.in template. -CuraSDKVersion = "6.1.0" +CuraSDKVersion = "6.2.0" diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index bf745b1db3..65534a4ccd 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1262,7 +1262,7 @@ class CuraApplication(QtApplication): @pyqtSlot() def arrangeObjectsToAllBuildPlates(self) -> None: nodes_to_arrange = [] - for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore + for node in DepthFirstIterator(self.getController().getScene().getRoot()): if not isinstance(node, SceneNode): continue @@ -1289,7 +1289,7 @@ class CuraApplication(QtApplication): def arrangeAll(self) -> None: nodes_to_arrange = [] active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate - for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore + for node in DepthFirstIterator(self.getController().getScene().getRoot()): if not isinstance(node, SceneNode): continue @@ -1327,7 +1327,7 @@ class CuraApplication(QtApplication): Logger.log("i", "Reloading all loaded mesh data.") nodes = [] has_merged_nodes = False - for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore + for node in DepthFirstIterator(self.getController().getScene().getRoot()): if not isinstance(node, CuraSceneNode) or not node.getMeshData(): if node.getName() == "MergedMesh": has_merged_nodes = True @@ -1339,9 +1339,9 @@ class CuraApplication(QtApplication): return for node in nodes: - file_name = node.getMeshData().getFileName() - if file_name: - job = ReadMeshJob(file_name) + mesh_data = node.getMeshData() + if mesh_data and mesh_data.getFileName(): + job = ReadMeshJob(mesh_data.getFileName()) job._node = node # type: ignore job.finished.connect(self._reloadMeshFinished) if has_merged_nodes: diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py index 49e2befd28..2fe6e7971f 100644 --- a/cura/PreviewPass.py +++ b/cura/PreviewPass.py @@ -3,6 +3,8 @@ from typing import Optional, TYPE_CHECKING +from numpy import cast + from UM.Application import Application from UM.Resources import Resources @@ -12,6 +14,7 @@ from UM.View.RenderBatch import RenderBatch from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from cura.Scene.CuraSceneNode import CuraSceneNode if TYPE_CHECKING: from UM.View.GL.ShaderProgram import ShaderProgram @@ -44,9 +47,9 @@ class PreviewPass(RenderPass): self._renderer = Application.getInstance().getRenderer() - self._shader = None #type: Optional[ShaderProgram] - self._non_printing_shader = None #type: Optional[ShaderProgram] - self._support_mesh_shader = None #type: Optional[ShaderProgram] + self._shader = None # type: Optional[ShaderProgram] + self._non_printing_shader = None # type: Optional[ShaderProgram] + self._support_mesh_shader = None # type: Optional[ShaderProgram] self._scene = Application.getInstance().getController().getScene() # Set the camera to be used by this render pass @@ -83,8 +86,8 @@ class PreviewPass(RenderPass): batch_support_mesh = RenderBatch(self._support_mesh_shader) # Fill up the batch with objects that can be sliced. - for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. - if hasattr(node, "_outside_buildarea") and not node._outside_buildarea: + for node in DepthFirstIterator(self._scene.getRoot()): + if hasattr(node, "_outside_buildarea") and not getattr(node, "_outside_buildarea"): if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible(): per_mesh_stack = node.callDecoration("getStack") if node.callDecoration("isNonThumbnailVisibleMesh"): @@ -94,7 +97,7 @@ class PreviewPass(RenderPass): # Support mesh uniforms = {} shade_factor = 0.6 - diffuse_color = node.getDiffuseColor() + diffuse_color = cast(CuraSceneNode, node).getDiffuseColor() diffuse_color2 = [ diffuse_color[0] * shade_factor, diffuse_color[1] * shade_factor, @@ -106,7 +109,7 @@ class PreviewPass(RenderPass): else: # Normal scene node uniforms = {} - uniforms["diffuse_color"] = prettier_color(node.getDiffuseColor()) + uniforms["diffuse_color"] = prettier_color(cast(CuraSceneNode, node).getDiffuseColor()) batch.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms) self.bind() diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 60849f0dd9..c00852cbc0 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -115,7 +115,7 @@ class ExtruderManager(QObject): selected_nodes = [] # type: List["SceneNode"] for node in Selection.getAllSelectedObjects(): if node.callDecoration("isGroup"): - for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for grouped_node in BreadthFirstIterator(node): if grouped_node.callDecoration("isGroup"): continue diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 9d94467555..472e493cbe 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -949,7 +949,7 @@ class MachineManager(QObject): # Check to see if any objects are set to print with an extruder that will no longer exist root_node = self._application.getController().getScene().getRoot() - for node in DepthFirstIterator(root_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for node in DepthFirstIterator(root_node): if node.getMeshData(): extruder_nr = node.callDecoration("getActiveExtruderPosition") diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 6b558bc65b..ae18e76e5a 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -369,7 +369,7 @@ class CuraEngineBackend(QObject, Backend): elif job.getResult() == StartJobResult.ObjectSettingError: errors = {} - for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): stack = node.callDecoration("getStack") if not stack: continue @@ -438,7 +438,7 @@ class CuraEngineBackend(QObject, Backend): if not self._application.getPreferences().getValue("general/auto_slice"): enable_timer = False - for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("isBlockSlicing"): enable_timer = False self.setState(BackendState.Disabled) @@ -460,7 +460,7 @@ class CuraEngineBackend(QObject, Backend): ## Return a dict with number of objects per build plate def _numObjectsPerBuildPlate(self) -> Dict[int, int]: num_objects = defaultdict(int) #type: Dict[int, int] - for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for node in DepthFirstIterator(self._scene.getRoot()): # Only count sliceable objects if node.callDecoration("isSliceable"): build_plate_number = node.callDecoration("getBuildPlateNumber") @@ -548,10 +548,11 @@ class CuraEngineBackend(QObject, Backend): # Clear out any old gcode self._scene.gcode_dict = {} # type: ignore - for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("getLayerData"): if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers: - node.getParent().removeChild(node) + # We can asume that all nodes have a parent as we're looping through the scene (and filter out root) + cast(SceneNode, node.getParent()).removeChild(node) def markSliceAll(self) -> None: for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1): diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index fc4de3dfa5..72eb21c122 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -11,6 +11,7 @@ import Arcus #For typing. from UM.Job import Job from UM.Logger import Logger +from UM.Scene.SceneNode import SceneNode from UM.Settings.ContainerStack import ContainerStack #For typing. from UM.Settings.SettingRelation import SettingRelation #For typing. @@ -150,7 +151,7 @@ class StartSliceJob(Job): # Don't slice if there is a per object setting with an error value. - for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for node in DepthFirstIterator(self._scene.getRoot()): if not isinstance(node, CuraSceneNode) or not node.isSelectable(): continue @@ -160,15 +161,16 @@ class StartSliceJob(Job): with self._scene.getSceneLock(): # Remove old layer data. - for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number: - node.getParent().removeChild(node) + # Singe we walk through all nodes in the scene, they always have a parent. + cast(SceneNode, node.getParent()).removeChild(node) break # Get the objects in their groups to print. object_groups = [] if stack.getProperty("print_sequence", "value") == "one_at_a_time": - for node in OneAtATimeIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for node in OneAtATimeIterator(self._scene.getRoot()): temp_list = [] # Node can't be printed, so don't bother sending it. @@ -183,7 +185,8 @@ class StartSliceJob(Job): children = node.getAllChildren() children.append(node) for child_node in children: - if child_node.getMeshData() and child_node.getMeshData().getVertices() is not None: + mesh_data = child_node.getMeshData() + if mesh_data and mesh_data.getVertices() is not None: temp_list.append(child_node) if temp_list: @@ -194,8 +197,9 @@ class StartSliceJob(Job): else: temp_list = [] has_printing_mesh = False - for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. - if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None: + for node in DepthFirstIterator(self._scene.getRoot()): + mesh_data = node.getMeshData() + if node.callDecoration("isSliceable") and mesh_data and mesh_data.getVertices() is not None: is_non_printing_mesh = bool(node.callDecoration("isNonPrintingMesh")) # Find a reason not to add the node @@ -210,7 +214,7 @@ class StartSliceJob(Job): Job.yieldThread() - #If the list doesn't have any model with suitable settings then clean the list + # If the list doesn't have any model with suitable settings then clean the list # otherwise CuraEngine will crash if not has_printing_mesh: temp_list.clear() @@ -261,10 +265,14 @@ class StartSliceJob(Job): for group in filtered_object_groups: group_message = self._slice_message.addRepeatedMessage("object_lists") - if group[0].getParent() is not None and group[0].getParent().callDecoration("isGroup"): - self._handlePerObjectSettings(group[0].getParent(), group_message) + parent = group[0].getParent() + if parent is not None and parent.callDecoration("isGroup"): + self._handlePerObjectSettings(cast(CuraSceneNode, parent), group_message) + for object in group: mesh_data = object.getMeshData() + if mesh_data is None: + continue rot_scale = object.getWorldTransformation().getTransposed().getData()[0:3, 0:3] translate = object.getWorldTransformation().getData()[:3, 3] @@ -288,7 +296,7 @@ class StartSliceJob(Job): obj.vertices = flat_verts - self._handlePerObjectSettings(object, obj) + self._handlePerObjectSettings(cast(CuraSceneNode, object), obj) Job.yieldThread() diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 18f436b13a..20471f9763 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -218,10 +218,10 @@ class SimulationView(CuraView): if theme is not None: self._ghost_shader.setUniformValue("u_color", Color(*theme.getColor("layerview_ghost").getRgb())) - for node in DepthFirstIterator(scene.getRoot()): # type: ignore + for node in DepthFirstIterator(scene.getRoot()): # We do not want to render ConvexHullNode as it conflicts with the bottom layers. # However, it is somewhat relevant when the node is selected, so do render it then. - if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()): + if type(node) is ConvexHullNode and not Selection.isSelected(cast(ConvexHullNode, node).getWatchedNode()): continue if not node.render(renderer): diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml index b863712481..ea6da9c25d 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml @@ -243,10 +243,11 @@ Item enabled: !contextMenuButton.enabled } - MonitorInfoBlurb - { - id: contextMenuDisabledInfo - text: catalog.i18nc("@info", "Please update your printer's firmware to manage the queue remotely.") - target: contextMenuButton - } + // TODO: uncomment this tooltip as soon as the required firmware is released + // MonitorInfoBlurb + // { + // id: contextMenuDisabledInfo + // text: catalog.i18nc("@info", "Please update your printer's firmware to manage the queue remotely.") + // target: contextMenuButton + // } } diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml index 1f5a4cfcb2..8562cec59d 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml @@ -81,7 +81,7 @@ Item mipmap: true } } - + Item { @@ -99,7 +99,7 @@ Item height: 18 * screenScaleFactor // TODO: Theme! width: parent.width radius: 2 * screenScaleFactor // TODO: Theme! - + Label { text: printer && printer.name ? printer.name : "" @@ -202,12 +202,13 @@ Item enabled: !contextMenuButton.enabled } - MonitorInfoBlurb - { - id: contextMenuDisabledInfo - text: catalog.i18nc("@info", "Please update your printer's firmware to manage the queue remotely.") - target: contextMenuButton - } + // TODO: uncomment this tooltip as soon as the required firmware is released + // MonitorInfoBlurb + // { + // id: contextMenuDisabledInfo + // text: catalog.i18nc("@info", "Please update your printer's firmware to manage the queue remotely.") + // target: contextMenuButton + // } CameraButton { @@ -454,4 +455,4 @@ Item id: overrideConfirmationDialog printer: base.printer } -} \ No newline at end of file +} diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py index d55fd8ab28..30bdd8e774 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py @@ -174,9 +174,13 @@ class CloudApiClient: model: Type[CloudApiClientModel], ) -> None: def parse() -> None: + # Don't try to parse the reply if we didn't get one + if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) is None: + return status_code, response = self._parseReply(reply) self._anti_gc_callbacks.remove(parse) - return self._parseModels(response, on_finished, model) + self._parseModels(response, on_finished, model) + return self._anti_gc_callbacks.append(parse) reply.finished.connect(parse) diff --git a/tests/TestCuraSceneNode.py b/tests/TestCuraSceneNode.py index d4c1809c1e..47a4dc3cb0 100644 --- a/tests/TestCuraSceneNode.py +++ b/tests/TestCuraSceneNode.py @@ -6,7 +6,7 @@ import pytest from unittest.mock import patch -class TestConvexHullDecorator(SceneNodeDecorator): +class MockedConvexHullDecorator(SceneNodeDecorator): def __init__(self): super().__init__() @@ -14,7 +14,7 @@ class TestConvexHullDecorator(SceneNodeDecorator): return Polygon([[5, 5], [-5, 5], [-5, -5], [5, -5]]) -class TestInvalidConvexHullDecorator(SceneNodeDecorator): +class InvalidConvexHullDecorator(SceneNodeDecorator): def __init__(self): super().__init__() @@ -34,16 +34,16 @@ class TestCollidesWithAreas: assert not cura_scene_node.collidesWithAreas([Polygon([[10, 10], [-10, 10], [-10, -10], [10, -10]])]) def test_convexHullIntersects(self, cura_scene_node): - cura_scene_node.addDecorator(TestConvexHullDecorator()) + cura_scene_node.addDecorator(MockedConvexHullDecorator()) assert cura_scene_node.collidesWithAreas([Polygon([[10, 10], [-10, 10], [-10, -10], [10, -10]])]) def test_convexHullNoIntersection(self, cura_scene_node): - cura_scene_node.addDecorator(TestConvexHullDecorator()) + cura_scene_node.addDecorator(MockedConvexHullDecorator()) assert not cura_scene_node.collidesWithAreas([Polygon([[60, 60], [40, 60], [40, 40], [60, 40]])]) def test_invalidConvexHull(self, cura_scene_node): - cura_scene_node.addDecorator(TestInvalidConvexHullDecorator()) + cura_scene_node.addDecorator(InvalidConvexHullDecorator()) assert not cura_scene_node.collidesWithAreas([Polygon([[10, 10], [-10, 10], [-10, -10], [10, -10]])])