From 2d6a327a36d64cc6c8390162e4a9a5adc87c3150 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 12 Apr 2024 10:03:11 +0200 Subject: [PATCH 1/8] clearing memory stack CURA-11658 --- UM/Operations/OperationStack.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/UM/Operations/OperationStack.py b/UM/Operations/OperationStack.py index de8b9d43f9..021a5cc092 100644 --- a/UM/Operations/OperationStack.py +++ b/UM/Operations/OperationStack.py @@ -1,6 +1,6 @@ # Copyright (c) 2019 Ultimaker B.V. # Uranium is released under the terms of the LGPLv3 or higher. - +import sys import threading import time @@ -34,6 +34,13 @@ def __init__(self, controller) -> None: def _onToolOperationStarted(self, tool): self._merge_operations = False + def controlStackSize(self): + if sys.getsizeof(self._operations)> 5000: + print("getting stack size", sys.getsizeof(self._operations), len(self._operations)) + self._current_index -=1 + self._operations.pop(0) + self.controlStackSize() + def _onToolOperationStopped(self, tool): self._merge_operations = False @@ -67,6 +74,7 @@ def push(self, operation): self.changed.emit() finally: self._lock.release() + self.controlStackSize() elapsed_time = time.time() - start_time Logger.log("d", " ".join(repr(operation).splitlines()) + ", took {0}ms".format(int(elapsed_time * 1000))) #Don't remove; used in regression-tests. From 225e27cb33e7eef7ff9bd06d301e4b9077e13ded Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 12 Apr 2024 15:25:00 +0200 Subject: [PATCH 2/8] Clearing operation stack when a new project is loaded CURA-11658 --- UM/Controller.py | 5 ++++- UM/Operations/Operation.py | 5 +++++ UM/Operations/OperationStack.py | 11 ++++------- UM/Qt/QtApplication.py | 6 +++--- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/UM/Controller.py b/UM/Controller.py index 451a899ddb..75ecd22126 100644 --- a/UM/Controller.py +++ b/UM/Controller.py @@ -464,7 +464,7 @@ def getToolsEnabled(self) -> bool: def setToolsEnabled(self, enabled: bool) -> None: self._tools_enabled = enabled - def deleteAllNodesWithMeshData(self, only_selectable:bool = True) -> None: + def deleteAllNodesWithMeshData(self, only_selectable:bool = True, clear_all :bool = False) -> None: Logger.log("i", "Clearing scene") if not self.getToolsEnabled(): return @@ -492,9 +492,12 @@ def deleteAllNodesWithMeshData(self, only_selectable:bool = True) -> None: self.getScene().sceneChanged.emit(node) op.push() + if clear_all: + op.clear() from UM.Scene.Selection import Selection Selection.clear() + # Rotate camera view according defined angle def setCameraRotation(self, coordinate: str = "x", angle: int = 0) -> None: camera = self._scene.getActiveCamera() diff --git a/UM/Operations/Operation.py b/UM/Operations/Operation.py index e2fb360b2d..b3c71c5e26 100644 --- a/UM/Operations/Operation.py +++ b/UM/Operations/Operation.py @@ -57,3 +57,8 @@ def push(self) -> None: # Because of circular dependency from UM.Application import Application Application.getInstance().getOperationStack().push(self) + + def clear(self) -> None: + # Because of circular dependency + from UM.Application import Application + Application.getInstance().getOperationStack().clearStack() diff --git a/UM/Operations/OperationStack.py b/UM/Operations/OperationStack.py index 021a5cc092..97e8da3956 100644 --- a/UM/Operations/OperationStack.py +++ b/UM/Operations/OperationStack.py @@ -34,12 +34,10 @@ def __init__(self, controller) -> None: def _onToolOperationStarted(self, tool): self._merge_operations = False - def controlStackSize(self): - if sys.getsizeof(self._operations)> 5000: - print("getting stack size", sys.getsizeof(self._operations), len(self._operations)) - self._current_index -=1 - self._operations.pop(0) - self.controlStackSize() + def clearStack(self): + self._operations = [] + self._current_index = -1 + self._merge_operations = False def _onToolOperationStopped(self, tool): self._merge_operations = False @@ -74,7 +72,6 @@ def push(self, operation): self.changed.emit() finally: self._lock.release() - self.controlStackSize() elapsed_time = time.time() - start_time Logger.log("d", " ".join(repr(operation).splitlines()) + ", took {0}ms".format(int(elapsed_time * 1000))) #Don't remove; used in regression-tests. diff --git a/UM/Qt/QtApplication.py b/UM/Qt/QtApplication.py index c4575fd42d..de85e835db 100644 --- a/UM/Qt/QtApplication.py +++ b/UM/Qt/QtApplication.py @@ -627,18 +627,18 @@ def createQmlComponent(self, qml_file_path: str, context_properties: Dict[str, " return result @pyqtSlot() - def deleteAll(self, only_selectable = True) -> None: + def deleteAll(self, only_selectable = True, clear_all:bool = False) -> None: """Delete all nodes containing mesh data in the scene. :param only_selectable:. Set this to False to delete objects from all build plates """ - self.getController().deleteAllNodesWithMeshData(only_selectable) + self.getController().deleteAllNodesWithMeshData(only_selectable, clear_all = clear_all) @pyqtSlot() def resetWorkspace(self) -> None: self._workspace_metadata_storage.clear() self._current_workspace_information.clear() - self.deleteAll() + self.deleteAll(clear_all = True) self.workspaceLoaded.emit("") self.getController().getScene().clearMetaData() From 90aad6b19598099877cbba440d540813ea893416 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Fri, 12 Apr 2024 15:28:03 +0200 Subject: [PATCH 3/8] Remove unused Sys import and fix code formatting CURA-11658 --- UM/Controller.py | 2 +- UM/Operations/OperationStack.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/UM/Controller.py b/UM/Controller.py index 75ecd22126..46fe91810e 100644 --- a/UM/Controller.py +++ b/UM/Controller.py @@ -464,7 +464,7 @@ def getToolsEnabled(self) -> bool: def setToolsEnabled(self, enabled: bool) -> None: self._tools_enabled = enabled - def deleteAllNodesWithMeshData(self, only_selectable:bool = True, clear_all :bool = False) -> None: + def deleteAllNodesWithMeshData(self, only_selectable:bool = True, clear_all:bool = False) -> None: Logger.log("i", "Clearing scene") if not self.getToolsEnabled(): return diff --git a/UM/Operations/OperationStack.py b/UM/Operations/OperationStack.py index 97e8da3956..b89e54cb37 100644 --- a/UM/Operations/OperationStack.py +++ b/UM/Operations/OperationStack.py @@ -1,6 +1,5 @@ # Copyright (c) 2019 Ultimaker B.V. # Uranium is released under the terms of the LGPLv3 or higher. -import sys import threading import time From bb9ba9ae05cb178acd1d200f29ffdc8a22e1ad48 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 17 Apr 2024 09:52:58 +0200 Subject: [PATCH 4/8] Enhance memory cleanup in various operation types This update refines the cleanup process across different parts of the application. Node reset functionality has been introduced which thoroughly cleans up the memory. Clear functions within SettingOverrideDecorator, SceneNodeDecorator, ConvexHullDecorator and others are improved to completely cleanse after usage. This approach ensures a more efficient use and management of memory resources. CURA-11658 --- UM/Operations/AddSceneNodeOperation.py | 5 +++++ UM/Operations/GroupedOperation.py | 4 ++++ UM/Operations/Operation.py | 3 +++ UM/Operations/OperationStack.py | 20 +++++++++++++---- UM/Operations/RemoveSceneNodeOperation.py | 5 +++++ UM/Scene/SceneNode.py | 26 ++++++++++++++++++++++- UM/Scene/SceneNodeDecorator.py | 2 +- 7 files changed, 59 insertions(+), 6 deletions(-) diff --git a/UM/Operations/AddSceneNodeOperation.py b/UM/Operations/AddSceneNodeOperation.py index 2be09ef331..b86d20c0da 100644 --- a/UM/Operations/AddSceneNodeOperation.py +++ b/UM/Operations/AddSceneNodeOperation.py @@ -46,3 +46,8 @@ def redo(self) -> None: self._node.setParent(self._parent) if self._selected: # It was selected while the operation was undone. We should restore that selection. Selection.add(self._node) + + def delete(self) -> None: + self._node.reset() + if self._parent.getName() != "Root": + self._parent.reset() \ No newline at end of file diff --git a/UM/Operations/GroupedOperation.py b/UM/Operations/GroupedOperation.py index 840d45a555..6df468123f 100644 --- a/UM/Operations/GroupedOperation.py +++ b/UM/Operations/GroupedOperation.py @@ -91,3 +91,7 @@ def __repr__(self): for child in self._children: output += "{0!r}\n".format(child) return output + + def delete(self) -> None: + for node in self._children: + node.delete() \ No newline at end of file diff --git a/UM/Operations/Operation.py b/UM/Operations/Operation.py index b3c71c5e26..72da53f664 100644 --- a/UM/Operations/Operation.py +++ b/UM/Operations/Operation.py @@ -62,3 +62,6 @@ def clear(self) -> None: # Because of circular dependency from UM.Application import Application Application.getInstance().getOperationStack().clearStack() + + def delete(self) -> None: + pass diff --git a/UM/Operations/OperationStack.py b/UM/Operations/OperationStack.py index b89e54cb37..e8475ea532 100644 --- a/UM/Operations/OperationStack.py +++ b/UM/Operations/OperationStack.py @@ -33,10 +33,22 @@ def __init__(self, controller) -> None: def _onToolOperationStarted(self, tool): self._merge_operations = False + def clearStackMemory(self, indexStart, indexEnd): + for node in self._operations[indexStart:indexEnd]: + node.delete() + del self._operations[0:len(self._operations)] + + def clearStack(self): - self._operations = [] - self._current_index = -1 - self._merge_operations = False + try: + with self._lock: + self.clearStackMemory(0, len(self._operations)-1) + self._current_index = -1 + self._merge_operations = False + self.changed.emit() + except Exception as e: + print("Error in clearing memory", e) + def _onToolOperationStopped(self, tool): self._merge_operations = False @@ -60,7 +72,7 @@ def push(self, operation): try: if self._current_index < len(self._operations) - 1: - del self._operations[self._current_index + 1:len(self._operations)] + self.clearStackMemory(self._current_index + 1, len(self._operations)) self._operations.append(operation) operation.redo() diff --git a/UM/Operations/RemoveSceneNodeOperation.py b/UM/Operations/RemoveSceneNodeOperation.py index 90a14f4878..eb3712d203 100644 --- a/UM/Operations/RemoveSceneNodeOperation.py +++ b/UM/Operations/RemoveSceneNodeOperation.py @@ -43,3 +43,8 @@ def redo(self) -> None: pass if Selection.isSelected(self._node): # Also remove the selection. Selection.remove(self._node) + + def delete(self) -> None: + self._node.reset() + if self._parent.getName() != "Root": + self._parent.reset() diff --git a/UM/Scene/SceneNode.py b/UM/Scene/SceneNode.py index 0ec1af8a7f..daba35ec05 100644 --- a/UM/Scene/SceneNode.py +++ b/UM/Scene/SceneNode.py @@ -101,6 +101,31 @@ def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, n if parent: parent.addChild(self) + def reset(self): + """Reset this scene node instance""" + self._children = [] + self._mesh_data = None + self.metadata = {} + self._transformation = Matrix() + self._position = Vector() + self._scale = Vector(1.0, 1.0, 1.0) + self._shear = Vector(0.0, 0.0, 0.0) + self._mirror = Vector(1.0, 1.0, 1.0) + self._orientation = Quaternion() + self._world_transformation = Matrix() + self._cached_normal_matrix = Matrix() + self._derived_position = Vector() + self._derived_orientation = Quaternion() + self._derived_scale = Vector() + self._enabled = False + self._selectable = False + self._resetAABB() + self._visible = False + self._name = "" + self._id = "" + self.removeDecorators() + self._settings = {} + def __deepcopy__(self, memo: Dict[int, object]) -> "SceneNode": copy = self.__class__() copy.setTransformation(self.getLocalTransformation()) @@ -262,7 +287,6 @@ def getDecorator(self, dec_type: type) -> Optional[SceneNodeDecorator]: def removeDecorators(self): """Remove all decorators""" - for decorator in self._decorators: decorator.clear() self._decorators = [] diff --git a/UM/Scene/SceneNodeDecorator.py b/UM/Scene/SceneNodeDecorator.py index c7c9562895..d398b45832 100644 --- a/UM/Scene/SceneNodeDecorator.py +++ b/UM/Scene/SceneNodeDecorator.py @@ -26,7 +26,7 @@ def getNode(self) -> Optional["SceneNode"]: def clear(self) -> None: """Clear all data associated with this decorator. This will be called before the decorator is removed""" - + self._node = None pass def __deepcopy__(self, memo: Dict[int, object]) -> "SceneNodeDecorator": From 772ec57d790034e19e7a5089262516b2d9997c79 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 17 Apr 2024 09:59:11 +0200 Subject: [PATCH 5/8] Optimize operation stack memory cleanup only the set of operations within the specified range are deleted. This fine-tuned approach minimizes needless memory cleanups and enhances overall memory management. CURA-11658 --- UM/Operations/OperationStack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UM/Operations/OperationStack.py b/UM/Operations/OperationStack.py index e8475ea532..7f74e954ea 100644 --- a/UM/Operations/OperationStack.py +++ b/UM/Operations/OperationStack.py @@ -36,7 +36,7 @@ def _onToolOperationStarted(self, tool): def clearStackMemory(self, indexStart, indexEnd): for node in self._operations[indexStart:indexEnd]: node.delete() - del self._operations[0:len(self._operations)] + del self._operations[indexStart:indexEnd] def clearStack(self): From f1df76f9785558a8a7f411c8deaabbdbc9749ef4 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 17 Apr 2024 10:11:16 +0200 Subject: [PATCH 6/8] correcting the index for clearing memory CURA-11658 --- UM/Operations/OperationStack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UM/Operations/OperationStack.py b/UM/Operations/OperationStack.py index 7f74e954ea..50ba139fb3 100644 --- a/UM/Operations/OperationStack.py +++ b/UM/Operations/OperationStack.py @@ -42,7 +42,7 @@ def clearStackMemory(self, indexStart, indexEnd): def clearStack(self): try: with self._lock: - self.clearStackMemory(0, len(self._operations)-1) + self.clearStackMemory(0, len(self._operations)) self._current_index = -1 self._merge_operations = False self.changed.emit() From afc5f5046609e7e255f8d23c0e24058e25a84408 Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Wed, 17 Apr 2024 10:16:14 +0200 Subject: [PATCH 7/8] Remove unnecessary 'pass' statement in SceneNodeDecorator CURA-11658 --- UM/Scene/SceneNodeDecorator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/UM/Scene/SceneNodeDecorator.py b/UM/Scene/SceneNodeDecorator.py index d398b45832..4d130b3ef8 100644 --- a/UM/Scene/SceneNodeDecorator.py +++ b/UM/Scene/SceneNodeDecorator.py @@ -27,7 +27,6 @@ def getNode(self) -> Optional["SceneNode"]: def clear(self) -> None: """Clear all data associated with this decorator. This will be called before the decorator is removed""" self._node = None - pass def __deepcopy__(self, memo: Dict[int, object]) -> "SceneNodeDecorator": raise NotImplementedError("Subclass {0} of SceneNodeDecorator should implement their own __deepcopy__() method.".format(str(self))) From cf7378d67af1529ef1be5cad7652eb32fb00ecce Mon Sep 17 00:00:00 2001 From: Saumya Jain Date: Mon, 22 Apr 2024 15:20:34 +0200 Subject: [PATCH 8/8] Rename "clear" method to "clearDecoratorData" The "clear" method has been renamed to "clearDecoratorData" to improve clarity on its function in several classes. This change affects the SettingOverrideDecorator, SceneNodeDecorator, ConvexHullDecorator, and SceneNode classes. CURA-11658 --- UM/Scene/SceneNode.py | 2 +- UM/Scene/SceneNodeDecorator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UM/Scene/SceneNode.py b/UM/Scene/SceneNode.py index daba35ec05..6a9165b6ed 100644 --- a/UM/Scene/SceneNode.py +++ b/UM/Scene/SceneNode.py @@ -288,7 +288,7 @@ def getDecorator(self, dec_type: type) -> Optional[SceneNodeDecorator]: def removeDecorators(self): """Remove all decorators""" for decorator in self._decorators: - decorator.clear() + decorator.clearDecoratorData() self._decorators = [] self.decoratorsChanged.emit(self) diff --git a/UM/Scene/SceneNodeDecorator.py b/UM/Scene/SceneNodeDecorator.py index 4d130b3ef8..c6efe88ccf 100644 --- a/UM/Scene/SceneNodeDecorator.py +++ b/UM/Scene/SceneNodeDecorator.py @@ -24,7 +24,7 @@ def setNode(self, node: "SceneNode") -> None: def getNode(self) -> Optional["SceneNode"]: return self._node - def clear(self) -> None: + def clearDecoratorData(self) -> None: """Clear all data associated with this decorator. This will be called before the decorator is removed""" self._node = None