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() diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 315eca9fe5..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" @@ -106,8 +108,6 @@ class BuildVolume(SceneNode): self._application.globalContainerStackChanged.connect(self._onStackChanged) - self._onStackChanged() - self._engine_ready = False self._application.engineCreatedSignal.connect(self._onEngineCreated) @@ -118,7 +118,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) @@ -747,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. @@ -1124,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() @@ -1139,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) diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py index bc9ef723d4..818d62de7c 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() # type: Set[str] + 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() 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: 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 46caa5b9e0..6717a98dcd 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: @@ -273,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/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py index 765dae26a2..cb4cffca12 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 @@ -79,15 +86,15 @@ 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) - - 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) + 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 + renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8) return True @@ -97,7 +104,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 - 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/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 2cc9ec4631..67c582ad5e 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -204,49 +204,50 @@ 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 - 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 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 - 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")): - continue - mesh_list.append(mesh) - - for mesh in mesh_list: - extruder_stack_id = mesh.callDecoration("getActiveExtruder") + for node in nodes: + 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) + 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 = 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] - 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: diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 1b5fb84f4a..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. @@ -46,6 +46,10 @@ 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._is_anti_overhang_mesh = False self._stack.propertyChanged.connect(self._onSettingChanged) @@ -53,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) @@ -74,11 +79,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): @@ -104,7 +104,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): """ # for support_meshes, always use the support_extruder - if self.getStack().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 +114,30 @@ 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 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")) + + 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 +156,16 @@ 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 == "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() + elif setting_key == "infill_mesh": + self._is_infill_mesh = self._evaluateInfillMesh() + Application.getInstance().getBackend().needsSlicing() Application.getInstance().getBackend().tickle() diff --git a/cura/UI/ObjectsModel.py b/cura/UI/ObjectsModel.py index 1383476665..02d4096278 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 @@ -186,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": 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/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 693b129f43..bd42d81566 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -8,12 +8,14 @@ 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 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. @@ -352,8 +354,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. @@ -373,9 +374,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. @@ -420,10 +423,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 = 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. - 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") @@ -454,11 +462,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) 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) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index dc88265d68..b65c75aaa1 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."), @@ -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,77 +180,77 @@ 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. 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 not node.render(renderer): - if node.getMeshData() and node.isVisible() and not node.callDecoration("getLayerData"): - 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") - # 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")) + extruder_index = node.callDecoration("getActiveExtruderPosition") + if extruder_index is None: + extruder_index = "0" + extruder_index = int(extruder_index) - try: - material_color = self._extruders_model.getItem(extruder_index)["color"] - except KeyError: - material_color = self._extruders_model.defaultColors[0] + try: + material_color = self._extruders_model.getItem(extruder_index)["color"] + except KeyError: + material_color = self._extruders_model.defaultColors[0] - if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: - # Shade objects that are printed with the non-active extruder 25% darker - shade_factor = 0.6 + if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: + # Shade objects that are printed with the non-active extruder 25% darker + shade_factor = 0.6 - 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 - ] + 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 + ] - # 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 + # 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 (per_mesh_stack.getProperty("infill_mesh", "value") or per_mesh_stack.getProperty("cutting_mesh", "value")): - 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"): - # 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) + 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): + 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 + 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: + 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) def endRendering(self): # check whether the xray overlay is showing badness