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![](https://vedo.embl.es/images/basic/multirenderers.png)
  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            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
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            ![](https://user-images.githubusercontent.com/32848391/52859555-4efcf200-312d-11e9-9290-6988c8295163.png)
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            ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
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            ![](https://vedo.embl.es/images/basic/spline_tool.png)
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            ![](https://vedo.embl.es/images/basic/ssao.jpg)
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            ![](https://vedo.embl.es/images/pyplot/earthquake_browser.jpg)
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            ![](https://vedo.embl.es/images/feats/scale_indicator.png)
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                ![](https://vedo.embl.es/images/advanced/spline_draw.png)
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            ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
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            ![](https://vedo.embl.es/images/basic/mousehover3.jpg)
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            ![](https://user-images.githubusercontent.com/32848391/56758560-3c3f1300-6797-11e9-9b33-49f5a4876039.jpg)
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