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
|
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from UM.Event import MouseEvent, KeyEvent
from UM.Tool import Tool
from UM.Application import Application
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Logger import Logger
from PyQt5 import QtCore, QtWidgets
## Provides the tool to select meshes and groups
#
# Note that the tool has two implementations for different modes of selection:
# Pixel Selection Mode and BoundingBox Selection Mode. Of these two, only Pixel Selection Mode
# is in active use. BoundingBox Selection Mode may not be functional.
class SelectionTool(Tool):
PixelSelectionMode = 1
BoundingBoxSelectionMode = 2
def __init__(self):
super().__init__()
self._scene = Application.getInstance().getController().getScene()
self._renderer = Application.getInstance().getRenderer()
self._selection_pass = None
self._selection_mode = self.PixelSelectionMode
self._ctrl_is_active = None # Ctrl modifier key is used for sub-selection
self._alt_is_active = None
self._shift_is_active = None # Shift modifier key is used for multi-selection
## Prepare modifier-key variables on each event
#
# \param event type(Event) event passed from event handler
def checkModifierKeys(self, event):
modifiers = QtWidgets.QApplication.keyboardModifiers()
self._shift_is_active = modifiers & QtCore.Qt.ShiftModifier
self._ctrl_is_active = modifiers & QtCore.Qt.ControlModifier
self._alt_is_active = modifiers & QtCore.Qt.AltModifier
## Set the selection mode
#
# The tool has two implementations for different modes of selection: PixelSelectionMode and BoundingboxSelectionMode.
# Of these two, only Pixel Selection Mode is in active use. BoundingBox Selection Mode may not be functional.
# \param mode type(SelectionTool enum)
def setSelectionMode(self, mode):
self._selection_mode = mode
## Handle mouse and keyboard events
#
# \param event type(Event)
def event(self, event):
# The selection renderpass is used to identify objects in the current view
if self._selection_pass is None:
self._selection_pass = self._renderer.getRenderPass("selection")
self.checkModifierKeys(event)
if event.type == MouseEvent.MousePressEvent and MouseEvent.LeftButton in event.buttons and self._controller.getToolsEnabled():
# Perform a selection operation
if self._selection_mode == self.PixelSelectionMode:
self._pixelSelection(event)
else:
self._boundingBoxSelection(event)
elif event.type == MouseEvent.MouseReleaseEvent and MouseEvent.LeftButton in event.buttons:
Application.getInstance().getController().toolOperationStopped.emit(self)
return False
## Handle mouse and keyboard events for bounding box selection
#
# \param event type(Event) passed from self.event()
def _boundingBoxSelection(self, event):
root = self._scene.getRoot()
ray = self._scene.getActiveCamera().getRay(event.x, event.y)
intersections = []
for node in BreadthFirstIterator(root):
if node.isEnabled() and not node.isLocked():
intersection = node.getBoundingBox().intersectsRay(ray)
if intersection:
intersections.append((node, intersection[0], intersection[1]))
if intersections:
intersections.sort(key=lambda k: k[1])
node = intersections[0][0]
if not Selection.isSelected(node):
if not self._shift_is_active:
Selection.clear()
Selection.add(node)
else:
Selection.clear()
## Handle mouse and keyboard events for pixel selection
#
# \param event type(Event) passed from self.event()
def _pixelSelection(self, event):
# Find a node id by looking at a pixel value at the requested location
if self._selection_pass:
item_id = self._selection_pass.getIdAtPosition(event.x, event.y)
else:
Logger.log("w", "Selection pass is None. getRenderPass('selection') returned None")
return
if not item_id and not self._shift_is_active:
Selection.clear()
return
# Find the scene-node which matches the node-id
for node in BreadthFirstIterator(self._scene.getRoot()):
if id(node) == item_id:
if self._isNodeInGroup(node):
is_selected = Selection.isSelected(self._findTopGroupNode(node))
else:
is_selected = Selection.isSelected(node)
if self._shift_is_active:
if is_selected:
# Deselect the scenenode and its sibblings in a group
if node.getParent():
if self._ctrl_is_active or not self._isNodeInGroup(node):
Selection.remove(node)
else:
Selection.remove(self._findTopGroupNode(node))
else:
# Select the scenenode and its sibblings in a group
if node.getParent():
if self._ctrl_is_active or not self._isNodeInGroup(node):
Selection.add(node)
else:
Selection.add(self._findTopGroupNode(node))
else:
if not is_selected or Selection.getCount() > 1:
# Select only the scenenode and its sibblings in a group
Selection.clear()
if node.getParent():
if self._ctrl_is_active or not self._isNodeInGroup(node):
Selection.add(node)
else:
Selection.add(self._findTopGroupNode(node))
elif self._isNodeInGroup(node) and self._ctrl_is_active:
Selection.clear()
Selection.add(node)
## Check whether a node is in a group
#
# \param node type(SceneNode)
# \return in_group type(boolean)
def _isNodeInGroup(self, node):
parent_node = node.getParent()
if not parent_node:
return False
return parent_node.callDecoration("isGroup")
## Get the top root group for a node
#
# \param node type(SceneNode)
# \return group type(SceneNode)
def _findTopGroupNode(self, node):
group_node = node
while group_node.getParent().callDecoration("isGroup"):
group_node = group_node.getParent()
return group_node
|