1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
import copy
from . import Operation
from UM.Scene.SceneNode import SceneNode #To get the world transformation.
from UM.Math.Vector import Vector
class ScaleOperation(Operation.Operation):
"""Operation that scales a scene node, uniformly or non-uniformly."""
def __init__(self, node, scale, set_scale = False, add_scale = False, relative_scale = False, scale_around_point = Vector(0, 0, 0), snap = False):
"""Initialises the scale operation.
:param node: The scene node to scale.
:param scale: A matrix to scale the node with. This matrix should only be
non-zero on the diagonal.
:param set_scale: Whether to simply replace the old scale with the new
one (True) or modify the old scale (False).
:param add_scale: Whether to add to the old scale (True) or multiply with
it (False).
:param relative_scale: Whether to multiply the scale relative to the
current scale (True) or simply multiply it with a constant (False).
:param scale_around_point: All coordinates are moved away from or towards
this point.
:param snap: Whether to use snap scaling (True) or not (False).
"""
super().__init__()
self._node = node #The scene node to scale.
self._old_transformation = node.getLocalTransformation() #The transformation of the node before scaling.
self._set_scale = set_scale #Whether to simply change the scale.
self._add_scale = add_scale #Whether to add to the old scale.
self._relative_scale = relative_scale #Whether to multiply relatively.
self._scale_around_point = scale_around_point #The origin of the scale operation.
self._snap = snap #Use snap scaling?
self._scale = scale #The transformation matrix that scales space correctly.
self._min_scale = 0.01 #A minimum scale factor. Much lower would introduce rounding errors.
def undo(self):
"""Undo the scale operation."""
self._node.setTransformation(self._old_transformation)
def redo(self):
"""Redo the scale operation."""
if self._set_scale: #Simply change the scale.
self._node.setScale(self._scale, SceneNode.TransformSpace.World)
elif self._add_scale: #Add to the current scale.
self._node.setScale(self._node.getScale() + self._scale)
elif self._relative_scale: #Scale relatively to the current scale.
scale_factor = Vector()
## Ensure that the direction is correctly applied (it can be flipped due to mirror)
if self._scale.z == self._scale.y and self._scale.y == self._scale.x:
ratio = (1 / (self._node.getScale().x + self._node.getScale().y + self._node.getScale().z)) * 3
ratio_vector = ratio * self._node.getScale()
self._scale *= ratio_vector
self._scale *= self._node.getScale().length()
if self._node.getScale().x > 0:
scale_factor = scale_factor.set(x=abs(self._node.getScale().x + self._scale.x))
else:
scale_factor = scale_factor.set(x=-abs(self._node.getScale().x - self._scale.x))
if self._node.getScale().y > 0:
scale_factor = scale_factor.set(y=abs(self._node.getScale().y + self._scale.y))
else:
scale_factor = scale_factor.set(y=-abs(self._node.getScale().y - self._scale.y))
if self._node.getScale().z > 0:
scale_factor = scale_factor.set(z=abs(self._node.getScale().z + self._scale.z))
else:
scale_factor = scale_factor.set(z=-abs(self._node.getScale().z - self._scale.z))
self._node.setPosition(-self._scale_around_point) #If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation.
self._node.setScale(scale_factor, SceneNode.TransformSpace.World)
self._node.setPosition(self._scale_around_point)
new_scale = self._node.getScale()
if self._snap:
if scale_factor.x != 1.0:
new_scale = new_scale.set(x=round(new_scale.x, 2))
if scale_factor.y != 1.0:
new_scale = new_scale.set(y=round(new_scale.y, 2))
if scale_factor.z != 1.0 :
new_scale = new_scale.set(z=round(new_scale.z, 2))
# Enforce min size.
if new_scale.x < self._min_scale and new_scale.x >= 0:
new_scale = new_scale.set(x=self._min_scale)
if new_scale.y < self._min_scale and new_scale.y >= 0:
new_scale = new_scale.set(y=self._min_scale)
if new_scale.z < self._min_scale and new_scale.z >= 0:
new_scale = new_scale.set(z=self._min_scale)
# Enforce min size (when mirrored)
if new_scale.x > -self._min_scale and new_scale.x <= 0:
new_scale = new_scale.set(x=-self._min_scale)
if new_scale.y > -self._min_scale and new_scale.y <= 0:
new_scale = new_scale.set(y=-self._min_scale)
if new_scale.z > -self._min_scale and new_scale.z <=0:
new_scale = new_scale.set(z=-self._min_scale)
self._node.setScale(new_scale, SceneNode.TransformSpace.World)
else:
self._node.setPosition(-self._scale_around_point, SceneNode.TransformSpace.World) # If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation.
self._node.scale(self._scale, SceneNode.TransformSpace.World) #Default to _set_scale
self._node.setPosition(self._scale_around_point, SceneNode.TransformSpace.World) # If scaling around a point, shift that point to the axis origin first and shift it back after performing the transformation.
def mergeWith(self, other):
"""Merge this operation with another scale operation.
This prevents the user from having to undo multiple operations if they
were not his operations.
You should ONLY merge this operation with an older operation. It is NOT
symmetric.
:param other: The older scale operation to merge this operation with.
:return: A new operation that performs both scale operations.
"""
if type(other) is not ScaleOperation:
return False
if other._node != self._node: #Must be scaling the same node.
return False
if other._set_scale and not self._set_scale: #Must be the same type of scaling.
return False
if other._add_scale and not self._add_scale:
return False
if other._relative_scale and not self._relative_scale:
return False
if other._scale_around_point != self._scale_around_point:
return False
if other._snap != self._snap:
return False
op = ScaleOperation(self._node, self._scale + other._scale, set_scale = self._set_scale, add_scale = self._add_scale, relative_scale = self._relative_scale, scale_around_point = self._scale_around_point, snap = self._snap)
op._old_transformation = copy.deepcopy(other._old_transformation) #Use the oldest transformation of the two.
return op
def __repr__(self):
"""Returns a programmer-readable representation of this operation.
:return: A programmer-readable representation of this operation.
"""
mode = "set" if self._set_scale else "add" if self._add_scale else "relative"
return "ScaleOp.(node={0},scale={1},mode={2})".format(self._node, self._scale, mode)
|