Scale geometry in VR by using controllers¶
This is the script that is part of the example scene ObjectScaling.vpb. The default pointer interaction is used to implement the scaling. For this, the device actions of the pointer are connected to methods of the GeometryScaler class. Further information of the connection to default interactions can be found in the example “Connect to signals of device actions”.
A geometry can be selected with the ray of the pointer, by pressing the trigger completely down. Then a ray on the second controller is activated. Scaling is done by targeting the same object with the second controller and also pressing the trigger completely down. When the controllers are moved, the scale of the geometry is adjusted. The scaling can be stopped by releasing the trigger on one of the controllers or if one of the rays does not intersect with the geometry anymore.
1# © 2024 Autodesk, Inc. All rights reserved.
2
3class GeometryScaler:
4 def __init__(self):
5
6 # Init all the class variables
7 # Two devices are needed. The major device is the one that starts the whole scaling procedure and selects the object
8 self.majorDevice = vrdVRDevice()
9 self.secondaryDevice = vrdVRDevice()
10 # Flags that indicate the current state
11 self.objectSelected = False
12 self.isSelecting = False
13 self.isScaling = False
14 # Picked node
15 self.hitNode = vrdNode()
16 # Hitpoints of the controller rays
17 self.hitPoint1 = PySide6.QtGui.QVector3D(0.0, 0.0, 0.0)
18 self.hitPoint2 = PySide6.QtGui.QVector3D(0.0, 0.0, 0.0)
19 # Distance of hitpoints, when the scaling starts
20 self.initialDistance = 0.0
21
22 # Get the default pointer actions ...
23 pointer = vrDeviceService.getInteraction("Pointer")
24 self.startSelectionAction = pointer.getControllerAction("prepare")
25 self.selectAction = pointer.getControllerAction("start")
26 self.unselectAction = pointer.getControllerAction("execute")
27 self.stopSelectionAction = pointer.getControllerAction("abort")
28 # ... and connect the corresponding methods
29 self.startSelectionAction.signal().triggered.connect(self.startSelection)
30 self.selectAction.signal().triggered.connect(self.selectElement)
31 self.unselectAction.signal().triggered.connect(self.unselectElement)
32 self.stopSelectionAction.signal().triggered.connect(self.stopSelection)
33
34 # Get the controllers for easy access
35 self.leftController = vrDeviceService.getVRDevice("left-controller")
36 self.rightController = vrDeviceService.getVRDevice("right-controller")
37
38
39 def startSelection(self, action, device):
40 # Check if the state and device is correct
41 if self.objectSelected or self.isSelecting or self.isSecondaryDevice(device):
42 return
43
44 # Update which device is major and secondary device
45 self.updateDevices(device.getName())
46 # Set current state
47 self.isSelecting = True
48
49 def selectElement(self, action, device):
50 # If the major device selects a node it is marked for scaling,
51 # if the secondary device selects a node, it actually starts the scaling.
52 if self.isMajorDevice(device):
53 self.markNodeForScaling(device)
54 elif self.isSecondaryDevice(device):
55 self.startScaling(device)
56
57 def markNodeForScaling(self, device):
58 # Check if the state is correct
59 if not self.isSelecting:
60 return
61
62 # Intersect the pick ray with the scene
63 intersection = device.pick()
64 if not intersection.hasHit():
65 return
66
67 # Assign what actually has been intersected
68 self.hitNode = intersection.getNode()
69 self.hitPoint1 = intersection.getPoint()
70 self.isSelecting = False
71 self.objectSelected = True
72
73 # Activate the ray on the secondary device, which is needed for scaling
74 self.secondaryDevice.enableRay("controllerhandle")
75
76 def startScaling(self, device):
77 # Check if the state is correct
78 if not self.objectSelected:
79 return
80
81 # Intersect the pick ray of the secondary device with the scene
82 intersection = self.secondaryDevice.pick()
83 if not intersection.hasHit():
84 return
85
86 # Check if both rays intersect with the same node
87 node = intersection.getNode()
88 if node.getObjectId() != self.hitNode.getObjectId():
89 return
90
91 # Get the hintpoint and calculate the initial distance
92 self.hitPoint2 = intersection.getPoint()
93 self.initialDistance = self.hitPoint1.distanceToPoint(self.hitPoint2)
94 # Get the current scale of the node
95 self.initialScale = getTransformNodeScale(self.hitNode)
96
97 # Connect the actual scaling method here
98 self.majorDevice.signal().moved.connect(self.scale)
99
100 def unselectElement(self, action, device):
101 # Reset everything
102 self.stopScaling()
103 self.objectSelected = False
104 self.hitNode = vrdNode()
105 self.secondaryDevice.disableRay()
106
107 def stopScaling(self):
108 # Check the state
109 if not self.isScaling:
110 return
111
112 # Disconnect after scaling
113 self.majorDevice.signal().moved.disconnect(self.scale)
114 # Update the state
115 self.isScaling = False
116
117 def stopSelection(self, action, device):
118 if not self.isMajorDevice(device):
119 return
120
121 # Update the state
122 self.isSelecting = False
123 # Reset devices
124 self.majorDevice = vrdVRDevice()
125 self.secondaryDevice = vrdVRDevice()
126
127 def isMajorDevice(self, device):
128 return device.getName() == self.majorDevice.getName()
129
130 def isSecondaryDevice(self, device):
131 return device.getName() == self.secondaryDevice.getName()
132
133 def updateDevices(self, majorName):
134 # Update by name, which device is the major device and which is secondary
135 if majorName == self.leftController.getName():
136 self.majorDevice = self.leftController
137 self.secondaryDevice = self.rightController
138 else:
139 self.majorDevice = self.rightController
140 self.secondaryDevice = self.leftController
141
142 def scale(self, device):
143 intersection1 = self.majorDevice.pick()
144 intersection2 = self.secondaryDevice.pick()
145
146 self.hitPoint1 = intersection1.getPoint()
147 self.hitPoint2 = intersection2.getPoint()
148
149 nodeId1 = intersection1.getNode().getObjectId()
150 nodeId2 = intersection2.getNode().getObjectId()
151
152 # Check if both rays intersect with the same node
153 if nodeId1 != self.hitNode.getObjectId() or nodeId2 != self.hitNode.getObjectId():
154 self.majorDevice.signal().moved.disconnect(self.scale)
155 self.isScaling = False
156 return
157
158 # Update state
159 self.isScaling = True
160
161 # Calculate the scale factor depending on the distance of the two hitpoints
162 distance = self.hitPoint1.distanceToPoint(self.hitPoint2)
163 scaleFactor = max(min(distance / self.initialDistance, 5.0), 0.2)
164 scaleX = scaleFactor * self.initialScale.x()
165 scaleY = scaleFactor * self.initialScale.y()
166 scaleZ = scaleFactor * self.initialScale.z()
167
168 # Scale node
169 setTransformNodeScale(self.hitNode, scaleX, scaleY, scaleZ)
170
171scaler = GeometryScaler()