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