From 1b973b3f3ca59594b9aa2f6859a014a1c6cf94b1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 Jun 2020 16:31:37 +0200 Subject: [PATCH 01/32] Remove bit of code duplication CURA-7106 --- cura/Settings/SettingOverrideDecorator.py | 2 +- plugins/SolidView/SolidView.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 1b5fb84f4a..e33109b723 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -104,7 +104,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): """ # for support_meshes, always use the support_extruder - if self.getStack().getProperty("support_mesh", "value"): + if self.getStack().userChanges.getProperty("support_mesh", "value"): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: return str(global_container_stack.getProperty("support_extruder_nr", "value")) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index dc88265d68..118160382e 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -198,11 +198,6 @@ class SolidView(View): extruder_index = "0" extruder_index = int(extruder_index) - # Use the support extruder instead of the active extruder if this is a support_mesh - if per_mesh_stack: - if per_mesh_stack.getProperty("support_mesh", "value"): - extruder_index = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")) - try: material_color = self._extruders_model.getItem(extruder_index)["color"] except KeyError: From 16bc2071ee488a77568a3b9de4f755ff27b440ad Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 Jun 2020 16:44:37 +0200 Subject: [PATCH 02/32] Cache values to speed up the rendering CURA-7106 --- cura/Settings/SettingOverrideDecorator.py | 31 ++++++++++++++++++++++- plugins/SolidView/SolidView.py | 6 ++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index e33109b723..e6cb793c38 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -46,6 +46,9 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._is_non_printing_mesh = False self._is_non_thumbnail_visible_mesh = False + self._is_support_mesh = False + self._is_cutting_mesh = False + self._is_infill_mesh = False self._stack.propertyChanged.connect(self._onSettingChanged) @@ -104,7 +107,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): """ # for support_meshes, always use the support_extruder - if self.getStack().userChanges.getProperty("support_mesh", "value"): + if self._is_support_mesh: global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: return str(global_container_stack.getProperty("support_extruder_nr", "value")) @@ -114,6 +117,24 @@ class SettingOverrideDecorator(SceneNodeDecorator): container_stack = containers[0] return container_stack.getMetaDataEntry("position", default=None) + def isCuttingMesh(self): + return self._is_cutting_mesh + + def isSupportMesh(self): + return self._is_support_mesh + + def isInfillMesh(self): + return self._is_infill_mesh + + def _evaluateIsCuttingMesh(self): + return bool(self._stack.userChanges.getProperty("cutting_mesh", "value")) + + def _evaluateIsSupportMesh(self): + return bool(self._stack.userChanges.getProperty("support_mesh", "value")) + + def _evaluateInfillMesh(self): + return bool(self._stack.userChanges.getProperty("infill_mesh", "value")) + def isNonPrintingMesh(self): return self._is_non_printing_mesh @@ -132,6 +153,14 @@ class SettingOverrideDecorator(SceneNodeDecorator): # Trigger slice/need slicing if the value has changed. self._is_non_printing_mesh = self._evaluateIsNonPrintingMesh() self._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh() + + if setting_key == "support_mesh": + self._is_support_mesh = self._evaluateIsSupportMesh() + elif setting_key == "cutting_mesh": + self._is_cutting_mesh = self._evaluateIsCuttingMesh() + elif setting_key == "infill_mesh": + self._is_infill_mesh = self._evaluateInfillMesh() + Application.getInstance().getBackend().needsSlicing() Application.getInstance().getBackend().tickle() diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 118160382e..7c0e800247 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -167,7 +167,7 @@ class SolidView(View): self._composite_pass.setLayerBindings(["default", "selection", "xray"]) self._old_composite_shader = self._composite_pass.getCompositeShader() self._composite_pass.setCompositeShader(self._xray_composite_shader) - + def beginRendering(self): scene = self.getController().getScene() renderer = self.getRenderer() @@ -224,13 +224,13 @@ class SolidView(View): pass if node.callDecoration("isNonPrintingMesh"): - if per_mesh_stack and (per_mesh_stack.getProperty("infill_mesh", "value") or per_mesh_stack.getProperty("cutting_mesh", "value")): + if per_mesh_stack and (node.callDecoration("isInfillMesh") or node.callDecoration("isCuttingMesh")): renderer.queueNode(node, shader = self._non_printing_shader, uniforms = uniforms, transparent = True) else: renderer.queueNode(node, shader = self._non_printing_shader, transparent = True) elif getattr(node, "_outside_buildarea", False): renderer.queueNode(node, shader = self._disabled_shader) - elif per_mesh_stack and per_mesh_stack.getProperty("support_mesh", "value"): + elif per_mesh_stack and node.callDecoration("isSupportMesh"): # Render support meshes with a vertical stripe that is darker shade_factor = 0.6 uniforms["diffuse_color_2"] = [ From 570efc8310b245f85f91b2726478ea160c211d19 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 Jun 2020 17:18:22 +0200 Subject: [PATCH 03/32] Remove unneeded checks in convex hull node CURA-7106 --- cura/Scene/ConvexHullNode.py | 13 +++++-------- plugins/SolidView/SolidView.py | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/cura/Scene/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py index 765dae26a2..3046d04a67 100644 --- a/cura/Scene/ConvexHullNode.py +++ b/cura/Scene/ConvexHullNode.py @@ -80,14 +80,11 @@ class ConvexHullNode(SceneNode): ConvexHullNode.shader.setUniformValue("u_diffuseColor", self._color) ConvexHullNode.shader.setUniformValue("u_opacity", 0.6) - if self.getParent(): - if self.getMeshData() and isinstance(self._node, SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate: - # The object itself (+ adhesion in one-at-a-time mode) - renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8) - if self._convex_hull_head_mesh: - # The full head. Rendered as a hint to the user: If this area overlaps another object A; this object - # cannot be printed after A, because the head would hit A while printing the current object - renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8) + renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8) + if self._convex_hull_head_mesh: + # The full head. Rendered as a hint to the user: If this area overlaps another object A; this object + # cannot be printed after A, because the head would hit A while printing the current object + renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8) return True diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 7c0e800247..3ea78d84fb 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -167,7 +167,7 @@ class SolidView(View): self._composite_pass.setLayerBindings(["default", "selection", "xray"]) self._old_composite_shader = self._composite_pass.getCompositeShader() self._composite_pass.setCompositeShader(self._xray_composite_shader) - + def beginRendering(self): scene = self.getController().getScene() renderer = self.getRenderer() From f4cc8b4870373802c77a4c43ecf5446f54f67287 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 Jun 2020 17:22:47 +0200 Subject: [PATCH 04/32] Remove another check from rendering code CURA-7106 --- plugins/SolidView/SolidView.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 3ea78d84fb..7d4e0cfcce 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -103,7 +103,9 @@ class SolidView(View): except IndexError: pass else: - self._support_angle = support_angle_stack.getProperty("support_angle", "value") + angle = support_angle_stack.getProperty("support_angle", "value") + if angle is not None: + self._support_angle = angle def _checkSetup(self): if not self._extruders_model: @@ -178,7 +180,7 @@ class SolidView(View): if global_container_stack: if Application.getInstance().getPreferences().getValue("view/show_overhang"): # Make sure the overhang angle is valid before passing it to the shader - if self._support_angle is not None and self._support_angle >= 0 and self._support_angle <= 90: + if self._support_angle >= 0 and self._support_angle <= 90: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - self._support_angle))) else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang. From 333b2959cd8c43bd580226bb5dfe0fb17587cf93 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 Jun 2020 17:27:46 +0200 Subject: [PATCH 05/32] Decrease the frequency of the non-manifold check CURA-7106 --- plugins/SolidView/SolidView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 7d4e0cfcce..3ae26a5aaf 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -64,7 +64,7 @@ class SolidView(View): self._old_layer_bindings = None self._next_xray_checking_time = time.time() - self._xray_checking_update_time = 1.0 # seconds + self._xray_checking_update_time = 30.0 # seconds self._xray_warning_cooldown = 60 * 10 # reshow Model error message every 10 minutes self._xray_warning_message = Message( catalog.i18nc("@info:status", "Your model is not manifold. The highlighted areas indicate either missing or extraneous surfaces."), From ac7f27fddc2a81dbb66ea47bf4f9ba58d3c5dedb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 22 Jun 2020 17:32:30 +0200 Subject: [PATCH 06/32] Remove another unneded call in the solidview The check is called a lot, especially if there are a ton of models. CURA-7106 --- plugins/SolidView/SolidView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 3ae26a5aaf..fd5a2c7dc8 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -189,7 +189,7 @@ class SolidView(View): for node in DepthFirstIterator(scene.getRoot()): if not node.render(renderer): - if node.getMeshData() and node.isVisible() and not node.callDecoration("getLayerData"): + if node.getMeshData() and node.isVisible(): uniforms = {} shade_factor = 1.0 From f00e3f736317c8d6578866f6cb67fe2f7e973e90 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 09:21:24 +0200 Subject: [PATCH 07/32] Remove useless code CURA-7106 --- cura/Scene/ConvexHullNode.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cura/Scene/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py index 3046d04a67..6fe0aea4dd 100644 --- a/cura/Scene/ConvexHullNode.py +++ b/cura/Scene/ConvexHullNode.py @@ -94,7 +94,3 @@ class ConvexHullNode(SceneNode): convex_hull_head_builder = MeshBuilder() convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height - self._thickness) self._convex_hull_head_mesh = convex_hull_head_builder.build() - - if not node: - return - From ba34fb6e35bc6b128a6c705a9adf62b52a7fbf09 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 10:05:05 +0200 Subject: [PATCH 08/32] Decrease amount of nested if statements Wont add anything to speed, but it will make it easier to read CURA-7106 --- plugins/SolidView/SolidView.py | 102 +++++++++++++++++---------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index fd5a2c7dc8..f16f696150 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -188,64 +188,66 @@ class SolidView(View): self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) for node in DepthFirstIterator(scene.getRoot()): - if not node.render(renderer): - if node.getMeshData() and node.isVisible(): - uniforms = {} - shade_factor = 1.0 + if node.render(renderer): + continue - per_mesh_stack = node.callDecoration("getStack") + if node.getMeshData() and node.isVisible(): + uniforms = {} + shade_factor = 1.0 - extruder_index = node.callDecoration("getActiveExtruderPosition") - if extruder_index is None: - extruder_index = "0" - extruder_index = int(extruder_index) + per_mesh_stack = node.callDecoration("getStack") - try: - material_color = self._extruders_model.getItem(extruder_index)["color"] - except KeyError: - material_color = self._extruders_model.defaultColors[0] + extruder_index = node.callDecoration("getActiveExtruderPosition") + if extruder_index is None: + extruder_index = "0" + extruder_index = int(extruder_index) - if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: - # Shade objects that are printed with the non-active extruder 25% darker - shade_factor = 0.6 + try: + material_color = self._extruders_model.getItem(extruder_index)["color"] + except KeyError: + material_color = self._extruders_model.defaultColors[0] - try: - # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs - # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) - uniforms["diffuse_color"] = [ - shade_factor * int(material_color[1:3], 16) / 255, - shade_factor * int(material_color[3:5], 16) / 255, - shade_factor * int(material_color[5:7], 16) / 255, - 1.0 - ] + if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: + # Shade objects that are printed with the non-active extruder 25% darker + shade_factor = 0.6 - # Color the currently selected face-id. (Disable for now.) - #face = Selection.getHoverFace() - uniforms["hover_face"] = -1 #if not face or node != face[0] else face[1] - except ValueError: - pass + try: + # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs + # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) + uniforms["diffuse_color"] = [ + shade_factor * int(material_color[1:3], 16) / 255, + shade_factor * int(material_color[3:5], 16) / 255, + shade_factor * int(material_color[5:7], 16) / 255, + 1.0 + ] - if node.callDecoration("isNonPrintingMesh"): - if per_mesh_stack and (node.callDecoration("isInfillMesh") or node.callDecoration("isCuttingMesh")): - renderer.queueNode(node, shader = self._non_printing_shader, uniforms = uniforms, transparent = True) - else: - renderer.queueNode(node, shader = self._non_printing_shader, transparent = True) - elif getattr(node, "_outside_buildarea", False): - renderer.queueNode(node, shader = self._disabled_shader) - elif per_mesh_stack and node.callDecoration("isSupportMesh"): - # Render support meshes with a vertical stripe that is darker - shade_factor = 0.6 - uniforms["diffuse_color_2"] = [ - uniforms["diffuse_color"][0] * shade_factor, - uniforms["diffuse_color"][1] * shade_factor, - uniforms["diffuse_color"][2] * shade_factor, - 1.0 - ] - renderer.queueNode(node, shader = self._support_mesh_shader, uniforms = uniforms) + # Color the currently selected face-id. (Disable for now.) + #face = Selection.getHoverFace() + uniforms["hover_face"] = -1 #if not face or node != face[0] else face[1] + except ValueError: + pass + + if node.callDecoration("isNonPrintingMesh"): + if per_mesh_stack and (node.callDecoration("isInfillMesh") or node.callDecoration("isCuttingMesh")): + renderer.queueNode(node, shader = self._non_printing_shader, uniforms = uniforms, transparent = True) else: - renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms) - if node.callDecoration("isGroup") and Selection.isSelected(node): - renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = RenderBatch.RenderMode.LineLoop) + renderer.queueNode(node, shader = self._non_printing_shader, transparent = True) + elif getattr(node, "_outside_buildarea", False): + renderer.queueNode(node, shader = self._disabled_shader) + elif per_mesh_stack and node.callDecoration("isSupportMesh"): + # Render support meshes with a vertical stripe that is darker + shade_factor = 0.6 + uniforms["diffuse_color_2"] = [ + uniforms["diffuse_color"][0] * shade_factor, + uniforms["diffuse_color"][1] * shade_factor, + uniforms["diffuse_color"][2] * shade_factor, + 1.0 + ] + renderer.queueNode(node, shader = self._support_mesh_shader, uniforms = uniforms) + else: + renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms) + if node.callDecoration("isGroup") and Selection.isSelected(node): + renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = RenderBatch.RenderMode.LineLoop) def endRendering(self): # check whether the xray overlay is showing badness From 46076bf21dbdc873630286e90c4320d3160a3178 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 10:14:44 +0200 Subject: [PATCH 09/32] Simplify the renameNodes method Especially when duplicating large amounts of items this would cause a bit of a slowdown CURA-7106 --- cura/UI/ObjectsModel.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cura/UI/ObjectsModel.py b/cura/UI/ObjectsModel.py index 1383476665..6abb99932d 100644 --- a/cura/UI/ObjectsModel.py +++ b/cura/UI/ObjectsModel.py @@ -98,7 +98,8 @@ class ObjectsModel(ListModel): return True - def _renameNodes(self, node_info_dict: Dict[str, _NodeInfo]) -> List[SceneNode]: + @staticmethod + def _renameNodes(node_info_dict: Dict[str, _NodeInfo]) -> List[SceneNode]: # Go through all names and find out the names for all nodes that need to be renamed. all_nodes = [] # type: List[SceneNode] for name, node_info in node_info_dict.items(): @@ -118,9 +119,7 @@ class ObjectsModel(ListModel): else: new_group_name = "{0}#{1}".format(name, current_index) - old_name = node.getName() node.setName(new_group_name) - Logger.log("d", "Node [%s] renamed to [%s]", old_name, new_group_name) all_nodes.append(node) return all_nodes From 737a9faa5fa00c2ac38a955ebb894f10be981446 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 10:32:42 +0200 Subject: [PATCH 10/32] Speed up the objects model It was using a few expensive calls that had already been cached. CURA-7106 --- cura/Settings/SettingOverrideDecorator.py | 11 ++++++++++- cura/UI/ObjectsModel.py | 17 ++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index e6cb793c38..c264d41a22 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -49,6 +49,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._is_support_mesh = False self._is_cutting_mesh = False self._is_infill_mesh = False + self._is_anti_overhang_mesh = False self._stack.propertyChanged.connect(self._onSettingChanged) @@ -126,6 +127,12 @@ class SettingOverrideDecorator(SceneNodeDecorator): def isInfillMesh(self): return self._is_infill_mesh + def isAntiOverhangMesh(self): + return self._is_anti_overhang_mesh + + def _evaluateAntiOverhangMesh(self): + return bool(self._stack.userChanges.getProperty("anti_overhang_mesh", "value")) + def _evaluateIsCuttingMesh(self): return bool(self._stack.userChanges.getProperty("cutting_mesh", "value")) @@ -154,7 +161,9 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._is_non_printing_mesh = self._evaluateIsNonPrintingMesh() self._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh() - if setting_key == "support_mesh": + if setting_key == "anti_overhang_mesh": + self._is_anti_overhang_mesh = self._evaluateAntiOverhangMesh() + elif setting_key == "support_mesh": self._is_support_mesh = self._evaluateIsSupportMesh() elif setting_key == "cutting_mesh": self._is_cutting_mesh = self._evaluateIsCuttingMesh() diff --git a/cura/UI/ObjectsModel.py b/cura/UI/ObjectsModel.py index 6abb99932d..02d4096278 100644 --- a/cura/UI/ObjectsModel.py +++ b/cura/UI/ObjectsModel.py @@ -185,11 +185,18 @@ class ObjectsModel(ListModel): if per_object_stack: per_object_settings_count = per_object_stack.getTop().getNumInstances() - for mesh_type in ["anti_overhang_mesh", "infill_mesh", "cutting_mesh", "support_mesh"]: - if per_object_stack.getProperty(mesh_type, "value"): - node_mesh_type = mesh_type - per_object_settings_count -= 1 # do not count this mesh type setting - break + if node.callDecoration("isAntiOverhangMesh"): + node_mesh_type = "anti_overhang_mesh" + per_object_settings_count -= 1 # do not count this mesh type setting + elif node.callDecoration("isSupportMesh"): + node_mesh_type = "support_mesh" + per_object_settings_count -= 1 # do not count this mesh type setting + elif node.callDecoration("isCuttingMesh"): + node_mesh_type = "cutting_mesh" + per_object_settings_count -= 1 # do not count this mesh type setting + elif node.callDecoration("isInfillMesh"): + node_mesh_type = "infill_mesh" + per_object_settings_count -= 1 # do not count this mesh type setting if per_object_settings_count > 0: if node_mesh_type == "support_mesh": From 1505d59f4b1db50cef6c9ac7879222e94ef6bf9a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 10:38:21 +0200 Subject: [PATCH 11/32] Add extra process events to arrange job Doesn't actually speed up something, but it does prevent the GUI from freezing, so it looks less laggy. CURA-7106 --- cura/Arranging/ArrangeObjectsJob.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/Arranging/ArrangeObjectsJob.py b/cura/Arranging/ArrangeObjectsJob.py index aef051c838..387bf92688 100644 --- a/cura/Arranging/ArrangeObjectsJob.py +++ b/cura/Arranging/ArrangeObjectsJob.py @@ -1,5 +1,6 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from PyQt5.QtCore import QCoreApplication from UM.Application import Application from UM.Job import Job @@ -94,6 +95,7 @@ class ArrangeObjectsJob(Job): status_message.setProgress((idx + 1) / len(nodes_arr) * 100) Job.yieldThread() + QCoreApplication.processEvents() grouped_operation.push() From ea568f4ad55e12186d521c78ae6e9b1aded6ff0b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 10:52:39 +0200 Subject: [PATCH 12/32] Make the updating of the buildvolume a bit trigger happy CURA-7106 --- cura/BuildVolume.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 315eca9fe5..a2b8b39a8d 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -106,8 +106,6 @@ class BuildVolume(SceneNode): self._application.globalContainerStackChanged.connect(self._onStackChanged) - self._onStackChanged() - self._engine_ready = False self._application.engineCreatedSignal.connect(self._onEngineCreated) @@ -118,7 +116,7 @@ class BuildVolume(SceneNode): self._scene_objects = set() # type: Set[SceneNode] self._scene_change_timer = QTimer() - self._scene_change_timer.setInterval(100) + self._scene_change_timer.setInterval(200) self._scene_change_timer.setSingleShot(True) self._scene_change_timer.timeout.connect(self._onSceneChangeTimerFinished) From 9014fa79e2b4c3da0eff366ab61cb89d6fc88edf Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 11:01:43 +0200 Subject: [PATCH 13/32] Cache result of getEdgeDisallowedSize CURA-7106 --- cura/BuildVolume.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index a2b8b39a8d..c858897942 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -92,6 +92,8 @@ class BuildVolume(SceneNode): self._adhesion_type = None # type: Any self._platform = Platform(self) + self._edge_disallowed_size = None + self._build_volume_message = Message(catalog.i18nc("@info:status", "The build volume height has been reduced due to the value of the" " \"Print Sequence\" setting to prevent the gantry from colliding" @@ -745,6 +747,7 @@ class BuildVolume(SceneNode): self._error_areas = [] used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks() + self._edge_disallowed_size = None # Force a recalculation disallowed_border_size = self.getEdgeDisallowedSize() result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) # Normal machine disallowed areas can always be added. @@ -1122,6 +1125,9 @@ class BuildVolume(SceneNode): if not self._global_container_stack or not self._global_container_stack.extruderList: return 0 + if self._edge_disallowed_size is not None: + return self._edge_disallowed_size + container_stack = self._global_container_stack used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks() @@ -1137,8 +1143,8 @@ class BuildVolume(SceneNode): # Now combine our different pieces of data to get the final border size. # Support expansion is added to the bed adhesion, since the bed adhesion goes around support. # Support expansion is added to farthest shield distance, since the shields go around support. - border_size = max(move_from_wall_radius, support_expansion + farthest_shield_distance, support_expansion + bed_adhesion_size) - return border_size + self._edge_disallowed_size = max(move_from_wall_radius, support_expansion + farthest_shield_distance, support_expansion + bed_adhesion_size) + return self._edge_disallowed_size def _clamp(self, value, min_value, max_value): return max(min(value, max_value), min_value) From 85797c4cf54aa07d23923b2dabd18233f16c5eb7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 11:17:38 +0200 Subject: [PATCH 14/32] Add processEvents calls to multiply job Because we want them delicious updates CURA-7106 --- cura/MultiplyObjectsJob.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py index 134e579746..7507f2520e 100644 --- a/cura/MultiplyObjectsJob.py +++ b/cura/MultiplyObjectsJob.py @@ -4,6 +4,8 @@ import copy from typing import List +from PyQt5.QtCore import QCoreApplication + from UM.Job import Job from UM.Operations.GroupedOperation import GroupedOperation from UM.Message import Message @@ -93,8 +95,9 @@ class MultiplyObjectsJob(Job): nodes.append(new_node) current_progress += 1 status_message.setProgress((current_progress / total_progress) * 100) + QCoreApplication.processEvents() Job.yieldThread() - + QCoreApplication.processEvents() Job.yieldThread() if nodes: From 4729bd1d0ffbbcd8c468a15899523f227af40cd6 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 11:32:57 +0200 Subject: [PATCH 15/32] Remove unneeded check from deepcopy of settingoverride decorator CURA-7106 --- cura/Settings/SettingOverrideDecorator.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index c264d41a22..e57a5eb507 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -78,11 +78,6 @@ class SettingOverrideDecorator(SceneNodeDecorator): # Properly set the right extruder on the copy deep_copy.setActiveExtruder(self._extruder_stack) - # use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh" - # has not been updated yet. - deep_copy._is_non_printing_mesh = self._evaluateIsNonPrintingMesh() - deep_copy._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh() - return deep_copy def getActiveExtruder(self): From 1dc688dd4a1f978db8ecde0a1abb1b2a3afa0032 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 11:40:52 +0200 Subject: [PATCH 16/32] Prevent double updateNextStack during deepcopy CURA-7106 --- cura/Settings/SettingOverrideDecorator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index e57a5eb507..9d2b248ba7 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -35,7 +35,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): """ _non_thumbnail_visible_settings = {"anti_overhang_mesh", "infill_mesh", "cutting_mesh", "support_mesh"} - def __init__(self): + def __init__(self, *, force_update = True): super().__init__() self._stack = PerObjectContainerStack(container_id = "per_object_stack_" + str(id(self))) self._stack.setDirty(False) # This stack does not need to be saved. @@ -57,13 +57,14 @@ class SettingOverrideDecorator(SceneNodeDecorator): Application.getInstance().globalContainerStackChanged.connect(self._updateNextStack) self.activeExtruderChanged.connect(self._updateNextStack) - self._updateNextStack() + if force_update: + self._updateNextStack() def _generateUniqueName(self): return "SettingOverrideInstanceContainer-%s" % uuid.uuid1() def __deepcopy__(self, memo): - deep_copy = SettingOverrideDecorator() + deep_copy = SettingOverrideDecorator(force_update = False) """Create a fresh decorator object""" instance_container = copy.deepcopy(self._stack.getContainer(0), memo) From 1900b0841ac33ff2d4eef6db01f92e614f3be879 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 12:03:15 +0200 Subject: [PATCH 17/32] Use cheaper convexhull function when not using raft CURA-7106 --- cura/Scene/ConvexHullNode.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cura/Scene/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py index 6fe0aea4dd..13e8a1969c 100644 --- a/cura/Scene/ConvexHullNode.py +++ b/cura/Scene/ConvexHullNode.py @@ -57,13 +57,20 @@ class ConvexHullNode(SceneNode): self._hull = hull if self._hull: hull_mesh_builder = MeshBuilder() + if self._thickness == 0: + if hull_mesh_builder.addConvexPolygon( + self._hull.getPoints()[::], # bottom layer is reversed + self._mesh_height, color = self._color): - if hull_mesh_builder.addConvexPolygonExtrusion( - self._hull.getPoints()[::-1], # bottom layer is reversed - self._mesh_height - thickness, self._mesh_height, color = self._color): + hull_mesh = hull_mesh_builder.build() + self.setMeshData(hull_mesh) + else: + if hull_mesh_builder.addConvexPolygonExtrusion( + self._hull.getPoints()[::-1], # bottom layer is reversed + self._mesh_height - thickness, self._mesh_height, color = self._color): - hull_mesh = hull_mesh_builder.build() - self.setMeshData(hull_mesh) + hull_mesh = hull_mesh_builder.build() + self.setMeshData(hull_mesh) def getHull(self): return self._hull From d897299b314c797203062e8b3451148a0806f751 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 12:55:13 +0200 Subject: [PATCH 18/32] Simplify recomputeConvexHull code CURA-7106 --- cura/Scene/ConvexHullDecorator.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index 46caa5b9e0..3465cd3ef9 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -53,6 +53,8 @@ class ConvexHullDecorator(SceneNodeDecorator): CuraApplication.getInstance().getController().toolOperationStarted.connect(self._onChanged) CuraApplication.getInstance().getController().toolOperationStopped.connect(self._onChanged) + self._root = Application.getInstance().getController().getScene().getRoot() + self._onGlobalStackChanged() def createRecomputeConvexHullTimer(self) -> None: @@ -198,23 +200,16 @@ class ConvexHullDecorator(SceneNodeDecorator): CuraApplication.getInstance().callLater(self.recomputeConvexHullDelayed) def recomputeConvexHull(self) -> None: - controller = Application.getInstance().getController() - root = controller.getScene().getRoot() - if self._node is None or controller.isToolOperationActive() or not self.__isDescendant(root, self._node): - # If the tool operation is still active, we need to compute the convex hull later after the controller is - # no longer active. - if controller.isToolOperationActive(): - self.recomputeConvexHullDelayed() - return - + if self._node is None or not self.__isDescendant(self._root, self._node): if self._convex_hull_node: + # Convex hull node still exists, but the node is removed or no longer in the scene. self._convex_hull_node.setParent(None) self._convex_hull_node = None return if self._convex_hull_node: self._convex_hull_node.setParent(None) - hull_node = ConvexHullNode.ConvexHullNode(self._node, self.getPrintingArea(), self._raft_thickness, root) + hull_node = ConvexHullNode.ConvexHullNode(self._node, self.getPrintingArea(), self._raft_thickness, self._root) self._convex_hull_node = hull_node def _onSettingValueChanged(self, key: str, property_name: str) -> None: From 1d9c92c47fc2c716fa1a9a087236589f0471e97f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 13:07:20 +0200 Subject: [PATCH 19/32] Use cached values for anti overhang and support mesh CURA-7106 --- cura/Settings/ExtruderManager.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 2cc9ec4631..045da07cc3 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -206,25 +206,24 @@ class ExtruderManager(QObject): return [] # Get the extruders of all printable meshes in the scene - meshes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()] #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + nodes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()] #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. # Exclude anti-overhang meshes - mesh_list = [] - for mesh in meshes: - stack = mesh.callDecoration("getStack") - if stack is not None and (stack.getProperty("anti_overhang_mesh", "value") or stack.getProperty("support_mesh", "value")): + node_list = [] + for node in nodes: + if node.callDecoration("isAntiOverhangMesh") or node.callDecoration("isSupportMesh"): continue - mesh_list.append(mesh) + node_list.append(node) - for mesh in mesh_list: - extruder_stack_id = mesh.callDecoration("getActiveExtruder") + for node in node_list: + extruder_stack_id = node.callDecoration("getActiveExtruder") if not extruder_stack_id: # No per-object settings for this node extruder_stack_id = self.extruderIds["0"] used_extruder_stack_ids.add(extruder_stack_id) # Get whether any of them use support. - stack_to_use = mesh.callDecoration("getStack") # if there is a per-mesh stack, we use it + stack_to_use = node.callDecoration("getStack") # if there is a per-mesh stack, we use it if not stack_to_use: # if there is no per-mesh stack, we use the build extruder for this mesh stack_to_use = container_registry.findContainerStacks(id = extruder_stack_id)[0] From 1f698fd6645d442fcf7a4faad0879a0209efaa39 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 13:13:49 +0200 Subject: [PATCH 20/32] Further simplify the getUsedExtruderStacks function Should be a tiny bit faster CURA-7106 --- cura/Settings/ExtruderManager.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 045da07cc3..cd3121fe75 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -206,16 +206,9 @@ class ExtruderManager(QObject): return [] # Get the extruders of all printable meshes in the scene - nodes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()] #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + nodes = [node for node in DepthFirstIterator(scene_root) if node.isSelectable() and not node.callDecoration("isAntiOverhangMesh") and not node.callDecoration("isSupportMesh")] #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. - # Exclude anti-overhang meshes - node_list = [] for node in nodes: - if node.callDecoration("isAntiOverhangMesh") or node.callDecoration("isSupportMesh"): - continue - node_list.append(node) - - for node in node_list: extruder_stack_id = node.callDecoration("getActiveExtruder") if not extruder_stack_id: # No per-object settings for this node From f9b288f3c6e15b05c76509d0fe1b50e8657fea7e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 13:24:22 +0200 Subject: [PATCH 21/32] Move checking for some global features outside of the per node loop CURA-7106 --- cura/Settings/ExtruderManager.py | 37 +++++++++++++++++--------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index cd3121fe75..b2095ba28c 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -221,24 +221,27 @@ class ExtruderManager(QObject): # if there is no per-mesh stack, we use the build extruder for this mesh stack_to_use = container_registry.findContainerStacks(id = extruder_stack_id)[0] - support_enabled |= stack_to_use.getProperty("support_enable", "value") - support_bottom_enabled |= stack_to_use.getProperty("support_bottom_enable", "value") - support_roof_enabled |= stack_to_use.getProperty("support_roof_enable", "value") + if not support_enabled: + support_enabled |= stack_to_use.getProperty("support_enable", "value") + if not support_bottom_enabled: + support_bottom_enabled |= stack_to_use.getProperty("support_bottom_enable", "value") + if not support_roof_enabled: + support_roof_enabled |= stack_to_use.getProperty("support_roof_enable", "value") - # Check limit to extruders - limit_to_extruder_feature_list = ["wall_0_extruder_nr", - "wall_x_extruder_nr", - "roofing_extruder_nr", - "top_bottom_extruder_nr", - "infill_extruder_nr", - ] - for extruder_nr_feature_name in limit_to_extruder_feature_list: - extruder_nr = int(global_stack.getProperty(extruder_nr_feature_name, "value")) - if extruder_nr == -1: - continue - if str(extruder_nr) not in self.extruderIds: - extruder_nr = int(self._application.getMachineManager().defaultExtruderPosition) - used_extruder_stack_ids.add(self.extruderIds[str(extruder_nr)]) + # Check limit to extruders + limit_to_extruder_feature_list = ["wall_0_extruder_nr", + "wall_x_extruder_nr", + "roofing_extruder_nr", + "top_bottom_extruder_nr", + "infill_extruder_nr", + ] + for extruder_nr_feature_name in limit_to_extruder_feature_list: + extruder_nr = int(global_stack.getProperty(extruder_nr_feature_name, "value")) + if extruder_nr == -1: + continue + if str(extruder_nr) not in self.extruderIds: + extruder_nr = int(self._application.getMachineManager().defaultExtruderPosition) + used_extruder_stack_ids.add(self.extruderIds[str(extruder_nr)]) # Check support extruders if support_enabled: From 7e8e051eb2eeb40cc0788327a9cda8667fb631ee Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 13:38:00 +0200 Subject: [PATCH 22/32] Short circuit finding the extruders that are active CURA-7106 --- cura/Settings/ExtruderManager.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index b2095ba28c..7d8feeb522 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -204,9 +204,12 @@ class ExtruderManager(QObject): # If no extruders are registered in the extruder manager yet, return an empty array if len(self.extruderIds) == 0: return [] + number_active_extruders = len([extruder for extruder in self.getActiveExtruderStacks() if extruder.isEnabled]) # Get the extruders of all printable meshes in the scene nodes = [node for node in DepthFirstIterator(scene_root) if node.isSelectable() and not node.callDecoration("isAntiOverhangMesh") and not node.callDecoration("isSupportMesh")] #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + if not nodes: + return [] for node in nodes: extruder_stack_id = node.callDecoration("getActiveExtruder") @@ -215,6 +218,11 @@ class ExtruderManager(QObject): extruder_stack_id = self.extruderIds["0"] used_extruder_stack_ids.add(extruder_stack_id) + if len(used_extruder_stack_ids) == number_active_extruders: + # We're already done. Stop looking. + # Especially with a lot of models on the buildplate, this will speed up things rather dramatically. + break + # Get whether any of them use support. stack_to_use = node.callDecoration("getStack") # if there is a per-mesh stack, we use it if not stack_to_use: From e001839512a36183fc023f3022b7582d3a3140d8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 13:45:43 +0200 Subject: [PATCH 23/32] Replace getUsedExtruderNumbers with the faster alternative CURA-7106 --- plugins/CuraEngineBackend/StartSliceJob.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 693b129f43..892393c798 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -352,8 +352,7 @@ class StartSliceJob(Job): result = {} for key in stack.getAllKeys(): - value = stack.getProperty(key, "value") - result[key] = value + result[key] = stack.getProperty(key, "value") Job.yieldThread() result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings. @@ -454,11 +453,10 @@ class StartSliceJob(Job): print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"] pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(print_temperature_settings) # match {setting} as well as {setting, extruder_nr} settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) == None + # Replace the setting tokens in start and end g-code. # Use values from the first used extruder by default so we get the expected temperatures - initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0] - initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value") - + initial_extruder_nr = CuraApplication.getInstance().getExtruderManager().getInitialExtruderNr() settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], initial_extruder_nr) settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], initial_extruder_nr) From 398540663441ee770b189491765232b2677195e3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 14:06:13 +0200 Subject: [PATCH 24/32] Speed up generation of extruder message CURA-7106 --- plugins/CuraEngineBackend/StartSliceJob.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 892393c798..514f6c5ee6 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -419,10 +419,15 @@ class StartSliceJob(Job): settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], extruder_nr) settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], extruder_nr) + global_definition = stack.getNextStack().getBottom() + own_definition = stack.getBottom() + for key, value in settings.items(): # Do not send settings that are not settable_per_extruder. - if not stack.getProperty(key, "settable_per_extruder"): - continue + # Since these can only be set in definition files, we only have to ask there. + if not global_definition.getProperty(key, "settable_per_extruder") and \ + not own_definition.getProperty(key, "settable_per_extruder"): + continue setting = message.getMessage("settings").addRepeatedMessage("settings") setting.name = key setting.value = str(value).encode("utf-8") From 90c618363411f473594b6c59cabed06d2ec6c3cb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 14:12:28 +0200 Subject: [PATCH 25/32] Add process events triggers to startSliceJob Prevents a freeze when the setting message is being calculated CURA-7106 --- plugins/CuraEngineBackend/StartSliceJob.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 514f6c5ee6..fbe820c445 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -8,6 +8,7 @@ import time from typing import Any, cast, Dict, List, Optional, Set import re import Arcus #For typing. +from PyQt5.QtCore import QCoreApplication from UM.Job import Job from UM.Logger import Logger @@ -372,9 +373,11 @@ class StartSliceJob(Job): self._all_extruders_settings = { "-1": self._buildReplacementTokens(global_stack) } + QCoreApplication.processEvents() # Ensure that the GUI does not freeze. for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): extruder_nr = extruder_stack.getProperty("extruder_nr", "value") self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack) + QCoreApplication.processEvents() # Ensure that the GUI does not freeze. def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str: """Replace setting tokens in a piece of g-code. From ac0c0d0698d2c7823df88fb25ff10d8625c3d3c9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 14:54:56 +0200 Subject: [PATCH 26/32] Let the error checker calculate the error values a bit more "on demand" Previously it would just re-try all settings, but this really isn't needed (since we have a setting relationship object that can tell us what settings depend on what). This won't speed things up in a worst case scenario (since that will still be "caluclate all the settings") but it will speed it up in most cases. Most setting changes now only trigger a calculation that takes <0.2 sec isntead of the 1.1 sec. Yes, those numbers seem big, but the error checking is already built in such a way that it spreads this out over multiple frames (so it's not actually freezing that time, but it is doing shit it shouldn't do!) CURA-7106 --- cura/Machines/MachineErrorChecker.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py index bc9ef723d4..7a2c509dda 100644 --- a/cura/Machines/MachineErrorChecker.py +++ b/cura/Machines/MachineErrorChecker.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2020 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import time @@ -14,6 +14,7 @@ from UM.Settings.Validator import ValidatorState import cura.CuraApplication + class MachineErrorChecker(QObject): """This class performs setting error checks for the currently active machine. @@ -50,6 +51,8 @@ class MachineErrorChecker(QObject): self._error_check_timer.setInterval(100) self._error_check_timer.setSingleShot(True) + self._keys_to_check = set() + def initialize(self) -> None: self._error_check_timer.timeout.connect(self._rescheduleCheck) @@ -103,6 +106,7 @@ class MachineErrorChecker(QObject): if property_name != "value": return + self._keys_to_check.add(key) self.startErrorCheck() def startErrorCheck(self, *args: Any) -> None: @@ -140,7 +144,10 @@ class MachineErrorChecker(QObject): # Populate the (stack, key) tuples to check self._stacks_and_keys_to_check = deque() for stack in global_stack.extruderList: - for key in stack.getAllKeys(): + if not self._keys_to_check: + self._keys_to_check = stack.getAllKeys() + + for key in self._keys_to_check: self._stacks_and_keys_to_check.append((stack, key)) self._application.callLater(self._checkStack) @@ -181,18 +188,25 @@ class MachineErrorChecker(QObject): validator = validator_type(key) validation_state = validator(stack) if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid): - # Finish - self._setResult(True) + # Since we don't know if any of the settings we didn't check is has an error value, store the list for the + # next check. + keys_to_recheck = {setting_key for stack, setting_key in self._stacks_and_keys_to_check} + keys_to_recheck.add(key) + self._setResult(True, keys_to_recheck = keys_to_recheck) return # Schedule the check for the next key self._application.callLater(self._checkStack) - def _setResult(self, result: bool) -> None: + def _setResult(self, result: bool, keys_to_recheck = None) -> None: if result != self._has_errors: self._has_errors = result self.hasErrorUpdated.emit() self._machine_manager.stacksValidationChanged.emit() + if keys_to_recheck is None: + self._keys_to_check = set() + else: + self._keys_to_check = keys_to_recheck self._need_to_check = False self._check_in_progress = False self.needToWaitForResultChanged.emit() From b0ed47daf1acf2c7b263321723ef41fb0f9119fd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 16:24:34 +0200 Subject: [PATCH 27/32] Don't copy transformations if it's not needed CURA=7106 --- cura/PickingPass.py | 2 +- cura/PreviewPass.py | 4 ++-- cura/Scene/ConvexHullDecorator.py | 2 +- cura/Scene/CuraSceneNode.py | 4 ++-- plugins/SliceInfoPlugin/SliceInfo.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cura/PickingPass.py b/cura/PickingPass.py index 13a115f64b..6ffc63cbd4 100644 --- a/cura/PickingPass.py +++ b/cura/PickingPass.py @@ -54,7 +54,7 @@ class PickingPass(RenderPass): # 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 node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible(): - batch.addItem(node.getWorldTransformation(), node.getMeshData()) + batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData()) self.bind() batch.render(self._scene.getActiveCamera()) diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py index 9cae5ad9bd..ba139bb2b3 100644 --- a/cura/PreviewPass.py +++ b/cura/PreviewPass.py @@ -114,12 +114,12 @@ class PreviewPass(RenderPass): 1.0] uniforms["diffuse_color"] = prettier_color(diffuse_color) uniforms["diffuse_color_2"] = diffuse_color2 - batch_support_mesh.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms) + batch_support_mesh.addItem(node.getWorldTransformation(copy = False), node.getMeshData(), uniforms = uniforms) else: # Normal scene node uniforms = {} uniforms["diffuse_color"] = prettier_color(cast(CuraSceneNode, node).getDiffuseColor()) - batch.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms) + batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData(), uniforms = uniforms) self.bind() diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index 3465cd3ef9..6717a98dcd 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -268,7 +268,7 @@ class ConvexHullDecorator(SceneNodeDecorator): if mesh is None: return Polygon([]) # Node has no mesh data, so just return an empty Polygon. - world_transform = self._node.getWorldTransformation() + world_transform = self._node.getWorldTransformation(copy= False) # Check the cache if mesh is self._2d_convex_hull_mesh and world_transform == self._2d_convex_hull_mesh_world_transform: diff --git a/cura/Scene/CuraSceneNode.py b/cura/Scene/CuraSceneNode.py index c22277a4b0..93a1511681 100644 --- a/cura/Scene/CuraSceneNode.py +++ b/cura/Scene/CuraSceneNode.py @@ -118,7 +118,7 @@ class CuraSceneNode(SceneNode): self._aabb = None if self._mesh_data: - self._aabb = self._mesh_data.getExtents(self.getWorldTransformation()) + self._aabb = self._mesh_data.getExtents(self.getWorldTransformation(copy = False)) else: # If there is no mesh_data, use a bounding box that encompasses the local (0,0,0) position = self.getWorldPosition() self._aabb = AxisAlignedBox(minimum = position, maximum = position) @@ -139,7 +139,7 @@ class CuraSceneNode(SceneNode): """Taken from SceneNode, but replaced SceneNode with CuraSceneNode""" copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later - copy.setTransformation(self.getLocalTransformation()) + copy.setTransformation(self.getLocalTransformation(copy= False)) copy.setMeshData(self._mesh_data) copy.setVisible(cast(bool, deepcopy(self._visible, memo))) copy._selectable = cast(bool, deepcopy(self._selectable, memo)) diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 20a563c291..284389064c 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -199,7 +199,7 @@ class SliceInfo(QObject, Extension): "maximum": {"x": bounding_box.maximum.x, "y": bounding_box.maximum.y, "z": bounding_box.maximum.z}} - model["transformation"] = {"data": str(node.getWorldTransformation().getData()).replace("\n", "")} + model["transformation"] = {"data": str(node.getWorldTransformation(copy = False).getData()).replace("\n", "")} extruder_position = node.callDecoration("getActiveExtruderPosition") model["extruder"] = 0 if extruder_position is None else int(extruder_position) From 03e66beafdfd538b55b65337843cce3f452d98fe Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 23 Jun 2020 17:12:47 +0200 Subject: [PATCH 28/32] Re-use some render batches This prevents us from having to re-create them everytime CURA-7106 --- cura/Scene/ConvexHullNode.py | 7 +++++-- plugins/SolidView/SolidView.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cura/Scene/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py index 13e8a1969c..cb4cffca12 100644 --- a/cura/Scene/ConvexHullNode.py +++ b/cura/Scene/ConvexHullNode.py @@ -86,8 +86,11 @@ class ConvexHullNode(SceneNode): ConvexHullNode.shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader")) ConvexHullNode.shader.setUniformValue("u_diffuseColor", self._color) ConvexHullNode.shader.setUniformValue("u_opacity", 0.6) - - renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8) + batch = renderer.getNamedBatch("convex_hull_node") + if not batch: + batch = renderer.createRenderBatch(transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8) + renderer.addRenderBatch(batch, name = "convex_hull_node") + batch.addItem(self.getWorldTransformation(copy = False), self.getMeshData()) if self._convex_hull_head_mesh: # The full head. Rendered as a hint to the user: If this area overlaps another object A; this object # cannot be printed after A, because the head would hit A while printing the current object diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index f16f696150..e1789e34ed 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -186,7 +186,8 @@ class SolidView(View): self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang. else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) - + disabled_batch = renderer.createRenderBatch(shader = self._disabled_shader) + renderer.addRenderBatch(disabled_batch) for node in DepthFirstIterator(scene.getRoot()): if node.render(renderer): continue @@ -233,7 +234,7 @@ class SolidView(View): else: renderer.queueNode(node, shader = self._non_printing_shader, transparent = True) elif getattr(node, "_outside_buildarea", False): - renderer.queueNode(node, shader = self._disabled_shader) + disabled_batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData()) elif per_mesh_stack and node.callDecoration("isSupportMesh"): # Render support meshes with a vertical stripe that is darker shade_factor = 0.6 From b1fe4793e43652abb8bdf60718f3b4bafe1a90e9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 24 Jun 2020 10:42:35 +0200 Subject: [PATCH 29/32] Put all objects normal models in a single render batch THis speeds things up a fair bit for build plates with multiple models CURA-7106 --- cura/XRayPass.py | 2 +- plugins/SolidView/SolidView.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cura/XRayPass.py b/cura/XRayPass.py index edc20ce62d..eb5f33cea2 100644 --- a/cura/XRayPass.py +++ b/cura/XRayPass.py @@ -29,7 +29,7 @@ class XRayPass(RenderPass): batch = RenderBatch(self._shader, type = RenderBatch.RenderType.NoType, backface_cull = False, blend_mode = RenderBatch.BlendMode.Additive) for node in DepthFirstIterator(self._scene.getRoot()): if isinstance(node, CuraSceneNode) and node.getMeshData() and node.isVisible(): - batch.addItem(node.getWorldTransformation(), node.getMeshData()) + batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData()) self.bind() diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index e1789e34ed..b65c75aaa1 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -187,7 +187,9 @@ class SolidView(View): else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) disabled_batch = renderer.createRenderBatch(shader = self._disabled_shader) + normal_object_batch = renderer.createRenderBatch(shader = self._enabled_shader) renderer.addRenderBatch(disabled_batch) + renderer.addRenderBatch(normal_object_batch) for node in DepthFirstIterator(scene.getRoot()): if node.render(renderer): continue @@ -246,7 +248,7 @@ class SolidView(View): ] renderer.queueNode(node, shader = self._support_mesh_shader, uniforms = uniforms) else: - renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms) + normal_object_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData(), uniforms=uniforms) if node.callDecoration("isGroup") and Selection.isSelected(node): renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = RenderBatch.RenderMode.LineLoop) From 68807d99d23e674fac9e3a0d4da787fb738ccbc5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 24 Jun 2020 15:18:20 +0200 Subject: [PATCH 30/32] Fix issue that no disallowed areas were show if there were no active objects CURA-7106 --- cura/Settings/ExtruderManager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 7d8feeb522..67c582ad5e 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -208,8 +208,6 @@ class ExtruderManager(QObject): # Get the extruders of all printable meshes in the scene nodes = [node for node in DepthFirstIterator(scene_root) if node.isSelectable() and not node.callDecoration("isAntiOverhangMesh") and not node.callDecoration("isSupportMesh")] #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. - if not nodes: - return [] for node in nodes: extruder_stack_id = node.callDecoration("getActiveExtruder") From 27277d4e541f4f2fb74773e8c5bfa66d01a402b3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 24 Jun 2020 15:28:41 +0200 Subject: [PATCH 31/32] Add missing typing --- cura/Machines/MachineErrorChecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py index 7a2c509dda..818d62de7c 100644 --- a/cura/Machines/MachineErrorChecker.py +++ b/cura/Machines/MachineErrorChecker.py @@ -51,7 +51,7 @@ class MachineErrorChecker(QObject): self._error_check_timer.setInterval(100) self._error_check_timer.setSingleShot(True) - self._keys_to_check = set() + self._keys_to_check = set() # type: Set[str] def initialize(self) -> None: self._error_check_timer.timeout.connect(self._rescheduleCheck) From 05f35a07e4f3b475ce987294143408d3286ac069 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 24 Jun 2020 15:40:33 +0200 Subject: [PATCH 32/32] Fix mypy issues For some reason my local mypy didn't spot them but the CI did. CURA-7106 --- plugins/CuraEngineBackend/StartSliceJob.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index fbe820c445..bd42d81566 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -15,6 +15,7 @@ from UM.Logger import Logger from UM.Scene.SceneNode import SceneNode from UM.Settings.ContainerStack import ContainerStack #For typing. from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.Interfaces import ContainerInterface from UM.Settings.SettingDefinition import SettingDefinition from UM.Settings.SettingRelation import SettingRelation #For typing. @@ -422,8 +423,8 @@ class StartSliceJob(Job): settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], extruder_nr) settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], extruder_nr) - global_definition = stack.getNextStack().getBottom() - own_definition = stack.getBottom() + global_definition = cast(ContainerInterface, cast(ContainerStack, stack.getNextStack()).getBottom()) + own_definition = cast(ContainerInterface, stack.getBottom()) for key, value in settings.items(): # Do not send settings that are not settable_per_extruder.