vedo.interactor_modes
Submodule to customize interaction modes.
1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3from dataclasses import dataclass 4from typing import Optional 5import numpy as np 6 7import vedo.vtkclasses as vtki 8 9__docformat__ = "google" 10 11__doc__ = """Submodule to customize interaction modes.""" 12 13 14class MousePan(vtki.vtkInteractorStyleUser): 15 """ 16 Interaction mode to pan the scene by dragging the mouse. 17 18 Controls: 19 - Left mouse button will pan the scene. 20 - Mouse middle button up/down is elevation, and left and right is azimuth. 21 - Right mouse button is rotate (left/right movement) and zoom in/out (up/down movement) 22 - Mouse scroll wheel is zoom in/out 23 24 Arguments: 25 enable_pan: (bool) 26 Enable panning the scene. 27 enable_zoom: (bool) 28 Enable zooming the scene. 29 enable_rotate: (bool) 30 Enable rotating the scene. 31 speed: (float) 32 Speed factor of the interaction, e.i. how fast the scene must move. 33 """ 34 35 def __init__(self, enable_pan=True, enable_zoom=True, enable_rotate=True, speed=1.0): 36 37 super().__init__() 38 39 self.left = False 40 self.middle = False 41 self.right = False 42 self.speed = speed 43 44 self.interactor = None 45 self.renderer = None 46 self.camera = None 47 48 self.oldpickD = [] 49 self.newpickD = [] 50 self.oldpickW = np.array([0, 0, 0, 0], dtype=float) 51 self.newpickW = np.array([0, 0, 0, 0], dtype=float) 52 self.fpD = np.array([0, 0, 0], dtype=float) 53 self.fpW = np.array([0, 0, 0], dtype=float) 54 self.posW = np.array([0, 0, 0], dtype=float) 55 self.motionD = np.array([0, 0], dtype=float) 56 self.motionW = np.array([0, 0, 0], dtype=float) 57 58 # print("MousePan: Left mouse button to pan the scene.") 59 self.AddObserver("LeftButtonPressEvent", self._left_down) 60 self.AddObserver("LeftButtonReleaseEvent", self._left_up) 61 62 if enable_rotate: 63 self.AddObserver("MiddleButtonPressEvent", self._middle_down) 64 self.AddObserver("MiddleButtonReleaseEvent", self._middle_up) 65 66 if enable_zoom: 67 self.AddObserver("RightButtonPressEvent", self._right_down) 68 self.AddObserver("RightButtonReleaseEvent", self._right_up) 69 self.AddObserver("MouseWheelForwardEvent", self._wheel_forward) 70 self.AddObserver("MouseWheelBackwardEvent", self._wheel_backward) 71 72 if enable_pan: 73 self.AddObserver("MouseMoveEvent", self._mouse_move) 74 75 def _get_motion(self): 76 self.oldpickD = np.array(self.interactor.GetLastEventPosition()) 77 self.newpickD = np.array(self.interactor.GetEventPosition()) 78 self.motionD = (self.newpickD - self.oldpickD) / 4 79 self.camera = self.renderer.GetActiveCamera() 80 self.fpW = self.camera.GetFocalPoint() 81 self.posW = self.camera.GetPosition() 82 self.ComputeWorldToDisplay(self.renderer, self.fpW[0], self.fpW[1], self.fpW[2], self.fpD) 83 focaldepth = self.fpD[2] 84 self.ComputeDisplayToWorld( 85 self.renderer, self.oldpickD[0], self.oldpickD[1], focaldepth, self.oldpickW 86 ) 87 self.ComputeDisplayToWorld( 88 self.renderer, self.newpickD[0], self.newpickD[1], focaldepth, self.newpickW 89 ) 90 self.motionW[:3] = (self.oldpickW[:3] - self.newpickW[:3]) * self.speed 91 92 def _mouse_left_move(self): 93 self._get_motion() 94 self.camera.SetFocalPoint(self.fpW[:3] + self.motionW[:3]) 95 self.camera.SetPosition(self.posW[:3] + self.motionW[:3]) 96 self.interactor.Render() 97 98 def _mouse_middle_move(self): 99 self._get_motion() 100 if abs(self.motionD[0]) > abs(self.motionD[1]): 101 self.camera.Azimuth(-2 * self.speed * self.motionD[0]) 102 else: 103 self.camera.Elevation(-self.speed *self.motionD[1]) 104 self.interactor.Render() 105 106 def _mouse_right_move(self): 107 self._get_motion() 108 if abs(self.motionD[0]) > abs(self.motionD[1]): 109 self.camera.Azimuth(-2.0 * self.speed * self.motionD[0]) 110 else: 111 self.camera.Zoom(1 + self.motionD[1] / 100) 112 self.interactor.Render() 113 114 def _mouse_wheel_forward(self): 115 self.camera = self.renderer.GetActiveCamera() 116 self.camera.Zoom(1.1 * self.speed) 117 self.interactor.Render() 118 119 def _mouse_wheel_backward(self): 120 self.camera = self.renderer.GetActiveCamera() 121 self.camera.Zoom(0.9 * self.speed) 122 self.interactor.Render() 123 124 def _left_down(self, _w, _e): 125 self.left = True 126 127 def _left_up(self, _w, _e): 128 self.left = False 129 130 def _middle_down(self, _w, _e): 131 self.middle = True 132 133 def _middle_up(self, _w, _e): 134 self.middle = False 135 136 def _right_down(self, _w, _e): 137 self.right = True 138 139 def _right_up(self, _w, _e): 140 self.right = False 141 142 def _wheel_forward(self, _w, _e): 143 self._mouse_wheel_forward() 144 145 def _wheel_backward(self, _w, _e): 146 self._mouse_wheel_backward() 147 148 def _mouse_move(self, _w, _e): 149 if self.left: 150 self._mouse_left_move() 151 if self.middle: 152 self._mouse_middle_move() 153 if self.right: 154 self._mouse_right_move() 155 156 157############################################################################# 158class FlyOverSurface(vtki.vtkInteractorStyleUser): 159 """ 160 Interaction mode to fly over a surface. 161 162 Controls: 163 - Press arrows to move the camera in the plane of the surface. 164 - "t" (or "PageUp") will move the camera upper in z. 165 - "g" (or "PageDown") will move the camera lower in z. 166 - "x" and "X" will reset the camera to the default position towards positive or negative x. 167 - "y" and "Y" will reset the camera to the default position towards positive or negative y. 168 - "." and "," will rotate azimuth to the right or left. 169 - "r" will reset the camera to the default position. 170 171 Left button: Select a point on the surface to focus the camera on it. 172 """ 173 174 def __init__(self, move_step=0.05, angle_step=1.5): 175 """ 176 Interaction mode to fly over a surface. 177 178 Arguments: 179 move_step: float, optional 180 The step size for moving the camera in the plane of the surface. 181 angle_step: float, optional 182 The step size for rotating the camera. 183 184 Example: 185 [interaction_modes3.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/interaction_modes3.py) 186 """ 187 188 super().__init__() 189 190 self.interactor = None # filled in plotter.py 191 self.renderer = None # filled in plotter.py 192 193 self.angle_step = angle_step 194 self.move_step = move_step 195 196 self.tleft = vtki.vtkTransform() 197 self.tleft.RotateZ(self.angle_step) 198 199 self.tright = vtki.vtkTransform() 200 self.tright.RotateZ(-self.angle_step) 201 202 self.bounds = () 203 204 self.AddObserver("KeyPressEvent", self._key) 205 self.AddObserver("RightButtonPressEvent", self._right_button_press) 206 self.AddObserver("MouseWheelForwardEvent", self._mouse_wheel_forward) 207 self.AddObserver("MouseWheelBackwardEvent", self._mouse_wheel_backward) 208 self.AddObserver("LeftButtonPressEvent", self._left_button_press) 209 210 @property 211 def camera(self): 212 """Return the active camera.""" 213 return self.renderer.GetActiveCamera() 214 215 @property 216 def position(self): 217 """Return the camera position.""" 218 return np.array(self.camera.GetPosition()) 219 220 @position.setter 221 def position(self, value): 222 """Set the camera position.""" 223 self.camera.SetPosition(value[:3]) 224 self.camera.SetViewUp(0.00001, 0, 1) 225 self.renderer.ResetCameraClippingRange() 226 227 @property 228 def focal_point(self): 229 """Return the camera focal point.""" 230 return np.array(self.camera.GetFocalPoint()) 231 232 @focal_point.setter 233 def focal_point(self, value): 234 """Set the camera focal point.""" 235 self.camera.SetFocalPoint(value[:3]) 236 self.camera.SetViewUp(0.00001, 0, 1) 237 self.renderer.ResetCameraClippingRange() 238 239 def _right_button_press(self, _obj, event): 240 """Right button press.""" 241 # print("Right button", event) 242 self._key("Down", event) 243 244 def _left_button_press(self, obj, _event): 245 """Left button press.""" 246 # print("Left button", event) 247 newPickPoint = [0, 0, 0, 0] 248 focalDepth = 0 249 x, y = obj.interactor.GetEventPosition() 250 self.ComputeDisplayToWorld(self.renderer, x, y, focalDepth, newPickPoint) 251 self.focal_point = np.array(newPickPoint) 252 self.interactor.Render() 253 254 def _mouse_wheel_backward(self, _obj, event): 255 """Mouse wheel backward.""" 256 # print("mouse_wheel_backward ", event) 257 self._key("Down", event) 258 259 def _mouse_wheel_forward(self, _obj, event): 260 """Mouse wheel forward.""" 261 # print("mouse_wheel_forward ", event) 262 self._key("Up", event) 263 264 def _key(self, obj, event): 265 """Key press.""" 266 if "Mouse" in event or "Button" in event: 267 k = obj 268 else: 269 k = obj.GetKeySym() 270 if obj.GetShiftKey(): 271 k = k.upper() 272 # print("FlyOverSurface. Key press", k) 273 274 if k in ["r", "x"]: 275 # print("r pressed, reset camera") 276 self.bounds = self.renderer.ComputeVisiblePropBounds() 277 x0, x1, y0, y1, z0, z1 = self.bounds 278 dx = x1 - x0 279 z = max(z1 * 1, z0 + (y1 - y0) / 4, z0 + (x1 - x0) / 4) 280 self.position = [x0 - dx, (y0 + y1) / 2, z] 281 self.focal_point = [x0 + dx / 2, (y0 + y1) / 2, z] 282 elif k in ["X"]: 283 # print("X pressed, reset camera") 284 self.bounds = self.renderer.ComputeVisiblePropBounds() 285 x0, x1, y0, y1, z0, z1 = self.bounds 286 dx = x1 - x0 287 z = max(z1 * 1, (y1 - y0) / 4, (x1 - x0) / 4) 288 self.position = [x1 + dx, (y0 + y1) / 2, z] 289 self.focal_point = [x0 - dx / 2, (y0 + y1) / 2, z] 290 291 elif k in ["y"]: 292 # print("y pressed, reset camera") 293 self.bounds = self.renderer.ComputeVisiblePropBounds() 294 x0, x1, y0, y1, z0, z1 = self.bounds 295 dy = y1 - y0 296 z = max(z1 * 1, z0 + (y1 - y0) / 4, z0 + (x1 - x0) / 4) 297 self.position = [(x0 + x1) / 2, y0 - dy, z] 298 self.focal_point = [(x0 + x1) / 2, y1 + dy / 2, z] 299 elif k in ["Y"]: 300 # print("Y pressed, reset camera") 301 self.bounds = self.renderer.ComputeVisiblePropBounds() 302 x0, x1, y0, y1, z0, z1 = self.bounds 303 dy = y1 - y0 304 z = max(z1 * 1, z0 + (y1 - y0) / 4, z0 + (x1 - x0) / 4) 305 self.position = [(x0 + x1) / 2, y1 + dy, z] 306 self.focal_point = [(x0 + x1) / 2, y0 - dy / 2, z] 307 308 elif k in ["Up", "w"]: 309 # print("Up pressed, move forward") 310 self.bounds = self.renderer.ComputeVisiblePropBounds() 311 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 312 dx = self.move_step * diagonal 313 p = np.array(self.camera.GetPosition()) 314 v = np.array(self.camera.GetDirectionOfProjection()) 315 newp = p + dx * v 316 self.position = [newp[0], newp[1], p[2]] 317 self.focal_point = self.focal_point + dx * v 318 elif k in ["Down", "s"]: 319 # print("Down pressed, move backward") 320 self.bounds = self.renderer.ComputeVisiblePropBounds() 321 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 322 dx = self.move_step * diagonal 323 p = np.array(self.camera.GetPosition()) 324 v = np.array(self.camera.GetDirectionOfProjection()) 325 newp = p - dx * v 326 self.position = [newp[0], newp[1], p[2]] 327 self.focal_point = self.focal_point - dx * v 328 329 elif k in ["Left", "a"]: 330 # print("Left pressed, rotate to the left") 331 self.bounds = self.renderer.ComputeVisiblePropBounds() 332 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 333 w = np.array(self.camera.GetDirectionOfProjection()) 334 p = np.array(self.camera.GetPosition()) 335 w2 = np.array(self.tleft.TransformFloatPoint(w)) 336 self.focal_point = self.focal_point + np.linalg.norm(p-self.focal_point) * w2 337 338 elif k in ["Right", "d"]: 339 # print("Right pressed, rotate to the right") 340 self.bounds = self.renderer.ComputeVisiblePropBounds() 341 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 342 w = np.array(self.camera.GetDirectionOfProjection()) 343 p = np.array(self.camera.GetPosition()) 344 w2 = np.array(self.tright.TransformFloatPoint(w)) 345 self.focal_point = self.focal_point + np.linalg.norm(p-self.focal_point) * w2 346 347 elif k in ["t", "Prior"]: 348 # print("t pressed, move z up") 349 self.bounds = self.renderer.ComputeVisiblePropBounds() 350 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 351 dx = self.move_step * diagonal 352 p = self.position 353 self.position = [p[0], p[1], p[2] + dx / 4] 354 355 elif k in ["g", "Next"]: 356 # print("g pressed, move z down") 357 self.bounds = self.renderer.ComputeVisiblePropBounds() 358 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 359 dx = self.move_step * diagonal 360 p = self.position 361 self.position = [p[0], p[1], p[2] - dx / 4] 362 363 elif k in ["comma", "COMMA"]: 364 # print("< pressed, rotate azimuth to the left") 365 self.bounds = self.renderer.ComputeVisiblePropBounds() 366 scene_center = [ 367 self.bounds[0] + self.bounds[1], 368 self.bounds[2] + self.bounds[3], 369 self.bounds[4] + self.bounds[5], 370 ] 371 scene_center = np.array(scene_center) / 2 372 p = self.position 373 v = p - scene_center 374 newp = scene_center + self.tleft.TransformFloatPoint(v) 375 self.position = [newp[0], newp[1], p[2]] 376 377 elif k in ["period", "PERIOD"]: 378 # print("< pressed, rotate azimuth to the right") 379 self.bounds = self.renderer.ComputeVisiblePropBounds() 380 scene_center = [ 381 self.bounds[0] + self.bounds[1], 382 self.bounds[2] + self.bounds[3], 383 self.bounds[4] + self.bounds[5], 384 ] 385 scene_center = np.array(scene_center) / 2 386 p = self.position 387 v = p - scene_center 388 newp = scene_center + self.tright.TransformFloatPoint(v) 389 self.position = [newp[0], newp[1], p[2]] 390 391 elif k in ["q", "Return"]: 392 self.interactor.ExitCallback() 393 return 394 395 else: 396 return 397 398 self.interactor.Render() 399 400 401################################################################################### 402@dataclass 403class _BlenderStyleDragInfo: 404 """Data structure containing the data required to execute dragging a node""" 405 406 # Scene related 407 dragged_node = None # Node 408 409 # VTK related 410 actors_dragging: list 411 dragged_actors_original_positions: list # original VTK positions 412 start_position_3d = np.array((0, 0, 0)) # start position of the cursor 413 414 delta = np.array((0, 0, 0)) 415 416 def __init__(self): 417 self.actors_dragging = [] 418 self.dragged_actors_original_positions = [] 419 420 421############################################### 422class BlenderStyle(vtki.vtkInteractorStyleUser): 423 """ 424 Create an interaction style using the Blender default key-bindings. 425 426 Camera action code is largely a translation of 427 [this](https://github.com/Kitware/VTK/blob/master/Interaction/Style/vtkInteractorStyleTrackballCamera.cxx) 428 Rubber band code 429 [here](https://gitlab.kitware.com/updega2/vtk/-/blob/d324b2e898b0da080edee76159c2f92e6f71abe2/Rendering/vtkInteractorStyleRubberBandZoom.cxx) 430 431 Interaction: 432 - Left button: select 433 - Left button drag: rubber band select or line select, depends on the dragged distance 434 435 - Middle button : rotate 436 - Middle button + shift : pan 437 - Middle button + ctrl : zoom 438 - Middle button + alt : center view on picked point 439 - Middle button + alt : zoom rubber band 440 441 - Mouse wheel : zoom 442 443 - Right key click: reserved for context-menu 444 445 446 Keys: 447 - 2 or 3 : toggle perspective view 448 - a : zoom all 449 - x,y,z : view direction (toggles positive and negative) 450 - left/right arrows: rotate 45 deg clockwise/ccw about z-axis, snaps to nearest 45 deg 451 - b : box zoom 452 - m : mouse middle lock (toggles) 453 - space : same as middle mouse button 454 - g : grab (move actors) 455 - enter : accept drag 456 - esc : cancel drag, call callbackEscape 457 458 459 Laptop Mode 460 ----------- 461 Use space or `m` as replacement for middle button (`m` is sticky, space is not), 462 if `callback_any_key` is assigned then this function is called on every key press. 463 If this function returns True then further processing of events is stopped. 464 465 466 Moving Actors 467 ------------- 468 Actors can be moved interactively by the user. 469 To support custom groups of actors to be moved as a whole the following system 470 is implemented: 471 472 When 'g' is pressed (grab) then a list of all the actors that are being dragged. 473 By default this is the selection, but this may be altered. 474 Drag is accepted using enter, click, or g. Drag is cancelled by esc 475 476 Events 477 ------ 478 - `callback_start_drag` is called when initializing the drag. 479 This is when to assign actors and other data to draginfo. 480 - `callback_end_drag` is called when the drag is accepted. 481 482 Responding to other events 483 -------------------------- 484 `callback_camera_direction_changed` : executed when camera has rotated but before re-rendering 485 486 Example: 487 [interaction_modes2.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/interaction_modes2.py) 488 489 .. note:: 490 This class is based on R. de Bruin's 491 [DAVE](https://github.com/RubendeBruin/DAVE/blob/master/src/DAVE/visual_helpers/vtkBlenderLikeInteractionStyle.py) 492 implementation as discussed [in this issue](https://github.com/marcomusy/vedo/discussions/788). 493 """ 494 495 def __init__(self): 496 497 super().__init__() 498 499 self.interactor = None 500 self.renderer = None 501 502 # callback_select is called whenever one or mode props are selected. 503 # callback will be called with a list of props of which the first entry 504 # is prop closest to the camera. 505 self.callback_select = None 506 self.callback_start_drag = None 507 self.callback_end_drag = None 508 self.callback_escape_key = None 509 self.callback_delete_key = None 510 self.callback_focus_key = None 511 self.callback_any_key = None 512 self.callback_measure = None # callback with argument float (meters) 513 self.callback_camera_direction_changed = None 514 515 # active drag 516 # assigned to a _BlenderStyleDragInfo object when dragging is active 517 self.draginfo: Optional[_BlenderStyleDragInfo] = None 518 519 # picking 520 self.picked_props = [] # will be filled by latest pick 521 522 # settings 523 self.mouse_motion_factor = 20 524 self.mouse_wheel_motion_factor = 0.1 525 self.zoom_motion_factor = 0.25 526 527 # internals 528 self.start_x = 0 # start of a drag 529 self.start_y = 0 530 self.end_x = 0 531 self.end_y = 0 532 533 self.middle_mouse_lock = False 534 self.middle_mouse_lock_actor = None # will be created when required 535 536 # Special Modes 537 self._is_box_zooming = False 538 539 # holds an image of the renderer output at the start of a drawing event 540 self._pixel_array = vtki.vtkUnsignedCharArray() 541 542 self._upside_down = False 543 544 self._left_button_down = False 545 self._middle_button_down = False 546 547 self.AddObserver("RightButtonPressEvent", self.right_button_press) 548 self.AddObserver("RightButtonReleaseEvent", self.right_button_release) 549 self.AddObserver("MiddleButtonPressEvent", self.middle_button_press) 550 self.AddObserver("MiddleButtonReleaseEvent", self.middle_button_release) 551 self.AddObserver("MouseWheelForwardEvent", self.mouse_wheel_forward) 552 self.AddObserver("MouseWheelBackwardEvent", self.mouse_wheel_backward) 553 self.AddObserver("LeftButtonPressEvent", self.left_button_press) 554 self.AddObserver("LeftButtonReleaseEvent", self.left_button_release) 555 self.AddObserver("MouseMoveEvent", self.mouse_move) 556 self.AddObserver("WindowResizeEvent", self.window_resized) 557 # ^does not seem to fire! 558 self.AddObserver("KeyPressEvent", self.key_press) 559 self.AddObserver("KeyReleaseEvent", self.key_release) 560 561 def right_button_press(self, _obj, _event): 562 pass 563 564 def right_button_release(self, _obj, _event): 565 pass 566 567 def middle_button_press(self, _obj, _event): 568 self._middle_button_down = True 569 570 def middle_button_release(self, _obj, _event): 571 self._middle_button_down = False 572 573 # perform middle button focus event if ALT is down 574 if self.GetInteractor().GetAltKey(): 575 # print("Middle button released while ALT is down") 576 # try to pick an object at the current mouse position 577 rwi = self.GetInteractor() 578 self.start_x, self.start_y = rwi.GetEventPosition() 579 props = self.perform_picking_on_selection() 580 581 if props: 582 self.focus_on(props[0]) 583 584 def mouse_wheel_backward(self, _obj, _event): 585 self.move_mouse_wheel(-1) 586 587 def mouse_wheel_forward(self, _obj, _event): 588 self.move_mouse_wheel(1) 589 590 def mouse_move(self, _obj, _event): 591 interactor = self.GetInteractor() 592 593 # Find the renderer that is active below the current mouse position 594 x, y = interactor.GetEventPosition() 595 self.FindPokedRenderer(x, y) 596 597 Shift = interactor.GetShiftKey() 598 Ctrl = interactor.GetControlKey() 599 Alt = interactor.GetAltKey() 600 601 MiddleButton = self._middle_button_down or self.middle_mouse_lock 602 603 # start with the special modes 604 if self._is_box_zooming: 605 self.draw_dragged_selection() 606 elif MiddleButton and not Shift and not Ctrl and not Alt: 607 self.rotate() 608 elif MiddleButton and Shift and not Ctrl and not Alt: 609 self.pan() 610 elif MiddleButton and Ctrl and not Shift and not Alt: 611 self.zoom() # Dolly 612 elif self.draginfo is not None: 613 self.execute_drag() 614 elif self._left_button_down and Ctrl and Shift: 615 self.draw_measurement() 616 elif self._left_button_down: 617 self.draw_dragged_selection() 618 619 self.InvokeEvent("InteractionEvent", None) 620 621 def move_mouse_wheel(self, direction): 622 rwi = self.GetInteractor() 623 624 # Find the renderer that is active below the current mouse position 625 x, y = rwi.GetEventPosition() 626 self.FindPokedRenderer(x, y) 627 628 # The movement 629 ren = self.GetCurrentRenderer() 630 631 # // Calculate the focal depth since we'll be using it a lot 632 camera = ren.GetActiveCamera() 633 viewFocus = camera.GetFocalPoint() 634 635 temp_out = [0, 0, 0] 636 self.ComputeWorldToDisplay( 637 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 638 ) 639 focalDepth = temp_out[2] 640 641 newPickPoint = [0, 0, 0, 0] 642 x, y = rwi.GetEventPosition() 643 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 644 645 # Has to recalc old mouse point since the viewport has moved, 646 # so can't move it outside the loop 647 oldPickPoint = [0, 0, 0, 0] 648 # xp, yp = rwi.GetLastEventPosition() 649 650 # find the center of the window 651 size = rwi.GetRenderWindow().GetSize() 652 xp = size[0] / 2 653 yp = size[1] / 2 654 655 self.ComputeDisplayToWorld(ren, xp, yp, focalDepth, oldPickPoint) 656 # Camera motion is reversed 657 move_factor = -1 * self.zoom_motion_factor * direction 658 659 motionVector = ( 660 move_factor * (oldPickPoint[0] - newPickPoint[0]), 661 move_factor * (oldPickPoint[1] - newPickPoint[1]), 662 move_factor * (oldPickPoint[2] - newPickPoint[2]), 663 ) 664 665 viewFocus = camera.GetFocalPoint() # do we need to do this again? Already did this 666 viewPoint = camera.GetPosition() 667 668 camera.SetFocalPoint( 669 motionVector[0] + viewFocus[0], 670 motionVector[1] + viewFocus[1], 671 motionVector[2] + viewFocus[2], 672 ) 673 camera.SetPosition( 674 motionVector[0] + viewPoint[0], 675 motionVector[1] + viewPoint[1], 676 motionVector[2] + viewPoint[2], 677 ) 678 679 # the zooming 680 factor = self.mouse_motion_factor * self.mouse_wheel_motion_factor 681 self.zoom_by_step(direction * factor) 682 683 def zoom_by_step(self, step): 684 if self.GetCurrentRenderer(): 685 self.StartDolly() 686 self.dolly(pow(1.1, step)) 687 self.EndDolly() 688 689 def left_button_press(self, _obj, _event): 690 if self._is_box_zooming: 691 return 692 if self.draginfo: 693 return 694 695 self._left_button_down = True 696 697 interactor = self.GetInteractor() 698 Shift = interactor.GetShiftKey() 699 Ctrl = interactor.GetControlKey() 700 701 if Shift and Ctrl: 702 if not self.GetCurrentRenderer().GetActiveCamera().GetParallelProjection(): 703 self.toggle_parallel_projection() 704 705 rwi = self.GetInteractor() 706 self.start_x, self.start_y = rwi.GetEventPosition() 707 self.end_x = self.start_x 708 self.end_y = self.start_y 709 710 self.initialize_screen_drawing() 711 712 def left_button_release(self, _obj, _event): 713 # print("LeftButtonRelease") 714 if self._is_box_zooming: 715 self._is_box_zooming = False 716 self.zoom_box(self.start_x, self.start_y, self.end_x, self.end_y) 717 return 718 719 if self.draginfo: 720 self.finish_drag() 721 return 722 723 self._left_button_down = False 724 725 interactor = self.GetInteractor() 726 727 Shift = interactor.GetShiftKey() 728 Ctrl = interactor.GetControlKey() 729 # Alt = interactor.GetAltKey() 730 731 if Ctrl and Shift: 732 pass # we were drawing the measurement 733 734 else: 735 if self.callback_select: 736 props = self.perform_picking_on_selection() 737 if props: # only call back if anything was selected 738 self.picked_props = tuple(props) 739 self.callback_select(props) 740 741 # remove the selection rubber band / line 742 self.GetInteractor().Render() 743 744 def key_press(self, obj, _event): 745 746 key = obj.GetKeySym() 747 KEY = key.upper() 748 749 # logging.info(f"Key Press: {key}") 750 if self.callback_any_key: 751 if self.callback_any_key(key): 752 return 753 754 if KEY == "M": 755 self.middle_mouse_lock = not self.middle_mouse_lock 756 self._update_middle_mouse_button_lock_actor() 757 elif KEY == "G": 758 if self.draginfo is not None: 759 self.finish_drag() 760 else: 761 if self.callback_start_drag: 762 self.callback_start_drag() 763 else: 764 self.start_drag() 765 # internally calls end-drag if drag is already active 766 elif KEY == "ESCAPE": 767 if self.callback_escape_key: 768 self.callback_escape_key() 769 if self.draginfo is not None: 770 self.cancel_drag() 771 elif KEY == "DELETE": 772 if self.callback_delete_key: 773 self.callback_delete_key() 774 elif KEY == "RETURN": 775 if self.draginfo: 776 self.finish_drag() 777 elif KEY == "SPACE": 778 self.middle_mouse_lock = True 779 # self._update_middle_mouse_button_lock_actor() 780 # self.GrabFocus("MouseMoveEvent", self) 781 # # TODO: grab and release focus; possible from python? 782 elif KEY == "B": 783 self._is_box_zooming = True 784 rwi = self.GetInteractor() 785 self.start_x, self.start_y = rwi.GetEventPosition() 786 self.end_x = self.start_x 787 self.end_y = self.start_y 788 self.initialize_screen_drawing() 789 elif KEY in ("2", "3"): 790 self.toggle_parallel_projection() 791 elif KEY == "A": 792 self.zoom_fit() 793 elif KEY == "X": 794 self.set_view_x() 795 elif KEY == "Y": 796 self.set_view_y() 797 elif KEY == "Z": 798 self.set_view_z() 799 elif KEY == "LEFT": 800 self.rotate_discrete_step(1) 801 elif KEY == "RIGHT": 802 self.rotate_discrete_step(-1) 803 elif KEY == "UP": 804 self.rotate_turtable_by(0, 10) 805 elif KEY == "DOWN": 806 self.rotate_turtable_by(0, -10) 807 elif KEY == "PLUS": 808 self.zoom_by_step(2) 809 elif KEY == "MINUS": 810 self.zoom_by_step(-2) 811 elif KEY == "F": 812 if self.callback_focus_key: 813 self.callback_focus_key() 814 815 self.InvokeEvent("InteractionEvent", None) 816 817 def key_release(self, obj, _event): 818 key = obj.GetKeySym() 819 KEY = key.upper() 820 # print(f"Key release: {key}") 821 if KEY == "SPACE": 822 if self.middle_mouse_lock: 823 self.middle_mouse_lock = False 824 self._update_middle_mouse_button_lock_actor() 825 826 def window_resized(self): 827 # print("window resized") 828 self.initialize_screen_drawing() 829 830 def rotate_discrete_step(self, movement_direction, step=22.5): 831 """ 832 Rotates CW or CCW to the nearest 45 deg angle 833 - includes some fuzzyness to determine about which axis 834 """ 835 camera = self.GetCurrentRenderer().GetActiveCamera() 836 837 step = np.deg2rad(step) 838 839 direction = -np.array(camera.GetViewPlaneNormal()) # current camera direction 840 841 if abs(direction[2]) < 0.7: 842 # horizontal view, rotate camera position about Z-axis 843 angle = np.arctan2(direction[1], direction[0]) 844 845 # find the nearest angle that is an integer number of steps 846 if movement_direction > 0: 847 angle = step * np.floor((angle + 0.1 * step) / step) + step 848 else: 849 angle = -step * np.floor(-(angle - 0.1 * step) / step) - step 850 851 dist = np.linalg.norm(direction[:2]) 852 direction[0] = np.cos(angle) * dist 853 direction[1] = np.sin(angle) * dist 854 855 self.set_camera_direction(direction) 856 857 else: # Top or bottom like view - rotate camera "up" direction 858 859 up = np.array(camera.GetViewUp()) 860 angle = np.arctan2(up[1], up[0]) 861 862 # find the nearest angle that is an integer number of steps 863 if movement_direction > 0: 864 angle = step * np.floor((angle + 0.1 * step) / step) + step 865 else: 866 angle = -step * np.floor(-(angle - 0.1 * step) / step) - step 867 868 dist = np.linalg.norm(up[:2]) 869 up[0] = np.cos(angle) * dist 870 up[1] = np.sin(angle) * dist 871 872 camera.SetViewUp(up) 873 camera.OrthogonalizeViewUp() 874 self.GetInteractor().Render() 875 876 def toggle_parallel_projection(self): 877 renderer = self.GetCurrentRenderer() 878 camera = renderer.GetActiveCamera() 879 camera.SetParallelProjection(not bool(camera.GetParallelProjection())) 880 self.GetInteractor().Render() 881 882 def set_view_x(self): 883 self.set_camera_plane_direction((1, 0, 0)) 884 885 def set_view_y(self): 886 self.set_camera_plane_direction((0, 1, 0)) 887 888 def set_view_z(self): 889 self.set_camera_plane_direction((0, 0, 1)) 890 891 def zoom_fit(self): 892 self.GetCurrentRenderer().ResetCamera() 893 self.GetInteractor().Render() 894 895 def set_camera_plane_direction(self, direction): 896 """ 897 Sets the camera to display a plane of which direction is the normal 898 - includes logic to reverse the direction if benificial 899 """ 900 camera = self.GetCurrentRenderer().GetActiveCamera() 901 902 direction = np.array(direction) 903 normal = camera.GetViewPlaneNormal() 904 # can not set the normal, need to change the position to do that 905 906 current_alignment = np.dot(normal, -direction) 907 if abs(current_alignment) > 0.9999: 908 # print("toggling") 909 direction = -np.array(normal) 910 elif current_alignment > 0: # find the nearest plane 911 # print("reversing to find nearest") 912 direction = -direction 913 914 self.set_camera_direction(-direction) 915 916 def set_camera_direction(self, direction): 917 """Sets the camera to this direction, sets view up if horizontal enough""" 918 direction = np.array(direction) 919 920 ren = self.GetCurrentRenderer() 921 camera = ren.GetActiveCamera() 922 rwi = self.GetInteractor() 923 924 pos = np.array(camera.GetPosition()) 925 focal = np.array(camera.GetFocalPoint()) 926 dist = np.linalg.norm(pos - focal) 927 928 pos = focal - dist * direction 929 camera.SetPosition(pos) 930 931 if abs(direction[2]) < 0.9: 932 camera.SetViewUp(0, 0, 1) 933 elif direction[2] > 0.9: 934 camera.SetViewUp(0, -1, 0) 935 else: 936 camera.SetViewUp(0, 1, 0) 937 938 camera.OrthogonalizeViewUp() 939 940 if self.GetAutoAdjustCameraClippingRange(): 941 ren.ResetCameraClippingRange() 942 943 if rwi.GetLightFollowCamera(): 944 ren.UpdateLightsGeometryToFollowCamera() 945 946 if self.callback_camera_direction_changed: 947 self.callback_camera_direction_changed() 948 949 self.GetInteractor().Render() 950 951 def perform_picking_on_selection(self): 952 """ 953 Performs 3d picking on the current dragged selection 954 955 If the distance between the start and endpoints is less than the threshold 956 then a SINGLE 3d prop is picked along the line. 957 958 The selection area is drawn by the rubber band and is defined by 959 `self.start_x, self.start_y, self.end_x, self.end_y` 960 """ 961 renderer = self.GetCurrentRenderer() 962 if not renderer: 963 return [] 964 965 assemblyPath = renderer.PickProp(self.start_x, self.start_y, self.end_x, self.end_y) 966 967 # re-pick in larger area if nothing is returned 968 if not assemblyPath: 969 self.start_x -= 2 970 self.end_x += 2 971 self.start_y -= 2 972 self.end_y += 2 973 assemblyPath = renderer.PickProp(self.start_x, self.start_y, self.end_x, self.end_y) 974 975 # The nearest prop (by Z-value) 976 if assemblyPath: 977 assert ( 978 assemblyPath.GetNumberOfItems() == 1 979 ), "Wrong assumption on number of returned nodes when picking" 980 nearest_prop = assemblyPath.GetItemAsObject(0).GetViewProp() 981 982 # all props 983 collection = renderer.GetPickResultProps() 984 props = [collection.GetItemAsObject(i) for i in range(collection.GetNumberOfItems())] 985 986 props.remove(nearest_prop) 987 props.insert(0, nearest_prop) 988 return props 989 990 else: 991 return [] 992 993 # ----------- actor dragging ------------ 994 def start_drag(self): 995 """Starts the drag operation""" 996 if self.callback_start_drag: 997 # print("Calling callback_start_drag") 998 self.callback_start_drag() 999 return 1000 else: # grab the current selection 1001 if self.picked_props: 1002 self.start_drag_on_props(self.picked_props) 1003 else: 1004 pass 1005 # print('Can not start drag, 1006 # nothing selected and callback_start_drag not assigned') 1007 1008 def finish_drag(self): 1009 """Finishes the drag operation""" 1010 # print('Finished drag') 1011 if self.callback_end_drag: 1012 # reset actor positions as actors positions will be controlled 1013 # by called functions 1014 for pos0, actor in zip( 1015 self.draginfo.dragged_actors_original_positions, 1016 self.draginfo.actors_dragging, 1017 ): 1018 actor.SetPosition(pos0) 1019 self.callback_end_drag(self.draginfo) 1020 1021 self.draginfo = None 1022 1023 def start_drag_on_props(self, props): 1024 """ 1025 Starts drag on the provided props (actors) by filling self.draginfo""" 1026 if self.draginfo is not None: 1027 self.finish_drag() 1028 return 1029 1030 # create and fill drag-info 1031 draginfo = _BlenderStyleDragInfo() 1032 draginfo.actors_dragging = props # [*actors, *outlines] 1033 1034 for a in draginfo.actors_dragging: 1035 draginfo.dragged_actors_original_positions.append(a.GetPosition()) 1036 1037 # Get the start position of the drag in 3d 1038 rwi = self.GetInteractor() 1039 ren = self.GetCurrentRenderer() 1040 camera = ren.GetActiveCamera() 1041 viewFocus = camera.GetFocalPoint() 1042 1043 temp_out = [0, 0, 0] 1044 self.ComputeWorldToDisplay( 1045 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1046 ) 1047 focalDepth = temp_out[2] 1048 1049 newPickPoint = [0, 0, 0, 0] 1050 x, y = rwi.GetEventPosition() 1051 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1052 1053 mouse_pos_3d = np.array(newPickPoint[:3]) 1054 draginfo.start_position_3d = mouse_pos_3d 1055 self.draginfo = draginfo 1056 1057 def execute_drag(self): 1058 """Executes the drag operation""" 1059 rwi = self.GetInteractor() 1060 ren = self.GetCurrentRenderer() 1061 1062 camera = ren.GetActiveCamera() 1063 viewFocus = camera.GetFocalPoint() 1064 1065 # Get the picked point in 3d 1066 temp_out = [0, 0, 0] 1067 self.ComputeWorldToDisplay( 1068 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1069 ) 1070 focalDepth = temp_out[2] 1071 1072 newPickPoint = [0, 0, 0, 0] 1073 x, y = rwi.GetEventPosition() 1074 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1075 1076 mouse_pos_3d = np.array(newPickPoint[:3]) 1077 1078 # compute the delta and execute 1079 1080 delta = np.array(mouse_pos_3d) - self.draginfo.start_position_3d 1081 # print(f'Delta = {delta}') 1082 view_normal = np.array(ren.GetActiveCamera().GetViewPlaneNormal()) 1083 1084 delta_inplane = delta - view_normal * np.dot(delta, view_normal) 1085 # print(f'delta_inplane = {delta_inplane}') 1086 1087 for pos0, actor in zip( 1088 self.draginfo.dragged_actors_original_positions, 1089 self.draginfo.actors_dragging, 1090 ): 1091 m = actor.GetUserMatrix() 1092 if m: 1093 print("UserMatrices/transforms not supported") 1094 # m.Invert() #inplace 1095 # rotated = m.MultiplyFloatPoint([*delta_inplane, 1]) 1096 # actor.SetPosition(pos0 + np.array(rotated[:3])) 1097 actor.SetPosition(pos0 + delta_inplane) 1098 1099 # print(f'Set position to {pos0 + delta_inplane}') 1100 self.draginfo.delta = delta_inplane # store the current delta 1101 1102 self.GetInteractor().Render() 1103 1104 def cancel_drag(self): 1105 """Cancels the drag and restored the original positions of all dragged actors""" 1106 for pos0, actor in zip( 1107 self.draginfo.dragged_actors_original_positions, 1108 self.draginfo.actors_dragging, 1109 ): 1110 actor.SetPosition(pos0) 1111 self.draginfo = None 1112 self.GetInteractor().Render() 1113 1114 # ----------- end dragging -------------- 1115 1116 def zoom(self): 1117 """Zooms the camera""" 1118 rwi = self.GetInteractor() 1119 _, y = rwi.GetEventPosition() 1120 _, yp = rwi.GetLastEventPosition() 1121 direction = y - yp 1122 self.move_mouse_wheel(direction / 10) 1123 1124 def pan(self): 1125 """Pans the camera""" 1126 ren = self.GetCurrentRenderer() 1127 1128 if ren: 1129 rwi = self.GetInteractor() 1130 1131 # // Calculate the focal depth since we'll be using it a lot 1132 camera = ren.GetActiveCamera() 1133 viewFocus = camera.GetFocalPoint() 1134 1135 temp_out = [0, 0, 0] 1136 self.ComputeWorldToDisplay( 1137 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1138 ) 1139 focalDepth = temp_out[2] 1140 1141 newPickPoint = [0, 0, 0, 0] 1142 x, y = rwi.GetEventPosition() 1143 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1144 1145 # Has to recalc old mouse point since the viewport has moved, 1146 # so can't move it outside the loop 1147 1148 oldPickPoint = [0, 0, 0, 0] 1149 xp, yp = rwi.GetLastEventPosition() 1150 self.ComputeDisplayToWorld(ren, xp, yp, focalDepth, oldPickPoint) 1151 # 1152 # Camera motion is reversed 1153 motionVector = ( 1154 oldPickPoint[0] - newPickPoint[0], 1155 oldPickPoint[1] - newPickPoint[1], 1156 oldPickPoint[2] - newPickPoint[2], 1157 ) 1158 1159 viewFocus = camera.GetFocalPoint() # do we need to do this again? Already did this 1160 viewPoint = camera.GetPosition() 1161 1162 camera.SetFocalPoint( 1163 motionVector[0] + viewFocus[0], 1164 motionVector[1] + viewFocus[1], 1165 motionVector[2] + viewFocus[2], 1166 ) 1167 camera.SetPosition( 1168 motionVector[0] + viewPoint[0], 1169 motionVector[1] + viewPoint[1], 1170 motionVector[2] + viewPoint[2], 1171 ) 1172 1173 if rwi.GetLightFollowCamera(): 1174 ren.UpdateLightsGeometryToFollowCamera() 1175 1176 self.GetInteractor().Render() 1177 1178 def rotate(self): 1179 """Rotates the camera""" 1180 ren = self.GetCurrentRenderer() 1181 1182 if ren: 1183 1184 rwi = self.GetInteractor() 1185 dx = rwi.GetEventPosition()[0] - rwi.GetLastEventPosition()[0] 1186 dy = rwi.GetEventPosition()[1] - rwi.GetLastEventPosition()[1] 1187 1188 size = ren.GetRenderWindow().GetSize() 1189 delta_elevation = -20.0 / size[1] 1190 delta_azimuth = -20.0 / size[0] 1191 1192 rxf = dx * delta_azimuth * self.mouse_motion_factor 1193 ryf = dy * delta_elevation * self.mouse_motion_factor 1194 1195 self.rotate_turtable_by(rxf, ryf) 1196 1197 def rotate_turtable_by(self, rxf, ryf): 1198 """Rotates the camera in turn-table mode""" 1199 ren = self.GetCurrentRenderer() 1200 rwi = self.GetInteractor() 1201 1202 # rfx is rotation about the global Z vector (turn-table mode) 1203 # rfy is rotation about the side vector 1204 1205 camera = ren.GetActiveCamera() 1206 campos = np.array(camera.GetPosition()) 1207 focal = np.array(camera.GetFocalPoint()) 1208 up = camera.GetViewUp() 1209 upside_down_factor = -1 if up[2] < 0 else 1 1210 1211 # rotate about focal point 1212 P = campos - focal # camera position 1213 1214 # Rotate left/right about the global Z axis 1215 H = np.linalg.norm(P[:2]) # horizontal distance of camera to focal point 1216 elev = np.arctan2(P[2], H) # elevation 1217 1218 # if the camera is near the poles, then derive the azimuth from the up-vector 1219 sin_elev = np.sin(elev) 1220 if abs(sin_elev) < 0.8: 1221 azi = np.arctan2(P[1], P[0]) # azimuth from camera position 1222 else: 1223 if sin_elev < -0.8: 1224 azi = np.arctan2(upside_down_factor * up[1], upside_down_factor * up[0]) 1225 else: 1226 azi = np.arctan2(-upside_down_factor * up[1], -upside_down_factor * up[0]) 1227 1228 D = np.linalg.norm(P) # distance from focal point to camera 1229 1230 # apply the change in azimuth and elevation 1231 azi_new = azi + rxf / 60 1232 elev_new = elev + upside_down_factor * ryf / 60 1233 1234 # the changed elevation changes H (D stays the same) 1235 Hnew = D * np.cos(elev_new) 1236 1237 # calculate new camera position relative to focal point 1238 Pnew = np.array((Hnew * np.cos(azi_new), Hnew * np.sin(azi_new), D * np.sin(elev_new))) 1239 1240 # calculate the up-direction of the camera 1241 up_z = upside_down_factor * np.cos(elev_new) # z follows directly from elevation 1242 up_h = upside_down_factor * np.sin(elev_new) # horizontal component 1243 # 1244 # if upside_down: 1245 # up_z = -up_z 1246 # up_h = -up_h 1247 up = (-up_h * np.cos(azi_new), -up_h * np.sin(azi_new), up_z) 1248 new_pos = focal + Pnew 1249 camera.SetViewUp(up) 1250 camera.SetPosition(new_pos) 1251 camera.OrthogonalizeViewUp() 1252 1253 # Update 1254 1255 if self.GetAutoAdjustCameraClippingRange(): 1256 ren.ResetCameraClippingRange() 1257 1258 if rwi.GetLightFollowCamera(): 1259 ren.UpdateLightsGeometryToFollowCamera() 1260 1261 if self.callback_camera_direction_changed: 1262 self.callback_camera_direction_changed() 1263 1264 self.GetInteractor().Render() 1265 1266 def zoom_box(self, x1, y1, x2, y2): 1267 """Zooms to a box""" 1268 if x1 > x2: 1269 #swap x1 and x2 1270 x1, x2 = x2, x1 1271 if y1 > y2: 1272 y1, y2 = y2, y1 1273 1274 width = x2 - x1 1275 height = y2 - y1 1276 1277 ren = self.GetCurrentRenderer() 1278 size = ren.GetSize() 1279 origin = ren.GetOrigin() 1280 camera = ren.GetActiveCamera() 1281 1282 # Assuming we're drawing the band on the view-plane 1283 rbcenter = (x1 + width / 2, y1 + height / 2, 0) 1284 1285 ren.SetDisplayPoint(rbcenter) 1286 ren.DisplayToView() 1287 ren.ViewToWorld() 1288 1289 worldRBCenter = ren.GetWorldPoint() 1290 1291 invw = 1.0 / worldRBCenter[3] 1292 worldRBCenter = [c * invw for c in worldRBCenter] 1293 winCenter = [origin[0] + 0.5 * size[0], origin[1] + 0.5 * size[1], 0] 1294 1295 ren.SetDisplayPoint(winCenter) 1296 ren.DisplayToView() 1297 ren.ViewToWorld() 1298 1299 worldWinCenter = ren.GetWorldPoint() 1300 invw = 1.0 / worldWinCenter[3] 1301 worldWinCenter = [c * invw for c in worldWinCenter] 1302 1303 translation = [ 1304 worldRBCenter[0] - worldWinCenter[0], 1305 worldRBCenter[1] - worldWinCenter[1], 1306 worldRBCenter[2] - worldWinCenter[2], 1307 ] 1308 1309 pos = camera.GetPosition() 1310 fp = camera.GetFocalPoint() 1311 # 1312 pos = [pos[i] + translation[i] for i in range(3)] 1313 fp = [fp[i] + translation[i] for i in range(3)] 1314 1315 # 1316 camera.SetPosition(pos) 1317 camera.SetFocalPoint(fp) 1318 1319 if width > height: 1320 if width: 1321 camera.Zoom(size[0] / width) 1322 else: 1323 if height: 1324 camera.Zoom(size[1] / height) 1325 1326 self.GetInteractor().Render() 1327 1328 def focus_on(self, prop3D): 1329 """Move the camera to focus on this particular prop3D""" 1330 1331 position = prop3D.GetPosition() 1332 1333 ren = self.GetCurrentRenderer() 1334 camera = ren.GetActiveCamera() 1335 1336 fp = camera.GetFocalPoint() 1337 pos = camera.GetPosition() 1338 1339 camera.SetFocalPoint(position) 1340 camera.SetPosition( 1341 position[0] - fp[0] + pos[0], 1342 position[1] - fp[1] + pos[1], 1343 position[2] - fp[2] + pos[2], 1344 ) 1345 1346 if self.GetAutoAdjustCameraClippingRange(): 1347 ren.ResetCameraClippingRange() 1348 1349 rwi = self.GetInteractor() 1350 if rwi.GetLightFollowCamera(): 1351 ren.UpdateLightsGeometryToFollowCamera() 1352 1353 self.GetInteractor().Render() 1354 1355 def dolly(self, factor): 1356 """Dolly the camera""" 1357 ren = self.GetCurrentRenderer() 1358 1359 if ren: 1360 camera = ren.GetActiveCamera() 1361 1362 if camera.GetParallelProjection(): 1363 camera.SetParallelScale(camera.GetParallelScale() / factor) 1364 else: 1365 camera.Dolly(factor) 1366 if self.GetAutoAdjustCameraClippingRange(): 1367 ren.ResetCameraClippingRange() 1368 1369 # if not do_not_update: 1370 # rwi = self.GetInteractor() 1371 # if rwi.GetLightFollowCamera(): 1372 # ren.UpdateLightsGeometryToFollowCamera() 1373 # # rwi.Render() 1374 1375 def draw_measurement(self): 1376 """Draws a measurement line""" 1377 rwi = self.GetInteractor() 1378 self.end_x, self.end_y = rwi.GetEventPosition() 1379 self.draw_line(self.start_x, self.end_x, self.start_y, self.end_y) 1380 1381 def draw_dragged_selection(self): 1382 """Draws the selection rubber band""" 1383 rwi = self.GetInteractor() 1384 self.end_x, self.end_y = rwi.GetEventPosition() 1385 self.draw_rubber_band(self.start_x, self.end_x, self.start_y, self.end_y) 1386 1387 def initialize_screen_drawing(self): 1388 """Initializes the screen drawing""" 1389 # make an image of the currently rendered image 1390 1391 rwi = self.GetInteractor() 1392 rwin = rwi.GetRenderWindow() 1393 1394 size = rwin.GetSize() 1395 1396 self._pixel_array.Initialize() 1397 self._pixel_array.SetNumberOfComponents(4) 1398 self._pixel_array.SetNumberOfTuples(size[0] * size[1]) 1399 1400 front = 1 # what does this do? 1401 rwin.GetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, front, self._pixel_array) 1402 1403 def draw_rubber_band(self, x1, x2, y1, y2): 1404 """Draws a rubber band""" 1405 rwi = self.GetInteractor() 1406 rwin = rwi.GetRenderWindow() 1407 1408 size = rwin.GetSize() 1409 1410 tempPA = vtki.vtkUnsignedCharArray() 1411 tempPA.DeepCopy(self._pixel_array) 1412 1413 # check size, viewport may have been resized in the mean-time 1414 if tempPA.GetNumberOfTuples() != size[0] * size[1]: 1415 # print( 1416 # "Starting new screen-image - viewport has resized without us knowing" 1417 # ) 1418 self.initialize_screen_drawing() 1419 self.draw_rubber_band(x1, x2, y1, y2) 1420 return 1421 1422 x2 = min(x2, size[0] - 1) 1423 y2 = min(y2, size[1] - 1) 1424 1425 x2 = max(x2, 0) 1426 y2 = max(y2, 0) 1427 1428 # Modify the pixel array 1429 width = abs(x2 - x1) 1430 height = abs(y2 - y1) 1431 minx = min(x2, x1) 1432 miny = min(y2, y1) 1433 1434 # draw top and bottom 1435 for i in range(width): 1436 1437 # c = round((10*i % 254)/254) * 254 # find some alternating color 1438 c = 0 1439 1440 idx = (miny * size[0]) + minx + i 1441 tempPA.SetTuple(idx, (c, c, c, 1)) 1442 1443 idx = ((miny + height) * size[0]) + minx + i 1444 tempPA.SetTuple(idx, (c, c, c, 1)) 1445 1446 # draw left and right 1447 for i in range(height): 1448 # c = round((10 * i % 254) / 254) * 254 # find some alternating color 1449 c = 0 1450 1451 idx = ((miny + i) * size[0]) + minx 1452 tempPA.SetTuple(idx, (c, c, c, 1)) 1453 1454 idx = idx + width 1455 tempPA.SetTuple(idx, (c, c, c, 1)) 1456 1457 # and Copy back to the window 1458 rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, tempPA, 0) 1459 rwin.Frame() 1460 1461 def line2pixels(self, x1, x2, y1, y2): 1462 """Returns the x and y values of the pixels on a line between x1,y1 and x2,y2. 1463 If start and end are identical then a single point is returned""" 1464 1465 dx = x2 - x1 1466 dy = y2 - y1 1467 1468 if dx == 0 and dy == 0: 1469 return [x1], [y1] 1470 1471 if abs(dx) > abs(dy): 1472 dhdw = dy / dx 1473 r = range(0, dx, int(dx / abs(dx))) 1474 x = [x1 + i for i in r] 1475 y = [round(y1 + dhdw * i) for i in r] 1476 else: 1477 dwdh = dx / dy 1478 r = range(0, dy, int(dy / abs(dy))) 1479 y = [y1 + i for i in r] 1480 x = [round(x1 + i * dwdh) for i in r] 1481 1482 return x, y 1483 1484 def draw_line(self, x1, x2, y1, y2): 1485 rwi = self.GetInteractor() 1486 rwin = rwi.GetRenderWindow() 1487 1488 size = rwin.GetSize() 1489 1490 x1 = min(max(x1, 0), size[0]) 1491 x2 = min(max(x2, 0), size[0]) 1492 y1 = min(max(y1, 0), size[1]) 1493 y2 = min(max(y2, 0), size[1]) 1494 1495 tempPA = vtki.vtkUnsignedCharArray() 1496 tempPA.DeepCopy(self._pixel_array) 1497 1498 xs, ys = self.line2pixels(x1, x2, y1, y2) 1499 for x, y in zip(xs, ys): 1500 idx = (y * size[0]) + x 1501 tempPA.SetTuple(idx, (0, 0, 0, 1)) 1502 1503 # and Copy back to the window 1504 rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, tempPA, 0) 1505 1506 camera = self.GetCurrentRenderer().GetActiveCamera() 1507 scale = camera.GetParallelScale() 1508 1509 # Set/Get the scaling used for a parallel projection, i.e. 1510 # 1511 # the half of the height of the viewport in world-coordinate distances. 1512 # The default is 1. Note that the "scale" parameter works as an "inverse scale" 1513 # larger numbers produce smaller images. 1514 # This method has no effect in perspective projection mode 1515 1516 half_height = size[1] / 2 1517 # half_height [px] = scale [world-coordinates] 1518 1519 length = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5 1520 meters_per_pixel = scale / half_height 1521 meters = length * meters_per_pixel 1522 1523 if camera.GetParallelProjection(): 1524 print(f"Line length = {length} px = {meters} m") 1525 else: 1526 print("Need to be in non-perspective mode to measure. Press 2 or 3 to get there") 1527 1528 if self.callback_measure: 1529 self.callback_measure(meters) 1530 1531 # # can we add something to the window here? 1532 # freeType = vtk.FreeTypeTools.GetInstance() 1533 # textProperty = vtki.vtkTextProperty() 1534 # textProperty.SetJustificationToLeft() 1535 # textProperty.SetFontSize(24) 1536 # textProperty.SetOrientation(25) 1537 # 1538 # textImage = vtki.vtkImageData() 1539 # freeType.RenderString(textProperty, "a somewhat longer text", 72, textImage) 1540 # # this does not give an error, assume it works 1541 # # 1542 # textImage.GetDimensions() 1543 # textImage.GetExtent() 1544 # 1545 # # # Now put the textImage in the RenderWindow 1546 # rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, textImage, 0) 1547 1548 rwin.Frame() 1549 1550 def _update_middle_mouse_button_lock_actor(self): 1551 1552 if self.middle_mouse_lock_actor is None: 1553 # create the actor 1554 # Create a text on the top-rightcenter 1555 textMapper = vtki.new("TextMapper") 1556 textMapper.SetInput("Middle mouse lock [m or space] active") 1557 textProp = textMapper.GetTextProperty() 1558 textProp.SetFontSize(12) 1559 textProp.SetFontFamilyToTimes() 1560 textProp.BoldOff() 1561 textProp.ItalicOff() 1562 textProp.ShadowOff() 1563 textProp.SetVerticalJustificationToTop() 1564 textProp.SetJustificationToCentered() 1565 textProp.SetColor((0, 0, 0)) 1566 1567 self.middle_mouse_lock_actor = vtki.vtkActor2D() 1568 self.middle_mouse_lock_actor.SetMapper(textMapper) 1569 self.middle_mouse_lock_actor.GetPositionCoordinate().SetCoordinateSystemToNormalizedDisplay() 1570 self.middle_mouse_lock_actor.GetPositionCoordinate().SetValue(0.5, 0.98) 1571 1572 self.GetCurrentRenderer().AddActor(self.middle_mouse_lock_actor) 1573 1574 self.middle_mouse_lock_actor.SetVisibility(self.middle_mouse_lock) 1575 self.GetInteractor().Render()
15class MousePan(vtki.vtkInteractorStyleUser): 16 """ 17 Interaction mode to pan the scene by dragging the mouse. 18 19 Controls: 20 - Left mouse button will pan the scene. 21 - Mouse middle button up/down is elevation, and left and right is azimuth. 22 - Right mouse button is rotate (left/right movement) and zoom in/out (up/down movement) 23 - Mouse scroll wheel is zoom in/out 24 25 Arguments: 26 enable_pan: (bool) 27 Enable panning the scene. 28 enable_zoom: (bool) 29 Enable zooming the scene. 30 enable_rotate: (bool) 31 Enable rotating the scene. 32 speed: (float) 33 Speed factor of the interaction, e.i. how fast the scene must move. 34 """ 35 36 def __init__(self, enable_pan=True, enable_zoom=True, enable_rotate=True, speed=1.0): 37 38 super().__init__() 39 40 self.left = False 41 self.middle = False 42 self.right = False 43 self.speed = speed 44 45 self.interactor = None 46 self.renderer = None 47 self.camera = None 48 49 self.oldpickD = [] 50 self.newpickD = [] 51 self.oldpickW = np.array([0, 0, 0, 0], dtype=float) 52 self.newpickW = np.array([0, 0, 0, 0], dtype=float) 53 self.fpD = np.array([0, 0, 0], dtype=float) 54 self.fpW = np.array([0, 0, 0], dtype=float) 55 self.posW = np.array([0, 0, 0], dtype=float) 56 self.motionD = np.array([0, 0], dtype=float) 57 self.motionW = np.array([0, 0, 0], dtype=float) 58 59 # print("MousePan: Left mouse button to pan the scene.") 60 self.AddObserver("LeftButtonPressEvent", self._left_down) 61 self.AddObserver("LeftButtonReleaseEvent", self._left_up) 62 63 if enable_rotate: 64 self.AddObserver("MiddleButtonPressEvent", self._middle_down) 65 self.AddObserver("MiddleButtonReleaseEvent", self._middle_up) 66 67 if enable_zoom: 68 self.AddObserver("RightButtonPressEvent", self._right_down) 69 self.AddObserver("RightButtonReleaseEvent", self._right_up) 70 self.AddObserver("MouseWheelForwardEvent", self._wheel_forward) 71 self.AddObserver("MouseWheelBackwardEvent", self._wheel_backward) 72 73 if enable_pan: 74 self.AddObserver("MouseMoveEvent", self._mouse_move) 75 76 def _get_motion(self): 77 self.oldpickD = np.array(self.interactor.GetLastEventPosition()) 78 self.newpickD = np.array(self.interactor.GetEventPosition()) 79 self.motionD = (self.newpickD - self.oldpickD) / 4 80 self.camera = self.renderer.GetActiveCamera() 81 self.fpW = self.camera.GetFocalPoint() 82 self.posW = self.camera.GetPosition() 83 self.ComputeWorldToDisplay(self.renderer, self.fpW[0], self.fpW[1], self.fpW[2], self.fpD) 84 focaldepth = self.fpD[2] 85 self.ComputeDisplayToWorld( 86 self.renderer, self.oldpickD[0], self.oldpickD[1], focaldepth, self.oldpickW 87 ) 88 self.ComputeDisplayToWorld( 89 self.renderer, self.newpickD[0], self.newpickD[1], focaldepth, self.newpickW 90 ) 91 self.motionW[:3] = (self.oldpickW[:3] - self.newpickW[:3]) * self.speed 92 93 def _mouse_left_move(self): 94 self._get_motion() 95 self.camera.SetFocalPoint(self.fpW[:3] + self.motionW[:3]) 96 self.camera.SetPosition(self.posW[:3] + self.motionW[:3]) 97 self.interactor.Render() 98 99 def _mouse_middle_move(self): 100 self._get_motion() 101 if abs(self.motionD[0]) > abs(self.motionD[1]): 102 self.camera.Azimuth(-2 * self.speed * self.motionD[0]) 103 else: 104 self.camera.Elevation(-self.speed *self.motionD[1]) 105 self.interactor.Render() 106 107 def _mouse_right_move(self): 108 self._get_motion() 109 if abs(self.motionD[0]) > abs(self.motionD[1]): 110 self.camera.Azimuth(-2.0 * self.speed * self.motionD[0]) 111 else: 112 self.camera.Zoom(1 + self.motionD[1] / 100) 113 self.interactor.Render() 114 115 def _mouse_wheel_forward(self): 116 self.camera = self.renderer.GetActiveCamera() 117 self.camera.Zoom(1.1 * self.speed) 118 self.interactor.Render() 119 120 def _mouse_wheel_backward(self): 121 self.camera = self.renderer.GetActiveCamera() 122 self.camera.Zoom(0.9 * self.speed) 123 self.interactor.Render() 124 125 def _left_down(self, _w, _e): 126 self.left = True 127 128 def _left_up(self, _w, _e): 129 self.left = False 130 131 def _middle_down(self, _w, _e): 132 self.middle = True 133 134 def _middle_up(self, _w, _e): 135 self.middle = False 136 137 def _right_down(self, _w, _e): 138 self.right = True 139 140 def _right_up(self, _w, _e): 141 self.right = False 142 143 def _wheel_forward(self, _w, _e): 144 self._mouse_wheel_forward() 145 146 def _wheel_backward(self, _w, _e): 147 self._mouse_wheel_backward() 148 149 def _mouse_move(self, _w, _e): 150 if self.left: 151 self._mouse_left_move() 152 if self.middle: 153 self._mouse_middle_move() 154 if self.right: 155 self._mouse_right_move()
Interaction mode to pan the scene by dragging the mouse.
Controls:
- Left mouse button will pan the scene.
- Mouse middle button up/down is elevation, and left and right is azimuth.
- Right mouse button is rotate (left/right movement) and zoom in/out (up/down movement)
- Mouse scroll wheel is zoom in/out
Arguments:
- enable_pan: (bool) Enable panning the scene.
- enable_zoom: (bool) Enable zooming the scene.
- enable_rotate: (bool) Enable rotating the scene.
- speed: (float) Speed factor of the interaction, e.i. how fast the scene must move.
36 def __init__(self, enable_pan=True, enable_zoom=True, enable_rotate=True, speed=1.0): 37 38 super().__init__() 39 40 self.left = False 41 self.middle = False 42 self.right = False 43 self.speed = speed 44 45 self.interactor = None 46 self.renderer = None 47 self.camera = None 48 49 self.oldpickD = [] 50 self.newpickD = [] 51 self.oldpickW = np.array([0, 0, 0, 0], dtype=float) 52 self.newpickW = np.array([0, 0, 0, 0], dtype=float) 53 self.fpD = np.array([0, 0, 0], dtype=float) 54 self.fpW = np.array([0, 0, 0], dtype=float) 55 self.posW = np.array([0, 0, 0], dtype=float) 56 self.motionD = np.array([0, 0], dtype=float) 57 self.motionW = np.array([0, 0, 0], dtype=float) 58 59 # print("MousePan: Left mouse button to pan the scene.") 60 self.AddObserver("LeftButtonPressEvent", self._left_down) 61 self.AddObserver("LeftButtonReleaseEvent", self._left_up) 62 63 if enable_rotate: 64 self.AddObserver("MiddleButtonPressEvent", self._middle_down) 65 self.AddObserver("MiddleButtonReleaseEvent", self._middle_up) 66 67 if enable_zoom: 68 self.AddObserver("RightButtonPressEvent", self._right_down) 69 self.AddObserver("RightButtonReleaseEvent", self._right_up) 70 self.AddObserver("MouseWheelForwardEvent", self._wheel_forward) 71 self.AddObserver("MouseWheelBackwardEvent", self._wheel_backward) 72 73 if enable_pan: 74 self.AddObserver("MouseMoveEvent", self._mouse_move)
159class FlyOverSurface(vtki.vtkInteractorStyleUser): 160 """ 161 Interaction mode to fly over a surface. 162 163 Controls: 164 - Press arrows to move the camera in the plane of the surface. 165 - "t" (or "PageUp") will move the camera upper in z. 166 - "g" (or "PageDown") will move the camera lower in z. 167 - "x" and "X" will reset the camera to the default position towards positive or negative x. 168 - "y" and "Y" will reset the camera to the default position towards positive or negative y. 169 - "." and "," will rotate azimuth to the right or left. 170 - "r" will reset the camera to the default position. 171 172 Left button: Select a point on the surface to focus the camera on it. 173 """ 174 175 def __init__(self, move_step=0.05, angle_step=1.5): 176 """ 177 Interaction mode to fly over a surface. 178 179 Arguments: 180 move_step: float, optional 181 The step size for moving the camera in the plane of the surface. 182 angle_step: float, optional 183 The step size for rotating the camera. 184 185 Example: 186 [interaction_modes3.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/interaction_modes3.py) 187 """ 188 189 super().__init__() 190 191 self.interactor = None # filled in plotter.py 192 self.renderer = None # filled in plotter.py 193 194 self.angle_step = angle_step 195 self.move_step = move_step 196 197 self.tleft = vtki.vtkTransform() 198 self.tleft.RotateZ(self.angle_step) 199 200 self.tright = vtki.vtkTransform() 201 self.tright.RotateZ(-self.angle_step) 202 203 self.bounds = () 204 205 self.AddObserver("KeyPressEvent", self._key) 206 self.AddObserver("RightButtonPressEvent", self._right_button_press) 207 self.AddObserver("MouseWheelForwardEvent", self._mouse_wheel_forward) 208 self.AddObserver("MouseWheelBackwardEvent", self._mouse_wheel_backward) 209 self.AddObserver("LeftButtonPressEvent", self._left_button_press) 210 211 @property 212 def camera(self): 213 """Return the active camera.""" 214 return self.renderer.GetActiveCamera() 215 216 @property 217 def position(self): 218 """Return the camera position.""" 219 return np.array(self.camera.GetPosition()) 220 221 @position.setter 222 def position(self, value): 223 """Set the camera position.""" 224 self.camera.SetPosition(value[:3]) 225 self.camera.SetViewUp(0.00001, 0, 1) 226 self.renderer.ResetCameraClippingRange() 227 228 @property 229 def focal_point(self): 230 """Return the camera focal point.""" 231 return np.array(self.camera.GetFocalPoint()) 232 233 @focal_point.setter 234 def focal_point(self, value): 235 """Set the camera focal point.""" 236 self.camera.SetFocalPoint(value[:3]) 237 self.camera.SetViewUp(0.00001, 0, 1) 238 self.renderer.ResetCameraClippingRange() 239 240 def _right_button_press(self, _obj, event): 241 """Right button press.""" 242 # print("Right button", event) 243 self._key("Down", event) 244 245 def _left_button_press(self, obj, _event): 246 """Left button press.""" 247 # print("Left button", event) 248 newPickPoint = [0, 0, 0, 0] 249 focalDepth = 0 250 x, y = obj.interactor.GetEventPosition() 251 self.ComputeDisplayToWorld(self.renderer, x, y, focalDepth, newPickPoint) 252 self.focal_point = np.array(newPickPoint) 253 self.interactor.Render() 254 255 def _mouse_wheel_backward(self, _obj, event): 256 """Mouse wheel backward.""" 257 # print("mouse_wheel_backward ", event) 258 self._key("Down", event) 259 260 def _mouse_wheel_forward(self, _obj, event): 261 """Mouse wheel forward.""" 262 # print("mouse_wheel_forward ", event) 263 self._key("Up", event) 264 265 def _key(self, obj, event): 266 """Key press.""" 267 if "Mouse" in event or "Button" in event: 268 k = obj 269 else: 270 k = obj.GetKeySym() 271 if obj.GetShiftKey(): 272 k = k.upper() 273 # print("FlyOverSurface. Key press", k) 274 275 if k in ["r", "x"]: 276 # print("r pressed, reset camera") 277 self.bounds = self.renderer.ComputeVisiblePropBounds() 278 x0, x1, y0, y1, z0, z1 = self.bounds 279 dx = x1 - x0 280 z = max(z1 * 1, z0 + (y1 - y0) / 4, z0 + (x1 - x0) / 4) 281 self.position = [x0 - dx, (y0 + y1) / 2, z] 282 self.focal_point = [x0 + dx / 2, (y0 + y1) / 2, z] 283 elif k in ["X"]: 284 # print("X pressed, reset camera") 285 self.bounds = self.renderer.ComputeVisiblePropBounds() 286 x0, x1, y0, y1, z0, z1 = self.bounds 287 dx = x1 - x0 288 z = max(z1 * 1, (y1 - y0) / 4, (x1 - x0) / 4) 289 self.position = [x1 + dx, (y0 + y1) / 2, z] 290 self.focal_point = [x0 - dx / 2, (y0 + y1) / 2, z] 291 292 elif k in ["y"]: 293 # print("y pressed, reset camera") 294 self.bounds = self.renderer.ComputeVisiblePropBounds() 295 x0, x1, y0, y1, z0, z1 = self.bounds 296 dy = y1 - y0 297 z = max(z1 * 1, z0 + (y1 - y0) / 4, z0 + (x1 - x0) / 4) 298 self.position = [(x0 + x1) / 2, y0 - dy, z] 299 self.focal_point = [(x0 + x1) / 2, y1 + dy / 2, z] 300 elif k in ["Y"]: 301 # print("Y pressed, reset camera") 302 self.bounds = self.renderer.ComputeVisiblePropBounds() 303 x0, x1, y0, y1, z0, z1 = self.bounds 304 dy = y1 - y0 305 z = max(z1 * 1, z0 + (y1 - y0) / 4, z0 + (x1 - x0) / 4) 306 self.position = [(x0 + x1) / 2, y1 + dy, z] 307 self.focal_point = [(x0 + x1) / 2, y0 - dy / 2, z] 308 309 elif k in ["Up", "w"]: 310 # print("Up pressed, move forward") 311 self.bounds = self.renderer.ComputeVisiblePropBounds() 312 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 313 dx = self.move_step * diagonal 314 p = np.array(self.camera.GetPosition()) 315 v = np.array(self.camera.GetDirectionOfProjection()) 316 newp = p + dx * v 317 self.position = [newp[0], newp[1], p[2]] 318 self.focal_point = self.focal_point + dx * v 319 elif k in ["Down", "s"]: 320 # print("Down pressed, move backward") 321 self.bounds = self.renderer.ComputeVisiblePropBounds() 322 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 323 dx = self.move_step * diagonal 324 p = np.array(self.camera.GetPosition()) 325 v = np.array(self.camera.GetDirectionOfProjection()) 326 newp = p - dx * v 327 self.position = [newp[0], newp[1], p[2]] 328 self.focal_point = self.focal_point - dx * v 329 330 elif k in ["Left", "a"]: 331 # print("Left pressed, rotate to the left") 332 self.bounds = self.renderer.ComputeVisiblePropBounds() 333 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 334 w = np.array(self.camera.GetDirectionOfProjection()) 335 p = np.array(self.camera.GetPosition()) 336 w2 = np.array(self.tleft.TransformFloatPoint(w)) 337 self.focal_point = self.focal_point + np.linalg.norm(p-self.focal_point) * w2 338 339 elif k in ["Right", "d"]: 340 # print("Right pressed, rotate to the right") 341 self.bounds = self.renderer.ComputeVisiblePropBounds() 342 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 343 w = np.array(self.camera.GetDirectionOfProjection()) 344 p = np.array(self.camera.GetPosition()) 345 w2 = np.array(self.tright.TransformFloatPoint(w)) 346 self.focal_point = self.focal_point + np.linalg.norm(p-self.focal_point) * w2 347 348 elif k in ["t", "Prior"]: 349 # print("t pressed, move z up") 350 self.bounds = self.renderer.ComputeVisiblePropBounds() 351 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 352 dx = self.move_step * diagonal 353 p = self.position 354 self.position = [p[0], p[1], p[2] + dx / 4] 355 356 elif k in ["g", "Next"]: 357 # print("g pressed, move z down") 358 self.bounds = self.renderer.ComputeVisiblePropBounds() 359 diagonal = np.linalg.norm(np.array(self.bounds[1::2]) - np.array(self.bounds[::2])) 360 dx = self.move_step * diagonal 361 p = self.position 362 self.position = [p[0], p[1], p[2] - dx / 4] 363 364 elif k in ["comma", "COMMA"]: 365 # print("< pressed, rotate azimuth to the left") 366 self.bounds = self.renderer.ComputeVisiblePropBounds() 367 scene_center = [ 368 self.bounds[0] + self.bounds[1], 369 self.bounds[2] + self.bounds[3], 370 self.bounds[4] + self.bounds[5], 371 ] 372 scene_center = np.array(scene_center) / 2 373 p = self.position 374 v = p - scene_center 375 newp = scene_center + self.tleft.TransformFloatPoint(v) 376 self.position = [newp[0], newp[1], p[2]] 377 378 elif k in ["period", "PERIOD"]: 379 # print("< pressed, rotate azimuth to the right") 380 self.bounds = self.renderer.ComputeVisiblePropBounds() 381 scene_center = [ 382 self.bounds[0] + self.bounds[1], 383 self.bounds[2] + self.bounds[3], 384 self.bounds[4] + self.bounds[5], 385 ] 386 scene_center = np.array(scene_center) / 2 387 p = self.position 388 v = p - scene_center 389 newp = scene_center + self.tright.TransformFloatPoint(v) 390 self.position = [newp[0], newp[1], p[2]] 391 392 elif k in ["q", "Return"]: 393 self.interactor.ExitCallback() 394 return 395 396 else: 397 return 398 399 self.interactor.Render()
Interaction mode to fly over a surface.
Controls:
- Press arrows to move the camera in the plane of the surface.
- "t" (or "PageUp") will move the camera upper in z.
- "g" (or "PageDown") will move the camera lower in z.
- "x" and "X" will reset the camera to the default position towards positive or negative x.
- "y" and "Y" will reset the camera to the default position towards positive or negative y.
- "." and "," will rotate azimuth to the right or left.
- "r" will reset the camera to the default position.
Left button: Select a point on the surface to focus the camera on it.
175 def __init__(self, move_step=0.05, angle_step=1.5): 176 """ 177 Interaction mode to fly over a surface. 178 179 Arguments: 180 move_step: float, optional 181 The step size for moving the camera in the plane of the surface. 182 angle_step: float, optional 183 The step size for rotating the camera. 184 185 Example: 186 [interaction_modes3.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/interaction_modes3.py) 187 """ 188 189 super().__init__() 190 191 self.interactor = None # filled in plotter.py 192 self.renderer = None # filled in plotter.py 193 194 self.angle_step = angle_step 195 self.move_step = move_step 196 197 self.tleft = vtki.vtkTransform() 198 self.tleft.RotateZ(self.angle_step) 199 200 self.tright = vtki.vtkTransform() 201 self.tright.RotateZ(-self.angle_step) 202 203 self.bounds = () 204 205 self.AddObserver("KeyPressEvent", self._key) 206 self.AddObserver("RightButtonPressEvent", self._right_button_press) 207 self.AddObserver("MouseWheelForwardEvent", self._mouse_wheel_forward) 208 self.AddObserver("MouseWheelBackwardEvent", self._mouse_wheel_backward) 209 self.AddObserver("LeftButtonPressEvent", self._left_button_press)
Interaction mode to fly over a surface.
Arguments:
- move_step: float, optional The step size for moving the camera in the plane of the surface.
- angle_step: float, optional The step size for rotating the camera.
Example:
211 @property 212 def camera(self): 213 """Return the active camera.""" 214 return self.renderer.GetActiveCamera()
Return the active camera.
423class BlenderStyle(vtki.vtkInteractorStyleUser): 424 """ 425 Create an interaction style using the Blender default key-bindings. 426 427 Camera action code is largely a translation of 428 [this](https://github.com/Kitware/VTK/blob/master/Interaction/Style/vtkInteractorStyleTrackballCamera.cxx) 429 Rubber band code 430 [here](https://gitlab.kitware.com/updega2/vtk/-/blob/d324b2e898b0da080edee76159c2f92e6f71abe2/Rendering/vtkInteractorStyleRubberBandZoom.cxx) 431 432 Interaction: 433 - Left button: select 434 - Left button drag: rubber band select or line select, depends on the dragged distance 435 436 - Middle button : rotate 437 - Middle button + shift : pan 438 - Middle button + ctrl : zoom 439 - Middle button + alt : center view on picked point 440 - Middle button + alt : zoom rubber band 441 442 - Mouse wheel : zoom 443 444 - Right key click: reserved for context-menu 445 446 447 Keys: 448 - 2 or 3 : toggle perspective view 449 - a : zoom all 450 - x,y,z : view direction (toggles positive and negative) 451 - left/right arrows: rotate 45 deg clockwise/ccw about z-axis, snaps to nearest 45 deg 452 - b : box zoom 453 - m : mouse middle lock (toggles) 454 - space : same as middle mouse button 455 - g : grab (move actors) 456 - enter : accept drag 457 - esc : cancel drag, call callbackEscape 458 459 460 Laptop Mode 461 ----------- 462 Use space or `m` as replacement for middle button (`m` is sticky, space is not), 463 if `callback_any_key` is assigned then this function is called on every key press. 464 If this function returns True then further processing of events is stopped. 465 466 467 Moving Actors 468 ------------- 469 Actors can be moved interactively by the user. 470 To support custom groups of actors to be moved as a whole the following system 471 is implemented: 472 473 When 'g' is pressed (grab) then a list of all the actors that are being dragged. 474 By default this is the selection, but this may be altered. 475 Drag is accepted using enter, click, or g. Drag is cancelled by esc 476 477 Events 478 ------ 479 - `callback_start_drag` is called when initializing the drag. 480 This is when to assign actors and other data to draginfo. 481 - `callback_end_drag` is called when the drag is accepted. 482 483 Responding to other events 484 -------------------------- 485 `callback_camera_direction_changed` : executed when camera has rotated but before re-rendering 486 487 Example: 488 [interaction_modes2.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/interaction_modes2.py) 489 490 .. note:: 491 This class is based on R. de Bruin's 492 [DAVE](https://github.com/RubendeBruin/DAVE/blob/master/src/DAVE/visual_helpers/vtkBlenderLikeInteractionStyle.py) 493 implementation as discussed [in this issue](https://github.com/marcomusy/vedo/discussions/788). 494 """ 495 496 def __init__(self): 497 498 super().__init__() 499 500 self.interactor = None 501 self.renderer = None 502 503 # callback_select is called whenever one or mode props are selected. 504 # callback will be called with a list of props of which the first entry 505 # is prop closest to the camera. 506 self.callback_select = None 507 self.callback_start_drag = None 508 self.callback_end_drag = None 509 self.callback_escape_key = None 510 self.callback_delete_key = None 511 self.callback_focus_key = None 512 self.callback_any_key = None 513 self.callback_measure = None # callback with argument float (meters) 514 self.callback_camera_direction_changed = None 515 516 # active drag 517 # assigned to a _BlenderStyleDragInfo object when dragging is active 518 self.draginfo: Optional[_BlenderStyleDragInfo] = None 519 520 # picking 521 self.picked_props = [] # will be filled by latest pick 522 523 # settings 524 self.mouse_motion_factor = 20 525 self.mouse_wheel_motion_factor = 0.1 526 self.zoom_motion_factor = 0.25 527 528 # internals 529 self.start_x = 0 # start of a drag 530 self.start_y = 0 531 self.end_x = 0 532 self.end_y = 0 533 534 self.middle_mouse_lock = False 535 self.middle_mouse_lock_actor = None # will be created when required 536 537 # Special Modes 538 self._is_box_zooming = False 539 540 # holds an image of the renderer output at the start of a drawing event 541 self._pixel_array = vtki.vtkUnsignedCharArray() 542 543 self._upside_down = False 544 545 self._left_button_down = False 546 self._middle_button_down = False 547 548 self.AddObserver("RightButtonPressEvent", self.right_button_press) 549 self.AddObserver("RightButtonReleaseEvent", self.right_button_release) 550 self.AddObserver("MiddleButtonPressEvent", self.middle_button_press) 551 self.AddObserver("MiddleButtonReleaseEvent", self.middle_button_release) 552 self.AddObserver("MouseWheelForwardEvent", self.mouse_wheel_forward) 553 self.AddObserver("MouseWheelBackwardEvent", self.mouse_wheel_backward) 554 self.AddObserver("LeftButtonPressEvent", self.left_button_press) 555 self.AddObserver("LeftButtonReleaseEvent", self.left_button_release) 556 self.AddObserver("MouseMoveEvent", self.mouse_move) 557 self.AddObserver("WindowResizeEvent", self.window_resized) 558 # ^does not seem to fire! 559 self.AddObserver("KeyPressEvent", self.key_press) 560 self.AddObserver("KeyReleaseEvent", self.key_release) 561 562 def right_button_press(self, _obj, _event): 563 pass 564 565 def right_button_release(self, _obj, _event): 566 pass 567 568 def middle_button_press(self, _obj, _event): 569 self._middle_button_down = True 570 571 def middle_button_release(self, _obj, _event): 572 self._middle_button_down = False 573 574 # perform middle button focus event if ALT is down 575 if self.GetInteractor().GetAltKey(): 576 # print("Middle button released while ALT is down") 577 # try to pick an object at the current mouse position 578 rwi = self.GetInteractor() 579 self.start_x, self.start_y = rwi.GetEventPosition() 580 props = self.perform_picking_on_selection() 581 582 if props: 583 self.focus_on(props[0]) 584 585 def mouse_wheel_backward(self, _obj, _event): 586 self.move_mouse_wheel(-1) 587 588 def mouse_wheel_forward(self, _obj, _event): 589 self.move_mouse_wheel(1) 590 591 def mouse_move(self, _obj, _event): 592 interactor = self.GetInteractor() 593 594 # Find the renderer that is active below the current mouse position 595 x, y = interactor.GetEventPosition() 596 self.FindPokedRenderer(x, y) 597 598 Shift = interactor.GetShiftKey() 599 Ctrl = interactor.GetControlKey() 600 Alt = interactor.GetAltKey() 601 602 MiddleButton = self._middle_button_down or self.middle_mouse_lock 603 604 # start with the special modes 605 if self._is_box_zooming: 606 self.draw_dragged_selection() 607 elif MiddleButton and not Shift and not Ctrl and not Alt: 608 self.rotate() 609 elif MiddleButton and Shift and not Ctrl and not Alt: 610 self.pan() 611 elif MiddleButton and Ctrl and not Shift and not Alt: 612 self.zoom() # Dolly 613 elif self.draginfo is not None: 614 self.execute_drag() 615 elif self._left_button_down and Ctrl and Shift: 616 self.draw_measurement() 617 elif self._left_button_down: 618 self.draw_dragged_selection() 619 620 self.InvokeEvent("InteractionEvent", None) 621 622 def move_mouse_wheel(self, direction): 623 rwi = self.GetInteractor() 624 625 # Find the renderer that is active below the current mouse position 626 x, y = rwi.GetEventPosition() 627 self.FindPokedRenderer(x, y) 628 629 # The movement 630 ren = self.GetCurrentRenderer() 631 632 # // Calculate the focal depth since we'll be using it a lot 633 camera = ren.GetActiveCamera() 634 viewFocus = camera.GetFocalPoint() 635 636 temp_out = [0, 0, 0] 637 self.ComputeWorldToDisplay( 638 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 639 ) 640 focalDepth = temp_out[2] 641 642 newPickPoint = [0, 0, 0, 0] 643 x, y = rwi.GetEventPosition() 644 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 645 646 # Has to recalc old mouse point since the viewport has moved, 647 # so can't move it outside the loop 648 oldPickPoint = [0, 0, 0, 0] 649 # xp, yp = rwi.GetLastEventPosition() 650 651 # find the center of the window 652 size = rwi.GetRenderWindow().GetSize() 653 xp = size[0] / 2 654 yp = size[1] / 2 655 656 self.ComputeDisplayToWorld(ren, xp, yp, focalDepth, oldPickPoint) 657 # Camera motion is reversed 658 move_factor = -1 * self.zoom_motion_factor * direction 659 660 motionVector = ( 661 move_factor * (oldPickPoint[0] - newPickPoint[0]), 662 move_factor * (oldPickPoint[1] - newPickPoint[1]), 663 move_factor * (oldPickPoint[2] - newPickPoint[2]), 664 ) 665 666 viewFocus = camera.GetFocalPoint() # do we need to do this again? Already did this 667 viewPoint = camera.GetPosition() 668 669 camera.SetFocalPoint( 670 motionVector[0] + viewFocus[0], 671 motionVector[1] + viewFocus[1], 672 motionVector[2] + viewFocus[2], 673 ) 674 camera.SetPosition( 675 motionVector[0] + viewPoint[0], 676 motionVector[1] + viewPoint[1], 677 motionVector[2] + viewPoint[2], 678 ) 679 680 # the zooming 681 factor = self.mouse_motion_factor * self.mouse_wheel_motion_factor 682 self.zoom_by_step(direction * factor) 683 684 def zoom_by_step(self, step): 685 if self.GetCurrentRenderer(): 686 self.StartDolly() 687 self.dolly(pow(1.1, step)) 688 self.EndDolly() 689 690 def left_button_press(self, _obj, _event): 691 if self._is_box_zooming: 692 return 693 if self.draginfo: 694 return 695 696 self._left_button_down = True 697 698 interactor = self.GetInteractor() 699 Shift = interactor.GetShiftKey() 700 Ctrl = interactor.GetControlKey() 701 702 if Shift and Ctrl: 703 if not self.GetCurrentRenderer().GetActiveCamera().GetParallelProjection(): 704 self.toggle_parallel_projection() 705 706 rwi = self.GetInteractor() 707 self.start_x, self.start_y = rwi.GetEventPosition() 708 self.end_x = self.start_x 709 self.end_y = self.start_y 710 711 self.initialize_screen_drawing() 712 713 def left_button_release(self, _obj, _event): 714 # print("LeftButtonRelease") 715 if self._is_box_zooming: 716 self._is_box_zooming = False 717 self.zoom_box(self.start_x, self.start_y, self.end_x, self.end_y) 718 return 719 720 if self.draginfo: 721 self.finish_drag() 722 return 723 724 self._left_button_down = False 725 726 interactor = self.GetInteractor() 727 728 Shift = interactor.GetShiftKey() 729 Ctrl = interactor.GetControlKey() 730 # Alt = interactor.GetAltKey() 731 732 if Ctrl and Shift: 733 pass # we were drawing the measurement 734 735 else: 736 if self.callback_select: 737 props = self.perform_picking_on_selection() 738 if props: # only call back if anything was selected 739 self.picked_props = tuple(props) 740 self.callback_select(props) 741 742 # remove the selection rubber band / line 743 self.GetInteractor().Render() 744 745 def key_press(self, obj, _event): 746 747 key = obj.GetKeySym() 748 KEY = key.upper() 749 750 # logging.info(f"Key Press: {key}") 751 if self.callback_any_key: 752 if self.callback_any_key(key): 753 return 754 755 if KEY == "M": 756 self.middle_mouse_lock = not self.middle_mouse_lock 757 self._update_middle_mouse_button_lock_actor() 758 elif KEY == "G": 759 if self.draginfo is not None: 760 self.finish_drag() 761 else: 762 if self.callback_start_drag: 763 self.callback_start_drag() 764 else: 765 self.start_drag() 766 # internally calls end-drag if drag is already active 767 elif KEY == "ESCAPE": 768 if self.callback_escape_key: 769 self.callback_escape_key() 770 if self.draginfo is not None: 771 self.cancel_drag() 772 elif KEY == "DELETE": 773 if self.callback_delete_key: 774 self.callback_delete_key() 775 elif KEY == "RETURN": 776 if self.draginfo: 777 self.finish_drag() 778 elif KEY == "SPACE": 779 self.middle_mouse_lock = True 780 # self._update_middle_mouse_button_lock_actor() 781 # self.GrabFocus("MouseMoveEvent", self) 782 # # TODO: grab and release focus; possible from python? 783 elif KEY == "B": 784 self._is_box_zooming = True 785 rwi = self.GetInteractor() 786 self.start_x, self.start_y = rwi.GetEventPosition() 787 self.end_x = self.start_x 788 self.end_y = self.start_y 789 self.initialize_screen_drawing() 790 elif KEY in ("2", "3"): 791 self.toggle_parallel_projection() 792 elif KEY == "A": 793 self.zoom_fit() 794 elif KEY == "X": 795 self.set_view_x() 796 elif KEY == "Y": 797 self.set_view_y() 798 elif KEY == "Z": 799 self.set_view_z() 800 elif KEY == "LEFT": 801 self.rotate_discrete_step(1) 802 elif KEY == "RIGHT": 803 self.rotate_discrete_step(-1) 804 elif KEY == "UP": 805 self.rotate_turtable_by(0, 10) 806 elif KEY == "DOWN": 807 self.rotate_turtable_by(0, -10) 808 elif KEY == "PLUS": 809 self.zoom_by_step(2) 810 elif KEY == "MINUS": 811 self.zoom_by_step(-2) 812 elif KEY == "F": 813 if self.callback_focus_key: 814 self.callback_focus_key() 815 816 self.InvokeEvent("InteractionEvent", None) 817 818 def key_release(self, obj, _event): 819 key = obj.GetKeySym() 820 KEY = key.upper() 821 # print(f"Key release: {key}") 822 if KEY == "SPACE": 823 if self.middle_mouse_lock: 824 self.middle_mouse_lock = False 825 self._update_middle_mouse_button_lock_actor() 826 827 def window_resized(self): 828 # print("window resized") 829 self.initialize_screen_drawing() 830 831 def rotate_discrete_step(self, movement_direction, step=22.5): 832 """ 833 Rotates CW or CCW to the nearest 45 deg angle 834 - includes some fuzzyness to determine about which axis 835 """ 836 camera = self.GetCurrentRenderer().GetActiveCamera() 837 838 step = np.deg2rad(step) 839 840 direction = -np.array(camera.GetViewPlaneNormal()) # current camera direction 841 842 if abs(direction[2]) < 0.7: 843 # horizontal view, rotate camera position about Z-axis 844 angle = np.arctan2(direction[1], direction[0]) 845 846 # find the nearest angle that is an integer number of steps 847 if movement_direction > 0: 848 angle = step * np.floor((angle + 0.1 * step) / step) + step 849 else: 850 angle = -step * np.floor(-(angle - 0.1 * step) / step) - step 851 852 dist = np.linalg.norm(direction[:2]) 853 direction[0] = np.cos(angle) * dist 854 direction[1] = np.sin(angle) * dist 855 856 self.set_camera_direction(direction) 857 858 else: # Top or bottom like view - rotate camera "up" direction 859 860 up = np.array(camera.GetViewUp()) 861 angle = np.arctan2(up[1], up[0]) 862 863 # find the nearest angle that is an integer number of steps 864 if movement_direction > 0: 865 angle = step * np.floor((angle + 0.1 * step) / step) + step 866 else: 867 angle = -step * np.floor(-(angle - 0.1 * step) / step) - step 868 869 dist = np.linalg.norm(up[:2]) 870 up[0] = np.cos(angle) * dist 871 up[1] = np.sin(angle) * dist 872 873 camera.SetViewUp(up) 874 camera.OrthogonalizeViewUp() 875 self.GetInteractor().Render() 876 877 def toggle_parallel_projection(self): 878 renderer = self.GetCurrentRenderer() 879 camera = renderer.GetActiveCamera() 880 camera.SetParallelProjection(not bool(camera.GetParallelProjection())) 881 self.GetInteractor().Render() 882 883 def set_view_x(self): 884 self.set_camera_plane_direction((1, 0, 0)) 885 886 def set_view_y(self): 887 self.set_camera_plane_direction((0, 1, 0)) 888 889 def set_view_z(self): 890 self.set_camera_plane_direction((0, 0, 1)) 891 892 def zoom_fit(self): 893 self.GetCurrentRenderer().ResetCamera() 894 self.GetInteractor().Render() 895 896 def set_camera_plane_direction(self, direction): 897 """ 898 Sets the camera to display a plane of which direction is the normal 899 - includes logic to reverse the direction if benificial 900 """ 901 camera = self.GetCurrentRenderer().GetActiveCamera() 902 903 direction = np.array(direction) 904 normal = camera.GetViewPlaneNormal() 905 # can not set the normal, need to change the position to do that 906 907 current_alignment = np.dot(normal, -direction) 908 if abs(current_alignment) > 0.9999: 909 # print("toggling") 910 direction = -np.array(normal) 911 elif current_alignment > 0: # find the nearest plane 912 # print("reversing to find nearest") 913 direction = -direction 914 915 self.set_camera_direction(-direction) 916 917 def set_camera_direction(self, direction): 918 """Sets the camera to this direction, sets view up if horizontal enough""" 919 direction = np.array(direction) 920 921 ren = self.GetCurrentRenderer() 922 camera = ren.GetActiveCamera() 923 rwi = self.GetInteractor() 924 925 pos = np.array(camera.GetPosition()) 926 focal = np.array(camera.GetFocalPoint()) 927 dist = np.linalg.norm(pos - focal) 928 929 pos = focal - dist * direction 930 camera.SetPosition(pos) 931 932 if abs(direction[2]) < 0.9: 933 camera.SetViewUp(0, 0, 1) 934 elif direction[2] > 0.9: 935 camera.SetViewUp(0, -1, 0) 936 else: 937 camera.SetViewUp(0, 1, 0) 938 939 camera.OrthogonalizeViewUp() 940 941 if self.GetAutoAdjustCameraClippingRange(): 942 ren.ResetCameraClippingRange() 943 944 if rwi.GetLightFollowCamera(): 945 ren.UpdateLightsGeometryToFollowCamera() 946 947 if self.callback_camera_direction_changed: 948 self.callback_camera_direction_changed() 949 950 self.GetInteractor().Render() 951 952 def perform_picking_on_selection(self): 953 """ 954 Performs 3d picking on the current dragged selection 955 956 If the distance between the start and endpoints is less than the threshold 957 then a SINGLE 3d prop is picked along the line. 958 959 The selection area is drawn by the rubber band and is defined by 960 `self.start_x, self.start_y, self.end_x, self.end_y` 961 """ 962 renderer = self.GetCurrentRenderer() 963 if not renderer: 964 return [] 965 966 assemblyPath = renderer.PickProp(self.start_x, self.start_y, self.end_x, self.end_y) 967 968 # re-pick in larger area if nothing is returned 969 if not assemblyPath: 970 self.start_x -= 2 971 self.end_x += 2 972 self.start_y -= 2 973 self.end_y += 2 974 assemblyPath = renderer.PickProp(self.start_x, self.start_y, self.end_x, self.end_y) 975 976 # The nearest prop (by Z-value) 977 if assemblyPath: 978 assert ( 979 assemblyPath.GetNumberOfItems() == 1 980 ), "Wrong assumption on number of returned nodes when picking" 981 nearest_prop = assemblyPath.GetItemAsObject(0).GetViewProp() 982 983 # all props 984 collection = renderer.GetPickResultProps() 985 props = [collection.GetItemAsObject(i) for i in range(collection.GetNumberOfItems())] 986 987 props.remove(nearest_prop) 988 props.insert(0, nearest_prop) 989 return props 990 991 else: 992 return [] 993 994 # ----------- actor dragging ------------ 995 def start_drag(self): 996 """Starts the drag operation""" 997 if self.callback_start_drag: 998 # print("Calling callback_start_drag") 999 self.callback_start_drag() 1000 return 1001 else: # grab the current selection 1002 if self.picked_props: 1003 self.start_drag_on_props(self.picked_props) 1004 else: 1005 pass 1006 # print('Can not start drag, 1007 # nothing selected and callback_start_drag not assigned') 1008 1009 def finish_drag(self): 1010 """Finishes the drag operation""" 1011 # print('Finished drag') 1012 if self.callback_end_drag: 1013 # reset actor positions as actors positions will be controlled 1014 # by called functions 1015 for pos0, actor in zip( 1016 self.draginfo.dragged_actors_original_positions, 1017 self.draginfo.actors_dragging, 1018 ): 1019 actor.SetPosition(pos0) 1020 self.callback_end_drag(self.draginfo) 1021 1022 self.draginfo = None 1023 1024 def start_drag_on_props(self, props): 1025 """ 1026 Starts drag on the provided props (actors) by filling self.draginfo""" 1027 if self.draginfo is not None: 1028 self.finish_drag() 1029 return 1030 1031 # create and fill drag-info 1032 draginfo = _BlenderStyleDragInfo() 1033 draginfo.actors_dragging = props # [*actors, *outlines] 1034 1035 for a in draginfo.actors_dragging: 1036 draginfo.dragged_actors_original_positions.append(a.GetPosition()) 1037 1038 # Get the start position of the drag in 3d 1039 rwi = self.GetInteractor() 1040 ren = self.GetCurrentRenderer() 1041 camera = ren.GetActiveCamera() 1042 viewFocus = camera.GetFocalPoint() 1043 1044 temp_out = [0, 0, 0] 1045 self.ComputeWorldToDisplay( 1046 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1047 ) 1048 focalDepth = temp_out[2] 1049 1050 newPickPoint = [0, 0, 0, 0] 1051 x, y = rwi.GetEventPosition() 1052 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1053 1054 mouse_pos_3d = np.array(newPickPoint[:3]) 1055 draginfo.start_position_3d = mouse_pos_3d 1056 self.draginfo = draginfo 1057 1058 def execute_drag(self): 1059 """Executes the drag operation""" 1060 rwi = self.GetInteractor() 1061 ren = self.GetCurrentRenderer() 1062 1063 camera = ren.GetActiveCamera() 1064 viewFocus = camera.GetFocalPoint() 1065 1066 # Get the picked point in 3d 1067 temp_out = [0, 0, 0] 1068 self.ComputeWorldToDisplay( 1069 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1070 ) 1071 focalDepth = temp_out[2] 1072 1073 newPickPoint = [0, 0, 0, 0] 1074 x, y = rwi.GetEventPosition() 1075 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1076 1077 mouse_pos_3d = np.array(newPickPoint[:3]) 1078 1079 # compute the delta and execute 1080 1081 delta = np.array(mouse_pos_3d) - self.draginfo.start_position_3d 1082 # print(f'Delta = {delta}') 1083 view_normal = np.array(ren.GetActiveCamera().GetViewPlaneNormal()) 1084 1085 delta_inplane = delta - view_normal * np.dot(delta, view_normal) 1086 # print(f'delta_inplane = {delta_inplane}') 1087 1088 for pos0, actor in zip( 1089 self.draginfo.dragged_actors_original_positions, 1090 self.draginfo.actors_dragging, 1091 ): 1092 m = actor.GetUserMatrix() 1093 if m: 1094 print("UserMatrices/transforms not supported") 1095 # m.Invert() #inplace 1096 # rotated = m.MultiplyFloatPoint([*delta_inplane, 1]) 1097 # actor.SetPosition(pos0 + np.array(rotated[:3])) 1098 actor.SetPosition(pos0 + delta_inplane) 1099 1100 # print(f'Set position to {pos0 + delta_inplane}') 1101 self.draginfo.delta = delta_inplane # store the current delta 1102 1103 self.GetInteractor().Render() 1104 1105 def cancel_drag(self): 1106 """Cancels the drag and restored the original positions of all dragged actors""" 1107 for pos0, actor in zip( 1108 self.draginfo.dragged_actors_original_positions, 1109 self.draginfo.actors_dragging, 1110 ): 1111 actor.SetPosition(pos0) 1112 self.draginfo = None 1113 self.GetInteractor().Render() 1114 1115 # ----------- end dragging -------------- 1116 1117 def zoom(self): 1118 """Zooms the camera""" 1119 rwi = self.GetInteractor() 1120 _, y = rwi.GetEventPosition() 1121 _, yp = rwi.GetLastEventPosition() 1122 direction = y - yp 1123 self.move_mouse_wheel(direction / 10) 1124 1125 def pan(self): 1126 """Pans the camera""" 1127 ren = self.GetCurrentRenderer() 1128 1129 if ren: 1130 rwi = self.GetInteractor() 1131 1132 # // Calculate the focal depth since we'll be using it a lot 1133 camera = ren.GetActiveCamera() 1134 viewFocus = camera.GetFocalPoint() 1135 1136 temp_out = [0, 0, 0] 1137 self.ComputeWorldToDisplay( 1138 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1139 ) 1140 focalDepth = temp_out[2] 1141 1142 newPickPoint = [0, 0, 0, 0] 1143 x, y = rwi.GetEventPosition() 1144 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1145 1146 # Has to recalc old mouse point since the viewport has moved, 1147 # so can't move it outside the loop 1148 1149 oldPickPoint = [0, 0, 0, 0] 1150 xp, yp = rwi.GetLastEventPosition() 1151 self.ComputeDisplayToWorld(ren, xp, yp, focalDepth, oldPickPoint) 1152 # 1153 # Camera motion is reversed 1154 motionVector = ( 1155 oldPickPoint[0] - newPickPoint[0], 1156 oldPickPoint[1] - newPickPoint[1], 1157 oldPickPoint[2] - newPickPoint[2], 1158 ) 1159 1160 viewFocus = camera.GetFocalPoint() # do we need to do this again? Already did this 1161 viewPoint = camera.GetPosition() 1162 1163 camera.SetFocalPoint( 1164 motionVector[0] + viewFocus[0], 1165 motionVector[1] + viewFocus[1], 1166 motionVector[2] + viewFocus[2], 1167 ) 1168 camera.SetPosition( 1169 motionVector[0] + viewPoint[0], 1170 motionVector[1] + viewPoint[1], 1171 motionVector[2] + viewPoint[2], 1172 ) 1173 1174 if rwi.GetLightFollowCamera(): 1175 ren.UpdateLightsGeometryToFollowCamera() 1176 1177 self.GetInteractor().Render() 1178 1179 def rotate(self): 1180 """Rotates the camera""" 1181 ren = self.GetCurrentRenderer() 1182 1183 if ren: 1184 1185 rwi = self.GetInteractor() 1186 dx = rwi.GetEventPosition()[0] - rwi.GetLastEventPosition()[0] 1187 dy = rwi.GetEventPosition()[1] - rwi.GetLastEventPosition()[1] 1188 1189 size = ren.GetRenderWindow().GetSize() 1190 delta_elevation = -20.0 / size[1] 1191 delta_azimuth = -20.0 / size[0] 1192 1193 rxf = dx * delta_azimuth * self.mouse_motion_factor 1194 ryf = dy * delta_elevation * self.mouse_motion_factor 1195 1196 self.rotate_turtable_by(rxf, ryf) 1197 1198 def rotate_turtable_by(self, rxf, ryf): 1199 """Rotates the camera in turn-table mode""" 1200 ren = self.GetCurrentRenderer() 1201 rwi = self.GetInteractor() 1202 1203 # rfx is rotation about the global Z vector (turn-table mode) 1204 # rfy is rotation about the side vector 1205 1206 camera = ren.GetActiveCamera() 1207 campos = np.array(camera.GetPosition()) 1208 focal = np.array(camera.GetFocalPoint()) 1209 up = camera.GetViewUp() 1210 upside_down_factor = -1 if up[2] < 0 else 1 1211 1212 # rotate about focal point 1213 P = campos - focal # camera position 1214 1215 # Rotate left/right about the global Z axis 1216 H = np.linalg.norm(P[:2]) # horizontal distance of camera to focal point 1217 elev = np.arctan2(P[2], H) # elevation 1218 1219 # if the camera is near the poles, then derive the azimuth from the up-vector 1220 sin_elev = np.sin(elev) 1221 if abs(sin_elev) < 0.8: 1222 azi = np.arctan2(P[1], P[0]) # azimuth from camera position 1223 else: 1224 if sin_elev < -0.8: 1225 azi = np.arctan2(upside_down_factor * up[1], upside_down_factor * up[0]) 1226 else: 1227 azi = np.arctan2(-upside_down_factor * up[1], -upside_down_factor * up[0]) 1228 1229 D = np.linalg.norm(P) # distance from focal point to camera 1230 1231 # apply the change in azimuth and elevation 1232 azi_new = azi + rxf / 60 1233 elev_new = elev + upside_down_factor * ryf / 60 1234 1235 # the changed elevation changes H (D stays the same) 1236 Hnew = D * np.cos(elev_new) 1237 1238 # calculate new camera position relative to focal point 1239 Pnew = np.array((Hnew * np.cos(azi_new), Hnew * np.sin(azi_new), D * np.sin(elev_new))) 1240 1241 # calculate the up-direction of the camera 1242 up_z = upside_down_factor * np.cos(elev_new) # z follows directly from elevation 1243 up_h = upside_down_factor * np.sin(elev_new) # horizontal component 1244 # 1245 # if upside_down: 1246 # up_z = -up_z 1247 # up_h = -up_h 1248 up = (-up_h * np.cos(azi_new), -up_h * np.sin(azi_new), up_z) 1249 new_pos = focal + Pnew 1250 camera.SetViewUp(up) 1251 camera.SetPosition(new_pos) 1252 camera.OrthogonalizeViewUp() 1253 1254 # Update 1255 1256 if self.GetAutoAdjustCameraClippingRange(): 1257 ren.ResetCameraClippingRange() 1258 1259 if rwi.GetLightFollowCamera(): 1260 ren.UpdateLightsGeometryToFollowCamera() 1261 1262 if self.callback_camera_direction_changed: 1263 self.callback_camera_direction_changed() 1264 1265 self.GetInteractor().Render() 1266 1267 def zoom_box(self, x1, y1, x2, y2): 1268 """Zooms to a box""" 1269 if x1 > x2: 1270 #swap x1 and x2 1271 x1, x2 = x2, x1 1272 if y1 > y2: 1273 y1, y2 = y2, y1 1274 1275 width = x2 - x1 1276 height = y2 - y1 1277 1278 ren = self.GetCurrentRenderer() 1279 size = ren.GetSize() 1280 origin = ren.GetOrigin() 1281 camera = ren.GetActiveCamera() 1282 1283 # Assuming we're drawing the band on the view-plane 1284 rbcenter = (x1 + width / 2, y1 + height / 2, 0) 1285 1286 ren.SetDisplayPoint(rbcenter) 1287 ren.DisplayToView() 1288 ren.ViewToWorld() 1289 1290 worldRBCenter = ren.GetWorldPoint() 1291 1292 invw = 1.0 / worldRBCenter[3] 1293 worldRBCenter = [c * invw for c in worldRBCenter] 1294 winCenter = [origin[0] + 0.5 * size[0], origin[1] + 0.5 * size[1], 0] 1295 1296 ren.SetDisplayPoint(winCenter) 1297 ren.DisplayToView() 1298 ren.ViewToWorld() 1299 1300 worldWinCenter = ren.GetWorldPoint() 1301 invw = 1.0 / worldWinCenter[3] 1302 worldWinCenter = [c * invw for c in worldWinCenter] 1303 1304 translation = [ 1305 worldRBCenter[0] - worldWinCenter[0], 1306 worldRBCenter[1] - worldWinCenter[1], 1307 worldRBCenter[2] - worldWinCenter[2], 1308 ] 1309 1310 pos = camera.GetPosition() 1311 fp = camera.GetFocalPoint() 1312 # 1313 pos = [pos[i] + translation[i] for i in range(3)] 1314 fp = [fp[i] + translation[i] for i in range(3)] 1315 1316 # 1317 camera.SetPosition(pos) 1318 camera.SetFocalPoint(fp) 1319 1320 if width > height: 1321 if width: 1322 camera.Zoom(size[0] / width) 1323 else: 1324 if height: 1325 camera.Zoom(size[1] / height) 1326 1327 self.GetInteractor().Render() 1328 1329 def focus_on(self, prop3D): 1330 """Move the camera to focus on this particular prop3D""" 1331 1332 position = prop3D.GetPosition() 1333 1334 ren = self.GetCurrentRenderer() 1335 camera = ren.GetActiveCamera() 1336 1337 fp = camera.GetFocalPoint() 1338 pos = camera.GetPosition() 1339 1340 camera.SetFocalPoint(position) 1341 camera.SetPosition( 1342 position[0] - fp[0] + pos[0], 1343 position[1] - fp[1] + pos[1], 1344 position[2] - fp[2] + pos[2], 1345 ) 1346 1347 if self.GetAutoAdjustCameraClippingRange(): 1348 ren.ResetCameraClippingRange() 1349 1350 rwi = self.GetInteractor() 1351 if rwi.GetLightFollowCamera(): 1352 ren.UpdateLightsGeometryToFollowCamera() 1353 1354 self.GetInteractor().Render() 1355 1356 def dolly(self, factor): 1357 """Dolly the camera""" 1358 ren = self.GetCurrentRenderer() 1359 1360 if ren: 1361 camera = ren.GetActiveCamera() 1362 1363 if camera.GetParallelProjection(): 1364 camera.SetParallelScale(camera.GetParallelScale() / factor) 1365 else: 1366 camera.Dolly(factor) 1367 if self.GetAutoAdjustCameraClippingRange(): 1368 ren.ResetCameraClippingRange() 1369 1370 # if not do_not_update: 1371 # rwi = self.GetInteractor() 1372 # if rwi.GetLightFollowCamera(): 1373 # ren.UpdateLightsGeometryToFollowCamera() 1374 # # rwi.Render() 1375 1376 def draw_measurement(self): 1377 """Draws a measurement line""" 1378 rwi = self.GetInteractor() 1379 self.end_x, self.end_y = rwi.GetEventPosition() 1380 self.draw_line(self.start_x, self.end_x, self.start_y, self.end_y) 1381 1382 def draw_dragged_selection(self): 1383 """Draws the selection rubber band""" 1384 rwi = self.GetInteractor() 1385 self.end_x, self.end_y = rwi.GetEventPosition() 1386 self.draw_rubber_band(self.start_x, self.end_x, self.start_y, self.end_y) 1387 1388 def initialize_screen_drawing(self): 1389 """Initializes the screen drawing""" 1390 # make an image of the currently rendered image 1391 1392 rwi = self.GetInteractor() 1393 rwin = rwi.GetRenderWindow() 1394 1395 size = rwin.GetSize() 1396 1397 self._pixel_array.Initialize() 1398 self._pixel_array.SetNumberOfComponents(4) 1399 self._pixel_array.SetNumberOfTuples(size[0] * size[1]) 1400 1401 front = 1 # what does this do? 1402 rwin.GetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, front, self._pixel_array) 1403 1404 def draw_rubber_band(self, x1, x2, y1, y2): 1405 """Draws a rubber band""" 1406 rwi = self.GetInteractor() 1407 rwin = rwi.GetRenderWindow() 1408 1409 size = rwin.GetSize() 1410 1411 tempPA = vtki.vtkUnsignedCharArray() 1412 tempPA.DeepCopy(self._pixel_array) 1413 1414 # check size, viewport may have been resized in the mean-time 1415 if tempPA.GetNumberOfTuples() != size[0] * size[1]: 1416 # print( 1417 # "Starting new screen-image - viewport has resized without us knowing" 1418 # ) 1419 self.initialize_screen_drawing() 1420 self.draw_rubber_band(x1, x2, y1, y2) 1421 return 1422 1423 x2 = min(x2, size[0] - 1) 1424 y2 = min(y2, size[1] - 1) 1425 1426 x2 = max(x2, 0) 1427 y2 = max(y2, 0) 1428 1429 # Modify the pixel array 1430 width = abs(x2 - x1) 1431 height = abs(y2 - y1) 1432 minx = min(x2, x1) 1433 miny = min(y2, y1) 1434 1435 # draw top and bottom 1436 for i in range(width): 1437 1438 # c = round((10*i % 254)/254) * 254 # find some alternating color 1439 c = 0 1440 1441 idx = (miny * size[0]) + minx + i 1442 tempPA.SetTuple(idx, (c, c, c, 1)) 1443 1444 idx = ((miny + height) * size[0]) + minx + i 1445 tempPA.SetTuple(idx, (c, c, c, 1)) 1446 1447 # draw left and right 1448 for i in range(height): 1449 # c = round((10 * i % 254) / 254) * 254 # find some alternating color 1450 c = 0 1451 1452 idx = ((miny + i) * size[0]) + minx 1453 tempPA.SetTuple(idx, (c, c, c, 1)) 1454 1455 idx = idx + width 1456 tempPA.SetTuple(idx, (c, c, c, 1)) 1457 1458 # and Copy back to the window 1459 rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, tempPA, 0) 1460 rwin.Frame() 1461 1462 def line2pixels(self, x1, x2, y1, y2): 1463 """Returns the x and y values of the pixels on a line between x1,y1 and x2,y2. 1464 If start and end are identical then a single point is returned""" 1465 1466 dx = x2 - x1 1467 dy = y2 - y1 1468 1469 if dx == 0 and dy == 0: 1470 return [x1], [y1] 1471 1472 if abs(dx) > abs(dy): 1473 dhdw = dy / dx 1474 r = range(0, dx, int(dx / abs(dx))) 1475 x = [x1 + i for i in r] 1476 y = [round(y1 + dhdw * i) for i in r] 1477 else: 1478 dwdh = dx / dy 1479 r = range(0, dy, int(dy / abs(dy))) 1480 y = [y1 + i for i in r] 1481 x = [round(x1 + i * dwdh) for i in r] 1482 1483 return x, y 1484 1485 def draw_line(self, x1, x2, y1, y2): 1486 rwi = self.GetInteractor() 1487 rwin = rwi.GetRenderWindow() 1488 1489 size = rwin.GetSize() 1490 1491 x1 = min(max(x1, 0), size[0]) 1492 x2 = min(max(x2, 0), size[0]) 1493 y1 = min(max(y1, 0), size[1]) 1494 y2 = min(max(y2, 0), size[1]) 1495 1496 tempPA = vtki.vtkUnsignedCharArray() 1497 tempPA.DeepCopy(self._pixel_array) 1498 1499 xs, ys = self.line2pixels(x1, x2, y1, y2) 1500 for x, y in zip(xs, ys): 1501 idx = (y * size[0]) + x 1502 tempPA.SetTuple(idx, (0, 0, 0, 1)) 1503 1504 # and Copy back to the window 1505 rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, tempPA, 0) 1506 1507 camera = self.GetCurrentRenderer().GetActiveCamera() 1508 scale = camera.GetParallelScale() 1509 1510 # Set/Get the scaling used for a parallel projection, i.e. 1511 # 1512 # the half of the height of the viewport in world-coordinate distances. 1513 # The default is 1. Note that the "scale" parameter works as an "inverse scale" 1514 # larger numbers produce smaller images. 1515 # This method has no effect in perspective projection mode 1516 1517 half_height = size[1] / 2 1518 # half_height [px] = scale [world-coordinates] 1519 1520 length = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5 1521 meters_per_pixel = scale / half_height 1522 meters = length * meters_per_pixel 1523 1524 if camera.GetParallelProjection(): 1525 print(f"Line length = {length} px = {meters} m") 1526 else: 1527 print("Need to be in non-perspective mode to measure. Press 2 or 3 to get there") 1528 1529 if self.callback_measure: 1530 self.callback_measure(meters) 1531 1532 # # can we add something to the window here? 1533 # freeType = vtk.FreeTypeTools.GetInstance() 1534 # textProperty = vtki.vtkTextProperty() 1535 # textProperty.SetJustificationToLeft() 1536 # textProperty.SetFontSize(24) 1537 # textProperty.SetOrientation(25) 1538 # 1539 # textImage = vtki.vtkImageData() 1540 # freeType.RenderString(textProperty, "a somewhat longer text", 72, textImage) 1541 # # this does not give an error, assume it works 1542 # # 1543 # textImage.GetDimensions() 1544 # textImage.GetExtent() 1545 # 1546 # # # Now put the textImage in the RenderWindow 1547 # rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, textImage, 0) 1548 1549 rwin.Frame() 1550 1551 def _update_middle_mouse_button_lock_actor(self): 1552 1553 if self.middle_mouse_lock_actor is None: 1554 # create the actor 1555 # Create a text on the top-rightcenter 1556 textMapper = vtki.new("TextMapper") 1557 textMapper.SetInput("Middle mouse lock [m or space] active") 1558 textProp = textMapper.GetTextProperty() 1559 textProp.SetFontSize(12) 1560 textProp.SetFontFamilyToTimes() 1561 textProp.BoldOff() 1562 textProp.ItalicOff() 1563 textProp.ShadowOff() 1564 textProp.SetVerticalJustificationToTop() 1565 textProp.SetJustificationToCentered() 1566 textProp.SetColor((0, 0, 0)) 1567 1568 self.middle_mouse_lock_actor = vtki.vtkActor2D() 1569 self.middle_mouse_lock_actor.SetMapper(textMapper) 1570 self.middle_mouse_lock_actor.GetPositionCoordinate().SetCoordinateSystemToNormalizedDisplay() 1571 self.middle_mouse_lock_actor.GetPositionCoordinate().SetValue(0.5, 0.98) 1572 1573 self.GetCurrentRenderer().AddActor(self.middle_mouse_lock_actor) 1574 1575 self.middle_mouse_lock_actor.SetVisibility(self.middle_mouse_lock) 1576 self.GetInteractor().Render()
Create an interaction style using the Blender default key-bindings.
Camera action code is largely a translation of this Rubber band code here
Interaction:
- Left button: select
Left button drag: rubber band select or line select, depends on the dragged distance
Middle button : rotate
- Middle button + shift : pan
- Middle button + ctrl : zoom
- Middle button + alt : center view on picked point
Middle button + alt : zoom rubber band
Mouse wheel : zoom
Right key click: reserved for context-menu
Keys:
- 2 or 3 : toggle perspective view
- a : zoom all
- x,y,z : view direction (toggles positive and negative)
- left/right arrows: rotate 45 deg clockwise/ccw about z-axis, snaps to nearest 45 deg
- b : box zoom
- m : mouse middle lock (toggles)
- space : same as middle mouse button
- g : grab (move actors)
- enter : accept drag
- esc : cancel drag, call callbackEscape
Laptop Mode
Use space or m
as replacement for middle button (m
is sticky, space is not),
if callback_any_key
is assigned then this function is called on every key press.
If this function returns True then further processing of events is stopped.
Moving Actors
Actors can be moved interactively by the user. To support custom groups of actors to be moved as a whole the following system is implemented:
When 'g' is pressed (grab) then a list of all the actors that are being dragged. By default this is the selection, but this may be altered. Drag is accepted using enter, click, or g. Drag is cancelled by esc
Events
callback_start_drag
is called when initializing the drag. This is when to assign actors and other data to draginfo.callback_end_drag
is called when the drag is accepted.
Responding to other events
callback_camera_direction_changed
: executed when camera has rotated but before re-rendering
Example:
This class is based on R. de Bruin's DAVE implementation as discussed in this issue.
496 def __init__(self): 497 498 super().__init__() 499 500 self.interactor = None 501 self.renderer = None 502 503 # callback_select is called whenever one or mode props are selected. 504 # callback will be called with a list of props of which the first entry 505 # is prop closest to the camera. 506 self.callback_select = None 507 self.callback_start_drag = None 508 self.callback_end_drag = None 509 self.callback_escape_key = None 510 self.callback_delete_key = None 511 self.callback_focus_key = None 512 self.callback_any_key = None 513 self.callback_measure = None # callback with argument float (meters) 514 self.callback_camera_direction_changed = None 515 516 # active drag 517 # assigned to a _BlenderStyleDragInfo object when dragging is active 518 self.draginfo: Optional[_BlenderStyleDragInfo] = None 519 520 # picking 521 self.picked_props = [] # will be filled by latest pick 522 523 # settings 524 self.mouse_motion_factor = 20 525 self.mouse_wheel_motion_factor = 0.1 526 self.zoom_motion_factor = 0.25 527 528 # internals 529 self.start_x = 0 # start of a drag 530 self.start_y = 0 531 self.end_x = 0 532 self.end_y = 0 533 534 self.middle_mouse_lock = False 535 self.middle_mouse_lock_actor = None # will be created when required 536 537 # Special Modes 538 self._is_box_zooming = False 539 540 # holds an image of the renderer output at the start of a drawing event 541 self._pixel_array = vtki.vtkUnsignedCharArray() 542 543 self._upside_down = False 544 545 self._left_button_down = False 546 self._middle_button_down = False 547 548 self.AddObserver("RightButtonPressEvent", self.right_button_press) 549 self.AddObserver("RightButtonReleaseEvent", self.right_button_release) 550 self.AddObserver("MiddleButtonPressEvent", self.middle_button_press) 551 self.AddObserver("MiddleButtonReleaseEvent", self.middle_button_release) 552 self.AddObserver("MouseWheelForwardEvent", self.mouse_wheel_forward) 553 self.AddObserver("MouseWheelBackwardEvent", self.mouse_wheel_backward) 554 self.AddObserver("LeftButtonPressEvent", self.left_button_press) 555 self.AddObserver("LeftButtonReleaseEvent", self.left_button_release) 556 self.AddObserver("MouseMoveEvent", self.mouse_move) 557 self.AddObserver("WindowResizeEvent", self.window_resized) 558 # ^does not seem to fire! 559 self.AddObserver("KeyPressEvent", self.key_press) 560 self.AddObserver("KeyReleaseEvent", self.key_release)
591 def mouse_move(self, _obj, _event): 592 interactor = self.GetInteractor() 593 594 # Find the renderer that is active below the current mouse position 595 x, y = interactor.GetEventPosition() 596 self.FindPokedRenderer(x, y) 597 598 Shift = interactor.GetShiftKey() 599 Ctrl = interactor.GetControlKey() 600 Alt = interactor.GetAltKey() 601 602 MiddleButton = self._middle_button_down or self.middle_mouse_lock 603 604 # start with the special modes 605 if self._is_box_zooming: 606 self.draw_dragged_selection() 607 elif MiddleButton and not Shift and not Ctrl and not Alt: 608 self.rotate() 609 elif MiddleButton and Shift and not Ctrl and not Alt: 610 self.pan() 611 elif MiddleButton and Ctrl and not Shift and not Alt: 612 self.zoom() # Dolly 613 elif self.draginfo is not None: 614 self.execute_drag() 615 elif self._left_button_down and Ctrl and Shift: 616 self.draw_measurement() 617 elif self._left_button_down: 618 self.draw_dragged_selection() 619 620 self.InvokeEvent("InteractionEvent", None)
622 def move_mouse_wheel(self, direction): 623 rwi = self.GetInteractor() 624 625 # Find the renderer that is active below the current mouse position 626 x, y = rwi.GetEventPosition() 627 self.FindPokedRenderer(x, y) 628 629 # The movement 630 ren = self.GetCurrentRenderer() 631 632 # // Calculate the focal depth since we'll be using it a lot 633 camera = ren.GetActiveCamera() 634 viewFocus = camera.GetFocalPoint() 635 636 temp_out = [0, 0, 0] 637 self.ComputeWorldToDisplay( 638 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 639 ) 640 focalDepth = temp_out[2] 641 642 newPickPoint = [0, 0, 0, 0] 643 x, y = rwi.GetEventPosition() 644 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 645 646 # Has to recalc old mouse point since the viewport has moved, 647 # so can't move it outside the loop 648 oldPickPoint = [0, 0, 0, 0] 649 # xp, yp = rwi.GetLastEventPosition() 650 651 # find the center of the window 652 size = rwi.GetRenderWindow().GetSize() 653 xp = size[0] / 2 654 yp = size[1] / 2 655 656 self.ComputeDisplayToWorld(ren, xp, yp, focalDepth, oldPickPoint) 657 # Camera motion is reversed 658 move_factor = -1 * self.zoom_motion_factor * direction 659 660 motionVector = ( 661 move_factor * (oldPickPoint[0] - newPickPoint[0]), 662 move_factor * (oldPickPoint[1] - newPickPoint[1]), 663 move_factor * (oldPickPoint[2] - newPickPoint[2]), 664 ) 665 666 viewFocus = camera.GetFocalPoint() # do we need to do this again? Already did this 667 viewPoint = camera.GetPosition() 668 669 camera.SetFocalPoint( 670 motionVector[0] + viewFocus[0], 671 motionVector[1] + viewFocus[1], 672 motionVector[2] + viewFocus[2], 673 ) 674 camera.SetPosition( 675 motionVector[0] + viewPoint[0], 676 motionVector[1] + viewPoint[1], 677 motionVector[2] + viewPoint[2], 678 ) 679 680 # the zooming 681 factor = self.mouse_motion_factor * self.mouse_wheel_motion_factor 682 self.zoom_by_step(direction * factor)
745 def key_press(self, obj, _event): 746 747 key = obj.GetKeySym() 748 KEY = key.upper() 749 750 # logging.info(f"Key Press: {key}") 751 if self.callback_any_key: 752 if self.callback_any_key(key): 753 return 754 755 if KEY == "M": 756 self.middle_mouse_lock = not self.middle_mouse_lock 757 self._update_middle_mouse_button_lock_actor() 758 elif KEY == "G": 759 if self.draginfo is not None: 760 self.finish_drag() 761 else: 762 if self.callback_start_drag: 763 self.callback_start_drag() 764 else: 765 self.start_drag() 766 # internally calls end-drag if drag is already active 767 elif KEY == "ESCAPE": 768 if self.callback_escape_key: 769 self.callback_escape_key() 770 if self.draginfo is not None: 771 self.cancel_drag() 772 elif KEY == "DELETE": 773 if self.callback_delete_key: 774 self.callback_delete_key() 775 elif KEY == "RETURN": 776 if self.draginfo: 777 self.finish_drag() 778 elif KEY == "SPACE": 779 self.middle_mouse_lock = True 780 # self._update_middle_mouse_button_lock_actor() 781 # self.GrabFocus("MouseMoveEvent", self) 782 # # TODO: grab and release focus; possible from python? 783 elif KEY == "B": 784 self._is_box_zooming = True 785 rwi = self.GetInteractor() 786 self.start_x, self.start_y = rwi.GetEventPosition() 787 self.end_x = self.start_x 788 self.end_y = self.start_y 789 self.initialize_screen_drawing() 790 elif KEY in ("2", "3"): 791 self.toggle_parallel_projection() 792 elif KEY == "A": 793 self.zoom_fit() 794 elif KEY == "X": 795 self.set_view_x() 796 elif KEY == "Y": 797 self.set_view_y() 798 elif KEY == "Z": 799 self.set_view_z() 800 elif KEY == "LEFT": 801 self.rotate_discrete_step(1) 802 elif KEY == "RIGHT": 803 self.rotate_discrete_step(-1) 804 elif KEY == "UP": 805 self.rotate_turtable_by(0, 10) 806 elif KEY == "DOWN": 807 self.rotate_turtable_by(0, -10) 808 elif KEY == "PLUS": 809 self.zoom_by_step(2) 810 elif KEY == "MINUS": 811 self.zoom_by_step(-2) 812 elif KEY == "F": 813 if self.callback_focus_key: 814 self.callback_focus_key() 815 816 self.InvokeEvent("InteractionEvent", None)
831 def rotate_discrete_step(self, movement_direction, step=22.5): 832 """ 833 Rotates CW or CCW to the nearest 45 deg angle 834 - includes some fuzzyness to determine about which axis 835 """ 836 camera = self.GetCurrentRenderer().GetActiveCamera() 837 838 step = np.deg2rad(step) 839 840 direction = -np.array(camera.GetViewPlaneNormal()) # current camera direction 841 842 if abs(direction[2]) < 0.7: 843 # horizontal view, rotate camera position about Z-axis 844 angle = np.arctan2(direction[1], direction[0]) 845 846 # find the nearest angle that is an integer number of steps 847 if movement_direction > 0: 848 angle = step * np.floor((angle + 0.1 * step) / step) + step 849 else: 850 angle = -step * np.floor(-(angle - 0.1 * step) / step) - step 851 852 dist = np.linalg.norm(direction[:2]) 853 direction[0] = np.cos(angle) * dist 854 direction[1] = np.sin(angle) * dist 855 856 self.set_camera_direction(direction) 857 858 else: # Top or bottom like view - rotate camera "up" direction 859 860 up = np.array(camera.GetViewUp()) 861 angle = np.arctan2(up[1], up[0]) 862 863 # find the nearest angle that is an integer number of steps 864 if movement_direction > 0: 865 angle = step * np.floor((angle + 0.1 * step) / step) + step 866 else: 867 angle = -step * np.floor(-(angle - 0.1 * step) / step) - step 868 869 dist = np.linalg.norm(up[:2]) 870 up[0] = np.cos(angle) * dist 871 up[1] = np.sin(angle) * dist 872 873 camera.SetViewUp(up) 874 camera.OrthogonalizeViewUp() 875 self.GetInteractor().Render()
Rotates CW or CCW to the nearest 45 deg angle
- includes some fuzzyness to determine about which axis
896 def set_camera_plane_direction(self, direction): 897 """ 898 Sets the camera to display a plane of which direction is the normal 899 - includes logic to reverse the direction if benificial 900 """ 901 camera = self.GetCurrentRenderer().GetActiveCamera() 902 903 direction = np.array(direction) 904 normal = camera.GetViewPlaneNormal() 905 # can not set the normal, need to change the position to do that 906 907 current_alignment = np.dot(normal, -direction) 908 if abs(current_alignment) > 0.9999: 909 # print("toggling") 910 direction = -np.array(normal) 911 elif current_alignment > 0: # find the nearest plane 912 # print("reversing to find nearest") 913 direction = -direction 914 915 self.set_camera_direction(-direction)
Sets the camera to display a plane of which direction is the normal
- includes logic to reverse the direction if benificial
917 def set_camera_direction(self, direction): 918 """Sets the camera to this direction, sets view up if horizontal enough""" 919 direction = np.array(direction) 920 921 ren = self.GetCurrentRenderer() 922 camera = ren.GetActiveCamera() 923 rwi = self.GetInteractor() 924 925 pos = np.array(camera.GetPosition()) 926 focal = np.array(camera.GetFocalPoint()) 927 dist = np.linalg.norm(pos - focal) 928 929 pos = focal - dist * direction 930 camera.SetPosition(pos) 931 932 if abs(direction[2]) < 0.9: 933 camera.SetViewUp(0, 0, 1) 934 elif direction[2] > 0.9: 935 camera.SetViewUp(0, -1, 0) 936 else: 937 camera.SetViewUp(0, 1, 0) 938 939 camera.OrthogonalizeViewUp() 940 941 if self.GetAutoAdjustCameraClippingRange(): 942 ren.ResetCameraClippingRange() 943 944 if rwi.GetLightFollowCamera(): 945 ren.UpdateLightsGeometryToFollowCamera() 946 947 if self.callback_camera_direction_changed: 948 self.callback_camera_direction_changed() 949 950 self.GetInteractor().Render()
Sets the camera to this direction, sets view up if horizontal enough
952 def perform_picking_on_selection(self): 953 """ 954 Performs 3d picking on the current dragged selection 955 956 If the distance between the start and endpoints is less than the threshold 957 then a SINGLE 3d prop is picked along the line. 958 959 The selection area is drawn by the rubber band and is defined by 960 `self.start_x, self.start_y, self.end_x, self.end_y` 961 """ 962 renderer = self.GetCurrentRenderer() 963 if not renderer: 964 return [] 965 966 assemblyPath = renderer.PickProp(self.start_x, self.start_y, self.end_x, self.end_y) 967 968 # re-pick in larger area if nothing is returned 969 if not assemblyPath: 970 self.start_x -= 2 971 self.end_x += 2 972 self.start_y -= 2 973 self.end_y += 2 974 assemblyPath = renderer.PickProp(self.start_x, self.start_y, self.end_x, self.end_y) 975 976 # The nearest prop (by Z-value) 977 if assemblyPath: 978 assert ( 979 assemblyPath.GetNumberOfItems() == 1 980 ), "Wrong assumption on number of returned nodes when picking" 981 nearest_prop = assemblyPath.GetItemAsObject(0).GetViewProp() 982 983 # all props 984 collection = renderer.GetPickResultProps() 985 props = [collection.GetItemAsObject(i) for i in range(collection.GetNumberOfItems())] 986 987 props.remove(nearest_prop) 988 props.insert(0, nearest_prop) 989 return props 990 991 else: 992 return []
Performs 3d picking on the current dragged selection
If the distance between the start and endpoints is less than the threshold then a SINGLE 3d prop is picked along the line.
The selection area is drawn by the rubber band and is defined by
self.start_x, self.start_y, self.end_x, self.end_y
995 def start_drag(self): 996 """Starts the drag operation""" 997 if self.callback_start_drag: 998 # print("Calling callback_start_drag") 999 self.callback_start_drag() 1000 return 1001 else: # grab the current selection 1002 if self.picked_props: 1003 self.start_drag_on_props(self.picked_props) 1004 else: 1005 pass 1006 # print('Can not start drag, 1007 # nothing selected and callback_start_drag not assigned')
Starts the drag operation
1009 def finish_drag(self): 1010 """Finishes the drag operation""" 1011 # print('Finished drag') 1012 if self.callback_end_drag: 1013 # reset actor positions as actors positions will be controlled 1014 # by called functions 1015 for pos0, actor in zip( 1016 self.draginfo.dragged_actors_original_positions, 1017 self.draginfo.actors_dragging, 1018 ): 1019 actor.SetPosition(pos0) 1020 self.callback_end_drag(self.draginfo) 1021 1022 self.draginfo = None
Finishes the drag operation
1024 def start_drag_on_props(self, props): 1025 """ 1026 Starts drag on the provided props (actors) by filling self.draginfo""" 1027 if self.draginfo is not None: 1028 self.finish_drag() 1029 return 1030 1031 # create and fill drag-info 1032 draginfo = _BlenderStyleDragInfo() 1033 draginfo.actors_dragging = props # [*actors, *outlines] 1034 1035 for a in draginfo.actors_dragging: 1036 draginfo.dragged_actors_original_positions.append(a.GetPosition()) 1037 1038 # Get the start position of the drag in 3d 1039 rwi = self.GetInteractor() 1040 ren = self.GetCurrentRenderer() 1041 camera = ren.GetActiveCamera() 1042 viewFocus = camera.GetFocalPoint() 1043 1044 temp_out = [0, 0, 0] 1045 self.ComputeWorldToDisplay( 1046 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1047 ) 1048 focalDepth = temp_out[2] 1049 1050 newPickPoint = [0, 0, 0, 0] 1051 x, y = rwi.GetEventPosition() 1052 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1053 1054 mouse_pos_3d = np.array(newPickPoint[:3]) 1055 draginfo.start_position_3d = mouse_pos_3d 1056 self.draginfo = draginfo
Starts drag on the provided props (actors) by filling self.draginfo
1058 def execute_drag(self): 1059 """Executes the drag operation""" 1060 rwi = self.GetInteractor() 1061 ren = self.GetCurrentRenderer() 1062 1063 camera = ren.GetActiveCamera() 1064 viewFocus = camera.GetFocalPoint() 1065 1066 # Get the picked point in 3d 1067 temp_out = [0, 0, 0] 1068 self.ComputeWorldToDisplay( 1069 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1070 ) 1071 focalDepth = temp_out[2] 1072 1073 newPickPoint = [0, 0, 0, 0] 1074 x, y = rwi.GetEventPosition() 1075 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1076 1077 mouse_pos_3d = np.array(newPickPoint[:3]) 1078 1079 # compute the delta and execute 1080 1081 delta = np.array(mouse_pos_3d) - self.draginfo.start_position_3d 1082 # print(f'Delta = {delta}') 1083 view_normal = np.array(ren.GetActiveCamera().GetViewPlaneNormal()) 1084 1085 delta_inplane = delta - view_normal * np.dot(delta, view_normal) 1086 # print(f'delta_inplane = {delta_inplane}') 1087 1088 for pos0, actor in zip( 1089 self.draginfo.dragged_actors_original_positions, 1090 self.draginfo.actors_dragging, 1091 ): 1092 m = actor.GetUserMatrix() 1093 if m: 1094 print("UserMatrices/transforms not supported") 1095 # m.Invert() #inplace 1096 # rotated = m.MultiplyFloatPoint([*delta_inplane, 1]) 1097 # actor.SetPosition(pos0 + np.array(rotated[:3])) 1098 actor.SetPosition(pos0 + delta_inplane) 1099 1100 # print(f'Set position to {pos0 + delta_inplane}') 1101 self.draginfo.delta = delta_inplane # store the current delta 1102 1103 self.GetInteractor().Render()
Executes the drag operation
1105 def cancel_drag(self): 1106 """Cancels the drag and restored the original positions of all dragged actors""" 1107 for pos0, actor in zip( 1108 self.draginfo.dragged_actors_original_positions, 1109 self.draginfo.actors_dragging, 1110 ): 1111 actor.SetPosition(pos0) 1112 self.draginfo = None 1113 self.GetInteractor().Render()
Cancels the drag and restored the original positions of all dragged actors
1117 def zoom(self): 1118 """Zooms the camera""" 1119 rwi = self.GetInteractor() 1120 _, y = rwi.GetEventPosition() 1121 _, yp = rwi.GetLastEventPosition() 1122 direction = y - yp 1123 self.move_mouse_wheel(direction / 10)
Zooms the camera
1125 def pan(self): 1126 """Pans the camera""" 1127 ren = self.GetCurrentRenderer() 1128 1129 if ren: 1130 rwi = self.GetInteractor() 1131 1132 # // Calculate the focal depth since we'll be using it a lot 1133 camera = ren.GetActiveCamera() 1134 viewFocus = camera.GetFocalPoint() 1135 1136 temp_out = [0, 0, 0] 1137 self.ComputeWorldToDisplay( 1138 ren, viewFocus[0], viewFocus[1], viewFocus[2], temp_out 1139 ) 1140 focalDepth = temp_out[2] 1141 1142 newPickPoint = [0, 0, 0, 0] 1143 x, y = rwi.GetEventPosition() 1144 self.ComputeDisplayToWorld(ren, x, y, focalDepth, newPickPoint) 1145 1146 # Has to recalc old mouse point since the viewport has moved, 1147 # so can't move it outside the loop 1148 1149 oldPickPoint = [0, 0, 0, 0] 1150 xp, yp = rwi.GetLastEventPosition() 1151 self.ComputeDisplayToWorld(ren, xp, yp, focalDepth, oldPickPoint) 1152 # 1153 # Camera motion is reversed 1154 motionVector = ( 1155 oldPickPoint[0] - newPickPoint[0], 1156 oldPickPoint[1] - newPickPoint[1], 1157 oldPickPoint[2] - newPickPoint[2], 1158 ) 1159 1160 viewFocus = camera.GetFocalPoint() # do we need to do this again? Already did this 1161 viewPoint = camera.GetPosition() 1162 1163 camera.SetFocalPoint( 1164 motionVector[0] + viewFocus[0], 1165 motionVector[1] + viewFocus[1], 1166 motionVector[2] + viewFocus[2], 1167 ) 1168 camera.SetPosition( 1169 motionVector[0] + viewPoint[0], 1170 motionVector[1] + viewPoint[1], 1171 motionVector[2] + viewPoint[2], 1172 ) 1173 1174 if rwi.GetLightFollowCamera(): 1175 ren.UpdateLightsGeometryToFollowCamera() 1176 1177 self.GetInteractor().Render()
Pans the camera
1179 def rotate(self): 1180 """Rotates the camera""" 1181 ren = self.GetCurrentRenderer() 1182 1183 if ren: 1184 1185 rwi = self.GetInteractor() 1186 dx = rwi.GetEventPosition()[0] - rwi.GetLastEventPosition()[0] 1187 dy = rwi.GetEventPosition()[1] - rwi.GetLastEventPosition()[1] 1188 1189 size = ren.GetRenderWindow().GetSize() 1190 delta_elevation = -20.0 / size[1] 1191 delta_azimuth = -20.0 / size[0] 1192 1193 rxf = dx * delta_azimuth * self.mouse_motion_factor 1194 ryf = dy * delta_elevation * self.mouse_motion_factor 1195 1196 self.rotate_turtable_by(rxf, ryf)
Rotates the camera
1198 def rotate_turtable_by(self, rxf, ryf): 1199 """Rotates the camera in turn-table mode""" 1200 ren = self.GetCurrentRenderer() 1201 rwi = self.GetInteractor() 1202 1203 # rfx is rotation about the global Z vector (turn-table mode) 1204 # rfy is rotation about the side vector 1205 1206 camera = ren.GetActiveCamera() 1207 campos = np.array(camera.GetPosition()) 1208 focal = np.array(camera.GetFocalPoint()) 1209 up = camera.GetViewUp() 1210 upside_down_factor = -1 if up[2] < 0 else 1 1211 1212 # rotate about focal point 1213 P = campos - focal # camera position 1214 1215 # Rotate left/right about the global Z axis 1216 H = np.linalg.norm(P[:2]) # horizontal distance of camera to focal point 1217 elev = np.arctan2(P[2], H) # elevation 1218 1219 # if the camera is near the poles, then derive the azimuth from the up-vector 1220 sin_elev = np.sin(elev) 1221 if abs(sin_elev) < 0.8: 1222 azi = np.arctan2(P[1], P[0]) # azimuth from camera position 1223 else: 1224 if sin_elev < -0.8: 1225 azi = np.arctan2(upside_down_factor * up[1], upside_down_factor * up[0]) 1226 else: 1227 azi = np.arctan2(-upside_down_factor * up[1], -upside_down_factor * up[0]) 1228 1229 D = np.linalg.norm(P) # distance from focal point to camera 1230 1231 # apply the change in azimuth and elevation 1232 azi_new = azi + rxf / 60 1233 elev_new = elev + upside_down_factor * ryf / 60 1234 1235 # the changed elevation changes H (D stays the same) 1236 Hnew = D * np.cos(elev_new) 1237 1238 # calculate new camera position relative to focal point 1239 Pnew = np.array((Hnew * np.cos(azi_new), Hnew * np.sin(azi_new), D * np.sin(elev_new))) 1240 1241 # calculate the up-direction of the camera 1242 up_z = upside_down_factor * np.cos(elev_new) # z follows directly from elevation 1243 up_h = upside_down_factor * np.sin(elev_new) # horizontal component 1244 # 1245 # if upside_down: 1246 # up_z = -up_z 1247 # up_h = -up_h 1248 up = (-up_h * np.cos(azi_new), -up_h * np.sin(azi_new), up_z) 1249 new_pos = focal + Pnew 1250 camera.SetViewUp(up) 1251 camera.SetPosition(new_pos) 1252 camera.OrthogonalizeViewUp() 1253 1254 # Update 1255 1256 if self.GetAutoAdjustCameraClippingRange(): 1257 ren.ResetCameraClippingRange() 1258 1259 if rwi.GetLightFollowCamera(): 1260 ren.UpdateLightsGeometryToFollowCamera() 1261 1262 if self.callback_camera_direction_changed: 1263 self.callback_camera_direction_changed() 1264 1265 self.GetInteractor().Render()
Rotates the camera in turn-table mode
1267 def zoom_box(self, x1, y1, x2, y2): 1268 """Zooms to a box""" 1269 if x1 > x2: 1270 #swap x1 and x2 1271 x1, x2 = x2, x1 1272 if y1 > y2: 1273 y1, y2 = y2, y1 1274 1275 width = x2 - x1 1276 height = y2 - y1 1277 1278 ren = self.GetCurrentRenderer() 1279 size = ren.GetSize() 1280 origin = ren.GetOrigin() 1281 camera = ren.GetActiveCamera() 1282 1283 # Assuming we're drawing the band on the view-plane 1284 rbcenter = (x1 + width / 2, y1 + height / 2, 0) 1285 1286 ren.SetDisplayPoint(rbcenter) 1287 ren.DisplayToView() 1288 ren.ViewToWorld() 1289 1290 worldRBCenter = ren.GetWorldPoint() 1291 1292 invw = 1.0 / worldRBCenter[3] 1293 worldRBCenter = [c * invw for c in worldRBCenter] 1294 winCenter = [origin[0] + 0.5 * size[0], origin[1] + 0.5 * size[1], 0] 1295 1296 ren.SetDisplayPoint(winCenter) 1297 ren.DisplayToView() 1298 ren.ViewToWorld() 1299 1300 worldWinCenter = ren.GetWorldPoint() 1301 invw = 1.0 / worldWinCenter[3] 1302 worldWinCenter = [c * invw for c in worldWinCenter] 1303 1304 translation = [ 1305 worldRBCenter[0] - worldWinCenter[0], 1306 worldRBCenter[1] - worldWinCenter[1], 1307 worldRBCenter[2] - worldWinCenter[2], 1308 ] 1309 1310 pos = camera.GetPosition() 1311 fp = camera.GetFocalPoint() 1312 # 1313 pos = [pos[i] + translation[i] for i in range(3)] 1314 fp = [fp[i] + translation[i] for i in range(3)] 1315 1316 # 1317 camera.SetPosition(pos) 1318 camera.SetFocalPoint(fp) 1319 1320 if width > height: 1321 if width: 1322 camera.Zoom(size[0] / width) 1323 else: 1324 if height: 1325 camera.Zoom(size[1] / height) 1326 1327 self.GetInteractor().Render()
Zooms to a box
1329 def focus_on(self, prop3D): 1330 """Move the camera to focus on this particular prop3D""" 1331 1332 position = prop3D.GetPosition() 1333 1334 ren = self.GetCurrentRenderer() 1335 camera = ren.GetActiveCamera() 1336 1337 fp = camera.GetFocalPoint() 1338 pos = camera.GetPosition() 1339 1340 camera.SetFocalPoint(position) 1341 camera.SetPosition( 1342 position[0] - fp[0] + pos[0], 1343 position[1] - fp[1] + pos[1], 1344 position[2] - fp[2] + pos[2], 1345 ) 1346 1347 if self.GetAutoAdjustCameraClippingRange(): 1348 ren.ResetCameraClippingRange() 1349 1350 rwi = self.GetInteractor() 1351 if rwi.GetLightFollowCamera(): 1352 ren.UpdateLightsGeometryToFollowCamera() 1353 1354 self.GetInteractor().Render()
Move the camera to focus on this particular prop3D
1356 def dolly(self, factor): 1357 """Dolly the camera""" 1358 ren = self.GetCurrentRenderer() 1359 1360 if ren: 1361 camera = ren.GetActiveCamera() 1362 1363 if camera.GetParallelProjection(): 1364 camera.SetParallelScale(camera.GetParallelScale() / factor) 1365 else: 1366 camera.Dolly(factor) 1367 if self.GetAutoAdjustCameraClippingRange(): 1368 ren.ResetCameraClippingRange() 1369 1370 # if not do_not_update: 1371 # rwi = self.GetInteractor() 1372 # if rwi.GetLightFollowCamera(): 1373 # ren.UpdateLightsGeometryToFollowCamera() 1374 # # rwi.Render()
Dolly the camera
1376 def draw_measurement(self): 1377 """Draws a measurement line""" 1378 rwi = self.GetInteractor() 1379 self.end_x, self.end_y = rwi.GetEventPosition() 1380 self.draw_line(self.start_x, self.end_x, self.start_y, self.end_y)
Draws a measurement line
1382 def draw_dragged_selection(self): 1383 """Draws the selection rubber band""" 1384 rwi = self.GetInteractor() 1385 self.end_x, self.end_y = rwi.GetEventPosition() 1386 self.draw_rubber_band(self.start_x, self.end_x, self.start_y, self.end_y)
Draws the selection rubber band
1388 def initialize_screen_drawing(self): 1389 """Initializes the screen drawing""" 1390 # make an image of the currently rendered image 1391 1392 rwi = self.GetInteractor() 1393 rwin = rwi.GetRenderWindow() 1394 1395 size = rwin.GetSize() 1396 1397 self._pixel_array.Initialize() 1398 self._pixel_array.SetNumberOfComponents(4) 1399 self._pixel_array.SetNumberOfTuples(size[0] * size[1]) 1400 1401 front = 1 # what does this do? 1402 rwin.GetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, front, self._pixel_array)
Initializes the screen drawing
1404 def draw_rubber_band(self, x1, x2, y1, y2): 1405 """Draws a rubber band""" 1406 rwi = self.GetInteractor() 1407 rwin = rwi.GetRenderWindow() 1408 1409 size = rwin.GetSize() 1410 1411 tempPA = vtki.vtkUnsignedCharArray() 1412 tempPA.DeepCopy(self._pixel_array) 1413 1414 # check size, viewport may have been resized in the mean-time 1415 if tempPA.GetNumberOfTuples() != size[0] * size[1]: 1416 # print( 1417 # "Starting new screen-image - viewport has resized without us knowing" 1418 # ) 1419 self.initialize_screen_drawing() 1420 self.draw_rubber_band(x1, x2, y1, y2) 1421 return 1422 1423 x2 = min(x2, size[0] - 1) 1424 y2 = min(y2, size[1] - 1) 1425 1426 x2 = max(x2, 0) 1427 y2 = max(y2, 0) 1428 1429 # Modify the pixel array 1430 width = abs(x2 - x1) 1431 height = abs(y2 - y1) 1432 minx = min(x2, x1) 1433 miny = min(y2, y1) 1434 1435 # draw top and bottom 1436 for i in range(width): 1437 1438 # c = round((10*i % 254)/254) * 254 # find some alternating color 1439 c = 0 1440 1441 idx = (miny * size[0]) + minx + i 1442 tempPA.SetTuple(idx, (c, c, c, 1)) 1443 1444 idx = ((miny + height) * size[0]) + minx + i 1445 tempPA.SetTuple(idx, (c, c, c, 1)) 1446 1447 # draw left and right 1448 for i in range(height): 1449 # c = round((10 * i % 254) / 254) * 254 # find some alternating color 1450 c = 0 1451 1452 idx = ((miny + i) * size[0]) + minx 1453 tempPA.SetTuple(idx, (c, c, c, 1)) 1454 1455 idx = idx + width 1456 tempPA.SetTuple(idx, (c, c, c, 1)) 1457 1458 # and Copy back to the window 1459 rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, tempPA, 0) 1460 rwin.Frame()
Draws a rubber band
1462 def line2pixels(self, x1, x2, y1, y2): 1463 """Returns the x and y values of the pixels on a line between x1,y1 and x2,y2. 1464 If start and end are identical then a single point is returned""" 1465 1466 dx = x2 - x1 1467 dy = y2 - y1 1468 1469 if dx == 0 and dy == 0: 1470 return [x1], [y1] 1471 1472 if abs(dx) > abs(dy): 1473 dhdw = dy / dx 1474 r = range(0, dx, int(dx / abs(dx))) 1475 x = [x1 + i for i in r] 1476 y = [round(y1 + dhdw * i) for i in r] 1477 else: 1478 dwdh = dx / dy 1479 r = range(0, dy, int(dy / abs(dy))) 1480 y = [y1 + i for i in r] 1481 x = [round(x1 + i * dwdh) for i in r] 1482 1483 return x, y
Returns the x and y values of the pixels on a line between x1,y1 and x2,y2. If start and end are identical then a single point is returned
1485 def draw_line(self, x1, x2, y1, y2): 1486 rwi = self.GetInteractor() 1487 rwin = rwi.GetRenderWindow() 1488 1489 size = rwin.GetSize() 1490 1491 x1 = min(max(x1, 0), size[0]) 1492 x2 = min(max(x2, 0), size[0]) 1493 y1 = min(max(y1, 0), size[1]) 1494 y2 = min(max(y2, 0), size[1]) 1495 1496 tempPA = vtki.vtkUnsignedCharArray() 1497 tempPA.DeepCopy(self._pixel_array) 1498 1499 xs, ys = self.line2pixels(x1, x2, y1, y2) 1500 for x, y in zip(xs, ys): 1501 idx = (y * size[0]) + x 1502 tempPA.SetTuple(idx, (0, 0, 0, 1)) 1503 1504 # and Copy back to the window 1505 rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, tempPA, 0) 1506 1507 camera = self.GetCurrentRenderer().GetActiveCamera() 1508 scale = camera.GetParallelScale() 1509 1510 # Set/Get the scaling used for a parallel projection, i.e. 1511 # 1512 # the half of the height of the viewport in world-coordinate distances. 1513 # The default is 1. Note that the "scale" parameter works as an "inverse scale" 1514 # larger numbers produce smaller images. 1515 # This method has no effect in perspective projection mode 1516 1517 half_height = size[1] / 2 1518 # half_height [px] = scale [world-coordinates] 1519 1520 length = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5 1521 meters_per_pixel = scale / half_height 1522 meters = length * meters_per_pixel 1523 1524 if camera.GetParallelProjection(): 1525 print(f"Line length = {length} px = {meters} m") 1526 else: 1527 print("Need to be in non-perspective mode to measure. Press 2 or 3 to get there") 1528 1529 if self.callback_measure: 1530 self.callback_measure(meters) 1531 1532 # # can we add something to the window here? 1533 # freeType = vtk.FreeTypeTools.GetInstance() 1534 # textProperty = vtki.vtkTextProperty() 1535 # textProperty.SetJustificationToLeft() 1536 # textProperty.SetFontSize(24) 1537 # textProperty.SetOrientation(25) 1538 # 1539 # textImage = vtki.vtkImageData() 1540 # freeType.RenderString(textProperty, "a somewhat longer text", 72, textImage) 1541 # # this does not give an error, assume it works 1542 # # 1543 # textImage.GetDimensions() 1544 # textImage.GetExtent() 1545 # 1546 # # # Now put the textImage in the RenderWindow 1547 # rwin.SetRGBACharPixelData(0, 0, size[0] - 1, size[1] - 1, textImage, 0) 1548 1549 rwin.Frame()