diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 9b113b7de8..f381faa482 100644 --- a/cura/CuraApplication.py +++ b/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) + if node.callDecoration("isGroup"): + op = GroupedOperation() - 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()) + 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() diff --git a/cura/SetParentOperation.py b/cura/SetParentOperation.py new file mode 100644 index 0000000000..c40ce54909 --- /dev/null +++ b/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)