vedo.plotter

This module defines the main class Plotter to manage actors and 3D rendering.

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