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