Browse Source

Refactor grouping/ungrouping into an operation that is undoable

CURA-1543
fieldOfView 8 years ago
parent
commit
4830943113
2 changed files with 73 additions and 24 deletions
  1. 23 24
      cura/CuraApplication.py
  2. 50 0
      cura/SetParentOperation.py

+ 23 - 24
cura/CuraApplication.py

@@ -23,6 +23,7 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
 from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
 from UM.Operations.GroupedOperation import GroupedOperation
 from UM.Operations.SetTransformOperation import SetTransformOperation
+from cura.SetParentOperation import SetParentOperation
 
 from UM.i18n import i18nCatalog
 
@@ -540,9 +541,10 @@ class CuraApplication(QtApplication):
         
         # Use the previously found center of the group bounding box as the new location of the group
         group_node.setPosition(group_node.getBoundingBox().center)
-    
+
     @pyqtSlot()
     def groupSelected(self):
+        # Create a group-node
         group_node = SceneNode()
         group_decorator = GroupDecorator()
         group_node.addDecorator(group_decorator)
@@ -552,40 +554,37 @@ class CuraApplication(QtApplication):
         group_node.setPosition(center)
         group_node.setCenterPosition(center)
 
-        for node in Selection.getAllSelectedObjects():
-            world = node.getWorldPosition()
-            node.setParent(group_node)
-            node.setPosition(world - center)
+        # Move selected nodes into the group-node
+        op = GroupedOperation()
+        nodes = Selection.getAllSelectedObjects()
+        for node in nodes:
+            op.addOperation(SetParentOperation(node, group_node))
+        op.push()
 
+        # Deselect individual nodes and select the groupnode instead
         for node in group_node.getChildren():
             Selection.remove(node)
-
         Selection.add(group_node)
 
     @pyqtSlot()
     def ungroupSelected(self):
-        ungrouped_nodes = []
         selected_objects = Selection.getAllSelectedObjects()[:] #clone the list
         for node in selected_objects:
-            if node.callDecoration("isGroup" ):
-                children_to_move = []
-                for child in node.getChildren():
-                    if type(child) is SceneNode:
-                        children_to_move.append(child)
-
-                for child in children_to_move:
-                    position = child.getWorldPosition()
-                    child.setParent(node.getParent())
-                    child.setPosition(position - node.getParent().getWorldPosition())
-                    child.scale(node.getScale())
-                    child.rotate(node.getOrientation())
+            if node.callDecoration("isGroup"):
+                op = GroupedOperation()
+
+                group_parent = node.getParent()
+                children = node.getChildren()[:] #clone the list
+                for child in children:
+                    # Set the parent of the children to the parent of the group-node
+                    op.addOperation(SetParentOperation(child, group_parent))
 
+                    # Add all individual nodes to the selection
                     Selection.add(child)
-                    child.callDecoration("setConvexHull",None)
-                node.setParent(None)
-                ungrouped_nodes.append(node)
-        for node in ungrouped_nodes:
-            Selection.remove(node)
+                    child.callDecoration("setConvexHull", None)
+
+                op.push()
+                # Note: The group removes itself from the scene once all its children have left it, see GroupDecorator._onChildrenChanged
 
     def _createSplashScreen(self):
         return CuraSplashScreen.CuraSplashScreen()

+ 50 - 0
cura/SetParentOperation.py

@@ -0,0 +1,50 @@
+# Copyright (c) 2016 Ultimaker B.V.
+# Uranium is released under the terms of the AGPLv3 or higher.
+
+from UM.Scene.SceneNode import SceneNode
+from UM.Operations import Operation
+
+from UM.Math.Vector import Vector
+
+##  An operation that parents a scene node to another scene node.
+
+class SetParentOperation(Operation.Operation):
+    ##  Initialises this SetParentOperation.
+    #
+    #   \param node The node which will be reparented.
+    #   \param parent_node The node which will be the parent.
+    def __init__(self, node, parent_node):
+        super().__init__()
+        self._node = node
+        self._parent = parent_node
+        self._old_parent = node.getParent() # To restore the previous parent in case of an undo.
+
+    ##  Undoes the set-parent operation, restoring the old parent.
+    def undo(self):
+        self._set_parent(self._old_parent)
+
+    ##  Re-applies the set-parent operation.
+    def redo(self):
+        self._set_parent(self._parent)
+
+    ##  Sets the parent of the node while applying transformations to the world-transform of the node stays the same.
+    #
+    #   \param new_parent The new parent. Note: this argument can be None, which would hide the node from the scene.
+    def _set_parent(self, new_parent):
+        if new_parent:
+            self._node.setPosition(self._node.getWorldPosition() - new_parent.getWorldPosition())
+            current_parent = self._node.getParent()
+            if current_parent:
+                self._node.scale(current_parent.getScale() / new_parent.getScale())
+                self._node.rotate(current_parent.getOrientation())
+            else:
+                self._node.scale(Vector(1, 1, 1) / new_parent.getScale())
+            self._node.rotate(new_parent.getOrientation().getInverse())
+
+        self._node.setParent(new_parent)
+
+    ##  Returns a programmer-readable representation of this operation.
+    #
+    #   \return A programmer-readable representation of this operation.
+    def __repr__(self):
+        return "SetParentOperation(node = {0}, parent_node={1})".format(self._node, self._parent)