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