File: Selection.py

package info (click to toggle)
uranium 5.0.0-9
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,328 kB
  • sloc: python: 31,765; sh: 132; makefile: 12
file content (209 lines) | stat: -rw-r--r-- 7,059 bytes parent folder | download | duplicates (4)
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.

from typing import List, Optional, Tuple

from UM.Signal import Signal
from UM.Math.Vector import Vector
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Scene.SceneNode import SceneNode

from UM.Operations.GroupedOperation import GroupedOperation


class Selection:
    """This class is responsible for keeping track of what objects are selected

    It uses signals to notify others of changes in the selection
    It also has a convenience function that allows it to apply a single operation
    to all selected objects.
    """

    @classmethod
    def add(cls, object: SceneNode) -> None:
        if object not in cls.__selection:
            cls.__selection.append(object)
            object.transformationChanged.connect(cls._onTransformationChanged)
            cls._onTransformationChanged(object)
            cls.selectionChanged.emit()

    @classmethod
    def remove(cls, object: SceneNode) -> None:
        if object in cls.__selection:
            cls.__selection.remove(object)
            cls.unsetFace(object)
            object.transformationChanged.disconnect(cls._onTransformationChanged)
            cls._onTransformationChanged(object)
            cls.selectionChanged.emit()

    @classmethod
    def getFaceSelectMode(cls) -> bool:
        return cls.__face_select_mode

    @classmethod
    def setFaceSelectMode(cls, select: bool) -> None:
        if select != cls.__face_select_mode:
            cls.__face_select_mode = select
            cls.selectedFaceChanged.emit()

    @classmethod
    def setFace(cls, object: SceneNode, face_id: int) -> None:
        # Don't force-add the object, as the parent may be the 'actual' selected one.
        cls.__selected_face = (object, face_id)
        cls.selectedFaceChanged.emit()

    @classmethod
    def unsetFace(cls, object: Optional["SceneNode"] = None) -> None:
        if object is None or cls.__selected_face is None or object == cls.__selected_face[0]:
            cls.__selected_face = None
            cls.selectedFaceChanged.emit()

    @classmethod
    def toggleFace(cls, object: SceneNode, face_id: int) -> None:
        current_face = cls.__selected_face
        if current_face is None or object != current_face[0] or face_id != current_face[1]:
            cls.setFace(object, face_id)
        else:
            cls.unsetFace(object)

    @classmethod
    def hoverFace(cls, object: SceneNode, face_id: int) -> None:
        current_hover = cls.__hover_face
        if current_hover is None or object != current_hover[0] or face_id != current_hover[1]:
            # Don't force-add the object, as the parent may be the 'actual' selected one.
            cls.__hover_face = (object, face_id)
            cls.hoverFaceChanged.emit()

    @classmethod
    def unhoverFace(cls, object: Optional["SceneNode"] = None) -> None:
        if object is None or not cls.__hover_face or object == cls.__hover_face[0]:
            cls.__hover_face = None
            cls.hoverFaceChanged.emit()

    @classmethod
    def getCount(cls) -> int:
        """Get number of selected objects"""

        return len(cls.__selection)

    @classmethod
    def getAllSelectedObjects(cls) -> List[SceneNode]:
        return cls.__selection

    @classmethod
    def getSelectedFace(cls) -> Optional[Tuple[SceneNode, int]]:
        return cls.__selected_face

    @classmethod
    def getHoverFace(cls) -> Optional[Tuple[SceneNode, int]]:
        return cls.__hover_face

    @classmethod
    def getBoundingBox(cls) -> AxisAlignedBox:
        bounding_box = None  # don't start with an empty bounding box, because that includes (0,0,0)
        for node in cls.__selection:
            if not bounding_box:
                bounding_box = node.getBoundingBox()
            else:
                bounding_box = bounding_box + node.getBoundingBox()

        if not bounding_box:
            bounding_box = AxisAlignedBox.Null

        return bounding_box

    @classmethod
    def getSelectedObject(cls, index: int) -> Optional[SceneNode]:
        """Get selected object by index

        :param index: index of the object to return
        :returns: selected object or None if index was incorrect / not found
        """

        try:
            return cls.__selection[index]
        except IndexError:
            return None

    @classmethod
    def isSelected(cls, object: SceneNode) -> bool:
        return object in cls.__selection

    @classmethod
    def clear(cls) -> None:
        cls.__selection.clear()
        cls.selectionChanged.emit()

    @classmethod
    def clearFace(cls) -> None:
        cls.__selected_face = None
        cls.__hover_face = None
        cls.selectedFaceChanged.emit()
        cls.hoverFaceChanged.emit()

    @classmethod
    def hasSelection(cls) -> bool:
        """Check if anything is selected at all."""

        return bool(cls.__selection)

    selectionChanged = Signal()

    selectionCenterChanged = Signal()

    selectedFaceChanged = Signal()

    hoverFaceChanged = Signal()

    @classmethod
    def getSelectionCenter(cls) -> Vector:
        if cls.__selection_center is None:
            cls.__selection_center = cls.getBoundingBox().center
        return cls.__selection_center

    @classmethod
    def applyOperation(cls, operation, *args, **kwargs):
        """Apply an operation to the entire selection

        This will create and push an operation onto the operation stack. Dependent
        on whether there is one item selected or multiple it will be just the
        operation or a grouped operation containing the operation for each selected
        node.

        :param operation: :type{Class} The operation to create and push. It should take a SceneNode as first positional parameter.
        :param args: The additional positional arguments passed along to the operation constructor.
        :param kwargs: The additional keyword arguments that will be passed along to the operation constructor.

        :return: list of instantiated operations
        """

        if not cls.__selection:
            return

        operations = []

        if len(cls.__selection) == 1:
            node = cls.__selection[0]
            op = operation(node, *args, **kwargs)
            operations.append(op)
        else:
            op = GroupedOperation()

            for node in Selection.getAllSelectedObjects():
                sub_op = operation(node, *args, **kwargs)
                op.addOperation(sub_op)
                operations.append(sub_op)

        op.push()
        return operations

    @classmethod
    def _onTransformationChanged(cls, _) -> None:
        cls.__selection_center = None
        cls.selectionCenterChanged.emit()

    __selection = []    # type: List[SceneNode]
    __selection_center = None  # type: Optional[Vector]
    __selected_face = None    # type: Optional[Tuple[SceneNode, int]]
    __hover_face = None    # type: Optional[Tuple[SceneNode, int]]
    __face_select_mode = False