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