vedo.plotter
This module defines the main class Plotter to manage objects and 3D rendering.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import os.path 4import sys 5import time 6from typing import MutableSequence, Callable, Any, Union 7from typing_extensions import Self 8import numpy as np 9 10import vedo.vtkclasses as vtki # a wrapper for lazy imports 11 12import vedo 13from vedo import transformations 14from vedo import utils 15from vedo import backends 16from vedo import addons 17 18 19__docformat__ = "google" 20 21__doc__ = """ 22This module defines the main class Plotter to manage objects and 3D rendering. 23 24![](https://vedo.embl.es/images/basic/multirenderers.png) 25""" 26 27__all__ = ["Plotter", "show", "close"] 28 29######################################################################################## 30class Event: 31 """ 32 This class holds the info from an event in the window, works as dictionary too. 33 """ 34 35 __slots__ = [ 36 "name", 37 "title", 38 "id", 39 "timerid", 40 "time", 41 "priority", 42 "at", 43 "object", 44 "actor", 45 "picked3d", 46 "keypress", 47 "picked2d", 48 "delta2d", 49 "angle2d", 50 "speed2d", 51 "delta3d", 52 "speed3d", 53 "isPoints", 54 "isMesh", 55 "isAssembly", 56 "isVolume", 57 "isImage", 58 "isActor2D", 59 ] 60 61 def __init__(self): 62 self.name = "event" 63 self.title = "" 64 self.id = 0 65 self.timerid = 0 66 self.time = 0 67 self.priority = 0 68 self.at = 0 69 self.object = None 70 self.actor = None 71 self.picked3d = () 72 self.keypress = "" 73 self.picked2d = () 74 self.delta2d = () 75 self.angle2d = 0 76 self.speed2d = () 77 self.delta3d = () 78 self.speed3d = 0 79 self.isPoints = False 80 self.isMesh = False 81 self.isAssembly = False 82 self.isVolume = False 83 self.isImage = False 84 self.isActor2D = False 85 86 def __getitem__(self, key): 87 return getattr(self, key) 88 89 def __setitem__(self, key, value): 90 setattr(self, key, value) 91 92 def __str__(self): 93 module = self.__class__.__module__ 94 name = self.__class__.__name__ 95 out = vedo.printc( 96 f"{module}.{name} at ({hex(id(self))})".ljust(75), 97 bold=True, invert=True, return_string=True, 98 ) 99 out += "\x1b[0m" 100 for n in self.__slots__: 101 if n == "actor": 102 continue 103 out += f"{n}".ljust(11) + ": " 104 val = str(self[n]).replace("\n", "")[:65].rstrip() 105 if val == "True": 106 out += "\x1b[32;1m" 107 elif val == "False": 108 out += "\x1b[31;1m" 109 out += val + "\x1b[0m\n" 110 return out.rstrip() 111 112 def keys(self): 113 return self.__slots__ 114 115 116############################################################################################## 117def show( 118 *objects, 119 at=None, 120 shape=(1, 1), 121 N=None, 122 pos=(0, 0), 123 size="auto", 124 screensize="auto", 125 title="vedo", 126 bg="white", 127 bg2=None, 128 axes=None, 129 interactive=None, 130 offscreen=False, 131 sharecam=True, 132 resetcam=True, 133 zoom=None, 134 viewup="", 135 azimuth=0.0, 136 elevation=0.0, 137 roll=0.0, 138 camera=None, 139 mode=None, 140 screenshot="", 141 new=False, 142) -> Union[Self, None]: 143 """ 144 Create on the fly an instance of class Plotter and show the object(s) provided. 145 146 Arguments: 147 at : (int) 148 number of the renderer to plot to, in case of more than one exists 149 shape : (list, str) 150 Number of sub-render windows inside of the main window. E.g.: 151 specify two across with shape=(2,1) and a two by two grid 152 with shape=(2, 2). By default there is only one renderer. 153 154 Can also accept a shape as string descriptor. E.g.: 155 - shape="3|1" means 3 plots on the left and 1 on the right, 156 - shape="4/2" means 4 plots on top of 2 at bottom. 157 N : (int) 158 number of desired sub-render windows arranged automatically in a grid 159 pos : (list) 160 position coordinates of the top-left corner of the rendering window 161 on the screen 162 size : (list) 163 size of the rendering window 164 screensize : (list) 165 physical size of the monitor screen 166 title : (str) 167 window title 168 bg : (color) 169 background color or specify jpg image file name with path 170 bg2 : (color) 171 background color of a gradient towards the top 172 axes : (int) 173 set the type of axes to be shown: 174 - 0, no axes 175 - 1, draw three gray grid walls 176 - 2, show cartesian axes from (0,0,0) 177 - 3, show positive range of cartesian axes from (0,0,0) 178 - 4, show a triad at bottom left 179 - 5, show a cube at bottom left 180 - 6, mark the corners of the bounding box 181 - 7, draw a 3D ruler at each side of the cartesian axes 182 - 8, show the `vtkCubeAxesActor` object 183 - 9, show the bounding box outLine 184 - 10, show three circles representing the maximum bounding box 185 - 11, show a large grid on the x-y plane 186 - 12, show polar axes 187 - 13, draw a simple ruler at the bottom of the window 188 - 14: draw a `CameraOrientationWidget` 189 190 Axis type-1 can be fully customized by passing a dictionary. 191 Check `vedo.addons.Axes()` for the full list of options. 192 azimuth/elevation/roll : (float) 193 move camera accordingly the specified value 194 viewup : (str, list) 195 either `['x', 'y', 'z']` or a vector to set vertical direction 196 resetcam : (bool) 197 re-adjust camera position to fit objects 198 camera : (dict, vtkCamera) 199 camera parameters can further be specified with a dictionary 200 assigned to the `camera` keyword (E.g. `show(camera={'pos':(1,2,3), 'thickness':1000,})`): 201 - **pos** (list), the position of the camera in world coordinates 202 - **focal_point** (list), the focal point of the camera in world coordinates 203 - **viewup** (list), the view up direction for the camera 204 - **distance** (float), set the focal point to the specified distance from the camera position. 205 - **clipping_range** (float), distance of the near and far clipping planes along the direction of projection. 206 - **parallel_scale** (float), 207 scaling used for a parallel projection, i.e. the height of the viewport 208 in world-coordinate distances. The default is 1. Note that the "scale" parameter works as 209 an "inverse scale", larger numbers produce smaller images. 210 This method has no effect in perspective projection mode. 211 - **thickness** (float), 212 set the distance between clipping planes. This method adjusts the far clipping 213 plane to be set a distance 'thickness' beyond the near clipping plane. 214 - **view_angle** (float), 215 the camera view angle, which is the angular height of the camera view 216 measured in degrees. The default angle is 30 degrees. 217 This method has no effect in parallel projection mode. 218 The formula for setting the angle up for perfect perspective viewing is: 219 angle = 2*atan((h/2)/d) where h is the height of the RenderWindow 220 (measured by holding a ruler up to your screen) and d is the distance 221 from your eyes to the screen. 222 interactive : (bool) 223 pause and interact with window (True) or continue execution (False) 224 rate : (float) 225 maximum rate of `show()` in Hertz 226 mode : (int, str) 227 set the type of interaction: 228 - 0 = TrackballCamera [default] 229 - 1 = TrackballActor 230 - 2 = JoystickCamera 231 - 3 = JoystickActor 232 - 4 = Flight 233 - 5 = RubberBand2D 234 - 6 = RubberBand3D 235 - 7 = RubberBandZoom 236 - 8 = Terrain 237 - 9 = Unicam 238 - 10 = Image 239 new : (bool) 240 if set to `True`, a call to show will instantiate 241 a new Plotter object (a new window) instead of reusing the first created. 242 If new is `True`, but the existing plotter was instantiated with a different 243 argument for `offscreen`, `new` is ignored and a new Plotter is created anyway. 244 """ 245 if len(objects) == 0: 246 objects = None 247 elif len(objects) == 1: 248 objects = objects[0] 249 else: 250 objects = utils.flatten(objects) 251 252 #Â If a plotter instance is already present, check if the offscreen argument 253 #Â is the same as the one requested by the user. If not, create a new 254 # plotter instance (see https://github.com/marcomusy/vedo/issues/1026) 255 if vedo.plotter_instance and vedo.plotter_instance.offscreen != offscreen: 256 new = True 257 258 if vedo.plotter_instance and not new: # Plotter exists 259 plt = vedo.plotter_instance 260 261 else: # Plotter must be created 262 263 if utils.is_sequence(at): # user passed a sequence for "at" 264 265 if not utils.is_sequence(objects): 266 vedo.logger.error("in show() input must be a list.") 267 raise RuntimeError() 268 if len(at) != len(objects): 269 vedo.logger.error("in show() lists 'input' and 'at' must have equal lengths") 270 raise RuntimeError() 271 if shape == (1, 1) and N is None: 272 N = max(at) + 1 273 274 elif at is None and (N or shape != (1, 1)): 275 276 if not utils.is_sequence(objects): 277 e = "in show(), N or shape is set, but input is not a sequence\n" 278 e += " you may need to specify e.g. at=0" 279 vedo.logger.error(e) 280 raise RuntimeError() 281 at = list(range(len(objects))) 282 283 plt = Plotter( 284 shape=shape, 285 N=N, 286 pos=pos, 287 size=size, 288 screensize=screensize, 289 title=title, 290 axes=axes, 291 sharecam=sharecam, 292 resetcam=resetcam, 293 interactive=interactive, 294 offscreen=offscreen, 295 bg=bg, 296 bg2=bg2, 297 ) 298 299 if vedo.settings.dry_run_mode >= 2: 300 return plt 301 302 # use _plt_to_return because plt.show() can return a k3d plot 303 _plt_to_return = None 304 305 if utils.is_sequence(at): 306 307 for i, act in enumerate(objects): 308 _plt_to_return = plt.show( 309 act, 310 at=i, 311 zoom=zoom, 312 resetcam=resetcam, 313 viewup=viewup, 314 azimuth=azimuth, 315 elevation=elevation, 316 roll=roll, 317 camera=camera, 318 interactive=False, 319 mode=mode, 320 screenshot=screenshot, 321 bg=bg, 322 bg2=bg2, 323 axes=axes, 324 ) 325 326 if ( 327 interactive 328 or len(at) == N 329 or (isinstance(shape[0], int) and len(at) == shape[0] * shape[1]) 330 ): 331 # note that shape can be a string 332 if plt.interactor and not offscreen and (interactive is None or interactive): 333 plt.interactor.Start() 334 if plt._must_close_now: 335 plt.interactor.GetRenderWindow().Finalize() 336 plt.interactor.TerminateApp() 337 plt.interactor = None 338 plt.window = None 339 plt.renderer = None 340 plt.renderers = [] 341 plt.camera = None 342 343 else: 344 345 _plt_to_return = plt.show( 346 objects, 347 at=at, 348 zoom=zoom, 349 resetcam=resetcam, 350 viewup=viewup, 351 azimuth=azimuth, 352 elevation=elevation, 353 roll=roll, 354 camera=camera, 355 interactive=interactive, 356 mode=mode, 357 screenshot=screenshot, 358 bg=bg, 359 bg2=bg2, 360 axes=axes, 361 ) 362 363 return _plt_to_return 364 365 366def close() -> None: 367 """Close the last created Plotter instance if it exists.""" 368 if not vedo.plotter_instance: 369 return 370 vedo.plotter_instance.close() 371 return 372 373 374######################################################################## 375class Plotter: 376 """Main class to manage objects.""" 377 378 def __init__( 379 self, 380 shape=(1, 1), 381 N=None, 382 pos=(0, 0), 383 size="auto", 384 screensize="auto", 385 title="vedo", 386 bg="white", 387 bg2=None, 388 axes=None, 389 sharecam=True, 390 resetcam=True, 391 interactive=None, 392 offscreen=False, 393 qt_widget=None, 394 wx_widget=None, 395 ): 396 """ 397 Arguments: 398 shape : (str, list) 399 shape of the grid of renderers in format (rows, columns). Ignored if N is specified. 400 N : (int) 401 number of desired renderers arranged in a grid automatically. 402 pos : (list) 403 (x,y) position in pixels of top-left corner of the rendering window on the screen 404 size : (str, list) 405 size of the rendering window. If 'auto', guess it based on screensize. 406 screensize : (list) 407 physical size of the monitor screen in pixels 408 bg : (color, str) 409 background color or specify jpg image file name with path 410 bg2 : (color) 411 background color of a gradient towards the top 412 title : (str) 413 window title 414 415 axes : (int) 416 417 Note that Axes type-1 can be fully customized by passing a dictionary `axes=dict()`. 418 Check out `vedo.addons.Axes()` for the available options. 419 420 - 0, no axes 421 - 1, draw three gray grid walls 422 - 2, show cartesian axes from (0,0,0) 423 - 3, show positive range of cartesian axes from (0,0,0) 424 - 4, show a triad at bottom left 425 - 5, show a cube at bottom left 426 - 6, mark the corners of the bounding box 427 - 7, draw a 3D ruler at each side of the cartesian axes 428 - 8, show the VTK CubeAxesActor object 429 - 9, show the bounding box outLine 430 - 10, show three circles representing the maximum bounding box 431 - 11, show a large grid on the x-y plane (use with zoom=8) 432 - 12, show polar axes 433 - 13, draw a simple ruler at the bottom of the window 434 - 14: draw a camera orientation widget 435 436 sharecam : (bool) 437 if False each renderer will have an independent camera 438 interactive : (bool) 439 if True will stop after show() to allow interaction with the 3d scene 440 offscreen : (bool) 441 if True will not show the rendering window 442 qt_widget : (QVTKRenderWindowInteractor) 443 render in a Qt-Widget using an QVTKRenderWindowInteractor. 444 See examples `qt_windows[1,2,3].py` and `qt_cutter.py`. 445 """ 446 vedo.plotter_instance = self 447 448 if interactive is None: 449 interactive = bool(N in (0, 1, None) and shape == (1, 1)) 450 self._interactive = interactive 451 # print("interactive", interactive, N, shape) 452 453 self.objects = [] # list of objects to be shown 454 self.clicked_object = None # holds the object that has been clicked 455 self.clicked_actor = None # holds the actor that has been clicked 456 457 self.shape = shape # nr. of subwindows in grid 458 self.axes = axes # show axes type nr. 459 self.title = title # window title 460 self.size = size # window size 461 self.backgrcol = bg # used also by backend notebooks 462 463 self.offscreen= offscreen 464 self.resetcam = resetcam 465 self.sharecam = sharecam # share the same camera if multiple renderers 466 self.pos = pos # used by vedo.file_io 467 468 self.picker = None # hold the vtkPicker object 469 self.picked2d = None # 2d coords of a clicked point on the rendering window 470 self.picked3d = None # 3d coords of a clicked point on an actor 471 472 self.qt_widget = qt_widget # QVTKRenderWindowInteractor 473 self.wx_widget = wx_widget # wxVTKRenderWindowInteractor 474 self.interactor = None 475 self.window = None 476 self.renderer = None 477 self.renderers = [] # list of renderers 478 479 # mostly internal stuff: 480 self.hover_legends = [] 481 self.justremoved = None 482 self.axes_instances = [] 483 self.clock = 0 484 self.sliders = [] 485 self.buttons = [] 486 self.widgets = [] 487 self.cutter_widget = None 488 self.hint_widget = None 489 self.background_renderer = None 490 self.last_event = None 491 self.skybox = None 492 self._icol = 0 493 self._clockt0 = time.time() 494 self._extralight = None 495 self._cocoa_initialized = False 496 self._cocoa_process_events = True # make one call in show() 497 self._must_close_now = False 498 499 ##################################################################### 500 if vedo.settings.default_backend == "2d": 501 self.offscreen = True 502 if self.size == "auto": 503 self.size = (800, 600) 504 505 elif vedo.settings.default_backend == "k3d": 506 if self.size == "auto": 507 self.size = (1000, 1000) 508 #################################### 509 return ############################ 510 #################################### 511 512 ############################################################# 513 if vedo.settings.default_backend in ["vtk", "2d", "trame"]: 514 515 if screensize == "auto": 516 screensize = (2160, 1440) # TODO: get actual screen size 517 518 # build the rendering window: 519 self.window = vtki.vtkRenderWindow() 520 521 self.window.GlobalWarningDisplayOff() 522 523 if self.title == "vedo": # check if dev version 524 if "dev" in vedo.__version__: 525 self.title = f"vedo ({vedo.__version__})" 526 self.window.SetWindowName(self.title) 527 528 # more vedo.settings 529 if vedo.settings.use_depth_peeling: 530 self.window.SetAlphaBitPlanes(vedo.settings.alpha_bit_planes) 531 self.window.SetMultiSamples(vedo.settings.multi_samples) 532 533 self.window.SetPolygonSmoothing(vedo.settings.polygon_smoothing) 534 self.window.SetLineSmoothing(vedo.settings.line_smoothing) 535 self.window.SetPointSmoothing(vedo.settings.point_smoothing) 536 537 ############################################################# 538 if N: # N = number of renderers. Find out the best 539 540 if shape != (1, 1): # arrangement based on minimum nr. of empty renderers 541 vedo.logger.warning("having set N, shape is ignored.") 542 543 x, y = screensize 544 nx = int(np.sqrt(int(N * y / x) + 1)) 545 ny = int(np.sqrt(int(N * x / y) + 1)) 546 lm = [ 547 (nx, ny), 548 (nx, ny + 1), 549 (nx - 1, ny), 550 (nx + 1, ny), 551 (nx, ny - 1), 552 (nx - 1, ny + 1), 553 (nx + 1, ny - 1), 554 (nx + 1, ny + 1), 555 (nx - 1, ny - 1), 556 ] 557 ind, minl = 0, 1000 558 for i, m in enumerate(lm): 559 l = m[0] * m[1] 560 if N <= l < minl: 561 ind = i 562 minl = l 563 shape = lm[ind] 564 565 ################################################## 566 if isinstance(shape, str): 567 568 if "|" in shape: 569 if self.size == "auto": 570 self.size = (800, 1200) 571 n = int(shape.split("|")[0]) 572 m = int(shape.split("|")[1]) 573 rangen = reversed(range(n)) 574 rangem = reversed(range(m)) 575 else: 576 if self.size == "auto": 577 self.size = (1200, 800) 578 m = int(shape.split("/")[0]) 579 n = int(shape.split("/")[1]) 580 rangen = range(n) 581 rangem = range(m) 582 583 if n >= m: 584 xsplit = m / (n + m) 585 else: 586 xsplit = 1 - n / (n + m) 587 if vedo.settings.window_splitting_position: 588 xsplit = vedo.settings.window_splitting_position 589 590 for i in rangen: 591 arenderer = vtki.vtkRenderer() 592 if "|" in shape: 593 arenderer.SetViewport(0, i / n, xsplit, (i + 1) / n) 594 else: 595 arenderer.SetViewport(i / n, 0, (i + 1) / n, xsplit) 596 self.renderers.append(arenderer) 597 598 for i in rangem: 599 arenderer = vtki.vtkRenderer() 600 601 if "|" in shape: 602 arenderer.SetViewport(xsplit, i / m, 1, (i + 1) / m) 603 else: 604 arenderer.SetViewport(i / m, xsplit, (i + 1) / m, 1) 605 self.renderers.append(arenderer) 606 607 for r in self.renderers: 608 r.SetLightFollowCamera(vedo.settings.light_follows_camera) 609 610 r.SetUseDepthPeeling(vedo.settings.use_depth_peeling) 611 # r.SetUseDepthPeelingForVolumes(vedo.settings.use_depth_peeling) 612 if vedo.settings.use_depth_peeling: 613 r.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels) 614 r.SetOcclusionRatio(vedo.settings.occlusion_ratio) 615 r.SetUseFXAA(vedo.settings.use_fxaa) 616 r.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer) 617 618 r.SetBackground(vedo.get_color(self.backgrcol)) 619 620 self.axes_instances.append(None) 621 622 self.shape = (n + m,) 623 624 elif utils.is_sequence(shape) and isinstance(shape[0], dict): 625 # passing a sequence of dicts for renderers specifications 626 627 if self.size == "auto": 628 self.size = (1000, 800) 629 630 for rd in shape: 631 x0, y0 = rd["bottomleft"] 632 x1, y1 = rd["topright"] 633 bg_ = rd.pop("bg", "white") 634 bg2_ = rd.pop("bg2", None) 635 636 arenderer = vtki.vtkRenderer() 637 arenderer.SetLightFollowCamera(vedo.settings.light_follows_camera) 638 639 arenderer.SetUseDepthPeeling(vedo.settings.use_depth_peeling) 640 # arenderer.SetUseDepthPeelingForVolumes(vedo.settings.use_depth_peeling) 641 if vedo.settings.use_depth_peeling: 642 arenderer.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels) 643 arenderer.SetOcclusionRatio(vedo.settings.occlusion_ratio) 644 arenderer.SetUseFXAA(vedo.settings.use_fxaa) 645 arenderer.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer) 646 647 arenderer.SetViewport(x0, y0, x1, y1) 648 arenderer.SetBackground(vedo.get_color(bg_)) 649 if bg2_: 650 arenderer.GradientBackgroundOn() 651 arenderer.SetBackground2(vedo.get_color(bg2_)) 652 653 self.renderers.append(arenderer) 654 self.axes_instances.append(None) 655 656 self.shape = (len(shape),) 657 658 else: 659 660 if isinstance(self.size, str) and self.size == "auto": 661 # figure out a reasonable window size 662 f = 1.5 663 x, y = screensize 664 xs = y / f * shape[1] # because y<x 665 ys = y / f * shape[0] 666 if xs > x / f: # shrink 667 xs = x / f 668 ys = xs / shape[1] * shape[0] 669 if ys > y / f: 670 ys = y / f 671 xs = ys / shape[0] * shape[1] 672 self.size = (int(xs), int(ys)) 673 if shape == (1, 1): 674 self.size = (int(y / f), int(y / f)) # because y<x 675 else: 676 self.size = (self.size[0], self.size[1]) 677 678 try: 679 image_actor = None 680 bgname = str(self.backgrcol).lower() 681 if ".jpg" in bgname or ".jpeg" in bgname or ".png" in bgname: 682 self.window.SetNumberOfLayers(2) 683 self.background_renderer = vtki.vtkRenderer() 684 self.background_renderer.SetLayer(0) 685 self.background_renderer.InteractiveOff() 686 self.background_renderer.SetBackground(vedo.get_color(bg2)) 687 image_actor = vedo.Image(self.backgrcol).actor 688 self.window.AddRenderer(self.background_renderer) 689 self.background_renderer.AddActor(image_actor) 690 except AttributeError: 691 pass 692 693 for i in reversed(range(shape[0])): 694 for j in range(shape[1]): 695 arenderer = vtki.vtkRenderer() 696 arenderer.SetLightFollowCamera(vedo.settings.light_follows_camera) 697 arenderer.SetTwoSidedLighting(vedo.settings.two_sided_lighting) 698 699 arenderer.SetUseDepthPeeling(vedo.settings.use_depth_peeling) 700 # arenderer.SetUseDepthPeelingForVolumes(vedo.settings.use_depth_peeling) 701 if vedo.settings.use_depth_peeling: 702 arenderer.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels) 703 arenderer.SetOcclusionRatio(vedo.settings.occlusion_ratio) 704 arenderer.SetUseFXAA(vedo.settings.use_fxaa) 705 arenderer.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer) 706 707 if image_actor: 708 arenderer.SetLayer(1) 709 710 arenderer.SetBackground(vedo.get_color(self.backgrcol)) 711 if bg2: 712 arenderer.GradientBackgroundOn() 713 arenderer.SetBackground2(vedo.get_color(bg2)) 714 715 x0 = i / shape[0] 716 y0 = j / shape[1] 717 x1 = (i + 1) / shape[0] 718 y1 = (j + 1) / shape[1] 719 arenderer.SetViewport(y0, x0, y1, x1) 720 self.renderers.append(arenderer) 721 self.axes_instances.append(None) 722 self.shape = shape 723 724 if self.renderers: 725 self.renderer = self.renderers[0] 726 self.camera.SetParallelProjection(vedo.settings.use_parallel_projection) 727 728 ######################################################### 729 if self.qt_widget or self.wx_widget: 730 if self.qt_widget: 731 self.window = self.qt_widget.GetRenderWindow() # overwrite 732 else: 733 self.window = self.wx_widget.GetRenderWindow() 734 self.interactor = self.window.GetInteractor() 735 736 ######################################################### 737 for r in self.renderers: 738 self.window.AddRenderer(r) 739 # set the background gradient if any 740 if vedo.settings.background_gradient_orientation > 0: 741 try: 742 modes = [ 743 vtki.vtkViewport.GradientModes.VTK_GRADIENT_VERTICAL, 744 vtki.vtkViewport.GradientModes.VTK_GRADIENT_HORIZONTAL, 745 vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_SIDE, 746 vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_CORNER, 747 ] 748 r.SetGradientMode(modes[vedo.settings.background_gradient_orientation]) 749 r.GradientBackgroundOn() 750 except AttributeError: 751 pass 752 753 ######################################################### 754 if self.qt_widget or self.wx_widget: 755 # self.window.SetSize(int(self.size[0]), int(self.size[1])) 756 self.interactor.SetRenderWindow(self.window) 757 # vsty = vtki.new("InteractorStyleTrackballCamera") 758 # self.interactor.SetInteractorStyle(vsty) 759 if vedo.settings.enable_default_keyboard_callbacks: 760 self.interactor.AddObserver("KeyPressEvent", self._default_keypress) 761 if vedo.settings.enable_default_mouse_callbacks: 762 self.interactor.AddObserver("LeftButtonPressEvent", self._default_mouseleftclick) 763 return ################ 764 ######################## 765 766 if self.size[0] == "f": # full screen 767 self.size = "fullscreen" 768 self.window.SetFullScreen(True) 769 self.window.BordersOn() 770 else: 771 self.window.SetSize(int(self.size[0]), int(self.size[1])) 772 773 if self.offscreen: 774 if self.axes in (4, 5, 8, 12, 14): 775 self.axes = 0 # does not work with those 776 self.window.SetOffScreenRendering(True) 777 self.interactor = None 778 self._interactive = False 779 return ################ 780 ######################## 781 782 self.window.SetPosition(pos) 783 784 ######################################################### 785 self.interactor = vtki.vtkRenderWindowInteractor() 786 787 self.interactor.SetRenderWindow(self.window) 788 vsty = vtki.new("InteractorStyleTrackballCamera") 789 self.interactor.SetInteractorStyle(vsty) 790 self.interactor.RemoveObservers("CharEvent") 791 792 if vedo.settings.enable_default_keyboard_callbacks: 793 self.interactor.AddObserver("KeyPressEvent", self._default_keypress) 794 if vedo.settings.enable_default_mouse_callbacks: 795 self.interactor.AddObserver("LeftButtonPressEvent", self._default_mouseleftclick) 796 797 ##################################################################### ..init ends here. 798 799 def __str__(self): 800 """Return Plotter info.""" 801 axtype = { 802 0: "(no axes)", 803 1: "(default customizable grid walls)", 804 2: "(cartesian axes from origin", 805 3: "(positive range of cartesian axes from origin", 806 4: "(axes triad at bottom left)", 807 5: "(oriented cube at bottom left)", 808 6: "(mark the corners of the bounding box)", 809 7: "(3D ruler at each side of the cartesian axes)", 810 8: "(the vtkCubeAxesActor object)", 811 9: "(the bounding box outline)", 812 10: "(circles of maximum bounding box range)", 813 11: "(show a large grid on the x-y plane)", 814 12: "(show polar axes)", 815 13: "(simple ruler at the bottom of the window)", 816 14: "(the vtkCameraOrientationWidget object)", 817 } 818 819 module = self.__class__.__module__ 820 name = self.__class__.__name__ 821 out = vedo.printc( 822 f"{module}.{name} at ({hex(id(self))})".ljust(75), 823 bold=True, invert=True, return_string=True, 824 ) 825 out += "\x1b[0m" 826 if self.interactor: 827 out += "window title".ljust(14) + ": " + self.title + "\n" 828 out += "window size".ljust(14) + f": {self.window.GetSize()}" 829 out += f", full_screen={self.window.GetScreenSize()}\n" 830 out += "activ renderer".ljust(14) + ": nr." + str(self.renderers.index(self.renderer)) 831 out += f" (out of {len(self.renderers)} renderers)\n" 832 833 bns, totpt = [], 0 834 for a in self.objects: 835 try: 836 b = a.bounds() 837 bns.append(b) 838 except AttributeError: 839 pass 840 try: 841 totpt += a.npoints 842 except AttributeError: 843 pass 844 out += "n. of objects".ljust(14) + f": {len(self.objects)}" 845 out += f" ({totpt} vertices)\n" if totpt else "\n" 846 847 if len(bns) > 0: 848 min_bns = np.min(bns, axis=0) 849 max_bns = np.max(bns, axis=0) 850 bx1, bx2 = utils.precision(min_bns[0], 3), utils.precision(max_bns[1], 3) 851 by1, by2 = utils.precision(min_bns[2], 3), utils.precision(max_bns[3], 3) 852 bz1, bz2 = utils.precision(min_bns[4], 3), utils.precision(max_bns[5], 3) 853 out += "bounds".ljust(14) + ":" 854 out += " x=(" + bx1 + ", " + bx2 + ")," 855 out += " y=(" + by1 + ", " + by2 + ")," 856 out += " z=(" + bz1 + ", " + bz2 + ")\n" 857 858 if utils.is_integer(self.axes): 859 out += "axes style".ljust(14) + f": {self.axes} {axtype[self.axes]}\n" 860 elif isinstance(self.axes, dict): 861 out += "axes style".ljust(14) + f": 1 {axtype[1]}\n" 862 else: 863 out += "axes style".ljust(14) + f": {[self.axes]}\n" 864 return out.rstrip() + "\x1b[0m" 865 866 def print(self): 867 """Print information about the current instance.""" 868 print(self.__str__()) 869 return self 870 871 def __iadd__(self, objects): 872 self.add(objects) 873 return self 874 875 def __isub__(self, objects): 876 self.remove(objects) 877 return self 878 879 def __enter__(self): 880 # context manager like in "with Plotter() as plt:" 881 return self 882 883 def __exit__(self, *args, **kwargs): 884 # context manager like in "with Plotter() as plt:" 885 self.close() 886 887 def initialize_interactor(self) -> Self: 888 """Initialize the interactor if not already initialized.""" 889 if self.offscreen: 890 return self 891 if self.interactor: 892 if not self.interactor.GetInitialized(): 893 self.interactor.Initialize() 894 self.interactor.RemoveObservers("CharEvent") 895 return self 896 897 def process_events(self) -> Self: 898 """Process all pending events.""" 899 self.initialize_interactor() 900 if self.interactor: 901 try: 902 self.interactor.ProcessEvents() 903 except AttributeError: 904 pass 905 return self 906 907 def at(self, nren: int, yren=None) -> Self: 908 """ 909 Select the current renderer number as an int. 910 Can also use the `[nx, ny]` format. 911 """ 912 if utils.is_sequence(nren): 913 if len(nren) == 2: 914 nren, yren = nren 915 else: 916 vedo.logger.error("at() argument must be a single number or a list of two numbers") 917 raise RuntimeError 918 919 if yren is not None: 920 a, b = self.shape 921 x, y = nren, yren 922 nren = x * b + y 923 # print("at (", x, y, ") -> ren", nren) 924 if nren < 0 or nren > len(self.renderers) or x >= a or y >= b: 925 vedo.logger.error(f"at({nren, yren}) is malformed!") 926 raise RuntimeError 927 928 self.renderer = self.renderers[nren] 929 return self 930 931 def add(self, *objs, at=None) -> Self: 932 """ 933 Append the input objects to the internal list of objects to be shown. 934 935 Arguments: 936 at : (int) 937 add the object at the specified renderer 938 """ 939 if at is not None: 940 ren = self.renderers[at] 941 else: 942 ren = self.renderer 943 944 objs = utils.flatten(objs) 945 for ob in objs: 946 if ob and ob not in self.objects: 947 self.objects.append(ob) 948 949 acts = self._scan_input_return_acts(objs) 950 951 for a in acts: 952 953 if ren: 954 if isinstance(a, vedo.addons.BaseCutter): 955 a.add_to(self) # from cutters 956 continue 957 958 if isinstance(a, vtki.vtkLight): 959 ren.AddLight(a) 960 continue 961 962 try: 963 ren.AddActor(a) 964 except TypeError: 965 ren.AddActor(a.actor) 966 967 try: 968 ir = self.renderers.index(ren) 969 a.rendered_at.add(ir) # might not have rendered_at 970 except (AttributeError, ValueError): 971 pass 972 973 if isinstance(a, vtki.vtkFollower): 974 a.SetCamera(self.camera) 975 elif isinstance(a, vedo.visual.LightKit): 976 a.lightkit.AddLightsToRenderer(ren) 977 978 return self 979 980 def remove(self, *objs, at=None) -> Self: 981 """ 982 Remove input object to the internal list of objects to be shown. 983 984 Objects to be removed can be referenced by their assigned name, 985 986 Arguments: 987 at : (int) 988 remove the object at the specified renderer 989 """ 990 # TODO and you can also use wildcards like `*` and `?`. 991 if at is not None: 992 ren = self.renderers[at] 993 else: 994 ren = self.renderer 995 996 objs = [ob for ob in utils.flatten(objs) if ob] 997 998 has_str = False 999 for ob in objs: 1000 if isinstance(ob, str): 1001 has_str = True 1002 break 1003 1004 has_actor = False 1005 for ob in objs: 1006 if hasattr(ob, "actor") and ob.actor: 1007 has_actor = True 1008 break 1009 1010 if has_str or has_actor: 1011 # need to get the actors to search for 1012 for a in self.get_actors(include_non_pickables=True): 1013 # print("PARSING", [a]) 1014 try: 1015 if (a.name and a.name in objs) or a in objs: 1016 objs.append(a) 1017 # if a.name: 1018 # bools = [utils.parse_pattern(ob, a.name)[0] for ob in objs] 1019 # if any(bools) or a in objs: 1020 # objs.append(a) 1021 # print('a.name',a.name, objs,any(bools)) 1022 except AttributeError: # no .name 1023 # passing the actor so get back the object with .retrieve_object() 1024 try: 1025 vobj = a.retrieve_object() 1026 if (vobj.name and vobj.name in objs) or vobj in objs: 1027 # print('vobj.name', vobj.name) 1028 objs.append(vobj) 1029 except AttributeError: 1030 pass 1031 1032 ir = self.renderers.index(ren) 1033 1034 ids = [] 1035 for ob in set(objs): 1036 1037 # will remove it from internal list if possible 1038 try: 1039 idx = self.objects.index(ob) 1040 ids.append(idx) 1041 except ValueError: 1042 pass 1043 1044 if ren: ### remove it from the renderer 1045 1046 if isinstance(ob, vedo.addons.BaseCutter): 1047 ob.remove_from(self) # from cutters 1048 continue 1049 1050 try: 1051 ren.RemoveActor(ob) 1052 except TypeError: 1053 try: 1054 ren.RemoveActor(ob.actor) 1055 except AttributeError: 1056 pass 1057 1058 if hasattr(ob, "rendered_at"): 1059 ob.rendered_at.discard(ir) 1060 1061 if hasattr(ob, "scalarbar") and ob.scalarbar: 1062 ren.RemoveActor(ob.scalarbar) 1063 if hasattr(ob, "_caption") and ob._caption: 1064 ren.RemoveActor(ob._caption) 1065 if hasattr(ob, "shadows") and ob.shadows: 1066 for sha in ob.shadows: 1067 ren.RemoveActor(sha.actor) 1068 if hasattr(ob, "trail") and ob.trail: 1069 ren.RemoveActor(ob.trail.actor) 1070 ob.trail_points = [] 1071 if hasattr(ob.trail, "shadows") and ob.trail.shadows: 1072 for sha in ob.trail.shadows: 1073 ren.RemoveActor(sha.actor) 1074 1075 elif isinstance(ob, vedo.visual.LightKit): 1076 ob.lightkit.RemoveLightsFromRenderer(ren) 1077 1078 # for i in ids: # WRONG way of doing it! 1079 # del self.objects[i] 1080 # instead we do: 1081 self.objects = [ele for i, ele in enumerate(self.objects) if i not in ids] 1082 return self 1083 1084 @property 1085 def actors(self): 1086 """Return the list of actors.""" 1087 return [ob.actor for ob in self.objects if hasattr(ob, "actor")] 1088 1089 def remove_lights(self) -> Self: 1090 """Remove all the present lights in the current renderer.""" 1091 if self.renderer: 1092 self.renderer.RemoveAllLights() 1093 return self 1094 1095 def pop(self, at=None) -> Self: 1096 """ 1097 Remove the last added object from the rendering window. 1098 This method is typically used in loops or callback functions. 1099 """ 1100 if at is not None and not isinstance(at, int): 1101 # wrong usage pitfall 1102 vedo.logger.error("argument of pop() must be an integer") 1103 raise RuntimeError() 1104 1105 if self.objects: 1106 self.remove(self.objects[-1], at) 1107 return self 1108 1109 def render(self, resetcam=False) -> Self: 1110 """Render the scene. This method is typically used in loops or callback functions.""" 1111 1112 if vedo.settings.dry_run_mode >= 2: 1113 return self 1114 1115 if not self.window: 1116 return self 1117 1118 self.initialize_interactor() 1119 1120 if resetcam: 1121 self.renderer.ResetCamera() 1122 1123 self.window.Render() 1124 1125 if self._cocoa_process_events and self.interactor and self.interactor.GetInitialized(): 1126 if "Darwin" in vedo.sys_platform and not self.offscreen: 1127 self.interactor.ProcessEvents() 1128 self._cocoa_process_events = False 1129 return self 1130 1131 def interactive(self) -> Self: 1132 """ 1133 Start window interaction. 1134 Analogous to `show(..., interactive=True)`. 1135 """ 1136 if vedo.settings.dry_run_mode >= 1: 1137 return self 1138 self.initialize_interactor() 1139 if self.interactor: 1140 # print("self.interactor.Start()") 1141 self.interactor.Start() 1142 # print("self.interactor.Start() done") 1143 if self._must_close_now: 1144 # print("self.interactor.TerminateApp()") 1145 if self.interactor: 1146 self.interactor.GetRenderWindow().Finalize() 1147 self.interactor.TerminateApp() 1148 self.interactor = None 1149 self.window = None 1150 self.renderer = None 1151 self.renderers = [] 1152 self.camera = None 1153 return self 1154 1155 def use_depth_peeling(self, at=None, value=True) -> Self: 1156 """ 1157 Specify whether use depth peeling algorithm at this specific renderer 1158 Call this method before the first rendering. 1159 """ 1160 if at is None: 1161 ren = self.renderer 1162 else: 1163 ren = self.renderers[at] 1164 ren.SetUseDepthPeeling(value) 1165 return self 1166 1167 def background(self, c1=None, c2=None, at=None, mode=0) -> Union[Self, "np.ndarray"]: 1168 """Set the color of the background for the current renderer. 1169 A different renderer index can be specified by keyword `at`. 1170 1171 Arguments: 1172 c1 : (list) 1173 background main color. 1174 c2 : (list) 1175 background color for the upper part of the window. 1176 at : (int) 1177 renderer index. 1178 mode : (int) 1179 background mode (needs vtk version >= 9.3) 1180 0 = vertical, 1181 1 = horizontal, 1182 2 = radial farthest side, 1183 3 = radia farthest corner. 1184 """ 1185 if not self.renderers: 1186 return self 1187 if at is None: 1188 r = self.renderer 1189 else: 1190 r = self.renderers[at] 1191 1192 if c1 is None and c2 is None: 1193 return np.array(r.GetBackground()) 1194 1195 if r: 1196 if c1 is not None: 1197 r.SetBackground(vedo.get_color(c1)) 1198 if c2 is not None: 1199 r.GradientBackgroundOn() 1200 r.SetBackground2(vedo.get_color(c2)) 1201 if mode: 1202 try: # only works with vtk>=9.3 1203 modes = [ 1204 vtki.vtkViewport.GradientModes.VTK_GRADIENT_VERTICAL, 1205 vtki.vtkViewport.GradientModes.VTK_GRADIENT_HORIZONTAL, 1206 vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_SIDE, 1207 vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_CORNER, 1208 ] 1209 r.SetGradientMode(modes[vedo.settings.background_gradient_orientation]) 1210 except AttributeError: 1211 pass 1212 1213 else: 1214 r.GradientBackgroundOff() 1215 return self 1216 1217 ################################################################## 1218 def get_meshes(self, at=None, include_non_pickables=False, unpack_assemblies=True) -> list: 1219 """ 1220 Return a list of Meshes from the specified renderer. 1221 1222 Arguments: 1223 at : (int) 1224 specify which renderer to look at. 1225 include_non_pickables : (bool) 1226 include non-pickable objects 1227 unpack_assemblies : (bool) 1228 unpack assemblies into their components 1229 """ 1230 if at is None: 1231 renderer = self.renderer 1232 at = self.renderers.index(renderer) 1233 elif isinstance(at, int): 1234 renderer = self.renderers[at] 1235 1236 has_global_axes = False 1237 if isinstance(self.axes_instances[at], vedo.Assembly): 1238 has_global_axes = True 1239 1240 if unpack_assemblies: 1241 acs = renderer.GetActors() 1242 else: 1243 acs = renderer.GetViewProps() 1244 1245 objs = [] 1246 acs.InitTraversal() 1247 for _ in range(acs.GetNumberOfItems()): 1248 1249 if unpack_assemblies: 1250 a = acs.GetNextItem() 1251 else: 1252 a = acs.GetNextProp() 1253 1254 if isinstance(a, vtki.vtkVolume): 1255 continue 1256 1257 if include_non_pickables or a.GetPickable(): 1258 if a == self.axes_instances[at]: 1259 continue 1260 if has_global_axes and a in self.axes_instances[at].actors: 1261 continue 1262 try: 1263 objs.append(a.retrieve_object()) 1264 except AttributeError: 1265 pass 1266 return objs 1267 1268 def get_volumes(self, at=None, include_non_pickables=False) -> list: 1269 """ 1270 Return a list of Volumes from the specified renderer. 1271 1272 Arguments: 1273 at : (int) 1274 specify which renderer to look at 1275 include_non_pickables : (bool) 1276 include non-pickable objects 1277 """ 1278 if at is None: 1279 renderer = self.renderer 1280 at = self.renderers.index(renderer) 1281 elif isinstance(at, int): 1282 renderer = self.renderers[at] 1283 1284 vols = [] 1285 acs = renderer.GetVolumes() 1286 acs.InitTraversal() 1287 for _ in range(acs.GetNumberOfItems()): 1288 a = acs.GetNextItem() 1289 if include_non_pickables or a.GetPickable(): 1290 try: 1291 vols.append(a.retrieve_object()) 1292 except AttributeError: 1293 pass 1294 return vols 1295 1296 def get_actors(self, at=None, include_non_pickables=False) -> list: 1297 """ 1298 Return a list of Volumes from the specified renderer. 1299 1300 Arguments: 1301 at : (int) 1302 specify which renderer to look at 1303 include_non_pickables : (bool) 1304 include non-pickable objects 1305 """ 1306 if at is None: 1307 renderer = self.renderer 1308 at = self.renderers.index(renderer) 1309 elif isinstance(at, int): 1310 renderer = self.renderers[at] 1311 1312 acts = [] 1313 acs = renderer.GetViewProps() 1314 acs.InitTraversal() 1315 for _ in range(acs.GetNumberOfItems()): 1316 a = acs.GetNextProp() 1317 if include_non_pickables or a.GetPickable(): 1318 acts.append(a) 1319 return acts 1320 1321 def check_actors_trasform(self, at=None) -> Self: 1322 """ 1323 Reset the transformation matrix of all actors at specified renderer. 1324 This is only useful when actors have been moved/rotated/scaled manually 1325 in an already rendered scene using interactors like 1326 'TrackballActor' or 'JoystickActor'. 1327 """ 1328 # see issue https://github.com/marcomusy/vedo/issues/1046 1329 for a in self.get_actors(at=at, include_non_pickables=True): 1330 try: 1331 M = a.GetMatrix() 1332 except AttributeError: 1333 continue 1334 if M and not M.IsIdentity(): 1335 try: 1336 a.retrieve_object().apply_transform_from_actor() 1337 # vedo.logger.info( 1338 # f"object '{a.retrieve_object().name}' " 1339 # "was manually moved. Updated to its current position." 1340 # ) 1341 except AttributeError: 1342 pass 1343 return self 1344 1345 def reset_camera(self, tight=None) -> Self: 1346 """ 1347 Reset the camera position and zooming. 1348 If tight (float) is specified the zooming reserves a padding space 1349 in the xy-plane expressed in percent of the average size. 1350 """ 1351 if tight is None: 1352 self.renderer.ResetCamera() 1353 else: 1354 x0, x1, y0, y1, z0, z1 = self.renderer.ComputeVisiblePropBounds() 1355 cam = self.camera 1356 1357 self.renderer.ComputeAspect() 1358 aspect = self.renderer.GetAspect() 1359 angle = np.pi * cam.GetViewAngle() / 180.0 1360 dx = x1 - x0 1361 dy = y1 - y0 1362 dist = max(dx / aspect[0], dy) / np.tan(angle / 2) / 2 1363 1364 cam.SetViewUp(0, 1, 0) 1365 cam.SetPosition(x0 + dx / 2, y0 + dy / 2, dist * (1 + tight)) 1366 cam.SetFocalPoint(x0 + dx / 2, y0 + dy / 2, 0) 1367 if cam.GetParallelProjection(): 1368 ps = max(dx / aspect[0], dy) / 2 1369 cam.SetParallelScale(ps * (1 + tight)) 1370 self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1) 1371 return self 1372 1373 def reset_clipping_range(self, bounds=None) -> Self: 1374 """ 1375 Reset the camera clipping range to include all visible actors. 1376 If bounds is given, it will be used instead of computing it. 1377 """ 1378 if bounds is None: 1379 self.renderer.ResetCameraClippingRange() 1380 else: 1381 self.renderer.ResetCameraClippingRange(bounds) 1382 return self 1383 1384 def reset_viewup(self, smooth=True) -> Self: 1385 """ 1386 Reset the orientation of the camera to the closest orthogonal direction and view-up. 1387 """ 1388 vbb = addons.compute_visible_bounds()[0] 1389 x0, x1, y0, y1, z0, z1 = vbb 1390 mx, my, mz = (x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2 1391 d = self.camera.GetDistance() 1392 1393 viewups = np.array( 1394 [(0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)] 1395 ) 1396 positions = np.array( 1397 [ 1398 (mx, my, mz + d), 1399 (mx, my, mz - d), 1400 (mx, my + d, mz), 1401 (mx, my - d, mz), 1402 (mx + d, my, mz), 1403 (mx - d, my, mz), 1404 ] 1405 ) 1406 1407 vu = np.array(self.camera.GetViewUp()) 1408 vui = np.argmin(np.linalg.norm(viewups - vu, axis=1)) 1409 1410 poc = np.array(self.camera.GetPosition()) 1411 foc = np.array(self.camera.GetFocalPoint()) 1412 a = poc - foc 1413 b = positions - foc 1414 a = a / np.linalg.norm(a) 1415 b = b.T * (1 / np.linalg.norm(b, axis=1)) 1416 pui = np.argmin(np.linalg.norm(b.T - a, axis=1)) 1417 1418 if smooth: 1419 outtimes = np.linspace(0, 1, num=11, endpoint=True) 1420 for t in outtimes: 1421 vv = vu * (1 - t) + viewups[vui] * t 1422 pp = poc * (1 - t) + positions[pui] * t 1423 ff = foc * (1 - t) + np.array([mx, my, mz]) * t 1424 self.camera.SetViewUp(vv) 1425 self.camera.SetPosition(pp) 1426 self.camera.SetFocalPoint(ff) 1427 self.render() 1428 1429 # interpolator does not respect parallel view...: 1430 # cam1 = dict( 1431 # pos=poc, 1432 # viewup=vu, 1433 # focal_point=(mx,my,mz), 1434 # clipping_range=self.camera.GetClippingRange() 1435 # ) 1436 # # cam1 = self.camera 1437 # cam2 = dict( 1438 # pos=positions[pui], 1439 # viewup=viewups[vui], 1440 # focal_point=(mx,my,mz), 1441 # clipping_range=self.camera.GetClippingRange() 1442 # ) 1443 # vcams = self.move_camera([cam1, cam2], output_times=outtimes, smooth=0) 1444 # for c in vcams: 1445 # self.renderer.SetActiveCamera(c) 1446 # self.render() 1447 else: 1448 1449 self.camera.SetViewUp(viewups[vui]) 1450 self.camera.SetPosition(positions[pui]) 1451 self.camera.SetFocalPoint(mx, my, mz) 1452 1453 self.renderer.ResetCameraClippingRange() 1454 1455 # vbb, _, _, _ = addons.compute_visible_bounds() 1456 # x0,x1, y0,y1, z0,z1 = vbb 1457 # self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1) 1458 self.render() 1459 return self 1460 1461 def move_camera(self, cameras, t=0, times=(), smooth=True, output_times=()) -> list: 1462 """ 1463 Takes as input two cameras set camera at an interpolated position: 1464 1465 Cameras can be vtkCamera or dictionaries in format: 1466 1467 `dict(pos=..., focal_point=..., viewup=..., distance=..., clipping_range=...)` 1468 1469 Press `shift-C` key in interactive mode to dump a python snipplet 1470 of parameters for the current camera view. 1471 """ 1472 nc = len(cameras) 1473 if len(times) == 0: 1474 times = np.linspace(0, 1, num=nc, endpoint=True) 1475 1476 assert len(times) == nc 1477 1478 cin = vtki.new("CameraInterpolator") 1479 1480 # cin.SetInterpolationTypeToLinear() # buggy? 1481 if nc > 2 and smooth: 1482 cin.SetInterpolationTypeToSpline() 1483 1484 for i, cam in enumerate(cameras): 1485 vcam = cam 1486 if isinstance(cam, dict): 1487 vcam = utils.camera_from_dict(cam) 1488 cin.AddCamera(times[i], vcam) 1489 1490 mint, maxt = cin.GetMinimumT(), cin.GetMaximumT() 1491 rng = maxt - mint 1492 1493 if len(output_times) == 0: 1494 cin.InterpolateCamera(t * rng, self.camera) 1495 return [self.camera] 1496 else: 1497 vcams = [] 1498 for tt in output_times: 1499 c = vtki.vtkCamera() 1500 cin.InterpolateCamera(tt * rng, c) 1501 vcams.append(c) 1502 return vcams 1503 1504 def fly_to(self, point) -> Self: 1505 """ 1506 Fly camera to the specified point. 1507 1508 Arguments: 1509 point : (list) 1510 point in space to place camera. 1511 1512 Example: 1513 ```python 1514 from vedo import * 1515 cone = Cone() 1516 plt = Plotter(axes=1) 1517 plt.show(cone) 1518 plt.fly_to([1,0,0]) 1519 plt.interactive().close() 1520 ``` 1521 """ 1522 if self.interactor: 1523 self.resetcam = False 1524 self.interactor.FlyTo(self.renderer, point) 1525 return self 1526 1527 def look_at(self, plane="xy") -> Self: 1528 """Move the camera so that it looks at the specified cartesian plane""" 1529 cam = self.renderer.GetActiveCamera() 1530 fp = np.array(cam.GetFocalPoint()) 1531 p = np.array(cam.GetPosition()) 1532 dist = np.linalg.norm(fp - p) 1533 plane = plane.lower() 1534 if "x" in plane and "y" in plane: 1535 cam.SetPosition(fp[0], fp[1], fp[2] + dist) 1536 cam.SetViewUp(0.0, 1.0, 0.0) 1537 elif "x" in plane and "z" in plane: 1538 cam.SetPosition(fp[0], fp[1] - dist, fp[2]) 1539 cam.SetViewUp(0.0, 0.0, 1.0) 1540 elif "y" in plane and "z" in plane: 1541 cam.SetPosition(fp[0] + dist, fp[1], fp[2]) 1542 cam.SetViewUp(0.0, 0.0, 1.0) 1543 else: 1544 vedo.logger.error(f"in plotter.look() cannot understand argument {plane}") 1545 return self 1546 1547 def record(self, filename="") -> str: 1548 """ 1549 Record camera, mouse, keystrokes and all other events. 1550 Recording can be toggled on/off by pressing key "R". 1551 1552 Arguments: 1553 filename : (str) 1554 ascii file to store events. 1555 The default is `vedo.settings.cache_directory+"vedo/recorded_events.log"`. 1556 1557 Returns: 1558 a string descriptor of events. 1559 1560 Examples: 1561 - [record_play.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/record_play.py) 1562 """ 1563 if vedo.settings.dry_run_mode >= 1: 1564 return "" 1565 if not self.interactor: 1566 vedo.logger.warning("Cannot record events, no interactor defined.") 1567 return "" 1568 erec = vtki.new("InteractorEventRecorder") 1569 erec.SetInteractor(self.interactor) 1570 if not filename: 1571 if not os.path.exists(vedo.settings.cache_directory): 1572 os.makedirs(vedo.settings.cache_directory) 1573 home_dir = os.path.expanduser("~") 1574 filename = os.path.join( 1575 home_dir, vedo.settings.cache_directory, "vedo", "recorded_events.log") 1576 print("Events will be recorded in", filename) 1577 erec.SetFileName(filename) 1578 erec.SetKeyPressActivationValue("R") 1579 erec.EnabledOn() 1580 erec.Record() 1581 self.interactor.Start() 1582 erec.Stop() 1583 erec.EnabledOff() 1584 with open(filename, "r", encoding="UTF-8") as fl: 1585 events = fl.read() 1586 erec = None 1587 return events 1588 1589 def play(self, recorded_events="", repeats=0) -> Self: 1590 """ 1591 Play camera, mouse, keystrokes and all other events. 1592 1593 Arguments: 1594 events : (str) 1595 file o string of events. 1596 The default is `vedo.settings.cache_directory+"vedo/recorded_events.log"`. 1597 repeats : (int) 1598 number of extra repeats of the same events. The default is 0. 1599 1600 Examples: 1601 - [record_play.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/record_play.py) 1602 """ 1603 if vedo.settings.dry_run_mode >= 1: 1604 return self 1605 if not self.interactor: 1606 vedo.logger.warning("Cannot play events, no interactor defined.") 1607 return self 1608 1609 erec = vtki.new("InteractorEventRecorder") 1610 erec.SetInteractor(self.interactor) 1611 1612 if not recorded_events: 1613 home_dir = os.path.expanduser("~") 1614 recorded_events = os.path.join( 1615 home_dir, vedo.settings.cache_directory, "vedo", "recorded_events.log") 1616 1617 if recorded_events.endswith(".log"): 1618 erec.ReadFromInputStringOff() 1619 erec.SetFileName(recorded_events) 1620 else: 1621 erec.ReadFromInputStringOn() 1622 erec.SetInputString(recorded_events) 1623 1624 erec.Play() 1625 for _ in range(repeats): 1626 erec.Rewind() 1627 erec.Play() 1628 erec.EnabledOff() 1629 erec = None 1630 return self 1631 1632 def parallel_projection(self, value=True, at=None) -> Self: 1633 """ 1634 Use parallel projection `at` a specified renderer. 1635 Object is seen from "infinite" distance, e.i. remove any perspective effects. 1636 An input value equal to -1 will toggle it on/off. 1637 """ 1638 if at is not None: 1639 r = self.renderers[at] 1640 else: 1641 r = self.renderer 1642 if value == -1: 1643 val = r.GetActiveCamera().GetParallelProjection() 1644 value = not val 1645 r.GetActiveCamera().SetParallelProjection(value) 1646 r.Modified() 1647 return self 1648 1649 def render_hidden_lines(self, value=True) -> Self: 1650 """Remove hidden lines when in wireframe mode.""" 1651 self.renderer.SetUseHiddenLineRemoval(not value) 1652 return self 1653 1654 def fov(self, angle: float) -> Self: 1655 """ 1656 Set the field of view angle for the camera. 1657 This is the angle of the camera frustum in the horizontal direction. 1658 High values will result in a wide-angle lens (fish-eye effect), 1659 and low values will result in a telephoto lens. 1660 1661 Default value is 30 degrees. 1662 """ 1663 self.renderer.GetActiveCamera().UseHorizontalViewAngleOn() 1664 self.renderer.GetActiveCamera().SetViewAngle(angle) 1665 return self 1666 1667 def zoom(self, zoom: float) -> Self: 1668 """Apply a zooming factor for the current camera view""" 1669 self.renderer.GetActiveCamera().Zoom(zoom) 1670 return self 1671 1672 def azimuth(self, angle: float) -> Self: 1673 """Rotate camera around the view up vector.""" 1674 self.renderer.GetActiveCamera().Azimuth(angle) 1675 return self 1676 1677 def elevation(self, angle: float) -> Self: 1678 """Rotate the camera around the cross product of the negative 1679 of the direction of projection and the view up vector.""" 1680 self.renderer.GetActiveCamera().Elevation(angle) 1681 return self 1682 1683 def roll(self, angle: float) -> Self: 1684 """Roll the camera about the direction of projection.""" 1685 self.renderer.GetActiveCamera().Roll(angle) 1686 return self 1687 1688 def dolly(self, value: float) -> Self: 1689 """Move the camera towards (value>0) or away from (value<0) the focal point.""" 1690 self.renderer.GetActiveCamera().Dolly(value) 1691 return self 1692 1693 ################################################################## 1694 def add_slider( 1695 self, 1696 sliderfunc, 1697 xmin, 1698 xmax, 1699 value=None, 1700 pos=4, 1701 title="", 1702 font="Calco", 1703 title_size=1, 1704 c=None, 1705 alpha=1, 1706 show_value=True, 1707 delayed=False, 1708 **options, 1709 ) -> "vedo.addons.Slider2D": 1710 """ 1711 Add a `vedo.addons.Slider2D` which can call an external custom function. 1712 1713 Arguments: 1714 sliderfunc : (Callable) 1715 external function to be called by the widget 1716 xmin : (float) 1717 lower value of the slider 1718 xmax : (float) 1719 upper value 1720 value : (float) 1721 current value 1722 pos : (list, str) 1723 position corner number: horizontal [1-5] or vertical [11-15] 1724 it can also be specified by corners coordinates [(x1,y1), (x2,y2)] 1725 and also by a string descriptor (eg. "bottom-left") 1726 title : (str) 1727 title text 1728 font : (str) 1729 title font face. Check [available fonts here](https://vedo.embl.es/fonts). 1730 title_size : (float) 1731 title text scale [1.0] 1732 show_value : (bool) 1733 if True current value is shown 1734 delayed : (bool) 1735 if True the callback is delayed until when the mouse button is released 1736 alpha : (float) 1737 opacity of the scalar bar texts 1738 slider_length : (float) 1739 slider length 1740 slider_width : (float) 1741 slider width 1742 end_cap_length : (float) 1743 length of the end cap 1744 end_cap_width : (float) 1745 width of the end cap 1746 tube_width : (float) 1747 width of the tube 1748 title_height : (float) 1749 width of the title 1750 tformat : (str) 1751 format of the title 1752 1753 Examples: 1754 - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py) 1755 - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py) 1756 1757 ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg) 1758 """ 1759 if c is None: # automatic black or white 1760 c = (0.8, 0.8, 0.8) 1761 if np.sum(vedo.get_color(self.backgrcol)) > 1.5: 1762 c = (0.2, 0.2, 0.2) 1763 else: 1764 c = vedo.get_color(c) 1765 1766 slider2d = addons.Slider2D( 1767 sliderfunc, 1768 xmin, 1769 xmax, 1770 value, 1771 pos, 1772 title, 1773 font, 1774 title_size, 1775 c, 1776 alpha, 1777 show_value, 1778 delayed, 1779 **options, 1780 ) 1781 1782 if self.renderer: 1783 slider2d.renderer = self.renderer 1784 if self.interactor: 1785 slider2d.interactor = self.interactor 1786 slider2d.on() 1787 self.sliders.append([slider2d, sliderfunc]) 1788 return slider2d 1789 1790 def add_slider3d( 1791 self, 1792 sliderfunc, 1793 pos1, 1794 pos2, 1795 xmin, 1796 xmax, 1797 value=None, 1798 s=0.03, 1799 t=1, 1800 title="", 1801 rotation=0.0, 1802 c=None, 1803 show_value=True, 1804 ) -> "vedo.addons.Slider3D": 1805 """ 1806 Add a 3D slider widget which can call an external custom function. 1807 1808 Arguments: 1809 sliderfunc : (function) 1810 external function to be called by the widget 1811 pos1 : (list) 1812 first position 3D coordinates 1813 pos2 : (list) 1814 second position coordinates 1815 xmin : (float) 1816 lower value 1817 xmax : (float) 1818 upper value 1819 value : (float) 1820 initial value 1821 s : (float) 1822 label scaling factor 1823 t : (float) 1824 tube scaling factor 1825 title : (str) 1826 title text 1827 c : (color) 1828 slider color 1829 rotation : (float) 1830 title rotation around slider axis 1831 show_value : (bool) 1832 if True current value is shown 1833 1834 Examples: 1835 - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py) 1836 1837 ![](https://user-images.githubusercontent.com/32848391/52859555-4efcf200-312d-11e9-9290-6988c8295163.png) 1838 """ 1839 if c is None: # automatic black or white 1840 c = (0.8, 0.8, 0.8) 1841 if np.sum(vedo.get_color(self.backgrcol)) > 1.5: 1842 c = (0.2, 0.2, 0.2) 1843 else: 1844 c = vedo.get_color(c) 1845 1846 slider3d = addons.Slider3D( 1847 sliderfunc, 1848 pos1, 1849 pos2, 1850 xmin, 1851 xmax, 1852 value, 1853 s, 1854 t, 1855 title, 1856 rotation, 1857 c, 1858 show_value, 1859 ) 1860 slider3d.renderer = self.renderer 1861 slider3d.interactor = self.interactor 1862 slider3d.on() 1863 self.sliders.append([slider3d, sliderfunc]) 1864 return slider3d 1865 1866 def add_button( 1867 self, 1868 fnc=None, 1869 states=("On", "Off"), 1870 c=("w", "w"), 1871 bc=("green4", "red4"), 1872 pos=(0.7, 0.1), 1873 size=24, 1874 font="Courier", 1875 bold=True, 1876 italic=False, 1877 alpha=1, 1878 angle=0, 1879 ) -> Union["vedo.addons.Button", None]: 1880 """ 1881 Add a button to the renderer window. 1882 1883 Arguments: 1884 states : (list) 1885 a list of possible states, e.g. ['On', 'Off'] 1886 c : (list) 1887 a list of colors for each state 1888 bc : (list) 1889 a list of background colors for each state 1890 pos : (list) 1891 2D position from left-bottom corner 1892 size : (float) 1893 size of button font 1894 font : (str) 1895 font type. Check [available fonts here](https://vedo.embl.es/fonts). 1896 bold : (bool) 1897 bold font face (False) 1898 italic : (bool) 1899 italic font face (False) 1900 alpha : (float) 1901 opacity level 1902 angle : (float) 1903 anticlockwise rotation in degrees 1904 1905 Returns: 1906 `vedo.addons.Button` object. 1907 1908 Examples: 1909 - [buttons1.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/buttons1.py) 1910 - [buttons2.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/buttons2.py) 1911 1912 ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg) 1913 """ 1914 if self.interactor: 1915 bu = addons.Button(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle) 1916 self.renderer.AddActor2D(bu) 1917 self.buttons.append(bu) 1918 # bu.function_id = self.add_callback("LeftButtonPress", bu.function) # not good 1919 bu.function_id = bu.add_observer("pick", bu.function, priority=10) 1920 return bu 1921 return None 1922 1923 def add_spline_tool( 1924 self, points, pc="k", ps=8, lc="r4", ac="g5", 1925 lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True, 1926 ) -> "vedo.addons.SplineTool": 1927 """ 1928 Add a spline tool to the current plotter. 1929 Nodes of the spline can be dragged in space with the mouse. 1930 Clicking on the line itself adds an extra point. 1931 Selecting a point and pressing del removes it. 1932 1933 Arguments: 1934 points : (Mesh, Points, array) 1935 the set of vertices forming the spline nodes. 1936 pc : (str) 1937 point color. The default is 'k'. 1938 ps : (str) 1939 point size. The default is 8. 1940 lc : (str) 1941 line color. The default is 'r4'. 1942 ac : (str) 1943 active point marker color. The default is 'g5'. 1944 lw : (int) 1945 line width. The default is 2. 1946 alpha : (float) 1947 line transparency. 1948 closed : (bool) 1949 spline is meant to be closed. The default is False. 1950 1951 Returns: 1952 a `SplineTool` object. 1953 1954 Examples: 1955 - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py) 1956 1957 ![](https://vedo.embl.es/images/basic/spline_tool.png) 1958 """ 1959 sw = addons.SplineTool(points, pc, ps, lc, ac, lw, alpha, closed, ontop, can_add_nodes) 1960 sw.interactor = self.interactor 1961 sw.on() 1962 sw.Initialize(sw.points.dataset) 1963 sw.representation.SetRenderer(self.renderer) 1964 sw.representation.SetClosedLoop(closed) 1965 sw.representation.BuildRepresentation() 1966 self.widgets.append(sw) 1967 return sw 1968 1969 def add_icon(self, icon, pos=3, size=0.08) -> "vedo.addons.Icon": 1970 """Add an inset icon mesh into the same renderer. 1971 1972 Arguments: 1973 pos : (int, list) 1974 icon position in the range [1-4] indicating one of the 4 corners, 1975 or it can be a tuple (x,y) as a fraction of the renderer size. 1976 size : (float) 1977 size of the square inset. 1978 1979 Examples: 1980 - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py) 1981 """ 1982 iconw = addons.Icon(icon, pos, size) 1983 1984 iconw.SetInteractor(self.interactor) 1985 iconw.EnabledOn() 1986 iconw.InteractiveOff() 1987 self.widgets.append(iconw) 1988 return iconw 1989 1990 def add_global_axes(self, axtype=None, c=None) -> Self: 1991 """Draw axes on scene. Available axes types: 1992 1993 Arguments: 1994 axtype : (int) 1995 - 0, no axes, 1996 - 1, draw three gray grid walls 1997 - 2, show cartesian axes from (0,0,0) 1998 - 3, show positive range of cartesian axes from (0,0,0) 1999 - 4, show a triad at bottom left 2000 - 5, show a cube at bottom left 2001 - 6, mark the corners of the bounding box 2002 - 7, draw a 3D ruler at each side of the cartesian axes 2003 - 8, show the vtkCubeAxesActor object 2004 - 9, show the bounding box outLine 2005 - 10, show three circles representing the maximum bounding box 2006 - 11, show a large grid on the x-y plane 2007 - 12, show polar axes 2008 - 13, draw a simple ruler at the bottom of the window 2009 2010 Axis type-1 can be fully customized by passing a dictionary axes=dict(). 2011 2012 Example: 2013 ```python 2014 from vedo import Box, show 2015 b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1) 2016 show( 2017 b, 2018 axes={ 2019 "xtitle": "Some long variable [a.u.]", 2020 "number_of_divisions": 4, 2021 # ... 2022 }, 2023 ) 2024 ``` 2025 2026 Examples: 2027 - [custom_axes1.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes1.py) 2028 - [custom_axes2.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes2.py) 2029 - [custom_axes3.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes3.py) 2030 - [custom_axes4.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes4.py) 2031 2032 <img src="https://user-images.githubusercontent.com/32848391/72752870-ab7d5280-3bc3-11ea-8911-9ace00211e23.png" width="600"> 2033 """ 2034 addons.add_global_axes(axtype, c) 2035 return self 2036 2037 def add_legend_box(self, **kwargs) -> "vedo.addons.LegendBox": 2038 """Add a legend to the top right. 2039 2040 Examples: 2041 - [legendbox.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/basic/legendbox.py), 2042 - [flag_labels1.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels1.py) 2043 - [flag_labels2.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels2.py) 2044 """ 2045 acts = self.get_meshes() 2046 lb = addons.LegendBox(acts, **kwargs) 2047 self.add(lb) 2048 return lb 2049 2050 def add_hint( 2051 self, 2052 obj, 2053 text="", 2054 c="k", 2055 bg="yellow9", 2056 font="Calco", 2057 size=18, 2058 justify=0, 2059 angle=0, 2060 delay=250, 2061 ) -> Union[vtki.vtkBalloonWidget, None]: 2062 """ 2063 Create a pop-up hint style message when hovering an object. 2064 Use `add_hint(obj, False)` to disable a hinting a specific object. 2065 Use `add_hint(None)` to disable all hints. 2066 2067 Arguments: 2068 obj : (Mesh, Points) 2069 the object to associate the pop-up to 2070 text : (str) 2071 string description of the pop-up 2072 delay : (int) 2073 milliseconds to wait before pop-up occurs 2074 """ 2075 if self.offscreen or not self.interactor: 2076 return None 2077 2078 if vedo.vtk_version[:2] == (9, 0) and "Linux" in vedo.sys_platform: 2079 # Linux vtk9.0 is bugged 2080 vedo.logger.warning( 2081 f"add_hint() is not available on Linux platforms for vtk{vedo.vtk_version}." 2082 ) 2083 return None 2084 2085 if obj is None: 2086 self.hint_widget.EnabledOff() 2087 self.hint_widget.SetInteractor(None) 2088 self.hint_widget = None 2089 return self.hint_widget 2090 2091 if text is False and self.hint_widget: 2092 self.hint_widget.RemoveBalloon(obj) 2093 return self.hint_widget 2094 2095 if text == "": 2096 if obj.name: 2097 text = obj.name 2098 elif obj.filename: 2099 text = obj.filename 2100 else: 2101 return None 2102 2103 if not self.hint_widget: 2104 self.hint_widget = vtki.vtkBalloonWidget() 2105 2106 rep = self.hint_widget.GetRepresentation() 2107 rep.SetBalloonLayoutToImageRight() 2108 2109 trep = rep.GetTextProperty() 2110 trep.SetFontFamily(vtki.VTK_FONT_FILE) 2111 trep.SetFontFile(utils.get_font_path(font)) 2112 trep.SetFontSize(size) 2113 trep.SetColor(vedo.get_color(c)) 2114 trep.SetBackgroundColor(vedo.get_color(bg)) 2115 trep.SetShadow(0) 2116 trep.SetJustification(justify) 2117 trep.UseTightBoundingBoxOn() 2118 2119 self.hint_widget.ManagesCursorOff() 2120 self.hint_widget.SetTimerDuration(delay) 2121 self.hint_widget.SetInteractor(self.interactor) 2122 if angle: 2123 trep.SetOrientation(angle) 2124 trep.SetBackgroundOpacity(0) 2125 # else: 2126 # trep.SetBackgroundOpacity(0.5) # doesnt work well 2127 self.hint_widget.SetRepresentation(rep) 2128 self.widgets.append(self.hint_widget) 2129 self.hint_widget.EnabledOn() 2130 2131 bst = self.hint_widget.GetBalloonString(obj.actor) 2132 if bst: 2133 self.hint_widget.UpdateBalloonString(obj.actor, text) 2134 else: 2135 self.hint_widget.AddBalloon(obj.actor, text) 2136 2137 return self.hint_widget 2138 2139 def add_shadows(self) -> Self: 2140 """Add shadows at the current renderer.""" 2141 if self.renderer: 2142 shadows = vtki.new("ShadowMapPass") 2143 seq = vtki.new("SequencePass") 2144 passes = vtki.new("RenderPassCollection") 2145 passes.AddItem(shadows.GetShadowMapBakerPass()) 2146 passes.AddItem(shadows) 2147 seq.SetPasses(passes) 2148 camerapass = vtki.new("CameraPass") 2149 camerapass.SetDelegatePass(seq) 2150 self.renderer.SetPass(camerapass) 2151 return self 2152 2153 def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self: 2154 """ 2155 Screen Space Ambient Occlusion. 2156 2157 For every pixel on the screen, the pixel shader samples the depth values around 2158 the current pixel and tries to compute the amount of occlusion from each of the sampled 2159 points. 2160 2161 Arguments: 2162 radius : (float) 2163 radius of influence in absolute units 2164 bias : (float) 2165 bias of the normals 2166 blur : (bool) 2167 add a blurring to the sampled positions 2168 samples : (int) 2169 number of samples to probe 2170 2171 Examples: 2172 - [ssao.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ssao.py) 2173 2174 ![](https://vedo.embl.es/images/basic/ssao.jpg) 2175 """ 2176 lights = vtki.new("LightsPass") 2177 2178 opaque = vtki.new("OpaquePass") 2179 2180 ssaoCam = vtki.new("CameraPass") 2181 ssaoCam.SetDelegatePass(opaque) 2182 2183 ssao = vtki.new("SSAOPass") 2184 ssao.SetRadius(radius) 2185 ssao.SetBias(bias) 2186 ssao.SetBlur(blur) 2187 ssao.SetKernelSize(samples) 2188 ssao.SetDelegatePass(ssaoCam) 2189 2190 translucent = vtki.new("TranslucentPass") 2191 2192 volpass = vtki.new("VolumetricPass") 2193 ddp = vtki.new("DualDepthPeelingPass") 2194 ddp.SetTranslucentPass(translucent) 2195 ddp.SetVolumetricPass(volpass) 2196 2197 over = vtki.new("OverlayPass") 2198 2199 collection = vtki.new("RenderPassCollection") 2200 collection.AddItem(lights) 2201 collection.AddItem(ssao) 2202 collection.AddItem(ddp) 2203 collection.AddItem(over) 2204 2205 sequence = vtki.new("SequencePass") 2206 sequence.SetPasses(collection) 2207 2208 cam = vtki.new("CameraPass") 2209 cam.SetDelegatePass(sequence) 2210 2211 self.renderer.SetPass(cam) 2212 return self 2213 2214 def add_depth_of_field(self, autofocus=True) -> Self: 2215 """Add a depth of field effect in the scene.""" 2216 lights = vtki.new("LightsPass") 2217 2218 opaque = vtki.new("OpaquePass") 2219 2220 dofCam = vtki.new("CameraPass") 2221 dofCam.SetDelegatePass(opaque) 2222 2223 dof = vtki.new("DepthOfFieldPass") 2224 dof.SetAutomaticFocalDistance(autofocus) 2225 dof.SetDelegatePass(dofCam) 2226 2227 collection = vtki.new("RenderPassCollection") 2228 collection.AddItem(lights) 2229 collection.AddItem(dof) 2230 2231 sequence = vtki.new("SequencePass") 2232 sequence.SetPasses(collection) 2233 2234 cam = vtki.new("CameraPass") 2235 cam.SetDelegatePass(sequence) 2236 2237 self.renderer.SetPass(cam) 2238 return self 2239 2240 def _add_skybox(self, hdrfile: str) -> Self: 2241 # many hdr files are at https://polyhaven.com/all 2242 2243 reader = vtki.new("HDRReader") 2244 # Check the image can be read. 2245 if not reader.CanReadFile(hdrfile): 2246 vedo.logger.error(f"Cannot read HDR file {hdrfile}") 2247 return self 2248 reader.SetFileName(hdrfile) 2249 reader.Update() 2250 2251 texture = vtki.vtkTexture() 2252 texture.SetColorModeToDirectScalars() 2253 texture.SetInputData(reader.GetOutput()) 2254 2255 # Convert to a cube map 2256 tcm = vtki.new("EquirectangularToCubeMapTexture") 2257 tcm.SetInputTexture(texture) 2258 # Enable mipmapping to handle HDR image 2259 tcm.MipmapOn() 2260 tcm.InterpolateOn() 2261 2262 self.renderer.SetEnvironmentTexture(tcm) 2263 self.renderer.UseImageBasedLightingOn() 2264 self.skybox = vtki.new("Skybox") 2265 self.skybox.SetTexture(tcm) 2266 self.renderer.AddActor(self.skybox) 2267 return self 2268 2269 def add_renderer_frame(self, c=None, alpha=None, lw=None, padding=None) -> "vedo.addons.RendererFrame": 2270 """ 2271 Add a frame to the renderer subwindow. 2272 2273 Arguments: 2274 c : (color) 2275 color name or index 2276 alpha : (float) 2277 opacity level 2278 lw : (int) 2279 line width in pixels. 2280 padding : (float) 2281 padding space in pixels. 2282 """ 2283 if c is None: # automatic black or white 2284 c = (0.9, 0.9, 0.9) 2285 if self.renderer: 2286 if np.sum(self.renderer.GetBackground()) > 1.5: 2287 c = (0.1, 0.1, 0.1) 2288 renf = addons.RendererFrame(c, alpha, lw, padding) 2289 if renf: 2290 self.renderer.AddActor(renf) 2291 return renf 2292 2293 def add_hover_legend( 2294 self, 2295 at=None, 2296 c=None, 2297 pos="bottom-left", 2298 font="Calco", 2299 s=0.75, 2300 bg="auto", 2301 alpha=0.1, 2302 maxlength=24, 2303 use_info=False, 2304 ) -> int: 2305 """ 2306 Add a legend with 2D text which is triggered by hovering the mouse on an object. 2307 2308 The created text object are stored in `plotter.hover_legends`. 2309 2310 Returns: 2311 the id of the callback function. 2312 2313 Arguments: 2314 c : (color) 2315 Text color. If None then black or white is chosen automatically 2316 pos : (str) 2317 text positioning 2318 font : (str) 2319 text font type. Check [available fonts here](https://vedo.embl.es/fonts). 2320 s : (float) 2321 text size scale 2322 bg : (color) 2323 background color of the 2D box containing the text 2324 alpha : (float) 2325 box transparency 2326 maxlength : (int) 2327 maximum number of characters per line 2328 use_info : (bool) 2329 visualize the content of the `obj.info` attribute 2330 2331 Examples: 2332 - [hover_legend.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/hover_legend.py) 2333 - [earthquake_browser.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/earthquake_browser.py) 2334 2335 ![](https://vedo.embl.es/images/pyplot/earthquake_browser.jpg) 2336 """ 2337 hoverlegend = vedo.shapes.Text2D(pos=pos, font=font, c=c, s=s, alpha=alpha, bg=bg) 2338 2339 if at is None: 2340 at = self.renderers.index(self.renderer) 2341 2342 def _legfunc(evt): 2343 if not evt.object or not self.renderer or at != evt.at: 2344 if hoverlegend.mapper.GetInput(): # clear and return 2345 hoverlegend.mapper.SetInput("") 2346 self.render() 2347 return 2348 2349 if use_info: 2350 if hasattr(evt.object, "info"): 2351 t = str(evt.object.info) 2352 else: 2353 return 2354 else: 2355 t, tp = "", "" 2356 if evt.isMesh: 2357 tp = "Mesh " 2358 elif evt.isPoints: 2359 tp = "Points " 2360 elif evt.isVolume: 2361 tp = "Volume " 2362 elif evt.isImage: 2363 tp = "Image " 2364 elif evt.isAssembly: 2365 tp = "Assembly " 2366 else: 2367 return 2368 2369 if evt.isAssembly: 2370 if not evt.object.name: 2371 t += f"Assembly object of {len(evt.object.unpack())} parts\n" 2372 else: 2373 t += f"Assembly name: {evt.object.name} ({len(evt.object.unpack())} parts)\n" 2374 else: 2375 if evt.object.name: 2376 t += f"{tp}name" 2377 if evt.isPoints: 2378 t += " " 2379 if evt.isMesh: 2380 t += " " 2381 t += f": {evt.object.name[:maxlength]}".ljust(maxlength) + "\n" 2382 2383 if evt.object.filename: 2384 t += f"{tp}filename: " 2385 t += f"{os.path.basename(evt.object.filename[-maxlength:])}".ljust(maxlength) 2386 t += "\n" 2387 if not evt.object.file_size: 2388 evt.object.file_size, evt.object.created = vedo.file_io.file_info(evt.object.filename) 2389 if evt.object.file_size: 2390 t += " : " 2391 sz, created = evt.object.file_size, evt.object.created 2392 t += f"{created[4:-5]} ({sz})" + "\n" 2393 2394 if evt.isPoints: 2395 indata = evt.object.dataset 2396 if indata.GetNumberOfPoints(): 2397 t += ( 2398 f"#points/cells: {indata.GetNumberOfPoints()}" 2399 f" / {indata.GetNumberOfCells()}" 2400 ) 2401 pdata = indata.GetPointData() 2402 cdata = indata.GetCellData() 2403 if pdata.GetScalars() and pdata.GetScalars().GetName(): 2404 t += f"\nPoint array : {pdata.GetScalars().GetName()}" 2405 if pdata.GetScalars().GetName() == evt.object.mapper.GetArrayName(): 2406 t += " *" 2407 if cdata.GetScalars() and cdata.GetScalars().GetName(): 2408 t += f"\nCell array : {cdata.GetScalars().GetName()}" 2409 if cdata.GetScalars().GetName() == evt.object.mapper.GetArrayName(): 2410 t += " *" 2411 2412 if evt.isImage: 2413 t = f"{os.path.basename(evt.object.filename[:maxlength+10])}".ljust(maxlength+10) 2414 t += f"\nImage shape: {evt.object.shape}" 2415 pcol = self.color_picker(evt.picked2d) 2416 t += f"\nPixel color: {vedo.colors.rgb2hex(pcol/255)} {pcol}" 2417 2418 # change box color if needed in 'auto' mode 2419 if evt.isPoints and "auto" in str(bg): 2420 actcol = evt.object.properties.GetColor() 2421 if hoverlegend.mapper.GetTextProperty().GetBackgroundColor() != actcol: 2422 hoverlegend.mapper.GetTextProperty().SetBackgroundColor(actcol) 2423 2424 # adapt to changes in bg color 2425 bgcol = self.renderers[at].GetBackground() 2426 _bgcol = c 2427 if _bgcol is None: # automatic black or white 2428 _bgcol = (0.9, 0.9, 0.9) 2429 if sum(bgcol) > 1.5: 2430 _bgcol = (0.1, 0.1, 0.1) 2431 if len(set(_bgcol).intersection(bgcol)) < 3: 2432 hoverlegend.color(_bgcol) 2433 2434 if hoverlegend.mapper.GetInput() != t: 2435 hoverlegend.mapper.SetInput(t) 2436 self.interactor.Render() 2437 2438 # print("ABORT", idcall, hoverlegend.actor.GetCommand(idcall)) 2439 # hoverlegend.actor.GetCommand(idcall).AbortFlagOn() 2440 2441 self.add(hoverlegend, at=at) 2442 self.hover_legends.append(hoverlegend) 2443 idcall = self.add_callback("MouseMove", _legfunc) 2444 return idcall 2445 2446 def add_scale_indicator( 2447 self, 2448 pos=(0.7, 0.05), 2449 s=0.02, 2450 length=2, 2451 lw=4, 2452 c="k1", 2453 alpha=1, 2454 units="", 2455 gap=0.05, 2456 ) -> Union["vedo.visual.Actor2D", None]: 2457 """ 2458 Add a Scale Indicator. Only works in parallel mode (no perspective). 2459 2460 Arguments: 2461 pos : (list) 2462 fractional (x,y) position on the screen. 2463 s : (float) 2464 size of the text. 2465 length : (float) 2466 length of the line. 2467 units : (str) 2468 string to show units. 2469 gap : (float) 2470 separation of line and text. 2471 2472 Example: 2473 ```python 2474 from vedo import settings, Cube, Plotter 2475 settings.use_parallel_projection = True # or else it does not make sense! 2476 cube = Cube().alpha(0.2) 2477 plt = Plotter(size=(900,600), axes=dict(xtitle='x (um)')) 2478 plt.add_scale_indicator(units='um', c='blue4') 2479 plt.show(cube, "Scale indicator with units").close() 2480 ``` 2481 ![](https://vedo.embl.es/images/feats/scale_indicator.png) 2482 """ 2483 # Note that this cannot go in addons.py 2484 # because it needs callbacks and window size 2485 if not self.interactor: 2486 return None 2487 2488 ppoints = vtki.vtkPoints() # Generate the polyline 2489 psqr = [[0.0, gap], [length / 10, gap]] 2490 dd = psqr[1][0] - psqr[0][0] 2491 for i, pt in enumerate(psqr): 2492 ppoints.InsertPoint(i, pt[0], pt[1], 0) 2493 lines = vtki.vtkCellArray() 2494 lines.InsertNextCell(len(psqr)) 2495 for i in range(len(psqr)): 2496 lines.InsertCellPoint(i) 2497 pd = vtki.vtkPolyData() 2498 pd.SetPoints(ppoints) 2499 pd.SetLines(lines) 2500 2501 wsx, wsy = self.window.GetSize() 2502 if not self.camera.GetParallelProjection(): 2503 vedo.logger.warning("add_scale_indicator called with use_parallel_projection OFF. Skip.") 2504 return None 2505 2506 rlabel = vtki.new("VectorText") 2507 rlabel.SetText("scale") 2508 tf = vtki.new("TransformPolyDataFilter") 2509 tf.SetInputConnection(rlabel.GetOutputPort()) 2510 t = vtki.vtkTransform() 2511 t.Scale(s * wsy / wsx, s, 1) 2512 tf.SetTransform(t) 2513 2514 app = vtki.new("AppendPolyData") 2515 app.AddInputConnection(tf.GetOutputPort()) 2516 app.AddInputData(pd) 2517 2518 mapper = vtki.new("PolyDataMapper2D") 2519 mapper.SetInputConnection(app.GetOutputPort()) 2520 cs = vtki.vtkCoordinate() 2521 cs.SetCoordinateSystem(1) 2522 mapper.SetTransformCoordinate(cs) 2523 2524 fractor = vedo.visual.Actor2D() 2525 csys = fractor.GetPositionCoordinate() 2526 csys.SetCoordinateSystem(3) 2527 fractor.SetPosition(pos) 2528 fractor.SetMapper(mapper) 2529 fractor.GetProperty().SetColor(vedo.get_color(c)) 2530 fractor.GetProperty().SetOpacity(alpha) 2531 fractor.GetProperty().SetLineWidth(lw) 2532 fractor.GetProperty().SetDisplayLocationToForeground() 2533 2534 def sifunc(iren, ev): 2535 wsx, wsy = self.window.GetSize() 2536 ps = self.camera.GetParallelScale() 2537 newtxt = utils.precision(ps / wsy * wsx * length * dd, 3) 2538 if units: 2539 newtxt += " " + units 2540 if rlabel.GetText() != newtxt: 2541 rlabel.SetText(newtxt) 2542 2543 self.renderer.AddActor(fractor) 2544 self.interactor.AddObserver("MouseWheelBackwardEvent", sifunc) 2545 self.interactor.AddObserver("MouseWheelForwardEvent", sifunc) 2546 self.interactor.AddObserver("InteractionEvent", sifunc) 2547 sifunc(0, 0) 2548 return fractor 2549 2550 def fill_event(self, ename="", pos=(), enable_picking=True) -> "Event": 2551 """ 2552 Create an Event object with information of what was clicked. 2553 2554 If `enable_picking` is False, no picking will be performed. 2555 This can be useful to avoid double picking when using buttons. 2556 """ 2557 if not self.interactor: 2558 return Event() 2559 2560 if len(pos) > 0: 2561 x, y = pos 2562 self.interactor.SetEventPosition(pos) 2563 else: 2564 x, y = self.interactor.GetEventPosition() 2565 self.renderer = self.interactor.FindPokedRenderer(x, y) 2566 2567 self.picked2d = (x, y) 2568 2569 key = self.interactor.GetKeySym() 2570 2571 if key: 2572 if "_L" in key or "_R" in key: 2573 # skip things like Shift_R 2574 key = "" # better than None 2575 else: 2576 if self.interactor.GetShiftKey(): 2577 key = key.upper() 2578 2579 if key == "MINUS": # fix: vtk9 is ignoring shift chars.. 2580 key = "underscore" 2581 elif key == "EQUAL": # fix: vtk9 is ignoring shift chars.. 2582 key = "plus" 2583 elif key == "SLASH": # fix: vtk9 is ignoring shift chars.. 2584 key = "?" 2585 2586 if self.interactor.GetControlKey(): 2587 key = "Ctrl+" + key 2588 2589 if self.interactor.GetAltKey(): 2590 key = "Alt+" + key 2591 2592 if enable_picking: 2593 if not self.picker: 2594 self.picker = vtki.vtkPropPicker() 2595 2596 self.picker.PickProp(x, y, self.renderer) 2597 actor = self.picker.GetProp3D() 2598 # Note that GetProp3D already picks Assembly 2599 2600 xp, yp = self.interactor.GetLastEventPosition() 2601 dx, dy = x - xp, y - yp 2602 2603 delta3d = np.array([0, 0, 0]) 2604 2605 if actor: 2606 picked3d = np.array(self.picker.GetPickPosition()) 2607 2608 try: 2609 vobj = actor.retrieve_object() 2610 old_pt = np.asarray(vobj.picked3d) 2611 vobj.picked3d = picked3d 2612 delta3d = picked3d - old_pt 2613 except (AttributeError, TypeError): 2614 pass 2615 2616 else: 2617 picked3d = None 2618 2619 if not actor: # try 2D 2620 actor = self.picker.GetActor2D() 2621 2622 event = Event() 2623 event.name = ename 2624 event.title = self.title 2625 event.id = -1 # will be set by the timer wrapper function 2626 event.timerid = -1 # will be set by the timer wrapper function 2627 event.priority = -1 # will be set by the timer wrapper function 2628 event.time = time.time() 2629 event.at = self.renderers.index(self.renderer) 2630 event.keypress = key 2631 if enable_picking: 2632 try: 2633 event.object = actor.retrieve_object() 2634 except AttributeError: 2635 event.object = actor 2636 try: 2637 event.actor = actor.retrieve_object() # obsolete use object instead 2638 except AttributeError: 2639 event.actor = actor 2640 event.picked3d = picked3d 2641 event.picked2d = (x, y) 2642 event.delta2d = (dx, dy) 2643 event.angle2d = np.arctan2(dy, dx) 2644 event.speed2d = np.sqrt(dx * dx + dy * dy) 2645 event.delta3d = delta3d 2646 event.speed3d = np.sqrt(np.dot(delta3d, delta3d)) 2647 event.isPoints = isinstance(event.object, vedo.Points) 2648 event.isMesh = isinstance(event.object, vedo.Mesh) 2649 event.isAssembly = isinstance(event.object, vedo.Assembly) 2650 event.isVolume = isinstance(event.object, vedo.Volume) 2651 event.isImage = isinstance(event.object, vedo.Image) 2652 event.isActor2D = isinstance(event.object, vtki.vtkActor2D) 2653 return event 2654 2655 def add_callback(self, event_name: str, func: Callable, priority=0.0, enable_picking=True) -> int: 2656 """ 2657 Add a function to be executed while show() is active. 2658 2659 Return a unique id for the callback. 2660 2661 The callback function (see example below) exposes a dictionary 2662 with the following information: 2663 - `name`: event name, 2664 - `id`: event unique identifier, 2665 - `priority`: event priority (float), 2666 - `interactor`: the interactor object, 2667 - `at`: renderer nr. where the event occurred 2668 - `keypress`: key pressed as string 2669 - `actor`: object picked by the mouse 2670 - `picked3d`: point picked in world coordinates 2671 - `picked2d`: screen coords of the mouse pointer 2672 - `delta2d`: shift wrt previous position (to calculate speed, direction) 2673 - `delta3d`: ...same but in 3D world coords 2674 - `angle2d`: angle of mouse movement on screen 2675 - `speed2d`: speed of mouse movement on screen 2676 - `speed3d`: speed of picked point in world coordinates 2677 - `isPoints`: True if of class 2678 - `isMesh`: True if of class 2679 - `isAssembly`: True if of class 2680 - `isVolume`: True if of class Volume 2681 - `isImage`: True if of class 2682 2683 If `enable_picking` is False, no picking will be performed. 2684 This can be useful to avoid double picking when using buttons. 2685 2686 Frequently used events are: 2687 - `KeyPress`, `KeyRelease`: listen to keyboard events 2688 - `LeftButtonPress`, `LeftButtonRelease`: listen to mouse clicks 2689 - `MiddleButtonPress`, `MiddleButtonRelease` 2690 - `RightButtonPress`, `RightButtonRelease` 2691 - `MouseMove`: listen to mouse pointer changing position 2692 - `MouseWheelForward`, `MouseWheelBackward` 2693 - `Enter`, `Leave`: listen to mouse entering or leaving the window 2694 - `Pick`, `StartPick`, `EndPick`: listen to object picking 2695 - `ResetCamera`, `ResetCameraClippingRange` 2696 - `Error`, `Warning` 2697 - `Char` 2698 - `Timer` 2699 2700 Check the complete list of events [here](https://vtk.org/doc/nightly/html/classvtkCommand.html). 2701 2702 Example: 2703 ```python 2704 from vedo import * 2705 2706 def func(evt): 2707 # this function is called every time the mouse moves 2708 # (evt is a dotted dictionary) 2709 if not evt.object: 2710 return # no hit, return 2711 print("point coords =", evt.picked3d) 2712 # print(evt) # full event dump 2713 2714 elli = Ellipsoid() 2715 plt = Plotter(axes=1) 2716 plt.add_callback('mouse hovering', func) 2717 plt.show(elli).close() 2718 ``` 2719 2720 Examples: 2721 - [spline_draw1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/spline_draw1.py) 2722 - [colorlines.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorlines.py) 2723 2724 ![](https://vedo.embl.es/images/advanced/spline_draw.png) 2725 2726 - ..and many others! 2727 """ 2728 from vtkmodules.util.misc import calldata_type 2729 2730 if not self.interactor: 2731 return 0 2732 2733 if vedo.settings.dry_run_mode >= 1: 2734 return 0 2735 2736 ######################################### 2737 @calldata_type(vtki.VTK_INT) 2738 def _func_wrap(iren, ename, timerid=None): 2739 event = self.fill_event(ename=ename, enable_picking=enable_picking) 2740 event.timerid = timerid 2741 event.id = cid 2742 event.priority = priority 2743 self.last_event = event 2744 func(event) 2745 2746 ######################################### 2747 2748 event_name = utils.get_vtk_name_event(event_name) 2749 2750 cid = self.interactor.AddObserver(event_name, _func_wrap, priority) 2751 # print(f"Registering event: {event_name} with id={cid}") 2752 return cid 2753 2754 def remove_callback(self, cid: Union[int, str]) -> Self: 2755 """ 2756 Remove a callback function by its id 2757 or a whole category of callbacks by their name. 2758 2759 Arguments: 2760 cid : (int, str) 2761 Unique id of the callback. 2762 If an event name is passed all callbacks of that type are removed. 2763 """ 2764 if self.interactor: 2765 if isinstance(cid, str): 2766 cid = utils.get_vtk_name_event(cid) 2767 self.interactor.RemoveObservers(cid) 2768 else: 2769 self.interactor.RemoveObserver(cid) 2770 return self 2771 2772 def remove_all_observers(self) -> Self: 2773 """ 2774 Remove all observers. 2775 2776 Example: 2777 ```python 2778 from vedo import * 2779 2780 def kfunc(event): 2781 print("Key pressed:", event.keypress) 2782 if event.keypress == 'q': 2783 plt.close() 2784 2785 def rfunc(event): 2786 if event.isImage: 2787 printc("Right-clicked!", event) 2788 plt.render() 2789 2790 img = Image(dataurl+"images/embryo.jpg") 2791 2792 plt = Plotter(size=(1050, 600)) 2793 plt.parallel_projection(True) 2794 plt.remove_all_observers() 2795 plt.add_callback("key press", kfunc) 2796 plt.add_callback("mouse right click", rfunc) 2797 plt.show("Right-Click Me! Press q to exit.", img) 2798 plt.close() 2799 ``` 2800 """ 2801 if self.interactor: 2802 self.interactor.RemoveAllObservers() 2803 return self 2804 2805 def timer_callback(self, action: str, timer_id=None, dt=1, one_shot=False) -> int: 2806 """ 2807 Start or stop an existing timer. 2808 2809 Arguments: 2810 action : (str) 2811 Either "create"/"start" or "destroy"/"stop" 2812 timer_id : (int) 2813 When stopping the timer, the ID of the timer as returned when created 2814 dt : (int) 2815 time in milliseconds between each repeated call 2816 one_shot : (bool) 2817 create a one shot timer of prescribed duration instead of a repeating one 2818 2819 Examples: 2820 - [timer_callback1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback1.py) 2821 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 2822 2823 ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg) 2824 """ 2825 if action in ("create", "start"): 2826 if timer_id is not None: 2827 vedo.logger.warning("you set a timer_id but it will be ignored.") 2828 if one_shot: 2829 timer_id = self.interactor.CreateOneShotTimer(dt) 2830 else: 2831 timer_id = self.interactor.CreateRepeatingTimer(dt) 2832 return timer_id 2833 2834 elif action in ("destroy", "stop"): 2835 if timer_id is not None: 2836 self.interactor.DestroyTimer(timer_id) 2837 else: 2838 vedo.logger.warning("please set a timer_id. Cannot stop timer.") 2839 else: 2840 e = f"in timer_callback(). Cannot understand action: {action}\n" 2841 e += " allowed actions are: ['start', 'stop']. Skipped." 2842 vedo.logger.error(e) 2843 return timer_id 2844 2845 def add_observer(self, event_name: str, func: Callable, priority=0.0) -> int: 2846 """ 2847 Add a callback function that will be called when an event occurs. 2848 Consider using `add_callback()` instead. 2849 """ 2850 if not self.interactor: 2851 return -1 2852 event_name = utils.get_vtk_name_event(event_name) 2853 idd = self.interactor.AddObserver(event_name, func, priority) 2854 return idd 2855 2856 def compute_world_coordinate( 2857 self, 2858 pos2d: MutableSequence[float], 2859 at=None, 2860 objs=(), 2861 bounds=(), 2862 offset=None, 2863 pixeltol=None, 2864 worldtol=None, 2865 ) -> np.ndarray: 2866 """ 2867 Transform a 2D point on the screen into a 3D point inside the rendering scene. 2868 If a set of meshes is passed then points are placed onto these. 2869 2870 Arguments: 2871 pos2d : (list) 2872 2D screen coordinates point. 2873 at : (int) 2874 renderer number. 2875 objs : (list) 2876 list of Mesh objects to project the point onto. 2877 bounds : (list) 2878 specify a bounding box as [xmin,xmax, ymin,ymax, zmin,zmax]. 2879 offset : (float) 2880 specify an offset value. 2881 pixeltol : (int) 2882 screen tolerance in pixels. 2883 worldtol : (float) 2884 world coordinates tolerance. 2885 2886 Returns: 2887 numpy array, the point in 3D world coordinates. 2888 2889 Examples: 2890 - [cut_freehand.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/cut_freehand.py) 2891 - [mousehover3.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mousehover3.py) 2892 2893 ![](https://vedo.embl.es/images/basic/mousehover3.jpg) 2894 """ 2895 if at is not None: 2896 renderer = self.renderers[at] 2897 else: 2898 renderer = self.renderer 2899 2900 if not objs: 2901 pp = vtki.vtkFocalPlanePointPlacer() 2902 else: 2903 pps = vtki.vtkPolygonalSurfacePointPlacer() 2904 for ob in objs: 2905 pps.AddProp(ob.actor) 2906 pp = pps # type: ignore 2907 2908 if len(bounds) == 6: 2909 pp.SetPointBounds(bounds) 2910 if pixeltol: 2911 pp.SetPixelTolerance(pixeltol) 2912 if worldtol: 2913 pp.SetWorldTolerance(worldtol) 2914 if offset: 2915 pp.SetOffset(offset) 2916 2917 worldPos: MutableSequence[float] = [0, 0, 0] 2918 worldOrient: MutableSequence[float] = [0, 0, 0, 0, 0, 0, 0, 0, 0] 2919 pp.ComputeWorldPosition(renderer, pos2d, worldPos, worldOrient) 2920 # validw = pp.ValidateWorldPosition(worldPos, worldOrient) 2921 # validd = pp.ValidateDisplayPosition(renderer, pos2d) 2922 return np.array(worldPos) 2923 2924 def compute_screen_coordinates(self, obj, full_window=False) -> np.ndarray: 2925 """ 2926 Given a 3D points in the current renderer (or full window), 2927 find the screen pixel coordinates. 2928 2929 Example: 2930 ```python 2931 from vedo import * 2932 2933 elli = Ellipsoid().point_size(5) 2934 2935 plt = Plotter() 2936 plt.show(elli, "Press q to continue and print the info") 2937 2938 xyscreen = plt.compute_screen_coordinates(elli) 2939 print('xyscreen coords:', xyscreen) 2940 2941 # simulate an event happening at one point 2942 event = plt.fill_event(pos=xyscreen[123]) 2943 print(event) 2944 ``` 2945 """ 2946 try: 2947 obj = obj.vertices 2948 except AttributeError: 2949 pass 2950 2951 if utils.is_sequence(obj): 2952 pts = obj 2953 p2d = [] 2954 cs = vtki.vtkCoordinate() 2955 cs.SetCoordinateSystemToWorld() 2956 cs.SetViewport(self.renderer) 2957 for p in pts: 2958 cs.SetValue(p) 2959 if full_window: 2960 p2d.append(cs.GetComputedDisplayValue(self.renderer)) 2961 else: 2962 p2d.append(cs.GetComputedViewportValue(self.renderer)) 2963 return np.array(p2d, dtype=int) 2964 2965 def pick_area(self, pos1, pos2, at=None) -> "vedo.Mesh": 2966 """ 2967 Pick all objects within a box defined by two corner points in 2D screen coordinates. 2968 2969 Returns a frustum Mesh that contains the visible field of view. 2970 This can be used to select objects in a scene or select vertices. 2971 2972 Example: 2973 ```python 2974 from vedo import * 2975 2976 settings.enable_default_mouse_callbacks = False 2977 2978 def mode_select(objs): 2979 print("Selected objects:", objs) 2980 d0 = mode.start_x, mode.start_y # display coords 2981 d1 = mode.end_x, mode.end_y 2982 2983 frustum = plt.pick_area(d0, d1) 2984 col = np.random.randint(0, 10) 2985 infru = frustum.inside_points(mesh) 2986 infru.point_size(10).color(col) 2987 plt.add(frustum, infru).render() 2988 2989 mesh = Mesh(dataurl+"cow.vtk") 2990 mesh.color("k5").linewidth(1) 2991 2992 mode = interactor_modes.BlenderStyle() 2993 mode.callback_select = mode_select 2994 2995 plt = Plotter().user_mode(mode) 2996 plt.show(mesh, axes=1) 2997 ``` 2998 """ 2999 if at is not None: 3000 ren = self.renderers[at] 3001 else: 3002 ren = self.renderer 3003 area_picker = vtki.vtkAreaPicker() 3004 area_picker.AreaPick(pos1[0], pos1[1], pos2[0], pos2[1], ren) 3005 planes = area_picker.GetFrustum() 3006 3007 fru = vtki.new("FrustumSource") 3008 fru.SetPlanes(planes) 3009 fru.ShowLinesOff() 3010 fru.Update() 3011 3012 afru = vedo.Mesh(fru.GetOutput()) 3013 afru.alpha(0.1).lw(1).pickable(False) 3014 afru.name = "Frustum" 3015 return afru 3016 3017 def _scan_input_return_acts(self, objs) -> Any: 3018 # scan the input and return a list of actors 3019 if not utils.is_sequence(objs): 3020 objs = [objs] 3021 3022 ################# 3023 wannabe_acts = [] 3024 for a in objs: 3025 3026 try: 3027 wannabe_acts.append(a.actor) 3028 except AttributeError: 3029 wannabe_acts.append(a) # already actor 3030 3031 try: 3032 wannabe_acts.append(a.scalarbar) 3033 except AttributeError: 3034 pass 3035 3036 try: 3037 for sh in a.shadows: 3038 wannabe_acts.append(sh.actor) 3039 except AttributeError: 3040 pass 3041 3042 try: 3043 wannabe_acts.append(a.trail.actor) 3044 if a.trail.shadows: # trails may also have shadows 3045 for sh in a.trail.shadows: 3046 wannabe_acts.append(sh.actor) 3047 except AttributeError: 3048 pass 3049 3050 ################# 3051 scanned_acts = [] 3052 for a in wannabe_acts: # scan content of list 3053 3054 if a is None: 3055 pass 3056 3057 elif isinstance(a, (vtki.vtkActor, vtki.vtkActor2D)): 3058 scanned_acts.append(a) 3059 3060 elif isinstance(a, str): 3061 # assume a 2D comment was given 3062 changed = False # check if one already exists so to just update text 3063 if self.renderer: # might be jupyter 3064 acs = self.renderer.GetActors2D() 3065 acs.InitTraversal() 3066 for i in range(acs.GetNumberOfItems()): 3067 act = acs.GetNextItem() 3068 if isinstance(act, vedo.shapes.Text2D): 3069 aposx, aposy = act.GetPosition() 3070 if aposx < 0.01 and aposy > 0.99: # "top-left" 3071 act.text(a) # update content! no appending nada 3072 changed = True 3073 break 3074 if not changed: 3075 out = vedo.shapes.Text2D(a) # append a new one 3076 scanned_acts.append(out) 3077 # scanned_acts.append(vedo.shapes.Text2D(a)) # naive version 3078 3079 elif isinstance(a, vtki.vtkPolyData): 3080 scanned_acts.append(vedo.Mesh(a).actor) 3081 3082 elif isinstance(a, vtki.vtkImageData): 3083 scanned_acts.append(vedo.Volume(a).actor) 3084 3085 elif isinstance(a, vedo.RectilinearGrid): 3086 scanned_acts.append(a.actor) 3087 3088 elif isinstance(a, vedo.StructuredGrid): 3089 scanned_acts.append(a.actor) 3090 3091 elif isinstance(a, vtki.vtkLight): 3092 scanned_acts.append(a) 3093 3094 elif isinstance(a, vedo.visual.LightKit): 3095 a.lightkit.AddLightsToRenderer(self.renderer) 3096 3097 elif isinstance(a, vtki.get_class("MultiBlockDataSet")): 3098 for i in range(a.GetNumberOfBlocks()): 3099 b = a.GetBlock(i) 3100 if isinstance(b, vtki.vtkPolyData): 3101 scanned_acts.append(vedo.Mesh(b).actor) 3102 elif isinstance(b, vtki.vtkImageData): 3103 scanned_acts.append(vedo.Volume(b).actor) 3104 3105 elif isinstance(a, (vtki.vtkProp, vtki.vtkInteractorObserver)): 3106 scanned_acts.append(a) 3107 3108 elif "trimesh" in str(type(a)): 3109 scanned_acts.append(utils.trimesh2vedo(a)) 3110 3111 elif "meshlab" in str(type(a)): 3112 if "MeshSet" in str(type(a)): 3113 for i in range(a.number_meshes()): 3114 if a.mesh_id_exists(i): 3115 scanned_acts.append(utils.meshlab2vedo(a.mesh(i))) 3116 else: 3117 scanned_acts.append(utils.meshlab2vedo(a)) 3118 3119 elif "dolfin" in str(type(a)): # assume a dolfin.Mesh object 3120 import vedo.dolfin as vdlf 3121 3122 scanned_acts.append(vdlf.IMesh(a).actor) 3123 3124 elif "madcad" in str(type(a)): 3125 scanned_acts.append(utils.madcad2vedo(a).actor) 3126 3127 elif "TetgenIO" in str(type(a)): 3128 scanned_acts.append(vedo.TetMesh(a).shrink(0.9).c("pink7").actor) 3129 3130 elif "matplotlib.figure.Figure" in str(type(a)): 3131 scanned_acts.append(vedo.Image(a).clone2d("top-right", 0.6)) 3132 3133 else: 3134 vedo.logger.error(f"cannot understand input in show(): {type(a)}") 3135 3136 return scanned_acts 3137 3138 def show( 3139 self, 3140 *objects, 3141 at=None, 3142 axes=None, 3143 resetcam=None, 3144 zoom=False, 3145 interactive=None, 3146 viewup="", 3147 azimuth=0.0, 3148 elevation=0.0, 3149 roll=0.0, 3150 camera=None, 3151 mode=None, 3152 rate=None, 3153 bg=None, 3154 bg2=None, 3155 size=None, 3156 title=None, 3157 screenshot="", 3158 ) -> Any: 3159 """ 3160 Render a list of objects. 3161 3162 Arguments: 3163 at : (int) 3164 number of the renderer to plot to, in case of more than one exists 3165 3166 axes : (int) 3167 axis type-1 can be fully customized by passing a dictionary. 3168 Check `addons.Axes()` for the full list of options. 3169 set the type of axes to be shown: 3170 - 0, no axes 3171 - 1, draw three gray grid walls 3172 - 2, show cartesian axes from (0,0,0) 3173 - 3, show positive range of cartesian axes from (0,0,0) 3174 - 4, show a triad at bottom left 3175 - 5, show a cube at bottom left 3176 - 6, mark the corners of the bounding box 3177 - 7, draw a 3D ruler at each side of the cartesian axes 3178 - 8, show the `vtkCubeAxesActor` object 3179 - 9, show the bounding box outLine 3180 - 10, show three circles representing the maximum bounding box 3181 - 11, show a large grid on the x-y plane 3182 - 12, show polar axes 3183 - 13, draw a simple ruler at the bottom of the window 3184 3185 azimuth/elevation/roll : (float) 3186 move camera accordingly the specified value 3187 3188 viewup: str, list 3189 either `['x', 'y', 'z']` or a vector to set vertical direction 3190 3191 resetcam : (bool) 3192 re-adjust camera position to fit objects 3193 3194 camera : (dict, vtkCamera) 3195 camera parameters can further be specified with a dictionary 3196 assigned to the `camera` keyword (E.g. `show(camera={'pos':(1,2,3), 'thickness':1000,})`): 3197 - pos, `(list)`, the position of the camera in world coordinates 3198 - focal_point `(list)`, the focal point of the camera in world coordinates 3199 - viewup `(list)`, the view up direction for the camera 3200 - distance `(float)`, set the focal point to the specified distance from the camera position. 3201 - clipping_range `(float)`, distance of the near and far clipping planes along the direction of projection. 3202 - parallel_scale `(float)`, scaling used for a parallel projection, i.e. the height of the viewport 3203 in world-coordinate distances. The default is 1. 3204 Note that the "scale" parameter works as an "inverse scale", larger numbers produce smaller images. 3205 This method has no effect in perspective projection mode. 3206 3207 - thickness `(float)`, set the distance between clipping planes. This method adjusts the far clipping 3208 plane to be set a distance 'thickness' beyond the near clipping plane. 3209 3210 - view_angle `(float)`, the camera view angle, which is the angular height of the camera view 3211 measured in degrees. The default angle is 30 degrees. 3212 This method has no effect in parallel projection mode. 3213 The formula for setting the angle up for perfect perspective viewing is: 3214 angle = 2*atan((h/2)/d) where h is the height of the RenderWindow 3215 (measured by holding a ruler up to your screen) and d is the distance from your eyes to the screen. 3216 3217 interactive : (bool) 3218 pause and interact with window (True) or continue execution (False) 3219 3220 rate : (float) 3221 maximum rate of `show()` in Hertz 3222 3223 mode : (int, str) 3224 set the type of interaction: 3225 - 0 = TrackballCamera [default] 3226 - 1 = TrackballActor 3227 - 2 = JoystickCamera 3228 - 3 = JoystickActor 3229 - 4 = Flight 3230 - 5 = RubberBand2D 3231 - 6 = RubberBand3D 3232 - 7 = RubberBandZoom 3233 - 8 = Terrain 3234 - 9 = Unicam 3235 - 10 = Image 3236 - Check out `vedo.interaction_modes` for more options. 3237 3238 bg : (str, list) 3239 background color in RGB format, or string name 3240 3241 bg2 : (str, list) 3242 second background color to create a gradient background 3243 3244 size : (str, list) 3245 size of the window, e.g. size="fullscreen", or size=[600,400] 3246 3247 title : (str) 3248 window title text 3249 3250 screenshot : (str) 3251 save a screenshot of the window to file 3252 """ 3253 3254 if vedo.settings.dry_run_mode >= 2: 3255 return self 3256 3257 if self.wx_widget: 3258 return self 3259 3260 if self.renderers: # in case of notebooks 3261 3262 if at is None: 3263 at = self.renderers.index(self.renderer) 3264 3265 else: 3266 3267 if at >= len(self.renderers): 3268 t = f"trying to show(at={at}) but only {len(self.renderers)} renderers exist" 3269 vedo.logger.error(t) 3270 return self 3271 3272 self.renderer = self.renderers[at] 3273 3274 if title is not None: 3275 self.title = title 3276 3277 if size is not None: 3278 self.size = size 3279 if self.size[0] == "f": # full screen 3280 self.size = "fullscreen" 3281 self.window.SetFullScreen(True) 3282 self.window.BordersOn() 3283 else: 3284 self.window.SetSize(int(self.size[0]), int(self.size[1])) 3285 3286 if vedo.settings.default_backend == "vtk": 3287 if str(bg).endswith(".hdr"): 3288 self._add_skybox(bg) 3289 else: 3290 if bg is not None: 3291 self.backgrcol = vedo.get_color(bg) 3292 self.renderer.SetBackground(self.backgrcol) 3293 if bg2 is not None: 3294 self.renderer.GradientBackgroundOn() 3295 self.renderer.SetBackground2(vedo.get_color(bg2)) 3296 3297 if axes is not None: 3298 if isinstance(axes, vedo.Assembly): # user passing show(..., axes=myaxes) 3299 objects = list(objects) 3300 objects.append(axes) # move it into the list of normal things to show 3301 axes = 0 3302 self.axes = axes 3303 3304 if interactive is not None: 3305 self._interactive = interactive 3306 if self.offscreen: 3307 self._interactive = False 3308 3309 # camera stuff 3310 if resetcam is not None: 3311 self.resetcam = resetcam 3312 3313 if camera is not None: 3314 self.resetcam = False 3315 viewup = "" 3316 if isinstance(camera, vtki.vtkCamera): 3317 cameracopy = vtki.vtkCamera() 3318 cameracopy.DeepCopy(camera) 3319 self.camera = cameracopy 3320 else: 3321 self.camera = utils.camera_from_dict(camera) 3322 3323 self.add(objects) 3324 3325 # Backend ############################################################### 3326 if vedo.settings.default_backend in ["k3d"]: 3327 return backends.get_notebook_backend(self.objects) 3328 ######################################################################### 3329 3330 for ia in utils.flatten(objects): 3331 try: 3332 # fix gray color labels and title to white or black 3333 ltc = np.array(ia.scalarbar.GetLabelTextProperty().GetColor()) 3334 if np.linalg.norm(ltc - (0.5, 0.5, 0.5)) / 3 < 0.05: 3335 c = (0.9, 0.9, 0.9) 3336 if np.sum(self.renderer.GetBackground()) > 1.5: 3337 c = (0.1, 0.1, 0.1) 3338 ia.scalarbar.GetLabelTextProperty().SetColor(c) 3339 ia.scalarbar.GetTitleTextProperty().SetColor(c) 3340 except AttributeError: 3341 pass 3342 3343 if self.sharecam: 3344 for r in self.renderers: 3345 r.SetActiveCamera(self.camera) 3346 3347 if self.axes is not None: 3348 if viewup != "2d" or self.axes in [1, 8] or isinstance(self.axes, dict): 3349 bns = self.renderer.ComputeVisiblePropBounds() 3350 addons.add_global_axes(self.axes, bounds=bns) 3351 3352 # Backend ############################################################### 3353 if vedo.settings.default_backend in ["ipyvtk", "trame"]: 3354 return backends.get_notebook_backend() 3355 ######################################################################### 3356 3357 if self.resetcam: 3358 self.renderer.ResetCamera() 3359 3360 if len(self.renderers) > 1: 3361 self.add_renderer_frame() 3362 3363 if vedo.settings.default_backend == "2d" and not zoom: 3364 zoom = "tightest" 3365 3366 if zoom: 3367 if zoom == "tight": 3368 self.reset_camera(tight=0.04) 3369 elif zoom == "tightest": 3370 self.reset_camera(tight=0.0001) 3371 else: 3372 self.camera.Zoom(zoom) 3373 if elevation: 3374 self.camera.Elevation(elevation) 3375 if azimuth: 3376 self.camera.Azimuth(azimuth) 3377 if roll: 3378 self.camera.Roll(roll) 3379 3380 if len(viewup) > 0: 3381 b = self.renderer.ComputeVisiblePropBounds() 3382 cm = np.array([(b[1] + b[0])/2, (b[3] + b[2])/2, (b[5] + b[4])/2]) 3383 sz = np.array([(b[1] - b[0]), (b[3] - b[2]), (b[5] - b[4])]) 3384 if viewup == "x": 3385 sz = np.linalg.norm(sz) 3386 self.camera.SetViewUp([1, 0, 0]) 3387 self.camera.SetPosition(cm + sz) 3388 elif viewup == "y": 3389 sz = np.linalg.norm(sz) 3390 self.camera.SetViewUp([0, 1, 0]) 3391 self.camera.SetPosition(cm + sz) 3392 elif viewup == "z": 3393 sz = np.array([(b[1]-b[0])*0.7, -(b[3]-b[2])*1.0, (b[5]-b[4])*1.2]) 3394 self.camera.SetViewUp([0, 0, 1]) 3395 self.camera.SetPosition(cm + 2 * sz) 3396 elif utils.is_sequence(viewup): 3397 sz = np.linalg.norm(sz) 3398 self.camera.SetViewUp(viewup) 3399 cpos = np.cross([0, 1, 0], viewup) 3400 self.camera.SetPosition(cm - 2 * sz * cpos) 3401 3402 self.renderer.ResetCameraClippingRange() 3403 3404 self.initialize_interactor() 3405 3406 if vedo.settings.immediate_rendering: 3407 self.window.Render() ##################### <-------------- Render 3408 3409 if self.interactor: # can be offscreen or not the vtk backend.. 3410 3411 self.window.SetWindowName(self.title) 3412 3413 # pic = vedo.Image(vedo.dataurl+'images/vtk_logo.png') 3414 # pic = vedo.Image('/home/musy/Downloads/icons8-3d-96.png') 3415 # print(pic.dataset)# Array 0 name PNGImage 3416 # self.window.SetIcon(pic.dataset) 3417 3418 try: 3419 # Needs "pip install pyobjc" on Mac OSX 3420 if ( 3421 self._cocoa_initialized is False 3422 and "Darwin" in vedo.sys_platform 3423 and not self.offscreen 3424 ): 3425 self._cocoa_initialized = True 3426 from Cocoa import NSRunningApplication, NSApplicationActivateIgnoringOtherApps # type: ignore 3427 pid = os.getpid() 3428 x = NSRunningApplication.runningApplicationWithProcessIdentifier_(int(pid)) 3429 x.activateWithOptions_(NSApplicationActivateIgnoringOtherApps) 3430 except: 3431 # vedo.logger.debug("On Mac OSX try: pip install pyobjc") 3432 pass 3433 3434 # Set the interaction style 3435 if mode is not None: 3436 self.user_mode(mode) 3437 if self.qt_widget and mode is None: 3438 self.user_mode(0) 3439 3440 if screenshot: 3441 self.screenshot(screenshot) 3442 3443 if self._interactive: 3444 self.interactor.Start() 3445 if self._must_close_now: 3446 self.interactor.GetRenderWindow().Finalize() 3447 self.interactor.TerminateApp() 3448 self.camera = None 3449 self.renderer = None 3450 self.renderers = [] 3451 self.window = None 3452 self.interactor = None 3453 return self 3454 3455 if rate: 3456 if self.clock is None: # set clock and limit rate 3457 self._clockt0 = time.time() 3458 self.clock = 0.0 3459 else: 3460 t = time.time() - self._clockt0 3461 elapsed = t - self.clock 3462 mint = 1.0 / rate 3463 if elapsed < mint: 3464 time.sleep(mint - elapsed) 3465 self.clock = time.time() - self._clockt0 3466 3467 # 2d #################################################################### 3468 if vedo.settings.default_backend == "2d": 3469 return backends.get_notebook_backend() 3470 ######################################################################### 3471 3472 return self 3473 3474 3475 def add_inset(self, *objects, **options) -> Union[vtki.vtkOrientationMarkerWidget, None]: 3476 """Add a draggable inset space into a renderer. 3477 3478 Arguments: 3479 at : (int) 3480 specify the renderer number 3481 pos : (list) 3482 icon position in the range [1-4] indicating one of the 4 corners, 3483 or it can be a tuple (x,y) as a fraction of the renderer size. 3484 size : (float) 3485 size of the square inset 3486 draggable : (bool) 3487 if True the subrenderer space can be dragged around 3488 c : (color) 3489 color of the inset frame when dragged 3490 3491 Examples: 3492 - [inset.py](https://github.com/marcomusy/vedo/tree/master/examples/other/inset.py) 3493 3494 ![](https://user-images.githubusercontent.com/32848391/56758560-3c3f1300-6797-11e9-9b33-49f5a4876039.jpg) 3495 """ 3496 if not self.interactor: 3497 return None 3498 3499 if not self.renderer: 3500 vedo.logger.warning("call add_inset() only after first rendering of the scene.") 3501 return None 3502 3503 options = dict(options) 3504 pos = options.pop("pos", 0) 3505 size = options.pop("size", 0.1) 3506 c = options.pop("c", "lb") 3507 at = options.pop("at", None) 3508 draggable = options.pop("draggable", True) 3509 3510 r, g, b = vedo.get_color(c) 3511 widget = vtki.vtkOrientationMarkerWidget() 3512 widget.SetOutlineColor(r, g, b) 3513 if len(objects) == 1: 3514 widget.SetOrientationMarker(objects[0].actor) 3515 else: 3516 widget.SetOrientationMarker(vedo.Assembly(objects)) 3517 3518 widget.SetInteractor(self.interactor) 3519 3520 if utils.is_sequence(pos): 3521 widget.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size) 3522 else: 3523 if pos < 2: 3524 widget.SetViewport(0, 1 - 2 * size, size * 2, 1) 3525 elif pos == 2: 3526 widget.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1) 3527 elif pos == 3: 3528 widget.SetViewport(0, 0, size * 2, size * 2) 3529 elif pos == 4: 3530 widget.SetViewport(1 - 2 * size, 0, 1, size * 2) 3531 widget.EnabledOn() 3532 widget.SetInteractive(draggable) 3533 if at is not None and at < len(self.renderers): 3534 widget.SetCurrentRenderer(self.renderers[at]) 3535 else: 3536 widget.SetCurrentRenderer(self.renderer) 3537 self.widgets.append(widget) 3538 return widget 3539 3540 def clear(self, at=None, deep=False) -> Self: 3541 """Clear the scene from all meshes and volumes.""" 3542 if at is not None: 3543 renderer = self.renderers[at] 3544 else: 3545 renderer = self.renderer 3546 if not renderer: 3547 return self 3548 3549 if deep: 3550 renderer.RemoveAllViewProps() 3551 else: 3552 for ob in set( 3553 self.get_meshes() 3554 + self.get_volumes() 3555 + self.objects 3556 + self.axes_instances 3557 ): 3558 if isinstance(ob, vedo.shapes.Text2D): 3559 continue 3560 self.remove(ob) 3561 try: 3562 if ob.scalarbar: 3563 self.remove(ob.scalarbar) 3564 except AttributeError: 3565 pass 3566 return self 3567 3568 def break_interaction(self) -> Self: 3569 """Break window interaction and return to the python execution flow""" 3570 if self.interactor: 3571 self.check_actors_trasform() 3572 self.interactor.ExitCallback() 3573 return self 3574 3575 def freeze(self, value=True) -> Self: 3576 """Freeze the current renderer. Use this with `sharecam=False`.""" 3577 if not self.interactor: 3578 return self 3579 if not self.renderer: 3580 return self 3581 self.renderer.SetInteractive(not value) 3582 return self 3583 3584 def user_mode(self, mode) -> Self: 3585 """ 3586 Modify the user interaction mode. 3587 3588 Examples: 3589 ```python 3590 from vedo import * 3591 mode = interactor_modes.MousePan() 3592 mesh = Mesh(dataurl+"cow.vtk") 3593 plt = Plotter().user_mode(mode) 3594 plt.show(mesh, axes=1) 3595 ``` 3596 See also: 3597 [VTK interactor styles](https://vtk.org/doc/nightly/html/classvtkInteractorStyle.html) 3598 """ 3599 if not self.interactor: 3600 return self 3601 3602 curr_style = self.interactor.GetInteractorStyle().GetClassName() 3603 # print("Current style:", curr_style) 3604 if curr_style.endswith("Actor"): 3605 self.check_actors_trasform() 3606 3607 if isinstance(mode, (str, int)): 3608 # Set the style of interaction 3609 # see https://vtk.org/doc/nightly/html/classvtkInteractorStyle.html 3610 if mode in (0, "TrackballCamera"): 3611 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTrackballCamera")) 3612 self.interactor.RemoveObservers("CharEvent") 3613 elif mode in (1, "TrackballActor"): 3614 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTrackballActor")) 3615 elif mode in (2, "JoystickCamera"): 3616 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleJoystickCamera")) 3617 elif mode in (3, "JoystickActor"): 3618 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleJoystickActor")) 3619 elif mode in (4, "Flight"): 3620 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleFlight")) 3621 elif mode in (5, "RubberBand2D"): 3622 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBand2D")) 3623 elif mode in (6, "RubberBand3D"): 3624 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBand3D")) 3625 elif mode in (7, "RubberBandZoom"): 3626 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBandZoom")) 3627 elif mode in (8, "Terrain"): 3628 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTerrain")) 3629 elif mode in (9, "Unicam"): 3630 self.interactor.SetInteractorStyle(vtki.new("InteractorStyleUnicam")) 3631 elif mode in (10, "Image", "image", "2d"): 3632 astyle = vtki.new("InteractorStyleImage") 3633 astyle.SetInteractionModeToImage3D() 3634 self.interactor.SetInteractorStyle(astyle) 3635 else: 3636 vedo.logger.warning(f"Unknown interaction mode: {mode}") 3637 3638 elif isinstance(mode, vtki.vtkInteractorStyleUser): 3639 # set a custom interactor style 3640 if hasattr(mode, "interactor"): 3641 mode.interactor = self.interactor 3642 mode.renderer = self.renderer # type: ignore 3643 mode.SetInteractor(self.interactor) 3644 mode.SetDefaultRenderer(self.renderer) 3645 self.interactor.SetInteractorStyle(mode) 3646 3647 return self 3648 3649 def close(self) -> Self: 3650 """Close the plotter.""" 3651 # https://examples.vtk.org/site/Cxx/Visualization/CloseWindow/ 3652 vedo.last_figure = None 3653 self.last_event = None 3654 self.sliders = [] 3655 self.buttons = [] 3656 self.widgets = [] 3657 self.hover_legends = [] 3658 self.background_renderer = None 3659 self._extralight = None 3660 3661 self.hint_widget = None 3662 self.cutter_widget = None 3663 3664 if vedo.settings.dry_run_mode >= 2: 3665 return self 3666 3667 if not hasattr(self, "window"): 3668 return self 3669 if not self.window: 3670 return self 3671 if not hasattr(self, "interactor"): 3672 return self 3673 if not self.interactor: 3674 return self 3675 3676 ################################################### 3677 try: 3678 if "Darwin" in vedo.sys_platform: 3679 self.interactor.ProcessEvents() 3680 except: 3681 pass 3682 3683 self._must_close_now = True 3684 3685 if vedo.plotter_instance == self: 3686 vedo.plotter_instance = None 3687 3688 if self.interactor and self._interactive: 3689 self.break_interaction() 3690 elif self._must_close_now: 3691 # dont call ExitCallback here 3692 if self.interactor: 3693 self.break_interaction() 3694 self.interactor.GetRenderWindow().Finalize() 3695 self.interactor.TerminateApp() 3696 self.camera = None 3697 self.renderer = None 3698 self.renderers = [] 3699 self.window = None 3700 self.interactor = None 3701 return self 3702 3703 @property 3704 def camera(self): 3705 """Return the current active camera.""" 3706 if self.renderer: 3707 return self.renderer.GetActiveCamera() 3708 3709 @camera.setter 3710 def camera(self, cam): 3711 if self.renderer: 3712 if isinstance(cam, dict): 3713 cam = utils.camera_from_dict(cam) 3714 self.renderer.SetActiveCamera(cam) 3715 3716 def screenshot(self, filename="screenshot.png", scale=1, asarray=False) -> Any: 3717 """ 3718 Take a screenshot of the Plotter window. 3719 3720 Arguments: 3721 scale : (int) 3722 set image magnification as an integer multiplicating factor 3723 asarray : (bool) 3724 return a numpy array of the image instead of writing a file 3725 3726 Warning: 3727 If you get black screenshots try to set `interactive=False` in `show()` 3728 then call `screenshot()` and `plt.interactive()` afterwards. 3729 3730 Example: 3731 ```py 3732 from vedo import * 3733 sphere = Sphere().linewidth(1) 3734 plt = show(sphere, interactive=False) 3735 plt.screenshot('image.png') 3736 plt.interactive() 3737 plt.close() 3738 ``` 3739 3740 Example: 3741 ```py 3742 from vedo import * 3743 sphere = Sphere().linewidth(1) 3744 plt = show(sphere, interactive=False) 3745 plt.screenshot('anotherimage.png') 3746 plt.interactive() 3747 plt.close() 3748 ``` 3749 """ 3750 return vedo.file_io.screenshot(filename, scale, asarray) 3751 3752 def toimage(self, scale=1) -> "vedo.image.Image": 3753 """ 3754 Generate a `Image` object from the current rendering window. 3755 3756 Arguments: 3757 scale : (int) 3758 set image magnification as an integer multiplicating factor 3759 """ 3760 if vedo.settings.screeshot_large_image: 3761 w2if = vtki.new("RenderLargeImage") 3762 w2if.SetInput(self.renderer) 3763 w2if.SetMagnification(scale) 3764 else: 3765 w2if = vtki.new("WindowToImageFilter") 3766 w2if.SetInput(self.window) 3767 if hasattr(w2if, "SetScale"): 3768 w2if.SetScale(scale, scale) 3769 if vedo.settings.screenshot_transparent_background: 3770 w2if.SetInputBufferTypeToRGBA() 3771 w2if.ReadFrontBufferOff() # read from the back buffer 3772 w2if.Update() 3773 return vedo.image.Image(w2if.GetOutput()) 3774 3775 def export(self, filename="scene.npz", binary=False) -> Self: 3776 """ 3777 Export scene to file to HTML, X3D or Numpy file. 3778 3779 Examples: 3780 - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py) 3781 - [export_numpy.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_numpy.py) 3782 """ 3783 vedo.file_io.export_window(filename, binary=binary) 3784 return self 3785 3786 def color_picker(self, xy, verbose=False): 3787 """Pick color of specific (x,y) pixel on the screen.""" 3788 w2if = vtki.new("WindowToImageFilter") 3789 w2if.SetInput(self.window) 3790 w2if.ReadFrontBufferOff() 3791 w2if.Update() 3792 nx, ny = self.window.GetSize() 3793 varr = w2if.GetOutput().GetPointData().GetScalars() 3794 3795 arr = utils.vtk2numpy(varr).reshape(ny, nx, 3) 3796 x, y = int(xy[0]), int(xy[1]) 3797 if y < ny and x < nx: 3798 3799 rgb = arr[y, x] 3800 3801 if verbose: 3802 vedo.printc(":rainbow:Pixel", [x, y], "has RGB[", end="") 3803 vedo.printc("â–ˆ", c=[rgb[0], 0, 0], end="") 3804 vedo.printc("â–ˆ", c=[0, rgb[1], 0], end="") 3805 vedo.printc("â–ˆ", c=[0, 0, rgb[2]], end="") 3806 vedo.printc("] = ", end="") 3807 cnm = vedo.get_color_name(rgb) 3808 if np.sum(rgb) < 150: 3809 vedo.printc( 3810 rgb.tolist(), 3811 vedo.colors.rgb2hex(np.array(rgb) / 255), 3812 c="w", 3813 bc=rgb, 3814 invert=1, 3815 end="", 3816 ) 3817 vedo.printc(" -> " + cnm, invert=1, c="w") 3818 else: 3819 vedo.printc( 3820 rgb.tolist(), 3821 vedo.colors.rgb2hex(np.array(rgb) / 255), 3822 c=rgb, 3823 end="", 3824 ) 3825 vedo.printc(" -> " + cnm, c=cnm) 3826 3827 return rgb 3828 3829 return None 3830 3831 ####################################################################### 3832 def _default_mouseleftclick(self, iren, event) -> None: 3833 x, y = iren.GetEventPosition() 3834 renderer = iren.FindPokedRenderer(x, y) 3835 picker = vtki.vtkPropPicker() 3836 picker.PickProp(x, y, renderer) 3837 3838 self.renderer = renderer 3839 3840 clicked_actor = picker.GetActor() 3841 # clicked_actor2D = picker.GetActor2D() 3842 3843 # print('_default_mouseleftclick mouse at', x, y) 3844 # print("picked Volume:", [picker.GetVolume()]) 3845 # print("picked Actor2D:", [picker.GetActor2D()]) 3846 # print("picked Assembly:", [picker.GetAssembly()]) 3847 # print("picked Prop3D:", [picker.GetProp3D()]) 3848 3849 if not clicked_actor: 3850 clicked_actor = picker.GetAssembly() 3851 3852 if not clicked_actor: 3853 clicked_actor = picker.GetProp3D() 3854 3855 if not hasattr(clicked_actor, "GetPickable") or not clicked_actor.GetPickable(): 3856 return 3857 3858 self.picked3d = picker.GetPickPosition() 3859 self.picked2d = np.array([x, y]) 3860 3861 if not clicked_actor: 3862 return 3863 3864 self.justremoved = None 3865 self.clicked_actor = clicked_actor 3866 3867 try: # might not be a vedo obj 3868 self.clicked_object = clicked_actor.retrieve_object() 3869 # save this info in the object itself 3870 self.clicked_object.picked3d = self.picked3d 3871 self.clicked_object.picked2d = self.picked2d 3872 except AttributeError: 3873 pass 3874 3875 # ----------- 3876 # if "Histogram1D" in picker.GetAssembly().__class__.__name__: 3877 # histo = picker.GetAssembly() 3878 # if histo.verbose: 3879 # x = self.picked3d[0] 3880 # idx = np.digitize(x, histo.edges) - 1 3881 # f = histo.frequencies[idx] 3882 # cn = histo.centers[idx] 3883 # vedo.colors.printc(f"{histo.name}, bin={idx}, center={cn}, value={f}") 3884 3885 ####################################################################### 3886 def _default_keypress(self, iren, event) -> None: 3887 # NB: qt creates and passes a vtkGenericRenderWindowInteractor 3888 3889 key = iren.GetKeySym() 3890 3891 if "_L" in key or "_R" in key: 3892 return 3893 3894 if iren.GetShiftKey(): 3895 key = key.upper() 3896 3897 if iren.GetControlKey(): 3898 key = "Ctrl+" + key 3899 3900 if iren.GetAltKey(): 3901 key = "Alt+" + key 3902 3903 ####################################################### 3904 # utils.vedo.printc('Pressed key:', key, c='y', box='-') 3905 # print(key, iren.GetShiftKey(), iren.GetAltKey(), iren.GetControlKey(), 3906 # iren.GetKeyCode(), iren.GetRepeatCount()) 3907 ####################################################### 3908 3909 x, y = iren.GetEventPosition() 3910 renderer = iren.FindPokedRenderer(x, y) 3911 3912 if key in ["q", "Return"]: 3913 self.break_interaction() 3914 return 3915 3916 elif key in ["Ctrl+q", "Ctrl+w", "Escape"]: 3917 self.close() 3918 return 3919 3920 elif key == "F1": 3921 vedo.logger.info("Execution aborted. Exiting python kernel now.") 3922 self.break_interaction() 3923 sys.exit(0) 3924 3925 elif key == "Down": 3926 if self.clicked_object and self.clicked_object in self.get_meshes(): 3927 self.clicked_object.alpha(0.02) 3928 if hasattr(self.clicked_object, "properties_backface"): 3929 bfp = self.clicked_actor.GetBackfaceProperty() 3930 self.clicked_object.properties_backface = bfp # save it 3931 self.clicked_actor.SetBackfaceProperty(None) 3932 else: 3933 for obj in self.get_meshes(): 3934 if obj: 3935 obj.alpha(0.02) 3936 bfp = obj.actor.GetBackfaceProperty() 3937 if bfp and hasattr(obj, "properties_backface"): 3938 obj.properties_backface = bfp 3939 obj.actor.SetBackfaceProperty(None) 3940 3941 elif key == "Left": 3942 if self.clicked_object and self.clicked_object in self.get_meshes(): 3943 ap = self.clicked_object.properties 3944 aal = max([ap.GetOpacity() * 0.75, 0.01]) 3945 ap.SetOpacity(aal) 3946 bfp = self.clicked_actor.GetBackfaceProperty() 3947 if bfp and hasattr(self.clicked_object, "properties_backface"): 3948 self.clicked_object.properties_backface = bfp 3949 self.clicked_actor.SetBackfaceProperty(None) 3950 else: 3951 for a in self.get_meshes(): 3952 if a: 3953 ap = a.properties 3954 aal = max([ap.GetOpacity() * 0.75, 0.01]) 3955 ap.SetOpacity(aal) 3956 bfp = a.actor.GetBackfaceProperty() 3957 if bfp and hasattr(a, "properties_backface"): 3958 a.properties_backface = bfp 3959 a.actor.SetBackfaceProperty(None) 3960 3961 elif key == "Right": 3962 if self.clicked_object and self.clicked_object in self.get_meshes(): 3963 ap = self.clicked_object.properties 3964 aal = min([ap.GetOpacity() * 1.25, 1.0]) 3965 ap.SetOpacity(aal) 3966 if ( 3967 aal == 1 3968 and hasattr(self.clicked_object, "properties_backface") 3969 and self.clicked_object.properties_backface 3970 ): 3971 # put back 3972 self.clicked_actor.SetBackfaceProperty( 3973 self.clicked_object.properties_backface) 3974 else: 3975 for a in self.get_meshes(): 3976 if a: 3977 ap = a.properties 3978 aal = min([ap.GetOpacity() * 1.25, 1.0]) 3979 ap.SetOpacity(aal) 3980 if aal == 1 and hasattr(a, "properties_backface") and a.properties_backface: 3981 a.actor.SetBackfaceProperty(a.properties_backface) 3982 3983 elif key == "Up": 3984 if self.clicked_object and self.clicked_object in self.get_meshes(): 3985 self.clicked_object.properties.SetOpacity(1) 3986 if hasattr(self.clicked_object, "properties_backface") and self.clicked_object.properties_backface: 3987 self.clicked_object.actor.SetBackfaceProperty(self.clicked_object.properties_backface) 3988 else: 3989 for a in self.get_meshes(): 3990 if a: 3991 a.properties.SetOpacity(1) 3992 if hasattr(a, "properties_backface") and a.properties_backface: 3993 a.actor.SetBackfaceProperty(a.properties_backface) 3994 3995 elif key == "P": 3996 if self.clicked_object and self.clicked_object in self.get_meshes(): 3997 objs = [self.clicked_object] 3998 else: 3999 objs = self.get_meshes() 4000 for ia in objs: 4001 try: 4002 ps = ia.properties.GetPointSize() 4003 if ps > 1: 4004 ia.properties.SetPointSize(ps - 1) 4005 ia.properties.SetRepresentationToPoints() 4006 except AttributeError: 4007 pass 4008 4009 elif key == "p": 4010 if self.clicked_object and self.clicked_object in self.get_meshes(): 4011 objs = [self.clicked_object] 4012 else: 4013 objs = self.get_meshes() 4014 for ia in objs: 4015 try: 4016 ps = ia.properties.GetPointSize() 4017 ia.properties.SetPointSize(ps + 2) 4018 ia.properties.SetRepresentationToPoints() 4019 except AttributeError: 4020 pass 4021 4022 elif key == "U": 4023 pval = renderer.GetActiveCamera().GetParallelProjection() 4024 renderer.GetActiveCamera().SetParallelProjection(not pval) 4025 if pval: 4026 renderer.ResetCamera() 4027 4028 elif key == "r": 4029 renderer.ResetCamera() 4030 4031 elif key == "h": 4032 msg = f" vedo {vedo.__version__}" 4033 msg += f" | vtk {vtki.vtkVersion().GetVTKVersion()}" 4034 msg += f" | numpy {np.__version__}" 4035 msg += f" | python {sys.version_info[0]}.{sys.version_info[1]}, press: " 4036 vedo.printc(msg.ljust(75), invert=True) 4037 msg = ( 4038 " i print info about the last clicked object \n" 4039 " I print color of the pixel under the mouse \n" 4040 " Y show the pipeline for this object as a graph \n" 4041 " <- -> use arrows to reduce/increase opacity \n" 4042 " x toggle mesh visibility \n" 4043 " w toggle wireframe/surface style \n" 4044 " l toggle surface edges visibility \n" 4045 " p/P hide surface faces and show only points \n" 4046 " 1-3 cycle surface color (2=light, 3=dark) \n" 4047 " 4 cycle color map (press shift-4 to go back) \n" 4048 " 5-6 cycle point-cell arrays (shift to go back) \n" 4049 " 7-8 cycle background and gradient color \n" 4050 " 09+- cycle axes styles (on keypad, or press +/-) \n" 4051 " k cycle available lighting styles \n" 4052 " K toggle shading as flat or phong \n" 4053 " A toggle anti-aliasing \n" 4054 " D toggle depth-peeling (for transparencies) \n" 4055 " U toggle perspective/parallel projection \n" 4056 " o/O toggle extra light to scene and rotate it \n" 4057 " a toggle interaction to Actor Mode \n" 4058 " n toggle surface normals \n" 4059 " r reset camera position \n" 4060 " R reset camera to the closest orthogonal view \n" 4061 " . fly camera to the last clicked point \n" 4062 " C print the current camera parameters state \n" 4063 " X invoke a cutter widget tool \n" 4064 " S save a screenshot of the current scene \n" 4065 " E/F export 3D scene to numpy file or X3D \n" 4066 " q return control to python script \n" 4067 " Esc abort execution and exit python kernel " 4068 ) 4069 vedo.printc(msg, dim=True, italic=True, bold=True) 4070 vedo.printc( 4071 " Check out the documentation at: https://vedo.embl.es ".ljust(75), 4072 invert=True, 4073 bold=True, 4074 ) 4075 return 4076 4077 elif key == "a": 4078 cur = iren.GetInteractorStyle() 4079 if isinstance(cur, vtki.get_class("InteractorStyleTrackballCamera")): 4080 msg = "Interactor style changed to TrackballActor\n" 4081 msg += " you can now move and rotate individual meshes:\n" 4082 msg += " press X twice to save the repositioned mesh\n" 4083 msg += " press 'a' to go back to normal style" 4084 vedo.printc(msg) 4085 iren.SetInteractorStyle(vtki.new("InteractorStyleTrackballActor")) 4086 else: 4087 iren.SetInteractorStyle(vtki.new("InteractorStyleTrackballCamera")) 4088 return 4089 4090 elif key == "A": # toggle antialiasing 4091 msam = self.window.GetMultiSamples() 4092 if not msam: 4093 self.window.SetMultiSamples(16) 4094 else: 4095 self.window.SetMultiSamples(0) 4096 msam = self.window.GetMultiSamples() 4097 if msam: 4098 vedo.printc(f"Antialiasing set to {msam} samples", c=bool(msam)) 4099 else: 4100 vedo.printc("Antialiasing disabled", c=bool(msam)) 4101 4102 elif key == "D": # toggle depthpeeling 4103 udp = not renderer.GetUseDepthPeeling() 4104 renderer.SetUseDepthPeeling(udp) 4105 # self.renderer.SetUseDepthPeelingForVolumes(udp) 4106 if udp: 4107 self.window.SetAlphaBitPlanes(1) 4108 renderer.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels) 4109 renderer.SetOcclusionRatio(vedo.settings.occlusion_ratio) 4110 self.interactor.Render() 4111 wasUsed = renderer.GetLastRenderingUsedDepthPeeling() 4112 rnr = self.renderers.index(renderer) 4113 vedo.printc(f"Depth peeling set to {udp} for renderer nr.{rnr}", c=udp) 4114 if not wasUsed and udp: 4115 vedo.printc("\t...but last rendering did not actually used it!", c=udp, invert=True) 4116 return 4117 4118 elif key == "period": 4119 if self.picked3d: 4120 self.fly_to(self.picked3d) 4121 return 4122 4123 elif key == "S": 4124 fname = "screenshot.png" 4125 i = 1 4126 while os.path.isfile(fname): 4127 fname = f"screenshot{i}.png" 4128 i += 1 4129 vedo.file_io.screenshot(fname) 4130 vedo.printc(rf":camera: Saved rendering window to {fname}", c="b") 4131 return 4132 4133 elif key == "C": 4134 # Precision needs to be 7 (or even larger) to guarantee a consistent camera when 4135 # the model coordinates are not centered at (0, 0, 0) and the mode is large. 4136 # This could happen for plotting geological models with UTM coordinate systems 4137 cam = renderer.GetActiveCamera() 4138 vedo.printc("\n###################################################", c="y") 4139 vedo.printc("## Template python code to position this camera: ##", c="y") 4140 vedo.printc("cam = dict(", c="y") 4141 vedo.printc(" pos=" + utils.precision(cam.GetPosition(), 6) + ",", c="y") 4142 vedo.printc(" focal_point=" + utils.precision(cam.GetFocalPoint(), 6) + ",", c="y") 4143 vedo.printc(" viewup=" + utils.precision(cam.GetViewUp(), 6) + ",", c="y") 4144 vedo.printc(" roll=" + utils.precision(cam.GetRoll(), 6) + ",", c="y") 4145 if cam.GetParallelProjection(): 4146 vedo.printc(' parallel_scale='+utils.precision(cam.GetParallelScale(),6)+',', c='y') 4147 else: 4148 vedo.printc(' distance=' +utils.precision(cam.GetDistance(),6)+',', c='y') 4149 vedo.printc(' clipping_range='+utils.precision(cam.GetClippingRange(),6)+',', c='y') 4150 vedo.printc(')', c='y') 4151 vedo.printc('show(mymeshes, camera=cam)', c='y') 4152 vedo.printc('###################################################', c='y') 4153 return 4154 4155 elif key == "R": 4156 self.reset_viewup() 4157 4158 elif key == "w": 4159 try: 4160 if self.clicked_object.properties.GetRepresentation() == 1: # toggle 4161 self.clicked_object.properties.SetRepresentationToSurface() 4162 else: 4163 self.clicked_object.properties.SetRepresentationToWireframe() 4164 except AttributeError: 4165 pass 4166 4167 elif key == "1": 4168 try: 4169 self._icol += 1 4170 self.clicked_object.mapper.ScalarVisibilityOff() 4171 pal = vedo.colors.palettes[vedo.settings.palette % len(vedo.colors.palettes)] 4172 self.clicked_object.c(pal[(self._icol) % 10]) 4173 self.remove(self.clicked_object.scalarbar) 4174 except AttributeError: 4175 pass 4176 4177 elif key == "2": # dark colors 4178 try: 4179 bsc = ["k1", "k2", "k3", "k4", 4180 "b1", "b2", "b3", "b4", 4181 "p1", "p2", "p3", "p4", 4182 "g1", "g2", "g3", "g4", 4183 "r1", "r2", "r3", "r4", 4184 "o1", "o2", "o3", "o4", 4185 "y1", "y2", "y3", "y4"] 4186 self._icol += 1 4187 if self.clicked_object: 4188 self.clicked_object.mapper.ScalarVisibilityOff() 4189 newcol = vedo.get_color(bsc[(self._icol) % len(bsc)]) 4190 self.clicked_object.c(newcol) 4191 self.remove(self.clicked_object.scalarbar) 4192 except AttributeError: 4193 pass 4194 4195 elif key == "3": # light colors 4196 try: 4197 bsc = ["k6", "k7", "k8", "k9", 4198 "b6", "b7", "b8", "b9", 4199 "p6", "p7", "p8", "p9", 4200 "g6", "g7", "g8", "g9", 4201 "r6", "r7", "r8", "r9", 4202 "o6", "o7", "o8", "o9", 4203 "y6", "y7", "y8", "y9"] 4204 self._icol += 1 4205 if self.clicked_object: 4206 self.clicked_object.mapper.ScalarVisibilityOff() 4207 newcol = vedo.get_color(bsc[(self._icol) % len(bsc)]) 4208 self.clicked_object.c(newcol) 4209 self.remove(self.clicked_object.scalarbar) 4210 except AttributeError: 4211 pass 4212 4213 elif key == "4": # cmap name cycle 4214 ob = self.clicked_object 4215 if not isinstance(ob, (vedo.Points, vedo.UnstructuredGrid)): 4216 return 4217 if not ob.mapper.GetScalarVisibility(): 4218 return 4219 onwhat = ob.mapper.GetScalarModeAsString() # UsePointData/UseCellData 4220 4221 cmap_names = [ 4222 "Accent", "Paired", 4223 "rainbow", "rainbow_r", 4224 "Spectral", "Spectral_r", 4225 "gist_ncar", "gist_ncar_r", 4226 "viridis", "viridis_r", 4227 "hot", "hot_r", 4228 "terrain", "ocean", 4229 "coolwarm", "seismic", "PuOr", "RdYlGn", 4230 ] 4231 try: 4232 i = cmap_names.index(ob._cmap_name) 4233 if iren.GetShiftKey(): 4234 i -= 1 4235 else: 4236 i += 1 4237 if i >= len(cmap_names): 4238 i = 0 4239 if i < 0: 4240 i = len(cmap_names) - 1 4241 except ValueError: 4242 i = 0 4243 4244 ob._cmap_name = cmap_names[i] 4245 ob.cmap(ob._cmap_name, on=onwhat) 4246 if ob.scalarbar: 4247 if isinstance(ob.scalarbar, vtki.vtkActor2D): 4248 self.remove(ob.scalarbar) 4249 title = ob.scalarbar.GetTitle() 4250 ob.add_scalarbar(title=title) 4251 self.add(ob.scalarbar).render() 4252 elif isinstance(ob.scalarbar, vedo.Assembly): 4253 self.remove(ob.scalarbar) 4254 ob.add_scalarbar3d(title=ob._cmap_name) 4255 self.add(ob.scalarbar) 4256 4257 vedo.printc( 4258 f"Name:'{ob.name}'," if ob.name else "", 4259 f"range:{utils.precision(ob.mapper.GetScalarRange(),3)},", 4260 f"colormap:'{ob._cmap_name}'", c="g", bold=False, 4261 ) 4262 4263 elif key == "5": # cycle pointdata array 4264 ob = self.clicked_object 4265 if not isinstance(ob, (vedo.Points, vedo.UnstructuredGrid)): 4266 return 4267 4268 arrnames = ob.pointdata.keys() 4269 arrnames = [a for a in arrnames if "normal" not in a.lower()] 4270 arrnames = [a for a in arrnames if "tcoord" not in a.lower()] 4271 arrnames = [a for a in arrnames if "textur" not in a.lower()] 4272 if len(arrnames) == 0: 4273 return 4274 ob.mapper.SetScalarVisibility(1) 4275 4276 if not ob._cmap_name: 4277 ob._cmap_name = "rainbow" 4278 4279 try: 4280 curr_name = ob.dataset.GetPointData().GetScalars().GetName() 4281 i = arrnames.index(curr_name) 4282 if "normals" in curr_name.lower(): 4283 return 4284 if iren.GetShiftKey(): 4285 i -= 1 4286 else: 4287 i += 1 4288 if i >= len(arrnames): 4289 i = 0 4290 if i < 0: 4291 i = len(arrnames) - 1 4292 except (ValueError, AttributeError): 4293 i = 0 4294 4295 ob.cmap(ob._cmap_name, arrnames[i], on="points") 4296 if ob.scalarbar: 4297 if isinstance(ob.scalarbar, vtki.vtkActor2D): 4298 self.remove(ob.scalarbar) 4299 title = ob.scalarbar.GetTitle() 4300 ob.scalarbar = None 4301 ob.add_scalarbar(title=arrnames[i]) 4302 self.add(ob.scalarbar) 4303 elif isinstance(ob.scalarbar, vedo.Assembly): 4304 self.remove(ob.scalarbar) 4305 ob.scalarbar = None 4306 ob.add_scalarbar3d(title=arrnames[i]) 4307 self.add(ob.scalarbar) 4308 else: 4309 vedo.printc( 4310 f"Name:'{ob.name}'," if ob.name else "", 4311 f"active pointdata array: '{arrnames[i]}'", 4312 c="g", bold=False, 4313 ) 4314 4315 elif key == "6": # cycle celldata array 4316 ob = self.clicked_object 4317 if not isinstance(ob, (vedo.Points, vedo.UnstructuredGrid)): 4318 return 4319 4320 arrnames = ob.celldata.keys() 4321 arrnames = [a for a in arrnames if "normal" not in a.lower()] 4322 arrnames = [a for a in arrnames if "tcoord" not in a.lower()] 4323 arrnames = [a for a in arrnames if "textur" not in a.lower()] 4324 if len(arrnames) == 0: 4325 return 4326 ob.mapper.SetScalarVisibility(1) 4327 4328 if not ob._cmap_name: 4329 ob._cmap_name = "rainbow" 4330 4331 try: 4332 curr_name = ob.dataset.GetCellData().GetScalars().GetName() 4333 i = arrnames.index(curr_name) 4334 if "normals" in curr_name.lower(): 4335 return 4336 if iren.GetShiftKey(): 4337 i -= 1 4338 else: 4339 i += 1 4340 if i >= len(arrnames): 4341 i = 0 4342 if i < 0: 4343 i = len(arrnames) - 1 4344 except (ValueError, AttributeError): 4345 i = 0 4346 4347 ob.cmap(ob._cmap_name, arrnames[i], on="cells") 4348 if ob.scalarbar: 4349 if isinstance(ob.scalarbar, vtki.vtkActor2D): 4350 self.remove(ob.scalarbar) 4351 title = ob.scalarbar.GetTitle() 4352 ob.scalarbar = None 4353 ob.add_scalarbar(title=arrnames[i]) 4354 self.add(ob.scalarbar) 4355 elif isinstance(ob.scalarbar, vedo.Assembly): 4356 self.remove(ob.scalarbar) 4357 ob.scalarbar = None 4358 ob.add_scalarbar3d(title=arrnames[i]) 4359 self.add(ob.scalarbar) 4360 else: 4361 vedo.printc( 4362 f"Name:'{ob.name}'," if ob.name else "", 4363 f"active celldata array: '{arrnames[i]}'", 4364 c="g", bold=False, 4365 ) 4366 4367 elif key == "7": 4368 bgc = np.array(renderer.GetBackground()).sum() / 3 4369 if bgc <= 0: 4370 bgc = 0.223 4371 elif 0 < bgc < 1: 4372 bgc = 1 4373 else: 4374 bgc = 0 4375 renderer.SetBackground(bgc, bgc, bgc) 4376 4377 elif key == "8": 4378 bg2cols = [ 4379 "lightyellow", 4380 "darkseagreen", 4381 "palegreen", 4382 "steelblue", 4383 "lightblue", 4384 "cadetblue", 4385 "lavender", 4386 "white", 4387 "blackboard", 4388 "black", 4389 ] 4390 bg2name = vedo.get_color_name(renderer.GetBackground2()) 4391 if bg2name in bg2cols: 4392 idx = bg2cols.index(bg2name) 4393 else: 4394 idx = 4 4395 if idx is not None: 4396 bg2name_next = bg2cols[(idx + 1) % (len(bg2cols) - 1)] 4397 if not bg2name_next: 4398 renderer.GradientBackgroundOff() 4399 else: 4400 renderer.GradientBackgroundOn() 4401 renderer.SetBackground2(vedo.get_color(bg2name_next)) 4402 4403 elif key in ["plus", "equal", "KP_Add", "minus", "KP_Subtract"]: # cycle axes style 4404 i = self.renderers.index(renderer) 4405 try: 4406 self.axes_instances[i].EnabledOff() 4407 self.axes_instances[i].SetInteractor(None) 4408 except AttributeError: 4409 # print("Cannot remove widget", [self.axes_instances[i]]) 4410 try: 4411 self.remove(self.axes_instances[i]) 4412 except: 4413 print("Cannot remove axes", [self.axes_instances[i]]) 4414 return 4415 self.axes_instances[i] = None 4416 4417 if not self.axes: 4418 self.axes = 0 4419 if isinstance(self.axes, dict): 4420 self.axes = 1 4421 4422 if key in ["minus", "KP_Subtract"]: 4423 if not self.camera.GetParallelProjection() and self.axes == 0: 4424 self.axes -= 1 # jump ruler doesnt make sense in perspective mode 4425 bns = self.renderer.ComputeVisiblePropBounds() 4426 addons.add_global_axes(axtype=(self.axes - 1) % 15, c=None, bounds=bns) 4427 else: 4428 if not self.camera.GetParallelProjection() and self.axes == 12: 4429 self.axes += 1 # jump ruler doesnt make sense in perspective mode 4430 bns = self.renderer.ComputeVisiblePropBounds() 4431 addons.add_global_axes(axtype=(self.axes + 1) % 15, c=None, bounds=bns) 4432 self.render() 4433 4434 elif "KP_" in key or key in [ 4435 "Insert","End","Down","Next","Left","Begin","Right","Home","Up","Prior" 4436 ]: 4437 asso = { # change axes style 4438 "KP_Insert": 0, "KP_0": 0, "Insert": 0, 4439 "KP_End": 1, "KP_1": 1, "End": 1, 4440 "KP_Down": 2, "KP_2": 2, "Down": 2, 4441 "KP_Next": 3, "KP_3": 3, "Next": 3, 4442 "KP_Left": 4, "KP_4": 4, "Left": 4, 4443 "KP_Begin": 5, "KP_5": 5, "Begin": 5, 4444 "KP_Right": 6, "KP_6": 6, "Right": 6, 4445 "KP_Home": 7, "KP_7": 7, "Home": 7, 4446 "KP_Up": 8, "KP_8": 8, "Up": 8, 4447 "Prior": 9, # on windows OS 4448 } 4449 clickedr = self.renderers.index(renderer) 4450 if key in asso: 4451 if self.axes_instances[clickedr]: 4452 if hasattr(self.axes_instances[clickedr], "EnabledOff"): # widget 4453 self.axes_instances[clickedr].EnabledOff() 4454 else: 4455 try: 4456 renderer.RemoveActor(self.axes_instances[clickedr]) 4457 except: 4458 pass 4459 self.axes_instances[clickedr] = None 4460 bounds = renderer.ComputeVisiblePropBounds() 4461 addons.add_global_axes(axtype=asso[key], c=None, bounds=bounds) 4462 self.interactor.Render() 4463 4464 if key == "O": 4465 renderer.RemoveLight(self._extralight) 4466 self._extralight = None 4467 4468 elif key == "