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