vedo.plotter

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

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

Main class to manage objects.

Plotter( shape=(1, 1), N=None, pos=(0, 0), size='auto', screensize='auto', title='vedo', bg='white', bg2=None, axes=None, sharecam=True, resetcam=True, interactive=None, offscreen=False, qt_widget=None, wx_widget=None)
379    def __init__(
380        self,
381        shape=(1, 1),
382        N=None,
383        pos=(0, 0),
384        size="auto",
385        screensize="auto",
386        title="vedo",
387        bg="white",
388        bg2=None,
389        axes=None,
390        sharecam=True,
391        resetcam=True,
392        interactive=None,
393        offscreen=False,
394        qt_widget=None,
395        wx_widget=None,
396    ):
397        """
398        Arguments:
399            shape : (str, list)
400                shape of the grid of renderers in format (rows, columns). Ignored if N is specified.
401            N : (int)
402                number of desired renderers arranged in a grid automatically.
403            pos : (list)
404                (x,y) position in pixels of top-left corner of the rendering window on the screen
405            size : (str, list)
406                size of the rendering window. If 'auto', guess it based on screensize.
407            screensize : (list)
408                physical size of the monitor screen in pixels
409            bg : (color, str)
410                background color or specify jpg image file name with path
411            bg2 : (color)
412                background color of a gradient towards the top
413            title : (str)
414                window title
415
416            axes : (int)
417
418                Note that Axes type-1 can be fully customized by passing a dictionary `axes=dict()`.
419                Check out `vedo.addons.Axes()` for the available options.
420
421                    - 0,  no axes
422                    - 1,  draw three gray grid walls
423                    - 2,  show cartesian axes from (0,0,0)
424                    - 3,  show positive range of cartesian axes from (0,0,0)
425                    - 4,  show a triad at bottom left
426                    - 5,  show a cube at bottom left
427                    - 6,  mark the corners of the bounding box
428                    - 7,  draw a 3D ruler at each side of the cartesian axes
429                    - 8,  show the VTK CubeAxesActor object
430                    - 9,  show the bounding box outLine
431                    - 10, show three circles representing the maximum bounding box
432                    - 11, show a large grid on the x-y plane (use with zoom=8)
433                    - 12, show polar axes
434                    - 13, draw a simple ruler at the bottom of the window
435                    - 14: draw a camera orientation widget
436
437            sharecam : (bool)
438                if False each renderer will have an independent camera
439            interactive : (bool)
440                if True will stop after show() to allow interaction with the 3d scene
441            offscreen : (bool)
442                if True will not show the rendering window
443            qt_widget : (QVTKRenderWindowInteractor)
444                render in a Qt-Widget using an QVTKRenderWindowInteractor.
445                See examples `qt_windows[1,2,3].py` and `qt_cutter.py`.
446        """
447        vedo.plotter_instance = self
448
449        if interactive is None:
450            interactive = bool(N in (0, 1, None) and shape == (1, 1))
451        self._interactive = interactive
452        # print("interactive", interactive, N, shape)
453
454        self.objects = []           # list of objects to be shown
455        self.clicked_object = None  # holds the object that has been clicked
456        self.clicked_actor = None   # holds the actor that has been clicked
457
458        self.shape = shape   # nr. of subwindows in grid
459        self.axes = axes     # show axes type nr.
460        self.title = title   # window title
461        self.size = size     # window size
462        self.backgrcol = bg  # used also by backend notebooks
463
464        self.offscreen= offscreen
465        self.resetcam = resetcam
466        self.sharecam = sharecam  # share the same camera if multiple renderers
467        self.pos      = pos       # used by vedo.file_io
468
469        self.picker   = None  # hold the vtkPicker object
470        self.picked2d = None  # 2d coords of a clicked point on the rendering window
471        self.picked3d = None  # 3d coords of a clicked point on an actor
472
473        self.qt_widget = qt_widget  # QVTKRenderWindowInteractor
474        self.wx_widget = wx_widget  # wxVTKRenderWindowInteractor
475        self.interactor = None
476        self.window = None
477        self.renderer = None
478        self.renderers = []  # list of renderers
479
480        # mostly internal stuff:
481        self.hover_legends = []
482        self.justremoved = None
483        self.axes_instances = []
484        self.clock = 0
485        self.sliders = []
486        self.buttons = []
487        self.widgets = []
488        self.cutter_widget = None
489        self.hint_widget = None
490        self.background_renderer = None
491        self.last_event = None
492        self.skybox = None
493        self._icol = 0
494        self._clockt0 = time.time()
495        self._extralight = None
496        self._cocoa_initialized = False
497        self._cocoa_process_events = True  # make one call in show()
498        self._must_close_now = False
499
500        #####################################################################
501        if vedo.settings.default_backend == "2d":
502            self.offscreen = True
503            if self.size == "auto":
504                self.size = (800, 600)
505
506        elif vedo.settings.default_backend == "k3d":
507            if self.size == "auto":
508                self.size = (1000, 1000)
509            ####################################
510            return  ############################
511            ####################################
512
513        #############################################################
514        if vedo.settings.default_backend in ["vtk", "2d", "trame"]:
515
516            if screensize == "auto":
517                screensize = (2160, 1440)  # TODO: get actual screen size
518
519            # build the rendering window:
520            self.window = vtki.vtkRenderWindow()
521
522            self.window.GlobalWarningDisplayOff()
523
524            if self.title == "vedo":  # check if dev version
525                if "dev" in vedo.__version__:
526                    self.title = f"vedo ({vedo.__version__})"
527            self.window.SetWindowName(self.title)
528
529            # more vedo.settings
530            if vedo.settings.use_depth_peeling:
531                self.window.SetAlphaBitPlanes(vedo.settings.alpha_bit_planes)
532            self.window.SetMultiSamples(vedo.settings.multi_samples)
533
534            self.window.SetPolygonSmoothing(vedo.settings.polygon_smoothing)
535            self.window.SetLineSmoothing(vedo.settings.line_smoothing)
536            self.window.SetPointSmoothing(vedo.settings.point_smoothing)
537
538        #############################################################
539        if N:  # N = number of renderers. Find out the best
540
541            if shape != (1, 1):  # arrangement based on minimum nr. of empty renderers
542                vedo.logger.warning("having set N, shape is ignored.")
543
544            x, y = screensize
545            nx = int(np.sqrt(int(N * y / x) + 1))
546            ny = int(np.sqrt(int(N * x / y) + 1))
547            lm = [
548                (nx, ny),
549                (nx, ny + 1),
550                (nx - 1, ny),
551                (nx + 1, ny),
552                (nx, ny - 1),
553                (nx - 1, ny + 1),
554                (nx + 1, ny - 1),
555                (nx + 1, ny + 1),
556                (nx - 1, ny - 1),
557            ]
558            ind, minl = 0, 1000
559            for i, m in enumerate(lm):
560                l = m[0] * m[1]
561                if N <= l < minl:
562                    ind = i
563                    minl = l
564            shape = lm[ind]
565
566        ##################################################
567        if isinstance(shape, str):
568
569            if "|" in shape:
570                if self.size == "auto":
571                    self.size = (800, 1200)
572                n = int(shape.split("|")[0])
573                m = int(shape.split("|")[1])
574                rangen = reversed(range(n))
575                rangem = reversed(range(m))
576            else:
577                if self.size == "auto":
578                    self.size = (1200, 800)
579                m = int(shape.split("/")[0])
580                n = int(shape.split("/")[1])
581                rangen = range(n)
582                rangem = range(m)
583
584            if n >= m:
585                xsplit = m / (n + m)
586            else:
587                xsplit = 1 - n / (n + m)
588            if vedo.settings.window_splitting_position:
589                xsplit = vedo.settings.window_splitting_position
590
591            for i in rangen:
592                arenderer = vtki.vtkRenderer()
593                if "|" in shape:
594                    arenderer.SetViewport(0, i / n, xsplit, (i + 1) / n)
595                else:
596                    arenderer.SetViewport(i / n, 0, (i + 1) / n, xsplit)
597                self.renderers.append(arenderer)
598
599            for i in rangem:
600                arenderer = vtki.vtkRenderer()
601
602                if "|" in shape:
603                    arenderer.SetViewport(xsplit, i / m, 1, (i + 1) / m)
604                else:
605                    arenderer.SetViewport(i / m, xsplit, (i + 1) / m, 1)
606                self.renderers.append(arenderer)
607
608            for r in self.renderers:
609                r.SetLightFollowCamera(vedo.settings.light_follows_camera)
610
611                r.SetUseDepthPeeling(vedo.settings.use_depth_peeling)
612                # r.SetUseDepthPeelingForVolumes(vedo.settings.use_depth_peeling)
613                if vedo.settings.use_depth_peeling:
614                    r.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels)
615                    r.SetOcclusionRatio(vedo.settings.occlusion_ratio)
616                r.SetUseFXAA(vedo.settings.use_fxaa)
617                r.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer)
618
619                r.SetBackground(vedo.get_color(self.backgrcol))
620
621                self.axes_instances.append(None)
622
623            self.shape = (n + m,)
624
625        elif utils.is_sequence(shape) and isinstance(shape[0], dict):
626            # passing a sequence of dicts for renderers specifications
627
628            if self.size == "auto":
629                self.size = (1000, 800)
630
631            for rd in shape:
632                x0, y0 = rd["bottomleft"]
633                x1, y1 = rd["topright"]
634                bg_ = rd.pop("bg", "white")
635                bg2_ = rd.pop("bg2", None)
636
637                arenderer = vtki.vtkRenderer()
638                arenderer.SetLightFollowCamera(vedo.settings.light_follows_camera)
639
640                arenderer.SetUseDepthPeeling(vedo.settings.use_depth_peeling)
641                # arenderer.SetUseDepthPeelingForVolumes(vedo.settings.use_depth_peeling)
642                if vedo.settings.use_depth_peeling:
643                    arenderer.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels)
644                    arenderer.SetOcclusionRatio(vedo.settings.occlusion_ratio)
645                arenderer.SetUseFXAA(vedo.settings.use_fxaa)
646                arenderer.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer)
647
648                arenderer.SetViewport(x0, y0, x1, y1)
649                arenderer.SetBackground(vedo.get_color(bg_))
650                if bg2_:
651                    arenderer.GradientBackgroundOn()
652                    arenderer.SetBackground2(vedo.get_color(bg2_))
653
654                self.renderers.append(arenderer)
655                self.axes_instances.append(None)
656
657            self.shape = (len(shape),)
658
659        else:
660
661            if isinstance(self.size, str) and self.size == "auto":
662                # figure out a reasonable window size
663                f = 1.5
664                x, y = screensize
665                xs = y / f * shape[1]  # because y<x
666                ys = y / f * shape[0]
667                if xs > x / f:  # shrink
668                    xs = x / f
669                    ys = xs / shape[1] * shape[0]
670                if ys > y / f:
671                    ys = y / f
672                    xs = ys / shape[0] * shape[1]
673                self.size = (int(xs), int(ys))
674                if shape == (1, 1):
675                    self.size = (int(y / f), int(y / f))  # because y<x
676            else:
677                self.size = (self.size[0], self.size[1])
678
679            try:
680                image_actor = None
681                bgname = str(self.backgrcol).lower()
682                if ".jpg" in bgname or ".jpeg" in bgname or ".png" in bgname:
683                    self.window.SetNumberOfLayers(2)
684                    self.background_renderer = vtki.vtkRenderer()
685                    self.background_renderer.SetLayer(0)
686                    self.background_renderer.InteractiveOff()
687                    self.background_renderer.SetBackground(vedo.get_color(bg2))
688                    image_actor = vedo.Image(self.backgrcol).actor
689                    self.window.AddRenderer(self.background_renderer)
690                    self.background_renderer.AddActor(image_actor)
691            except AttributeError:
692                pass
693
694            for i in reversed(range(shape[0])):
695                for j in range(shape[1]):
696                    arenderer = vtki.vtkRenderer()
697                    arenderer.SetLightFollowCamera(vedo.settings.light_follows_camera)
698                    arenderer.SetTwoSidedLighting(vedo.settings.two_sided_lighting)
699
700                    arenderer.SetUseDepthPeeling(vedo.settings.use_depth_peeling)
701                    # arenderer.SetUseDepthPeelingForVolumes(vedo.settings.use_depth_peeling)
702                    if vedo.settings.use_depth_peeling:
703                        arenderer.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels)
704                        arenderer.SetOcclusionRatio(vedo.settings.occlusion_ratio)
705                    arenderer.SetUseFXAA(vedo.settings.use_fxaa)
706                    arenderer.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer)
707
708                    if image_actor:
709                        arenderer.SetLayer(1)
710
711                    arenderer.SetBackground(vedo.get_color(self.backgrcol))
712                    if bg2:
713                        arenderer.GradientBackgroundOn()
714                        arenderer.SetBackground2(vedo.get_color(bg2))
715
716                    x0 = i / shape[0]
717                    y0 = j / shape[1]
718                    x1 = (i + 1) / shape[0]
719                    y1 = (j + 1) / shape[1]
720                    arenderer.SetViewport(y0, x0, y1, x1)
721                    self.renderers.append(arenderer)
722                    self.axes_instances.append(None)
723            self.shape = shape
724
725        if self.renderers:
726            self.renderer = self.renderers[0]
727            self.camera.SetParallelProjection(vedo.settings.use_parallel_projection)
728
729        #########################################################
730        if self.qt_widget or self.wx_widget:
731            if self.qt_widget:
732                self.window = self.qt_widget.GetRenderWindow()  # overwrite
733            else:
734                self.window = self.wx_widget.GetRenderWindow()
735            self.interactor = self.window.GetInteractor()
736
737        #########################################################
738        for r in self.renderers:
739            self.window.AddRenderer(r)
740            # set the background gradient if any
741            if vedo.settings.background_gradient_orientation > 0:
742                try:
743                    modes = [
744                        vtki.vtkViewport.GradientModes.VTK_GRADIENT_VERTICAL,
745                        vtki.vtkViewport.GradientModes.VTK_GRADIENT_HORIZONTAL,
746                        vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_SIDE,
747                        vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_CORNER,
748                    ]
749                    r.SetGradientMode(modes[vedo.settings.background_gradient_orientation])
750                    r.GradientBackgroundOn()
751                except AttributeError:
752                    pass
753
754        #########################################################
755        if self.qt_widget or self.wx_widget:
756            # self.window.SetSize(int(self.size[0]), int(self.size[1]))
757            self.interactor.SetRenderWindow(self.window)
758            # vsty = vtki.new("InteractorStyleTrackballCamera")
759            # self.interactor.SetInteractorStyle(vsty)
760            if vedo.settings.enable_default_keyboard_callbacks:
761                self.interactor.AddObserver("KeyPressEvent", self._default_keypress)
762            if vedo.settings.enable_default_mouse_callbacks:
763                self.interactor.AddObserver("LeftButtonPressEvent", self._default_mouseleftclick)
764            return  ################
765            ########################
766
767        if self.size[0] == "f":  # full screen
768            self.size = "fullscreen"
769            self.window.SetFullScreen(True)
770            self.window.BordersOn()
771        else:
772            self.window.SetSize(int(self.size[0]), int(self.size[1]))
773
774        if self.offscreen:
775            if self.axes in (4, 5, 8, 12, 14):
776                self.axes = 0  # does not work with those
777            self.window.SetOffScreenRendering(True)
778            self.interactor = None
779            self._interactive = False
780            return  ################
781            ########################
782
783        self.window.SetPosition(pos)
784
785        #########################################################
786        self.interactor = vtki.vtkRenderWindowInteractor()
787
788        self.interactor.SetRenderWindow(self.window)
789        vsty = vtki.new("InteractorStyleTrackballCamera")
790        self.interactor.SetInteractorStyle(vsty)
791        self.interactor.RemoveObservers("CharEvent")
792
793        if vedo.settings.enable_default_keyboard_callbacks:
794            self.interactor.AddObserver("KeyPressEvent", self._default_keypress)
795        if vedo.settings.enable_default_mouse_callbacks:
796            self.interactor.AddObserver("LeftButtonPressEvent", self._default_mouseleftclick)
Arguments:
  • shape : (str, list) shape of the grid of renderers in format (rows, columns). Ignored if N is specified.
  • N : (int) number of desired renderers arranged in a grid automatically.
  • pos : (list) (x,y) position in pixels of top-left corner of the rendering window on the screen
  • size : (str, list) size of the rendering window. If 'auto', guess it based on screensize.
  • screensize : (list) physical size of the monitor screen in pixels
  • bg : (color, str) background color or specify jpg image file name with path
  • bg2 : (color) background color of a gradient towards the top
  • title : (str) window title
  • axes : (int)

    Note that Axes type-1 can be fully customized by passing a dictionary axes=dict(). Check out vedo.addons.Axes() for the available options.

    - 0,  no axes
    - 1,  draw three gray grid walls
    - 2,  show cartesian axes from (0,0,0)
    - 3,  show positive range of cartesian axes from (0,0,0)
    - 4,  show a triad at bottom left
    - 5,  show a cube at bottom left
    - 6,  mark the corners of the bounding box
    - 7,  draw a 3D ruler at each side of the cartesian axes
    - 8,  show the VTK CubeAxesActor object
    - 9,  show the bounding box outLine
    - 10, show three circles representing the maximum bounding box
    - 11, show a large grid on the x-y plane (use with zoom=8)
    - 12, show polar axes
    - 13, draw a simple ruler at the bottom of the window
    - 14: draw a camera orientation widget
    
  • sharecam : (bool) if False each renderer will have an independent camera
  • interactive : (bool) if True will stop after show() to allow interaction with the 3d scene
  • offscreen : (bool) if True will not show the rendering window
  • qt_widget : (QVTKRenderWindowInteractor) render in a Qt-Widget using an QVTKRenderWindowInteractor. See examples qt_windows[1,2,3].py and qt_cutter.py.
def print(self):
867    def print(self):
868        """Print information about the current instance."""
869        print(self.__str__())
870        return self

Print information about the current instance.

def initialize_interactor(self) -> Self:
888    def initialize_interactor(self) -> Self:
889        """Initialize the interactor if not already initialized."""
890        if self.offscreen:
891            return self
892        if self.interactor:
893            if not self.interactor.GetInitialized():
894                self.interactor.Initialize()
895                self.interactor.RemoveObservers("CharEvent")
896        return self

Initialize the interactor if not already initialized.

def process_events(self) -> Self:
898    def process_events(self) -> Self:
899        """Process all pending events."""
900        self.initialize_interactor()
901        if self.interactor:
902            try:
903                self.interactor.ProcessEvents()
904            except AttributeError:
905                pass
906        return self

Process all pending events.

def at(self, nren: int, yren=None) -> Self:
908    def at(self, nren: int, yren=None) -> Self:
909        """
910        Select the current renderer number as an int.
911        Can also use the `[nx, ny]` format.
912        """
913        if utils.is_sequence(nren):
914            if len(nren) == 2:
915                nren, yren = nren
916            else:
917                vedo.logger.error("at() argument must be a single number or a list of two numbers")
918                raise RuntimeError
919
920        if yren is not None:
921            a, b = self.shape
922            x, y = nren, yren
923            nren = x * b + y
924            # print("at (", x, y, ")  -> ren", nren)
925            if nren < 0 or nren > len(self.renderers) or x >= a or y >= b:
926                vedo.logger.error(f"at({nren, yren}) is malformed!")
927                raise RuntimeError
928
929        self.renderer = self.renderers[nren]
930        return self

Select the current renderer number as an int. Can also use the [nx, ny] format.

def add(self, *objs, at=None) -> Self:
932    def add(self, *objs, at=None) -> Self:
933        """
934        Append the input objects to the internal list of objects to be shown.
935
936        Arguments:
937            at : (int)
938                add the object at the specified renderer
939        """
940        if at is not None:
941            ren = self.renderers[at]
942        else:
943            ren = self.renderer
944
945        objs = utils.flatten(objs)
946        for ob in objs:
947            if ob and ob not in self.objects:
948                self.objects.append(ob)
949
950        acts = self._scan_input_return_acts(objs)
951
952        for a in acts:
953
954            if ren:
955                if isinstance(a, vedo.addons.BaseCutter):
956                    a.add_to(self)  # from cutters
957                    continue
958
959                if isinstance(a, vtki.vtkLight):
960                    ren.AddLight(a)
961                    continue
962
963                try:
964                    ren.AddActor(a)
965                except TypeError:
966                    ren.AddActor(a.actor)
967
968                try:
969                    ir = self.renderers.index(ren)
970                    a.rendered_at.add(ir) # might not have rendered_at
971                except (AttributeError, ValueError):
972                    pass
973
974                if isinstance(a, vtki.vtkFollower):
975                    a.SetCamera(self.camera)
976                elif isinstance(a, vedo.visual.LightKit):
977                    a.lightkit.AddLightsToRenderer(ren)
978
979        return self

Append the input objects to the internal list of objects to be shown.

Arguments:
  • at : (int) add the object at the specified renderer
def remove(self, *objs, at=None) -> Self:
 981    def remove(self, *objs, at=None) -> Self:
 982        """
 983        Remove input object to the internal list of objects to be shown.
 984
 985        Objects to be removed can be referenced by their assigned name,
 986
 987        Arguments:
 988            at : (int)
 989                remove the object at the specified renderer
 990        """
 991        # TODO and you can also use wildcards like `*` and `?`.
 992        if at is not None:
 993            ren = self.renderers[at]
 994        else:
 995            ren = self.renderer
 996
 997        objs = [ob for ob in utils.flatten(objs) if ob]
 998
 999        has_str = False
1000        for ob in objs:
1001            if isinstance(ob, str):
1002                has_str = True
1003                break
1004
1005        has_actor = False
1006        for ob in objs:
1007            if hasattr(ob, "actor") and ob.actor:
1008                has_actor = True
1009                break
1010
1011        if has_str or has_actor:
1012            # need to get the actors to search for
1013            for a in self.get_actors(include_non_pickables=True):
1014                # print("PARSING", [a])
1015                try:
1016                    if (a.name and a.name in objs) or a in objs:
1017                        objs.append(a)
1018                    # if a.name:
1019                    #     bools = [utils.parse_pattern(ob, a.name)[0] for ob in objs]
1020                    #     if any(bools) or a in objs:
1021                    #         objs.append(a)
1022                    #     print('a.name',a.name, objs,any(bools))
1023                except AttributeError:  # no .name
1024                    # passing the actor so get back the object with .retrieve_object()
1025                    try:
1026                        vobj = a.retrieve_object()
1027                        if (vobj.name and vobj.name in objs) or vobj in objs:
1028                            # print('vobj.name', vobj.name)
1029                            objs.append(vobj)
1030                    except AttributeError:
1031                        pass
1032
1033        ir = self.renderers.index(ren)
1034
1035        ids = []
1036        for ob in set(objs):
1037
1038            # will remove it from internal list if possible
1039            try:
1040                idx = self.objects.index(ob)
1041                ids.append(idx)
1042            except ValueError:
1043                pass
1044
1045            if ren:  ### remove it from the renderer
1046
1047                if isinstance(ob, vedo.addons.BaseCutter):
1048                    ob.remove_from(self)  # from cutters
1049                    continue
1050
1051                try:
1052                    ren.RemoveActor(ob)
1053                except TypeError:
1054                    try:
1055                        ren.RemoveActor(ob.actor)
1056                    except AttributeError:
1057                        pass
1058
1059                if hasattr(ob, "rendered_at"):
1060                    ob.rendered_at.discard(ir)
1061
1062                if hasattr(ob, "scalarbar") and ob.scalarbar:
1063                    ren.RemoveActor(ob.scalarbar)
1064                if hasattr(ob, "_caption") and ob._caption:
1065                    ren.RemoveActor(ob._caption)
1066                if hasattr(ob, "shadows") and ob.shadows:
1067                    for sha in ob.shadows:
1068                        ren.RemoveActor(sha.actor)
1069                if hasattr(ob, "trail") and ob.trail:
1070                    ren.RemoveActor(ob.trail.actor)
1071                    ob.trail_points = []
1072                    if hasattr(ob.trail, "shadows") and ob.trail.shadows:
1073                        for sha in ob.trail.shadows:
1074                            ren.RemoveActor(sha.actor)
1075
1076                elif isinstance(ob, vedo.visual.LightKit):
1077                    ob.lightkit.RemoveLightsFromRenderer(ren)
1078
1079        # for i in ids: # WRONG way of doing it!
1080        #     del self.objects[i]
1081        # instead we do:
1082        self.objects = [ele for i, ele in enumerate(self.objects) if i not in ids]
1083        return self

Remove input object to the internal list of objects to be shown.

Objects to be removed can be referenced by their assigned name,

Arguments:
  • at : (int) remove the object at the specified renderer
actors
1085    @property
1086    def actors(self):
1087        """Return the list of actors."""
1088        return [ob.actor for ob in self.objects if hasattr(ob, "actor")]

Return the list of actors.

def remove_lights(self) -> Self:
1090    def remove_lights(self) -> Self:
1091        """Remove all the present lights in the current renderer."""
1092        if self.renderer:
1093            self.renderer.RemoveAllLights()
1094        return self

Remove all the present lights in the current renderer.

def pop(self, at=None) -> Self:
1096    def pop(self, at=None) -> Self:
1097        """
1098        Remove the last added object from the rendering window.
1099        This method is typically used in loops or callback functions.
1100        """
1101        if at is not None and not isinstance(at, int):
1102            # wrong usage pitfall
1103            vedo.logger.error("argument of pop() must be an integer")
1104            raise RuntimeError()
1105
1106        if self.objects:
1107            self.remove(self.objects[-1], at)
1108        return self

Remove the last added object from the rendering window. This method is typically used in loops or callback functions.

def render(self, resetcam=False) -> Self:
1110    def render(self, resetcam=False) -> Self:
1111        """Render the scene. This method is typically used in loops or callback functions."""
1112
1113        if vedo.settings.dry_run_mode >= 2:
1114            return self
1115
1116        if not self.window:
1117            return self
1118
1119        self.initialize_interactor()
1120
1121        if resetcam:
1122            self.renderer.ResetCamera()
1123
1124        self.window.Render()
1125
1126        if self._cocoa_process_events and self.interactor and self.interactor.GetInitialized():
1127            if "Darwin" in vedo.sys_platform and not self.offscreen:
1128                self.interactor.ProcessEvents()
1129                self._cocoa_process_events = False
1130        return self

Render the scene. This method is typically used in loops or callback functions.

def interactive(self) -> Self:
1132    def interactive(self) -> Self:
1133        """
1134        Start window interaction.
1135        Analogous to `show(..., interactive=True)`.
1136        """
1137        if vedo.settings.dry_run_mode >= 1:
1138            return self
1139        self.initialize_interactor()
1140        if self.interactor:
1141            # print("self.interactor.Start()")
1142            self.interactor.Start()
1143            # print("self.interactor.Start() done")
1144            if self._must_close_now:
1145                # print("self.interactor.TerminateApp()")
1146                if self.interactor:
1147                    self.interactor.GetRenderWindow().Finalize()
1148                    self.interactor.TerminateApp()
1149                self.interactor = None
1150                self.window = None
1151                self.renderer = None
1152                self.renderers = []
1153                self.camera = None
1154        return self

Start window interaction. Analogous to show(..., interactive=True).

def use_depth_peeling(self, at=None, value=True) -> Self:
1156    def use_depth_peeling(self, at=None, value=True) -> Self:
1157        """
1158        Specify whether use depth peeling algorithm at this specific renderer
1159        Call this method before the first rendering.
1160        """
1161        if at is None:
1162            ren = self.renderer
1163        else:
1164            ren = self.renderers[at]
1165        ren.SetUseDepthPeeling(value)
1166        return self

Specify whether use depth peeling algorithm at this specific renderer Call this method before the first rendering.

def background(self, c1=None, c2=None, at=None, mode=0) -> Union[Self, numpy.ndarray]:
1168    def background(self, c1=None, c2=None, at=None, mode=0) -> Union[Self, "np.ndarray"]:
1169        """Set the color of the background for the current renderer.
1170        A different renderer index can be specified by keyword `at`.
1171
1172        Arguments:
1173            c1 : (list)
1174                background main color.
1175            c2 : (list)
1176                background color for the upper part of the window.
1177            at : (int)
1178                renderer index.
1179            mode : (int)
1180                background mode (needs vtk version >= 9.3)
1181                    0 = vertical,
1182                    1 = horizontal,
1183                    2 = radial farthest side,
1184                    3 = radia farthest corner.
1185        """
1186        if not self.renderers:
1187            return self
1188        if at is None:
1189            r = self.renderer
1190        else:
1191            r = self.renderers[at]
1192
1193        if c1 is None and c2 is None:
1194            return np.array(r.GetBackground())
1195
1196        if r:
1197            if c1 is not None:
1198                r.SetBackground(vedo.get_color(c1))
1199            if c2 is not None:
1200                r.GradientBackgroundOn()
1201                r.SetBackground2(vedo.get_color(c2))
1202                if mode:
1203                    try:  # only works with vtk>=9.3
1204                        modes = [
1205                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_VERTICAL,
1206                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_HORIZONTAL,
1207                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_SIDE,
1208                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_CORNER,
1209                        ]
1210                        r.SetGradientMode(modes[vedo.settings.background_gradient_orientation])
1211                    except AttributeError:
1212                        pass
1213
1214            else:
1215                r.GradientBackgroundOff()
1216        return self

Set the color of the background for the current renderer. A different renderer index can be specified by keyword at.

Arguments:
  • c1 : (list) background main color.
  • c2 : (list) background color for the upper part of the window.
  • at : (int) renderer index.
  • mode : (int) background mode (needs vtk version >= 9.3) 0 = vertical, 1 = horizontal, 2 = radial farthest side, 3 = radia farthest corner.
def get_meshes( self, at=None, include_non_pickables=False, unpack_assemblies=True) -> list:
1219    def get_meshes(self, at=None, include_non_pickables=False, unpack_assemblies=True) -> list:
1220        """
1221        Return a list of Meshes from the specified renderer.
1222
1223        Arguments:
1224            at : (int)
1225                specify which renderer to look at.
1226            include_non_pickables : (bool)
1227                include non-pickable objects
1228            unpack_assemblies : (bool)
1229                unpack assemblies into their components
1230        """
1231        if at is None:
1232            renderer = self.renderer
1233            at = self.renderers.index(renderer)
1234        elif isinstance(at, int):
1235            renderer = self.renderers[at]
1236
1237        has_global_axes = False
1238        if isinstance(self.axes_instances[at], vedo.Assembly):
1239            has_global_axes = True
1240
1241        if unpack_assemblies:
1242            acs = renderer.GetActors()
1243        else:
1244            acs = renderer.GetViewProps()
1245
1246        objs = []
1247        acs.InitTraversal()
1248        for _ in range(acs.GetNumberOfItems()):
1249
1250            if unpack_assemblies:
1251                a = acs.GetNextItem()
1252            else:
1253                a = acs.GetNextProp()
1254
1255            if isinstance(a, vtki.vtkVolume):
1256                continue
1257
1258            if include_non_pickables or a.GetPickable():
1259                if a == self.axes_instances[at]:
1260                    continue
1261                if has_global_axes and a in self.axes_instances[at].actors:
1262                    continue
1263                try:
1264                    objs.append(a.retrieve_object())
1265                except AttributeError:
1266                    pass
1267        return objs

Return a list of Meshes from the specified renderer.

Arguments:
  • at : (int) specify which renderer to look at.
  • include_non_pickables : (bool) include non-pickable objects
  • unpack_assemblies : (bool) unpack assemblies into their components
def get_volumes(self, at=None, include_non_pickables=False) -> list:
1269    def get_volumes(self, at=None, include_non_pickables=False) -> list:
1270        """
1271        Return a list of Volumes from the specified renderer.
1272
1273        Arguments:
1274            at : (int)
1275                specify which renderer to look at
1276            include_non_pickables : (bool)
1277                include non-pickable objects
1278        """
1279        if at is None:
1280            renderer = self.renderer
1281            at = self.renderers.index(renderer)
1282        elif isinstance(at, int):
1283            renderer = self.renderers[at]
1284
1285        vols = []
1286        acs = renderer.GetVolumes()
1287        acs.InitTraversal()
1288        for _ in range(acs.GetNumberOfItems()):
1289            a = acs.GetNextItem()
1290            if include_non_pickables or a.GetPickable():
1291                try:
1292                    vols.append(a.retrieve_object())
1293                except AttributeError:
1294                    pass
1295        return vols

Return a list of Volumes from the specified renderer.

Arguments:
  • at : (int) specify which renderer to look at
  • include_non_pickables : (bool) include non-pickable objects
def get_actors(self, at=None, include_non_pickables=False) -> list:
1297    def get_actors(self, at=None, include_non_pickables=False) -> list:
1298        """
1299        Return a list of Volumes from the specified renderer.
1300
1301        Arguments:
1302            at : (int)
1303                specify which renderer to look at
1304            include_non_pickables : (bool)
1305                include non-pickable objects
1306        """
1307        if at is None:
1308            renderer = self.renderer
1309            at = self.renderers.index(renderer)
1310        elif isinstance(at, int):
1311            renderer = self.renderers[at]
1312
1313        acts = []
1314        acs = renderer.GetViewProps()
1315        acs.InitTraversal()
1316        for _ in range(acs.GetNumberOfItems()):
1317            a = acs.GetNextProp()
1318            if include_non_pickables or a.GetPickable():
1319                acts.append(a)
1320        return acts

Return a list of Volumes from the specified renderer.

Arguments:
  • at : (int) specify which renderer to look at
  • include_non_pickables : (bool) include non-pickable objects
def check_actors_trasform(self, at=None) -> Self:
1322    def check_actors_trasform(self, at=None) -> Self:
1323        """
1324        Reset the transformation matrix of all actors at specified renderer.
1325        This is only useful when actors have been moved/rotated/scaled manually
1326        in an already rendered scene using interactors like
1327        'TrackballActor' or 'JoystickActor'.
1328        """
1329        # see issue https://github.com/marcomusy/vedo/issues/1046
1330        for a in self.get_actors(at=at, include_non_pickables=True):
1331            try:
1332                M = a.GetMatrix()
1333            except AttributeError:
1334                continue
1335            if M and not M.IsIdentity():
1336                try:
1337                    a.retrieve_object().apply_transform_from_actor()
1338                    # vedo.logger.info(
1339                    #     f"object '{a.retrieve_object().name}' "
1340                    #     "was manually moved. Updated to its current position."
1341                    # )
1342                except AttributeError:
1343                    pass
1344        return self

Reset the transformation matrix of all actors at specified renderer. This is only useful when actors have been moved/rotated/scaled manually in an already rendered scene using interactors like 'TrackballActor' or 'JoystickActor'.

def reset_camera(self, tight=None) -> Self:
1346    def reset_camera(self, tight=None) -> Self:
1347        """
1348        Reset the camera position and zooming.
1349        If tight (float) is specified the zooming reserves a padding space
1350        in the xy-plane expressed in percent of the average size.
1351        """
1352        if tight is None:
1353            self.renderer.ResetCamera()
1354        else:
1355            x0, x1, y0, y1, z0, z1 = self.renderer.ComputeVisiblePropBounds()
1356            cam = self.camera
1357
1358            self.renderer.ComputeAspect()
1359            aspect = self.renderer.GetAspect()
1360            angle = np.pi * cam.GetViewAngle() / 180.0
1361            dx = x1 - x0
1362            dy = y1 - y0
1363            dist = max(dx / aspect[0], dy) / np.tan(angle / 2) / 2
1364
1365            cam.SetViewUp(0, 1, 0)
1366            cam.SetPosition(x0 + dx / 2, y0 + dy / 2, dist * (1 + tight))
1367            cam.SetFocalPoint(x0 + dx / 2, y0 + dy / 2, 0)
1368            if cam.GetParallelProjection():
1369                ps = max(dx / aspect[0], dy) / 2
1370                cam.SetParallelScale(ps * (1 + tight))
1371            self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
1372        return self

Reset the camera position and zooming. If tight (float) is specified the zooming reserves a padding space in the xy-plane expressed in percent of the average size.

def reset_clipping_range(self, bounds=None) -> Self:
1374    def reset_clipping_range(self, bounds=None) -> Self:
1375        """
1376        Reset the camera clipping range to include all visible actors.
1377        If bounds is given, it will be used instead of computing it.
1378        """
1379        if bounds is None:
1380            self.renderer.ResetCameraClippingRange()
1381        else:
1382            self.renderer.ResetCameraClippingRange(bounds)
1383        return self

Reset the camera clipping range to include all visible actors. If bounds is given, it will be used instead of computing it.

def reset_viewup(self, smooth=True) -> Self:
1385    def reset_viewup(self, smooth=True) -> Self:
1386        """
1387        Reset the orientation of the camera to the closest orthogonal direction and view-up.
1388        """
1389        vbb = addons.compute_visible_bounds()[0]
1390        x0, x1, y0, y1, z0, z1 = vbb
1391        mx, my, mz = (x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2
1392        d = self.camera.GetDistance()
1393
1394        viewups = np.array(
1395            [(0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)]
1396        )
1397        positions = np.array(
1398            [
1399                (mx, my, mz + d),
1400                (mx, my, mz - d),
1401                (mx, my + d, mz),
1402                (mx, my - d, mz),
1403                (mx + d, my, mz),
1404                (mx - d, my, mz),
1405            ]
1406        )
1407
1408        vu = np.array(self.camera.GetViewUp())
1409        vui = np.argmin(np.linalg.norm(viewups - vu, axis=1))
1410
1411        poc = np.array(self.camera.GetPosition())
1412        foc = np.array(self.camera.GetFocalPoint())
1413        a = poc - foc
1414        b = positions - foc
1415        a = a / np.linalg.norm(a)
1416        b = b.T * (1 / np.linalg.norm(b, axis=1))
1417        pui = np.argmin(np.linalg.norm(b.T - a, axis=1))
1418
1419        if smooth:
1420            outtimes = np.linspace(0, 1, num=11, endpoint=True)
1421            for t in outtimes:
1422                vv = vu * (1 - t) + viewups[vui] * t
1423                pp = poc * (1 - t) + positions[pui] * t
1424                ff = foc * (1 - t) + np.array([mx, my, mz]) * t
1425                self.camera.SetViewUp(vv)
1426                self.camera.SetPosition(pp)
1427                self.camera.SetFocalPoint(ff)
1428                self.render()
1429
1430            # interpolator does not respect parallel view...:
1431            # cam1 = dict(
1432            #     pos=poc,
1433            #     viewup=vu,
1434            #     focal_point=(mx,my,mz),
1435            #     clipping_range=self.camera.GetClippingRange()
1436            # )
1437            # # cam1 = self.camera
1438            # cam2 = dict(
1439            #     pos=positions[pui],
1440            #     viewup=viewups[vui],
1441            #     focal_point=(mx,my,mz),
1442            #     clipping_range=self.camera.GetClippingRange()
1443            # )
1444            # vcams = self.move_camera([cam1, cam2], output_times=outtimes, smooth=0)
1445            # for c in vcams:
1446            #     self.renderer.SetActiveCamera(c)
1447            #     self.render()
1448        else:
1449
1450            self.camera.SetViewUp(viewups[vui])
1451            self.camera.SetPosition(positions[pui])
1452            self.camera.SetFocalPoint(mx, my, mz)
1453
1454        self.renderer.ResetCameraClippingRange()
1455
1456        # vbb, _, _, _ = addons.compute_visible_bounds()
1457        # x0,x1, y0,y1, z0,z1 = vbb
1458        # self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
1459        self.render()
1460        return self

Reset the orientation of the camera to the closest orthogonal direction and view-up.

def move_camera(self, cameras, t=0, times=(), smooth=True, output_times=()) -> list:
1462    def move_camera(self, cameras, t=0, times=(), smooth=True, output_times=()) -> list:
1463        """
1464        Takes as input two cameras set camera at an interpolated position:
1465
1466        Cameras can be vtkCamera or dictionaries in format:
1467
1468            `dict(pos=..., focal_point=..., viewup=..., distance=..., clipping_range=...)`
1469
1470        Press `shift-C` key in interactive mode to dump a python snipplet
1471        of parameters for the current camera view.
1472        """
1473        nc = len(cameras)
1474        if len(times) == 0:
1475            times = np.linspace(0, 1, num=nc, endpoint=True)
1476
1477        assert len(times) == nc
1478
1479        cin = vtki.new("CameraInterpolator")
1480
1481        # cin.SetInterpolationTypeToLinear() # buggy?
1482        if nc > 2 and smooth:
1483            cin.SetInterpolationTypeToSpline()
1484
1485        for i, cam in enumerate(cameras):
1486            vcam = cam
1487            if isinstance(cam, dict):
1488                vcam = utils.camera_from_dict(cam)
1489            cin.AddCamera(times[i], vcam)
1490
1491        mint, maxt = cin.GetMinimumT(), cin.GetMaximumT()
1492        rng = maxt - mint
1493
1494        if len(output_times) == 0:
1495            cin.InterpolateCamera(t * rng, self.camera)
1496            return [self.camera]
1497        else:
1498            vcams = []
1499            for tt in output_times:
1500                c = vtki.vtkCamera()
1501                cin.InterpolateCamera(tt * rng, c)
1502                vcams.append(c)
1503            return vcams

Takes as input two cameras set camera at an interpolated position:

Cameras can be vtkCamera or dictionaries in format:

dict(pos=..., focal_point=..., viewup=..., distance=..., clipping_range=...)

Press shift-C key in interactive mode to dump a python snipplet of parameters for the current camera view.

def fly_to(self, point) -> Self:
1505    def fly_to(self, point) -> Self:
1506        """
1507        Fly camera to the specified point.
1508
1509        Arguments:
1510            point : (list)
1511                point in space to place camera.
1512
1513        Example:
1514            ```python
1515            from vedo import *
1516            cone = Cone()
1517            plt = Plotter(axes=1)
1518            plt.show(cone)
1519            plt.fly_to([1,0,0])
1520            plt.interactive().close()
1521            ```
1522        """
1523        if self.interactor:
1524            self.resetcam = False
1525            self.interactor.FlyTo(self.renderer, point)
1526        return self

Fly camera to the specified point.

Arguments:
  • point : (list) point in space to place camera.
Example:
from vedo import *
cone = Cone()
plt = Plotter(axes=1)
plt.show(cone)
plt.fly_to([1,0,0])
plt.interactive()close()
def look_at(self, plane='xy') -> Self:
1528    def look_at(self, plane="xy") -> Self:
1529        """Move the camera so that it looks at the specified cartesian plane"""
1530        cam = self.renderer.GetActiveCamera()
1531        fp = np.array(cam.GetFocalPoint())
1532        p = np.array(cam.GetPosition())
1533        dist = np.linalg.norm(fp - p)
1534        plane = plane.lower()
1535        if "x" in plane and "y" in plane:
1536            cam.SetPosition(fp[0], fp[1], fp[2] + dist)
1537            cam.SetViewUp(0.0, 1.0, 0.0)
1538        elif "x" in plane and "z" in plane:
1539            cam.SetPosition(fp[0], fp[1] - dist, fp[2])
1540            cam.SetViewUp(0.0, 0.0, 1.0)
1541        elif "y" in plane and "z" in plane:
1542            cam.SetPosition(fp[0] + dist, fp[1], fp[2])
1543            cam.SetViewUp(0.0, 0.0, 1.0)
1544        else:
1545            vedo.logger.error(f"in plotter.look() cannot understand argument {plane}")
1546        return self

Move the camera so that it looks at the specified cartesian plane

def record(self, filename='') -> str:
1548    def record(self, filename="") -> str:
1549        """
1550        Record camera, mouse, keystrokes and all other events.
1551        Recording can be toggled on/off by pressing key "R".
1552
1553        Arguments:
1554            filename : (str)
1555                ascii file to store events.
1556                The default is `vedo.settings.cache_directory+"vedo/recorded_events.log"`.
1557
1558        Returns:
1559            a string descriptor of events.
1560
1561        Examples:
1562            - [record_play.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/record_play.py)
1563        """
1564        if vedo.settings.dry_run_mode >= 1:
1565            return ""
1566        if not self.interactor:
1567            vedo.logger.warning("Cannot record events, no interactor defined.")
1568            return ""
1569        erec = vtki.new("InteractorEventRecorder")
1570        erec.SetInteractor(self.interactor)
1571        if not filename:
1572            if not os.path.exists(vedo.settings.cache_directory):
1573                os.makedirs(vedo.settings.cache_directory)
1574            home_dir = os.path.expanduser("~")
1575            filename = os.path.join(
1576                home_dir, vedo.settings.cache_directory, "vedo", "recorded_events.log")
1577            print("Events will be recorded in", filename)
1578        erec.SetFileName(filename)
1579        erec.SetKeyPressActivationValue("R")
1580        erec.EnabledOn()
1581        erec.Record()
1582        self.interactor.Start()
1583        erec.Stop()
1584        erec.EnabledOff()
1585        with open(filename, "r", encoding="UTF-8") as fl:
1586            events = fl.read()
1587        erec = None
1588        return events

Record camera, mouse, keystrokes and all other events. Recording can be toggled on/off by pressing key "R".

Arguments:
  • filename : (str) ascii file to store events. The default is vedo.settings.cache_directory+"vedo/recorded_events.log".
Returns:

a string descriptor of events.

Examples:
def play(self, recorded_events='', repeats=0) -> Self:
1590    def play(self, recorded_events="", repeats=0) -> Self:
1591        """
1592        Play camera, mouse, keystrokes and all other events.
1593
1594        Arguments:
1595            events : (str)
1596                file o string of events.
1597                The default is `vedo.settings.cache_directory+"vedo/recorded_events.log"`.
1598            repeats : (int)
1599                number of extra repeats of the same events. The default is 0.
1600
1601        Examples:
1602            - [record_play.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/record_play.py)
1603        """
1604        if vedo.settings.dry_run_mode >= 1:
1605            return self
1606        if not self.interactor:
1607            vedo.logger.warning("Cannot play events, no interactor defined.")
1608            return self
1609
1610        erec = vtki.new("InteractorEventRecorder")
1611        erec.SetInteractor(self.interactor)
1612
1613        if not recorded_events:
1614            home_dir = os.path.expanduser("~")
1615            recorded_events = os.path.join(
1616                home_dir, vedo.settings.cache_directory, "vedo", "recorded_events.log")
1617
1618        if recorded_events.endswith(".log"):
1619            erec.ReadFromInputStringOff()
1620            erec.SetFileName(recorded_events)
1621        else:
1622            erec.ReadFromInputStringOn()
1623            erec.SetInputString(recorded_events)
1624
1625        erec.Play()
1626        for _ in range(repeats):
1627            erec.Rewind()
1628            erec.Play()
1629        erec.EnabledOff()
1630        erec = None
1631        return self

Play camera, mouse, keystrokes and all other events.

Arguments:
  • events : (str) file o string of events. The default is vedo.settings.cache_directory+"vedo/recorded_events.log".
  • repeats : (int) number of extra repeats of the same events. The default is 0.
Examples:
def parallel_projection(self, value=True, at=None) -> Self:
1633    def parallel_projection(self, value=True, at=None) -> Self:
1634        """
1635        Use parallel projection `at` a specified renderer.
1636        Object is seen from "infinite" distance, e.i. remove any perspective effects.
1637        An input value equal to -1 will toggle it on/off.
1638        """
1639        if at is not None:
1640            r = self.renderers[at]
1641        else:
1642            r = self.renderer
1643        if value == -1:
1644            val = r.GetActiveCamera().GetParallelProjection()
1645            value = not val
1646        r.GetActiveCamera().SetParallelProjection(value)
1647        r.Modified()
1648        return self

Use parallel projection at a specified renderer. Object is seen from "infinite" distance, e.i. remove any perspective effects. An input value equal to -1 will toggle it on/off.

def render_hidden_lines(self, value=True) -> Self:
1650    def render_hidden_lines(self, value=True) -> Self:
1651        """Remove hidden lines when in wireframe mode."""
1652        self.renderer.SetUseHiddenLineRemoval(not value)
1653        return self

Remove hidden lines when in wireframe mode.

def fov(self, angle: float) -> Self:
1655    def fov(self, angle: float) -> Self:
1656        """
1657        Set the field of view angle for the camera.
1658        This is the angle of the camera frustum in the horizontal direction.
1659        High values will result in a wide-angle lens (fish-eye effect),
1660        and low values will result in a telephoto lens.
1661
1662        Default value is 30 degrees.
1663        """
1664        self.renderer.GetActiveCamera().UseHorizontalViewAngleOn()
1665        self.renderer.GetActiveCamera().SetViewAngle(angle)
1666        return self

Set the field of view angle for the camera. This is the angle of the camera frustum in the horizontal direction. High values will result in a wide-angle lens (fish-eye effect), and low values will result in a telephoto lens.

Default value is 30 degrees.

def zoom(self, zoom: float) -> Self:
1668    def zoom(self, zoom: float) -> Self:
1669        """Apply a zooming factor for the current camera view"""
1670        self.renderer.GetActiveCamera().Zoom(zoom)
1671        return self

Apply a zooming factor for the current camera view

def azimuth(self, angle: float) -> Self:
1673    def azimuth(self, angle: float) -> Self:
1674        """Rotate camera around the view up vector."""
1675        self.renderer.GetActiveCamera().Azimuth(angle)
1676        return self

Rotate camera around the view up vector.

def elevation(self, angle: float) -> Self:
1678    def elevation(self, angle: float) -> Self:
1679        """Rotate the camera around the cross product of the negative
1680        of the direction of projection and the view up vector."""
1681        self.renderer.GetActiveCamera().Elevation(angle)
1682        return self

Rotate the camera around the cross product of the negative of the direction of projection and the view up vector.

def roll(self, angle: float) -> Self:
1684    def roll(self, angle: float) -> Self:
1685        """Roll the camera about the direction of projection."""
1686        self.renderer.GetActiveCamera().Roll(angle)
1687        return self

Roll the camera about the direction of projection.

def dolly(self, value: float) -> Self:
1689    def dolly(self, value: float) -> Self:
1690        """Move the camera towards (value>0) or away from (value<0) the focal point."""
1691        self.renderer.GetActiveCamera().Dolly(value)
1692        return self

Move the camera towards (value>0) or away from (value<0) the focal point.

def add_slider( self, sliderfunc, xmin, xmax, value=None, pos=4, title='', font='Calco', title_size=1, c=None, alpha=1, show_value=True, delayed=False, **options) -> vedo.addons.Slider2D:
1695    def add_slider(
1696        self,
1697        sliderfunc,
1698        xmin,
1699        xmax,
1700        value=None,
1701        pos=4,
1702        title="",
1703        font="Calco",
1704        title_size=1,
1705        c=None,
1706        alpha=1,
1707        show_value=True,
1708        delayed=False,
1709        **options,
1710    ) -> "vedo.addons.Slider2D":
1711        """
1712        Add a `vedo.addons.Slider2D` which can call an external custom function.
1713
1714        Arguments:
1715            sliderfunc : (Callable)
1716                external function to be called by the widget
1717            xmin : (float)
1718                lower value of the slider
1719            xmax : (float)
1720                upper value
1721            value : (float)
1722                current value
1723            pos : (list, str)
1724                position corner number: horizontal [1-5] or vertical [11-15]
1725                it can also be specified by corners coordinates [(x1,y1), (x2,y2)]
1726                and also by a string descriptor (eg. "bottom-left")
1727            title : (str)
1728                title text
1729            font : (str)
1730                title font face. Check [available fonts here](https://vedo.embl.es/fonts).
1731            title_size : (float)
1732                title text scale [1.0]
1733            show_value : (bool)
1734                if True current value is shown
1735            delayed : (bool)
1736                if True the callback is delayed until when the mouse button is released
1737            alpha : (float)
1738                opacity of the scalar bar texts
1739            slider_length : (float)
1740                slider length
1741            slider_width : (float)
1742                slider width
1743            end_cap_length : (float)
1744                length of the end cap
1745            end_cap_width : (float)
1746                width of the end cap
1747            tube_width : (float)
1748                width of the tube
1749            title_height : (float)
1750                width of the title
1751            tformat : (str)
1752                format of the title
1753
1754        Examples:
1755            - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py)
1756            - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py)
1757
1758            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
1759        """
1760        if c is None:  # automatic black or white
1761            c = (0.8, 0.8, 0.8)
1762            if np.sum(vedo.get_color(self.backgrcol)) > 1.5:
1763                c = (0.2, 0.2, 0.2)
1764        else:
1765            c = vedo.get_color(c)
1766
1767        slider2d = addons.Slider2D(
1768            sliderfunc,
1769            xmin,
1770            xmax,
1771            value,
1772            pos,
1773            title,
1774            font,
1775            title_size,
1776            c,
1777            alpha,
1778            show_value,
1779            delayed,
1780            **options,
1781        )
1782
1783        if self.renderer:
1784            slider2d.renderer = self.renderer
1785            if self.interactor:
1786                slider2d.interactor = self.interactor
1787                slider2d.on()
1788                self.sliders.append([slider2d, sliderfunc])
1789        return slider2d

Add a vedo.addons.Slider2D which can call an external custom function.

Arguments:
  • sliderfunc : (Callable) external function to be called by the widget
  • xmin : (float) lower value of the slider
  • xmax : (float) upper value
  • value : (float) current value
  • pos : (list, str) position corner number: horizontal [1-5] or vertical [11-15] it can also be specified by corners coordinates [(x1,y1), (x2,y2)] and also by a string descriptor (eg. "bottom-left")
  • title : (str) title text
  • font : (str) title font face. Check available fonts here.
  • title_size : (float) title text scale [1.0]
  • show_value : (bool) if True current value is shown
  • delayed : (bool) if True the callback is delayed until when the mouse button is released
  • alpha : (float) opacity of the scalar bar texts
  • slider_length : (float) slider length
  • slider_width : (float) slider width
  • end_cap_length : (float) length of the end cap
  • end_cap_width : (float) width of the end cap
  • tube_width : (float) width of the tube
  • title_height : (float) width of the title
  • tformat : (str) format of the title
Examples:

def add_slider3d( self, sliderfunc, pos1, pos2, xmin, xmax, value=None, s=0.03, t=1, title='', rotation=0.0, c=None, show_value=True) -> vedo.addons.Slider3D:
1791    def add_slider3d(
1792        self,
1793        sliderfunc,
1794        pos1,
1795        pos2,
1796        xmin,
1797        xmax,
1798        value=None,
1799        s=0.03,
1800        t=1,
1801        title="",
1802        rotation=0.0,
1803        c=None,
1804        show_value=True,
1805    ) -> "vedo.addons.Slider3D":
1806        """
1807        Add a 3D slider widget which can call an external custom function.
1808
1809        Arguments:
1810            sliderfunc : (function)
1811                external function to be called by the widget
1812            pos1 : (list)
1813                first position 3D coordinates
1814            pos2 : (list)
1815                second position coordinates
1816            xmin : (float)
1817                lower value
1818            xmax : (float)
1819                upper value
1820            value : (float)
1821                initial value
1822            s : (float)
1823                label scaling factor
1824            t : (float)
1825                tube scaling factor
1826            title : (str)
1827                title text
1828            c : (color)
1829                slider color
1830            rotation : (float)
1831                title rotation around slider axis
1832            show_value : (bool)
1833                if True current value is shown
1834
1835        Examples:
1836            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
1837
1838            ![](https://user-images.githubusercontent.com/32848391/52859555-4efcf200-312d-11e9-9290-6988c8295163.png)
1839        """
1840        if c is None:  # automatic black or white
1841            c = (0.8, 0.8, 0.8)
1842            if np.sum(vedo.get_color(self.backgrcol)) > 1.5:
1843                c = (0.2, 0.2, 0.2)
1844        else:
1845            c = vedo.get_color(c)
1846
1847        slider3d = addons.Slider3D(
1848            sliderfunc,
1849            pos1,
1850            pos2,
1851            xmin,
1852            xmax,
1853            value,
1854            s,
1855            t,
1856            title,
1857            rotation,
1858            c,
1859            show_value,
1860        )
1861        slider3d.renderer = self.renderer
1862        slider3d.interactor = self.interactor
1863        slider3d.on()
1864        self.sliders.append([slider3d, sliderfunc])
1865        return slider3d

Add a 3D slider widget which can call an external custom function.

Arguments:
  • sliderfunc : (function) external function to be called by the widget
  • pos1 : (list) first position 3D coordinates
  • pos2 : (list) second position coordinates
  • xmin : (float) lower value
  • xmax : (float) upper value
  • value : (float) initial value
  • s : (float) label scaling factor
  • t : (float) tube scaling factor
  • title : (str) title text
  • c : (color) slider color
  • rotation : (float) title rotation around slider axis
  • show_value : (bool) if True current value is shown
Examples:

def add_button( self, fnc=None, states=('On', 'Off'), c=('w', 'w'), bc=('green4', 'red4'), pos=(0.7, 0.1), size=24, font='Courier', bold=True, italic=False, alpha=1, angle=0) -> Optional[vedo.addons.Button]:
1867    def add_button(
1868        self,
1869        fnc=None,
1870        states=("On", "Off"),
1871        c=("w", "w"),
1872        bc=("green4", "red4"),
1873        pos=(0.7, 0.1),
1874        size=24,
1875        font="Courier",
1876        bold=True,
1877        italic=False,
1878        alpha=1,
1879        angle=0,
1880    ) -> Union["vedo.addons.Button", None]:
1881        """
1882        Add a button to the renderer window.
1883
1884        Arguments:
1885            states : (list)
1886                a list of possible states, e.g. ['On', 'Off']
1887            c : (list)
1888                a list of colors for each state
1889            bc : (list)
1890                a list of background colors for each state
1891            pos : (list)
1892                2D position from left-bottom corner
1893            size : (float)
1894                size of button font
1895            font : (str)
1896                font type. Check [available fonts here](https://vedo.embl.es/fonts).
1897            bold : (bool)
1898                bold font face (False)
1899            italic : (bool)
1900                italic font face (False)
1901            alpha : (float)
1902                opacity level
1903            angle : (float)
1904                anticlockwise rotation in degrees
1905
1906        Returns:
1907            `vedo.addons.Button` object.
1908
1909        Examples:
1910            - [buttons1.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/buttons1.py)
1911            - [buttons2.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/buttons2.py)
1912
1913            ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
1914        """
1915        if self.interactor:
1916            bu = addons.Button(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle)
1917            self.renderer.AddActor2D(bu)
1918            self.buttons.append(bu)
1919            # bu.function_id = self.add_callback("LeftButtonPress", bu.function) # not good
1920            bu.function_id = bu.add_observer("pick", bu.function, priority=10)
1921            return bu
1922        return None

Add a button to the renderer window.

Arguments:
  • states : (list) a list of possible states, e.g. ['On', 'Off']
  • c : (list) a list of colors for each state
  • bc : (list) a list of background colors for each state
  • pos : (list) 2D position from left-bottom corner
  • size : (float) size of button font
  • font : (str) font type. Check available fonts here.
  • bold : (bool) bold font face (False)
  • italic : (bool) italic font face (False)
  • alpha : (float) opacity level
  • angle : (float) anticlockwise rotation in degrees
Returns:

vedo.addons.Button object.

Examples:

def add_spline_tool( self, points, pc='k', ps=8, lc='r4', ac='g5', lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True) -> vedo.addons.SplineTool:
1924    def add_spline_tool(
1925        self, points, pc="k", ps=8, lc="r4", ac="g5",
1926        lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True,
1927    ) -> "vedo.addons.SplineTool":
1928        """
1929        Add a spline tool to the current plotter.
1930        Nodes of the spline can be dragged in space with the mouse.
1931        Clicking on the line itself adds an extra point.
1932        Selecting a point and pressing del removes it.
1933
1934        Arguments:
1935            points : (Mesh, Points, array)
1936                the set of vertices forming the spline nodes.
1937            pc : (str)
1938                point color. The default is 'k'.
1939            ps : (str)
1940                point size. The default is 8.
1941            lc : (str)
1942                line color. The default is 'r4'.
1943            ac : (str)
1944                active point marker color. The default is 'g5'.
1945            lw : (int)
1946                line width. The default is 2.
1947            alpha : (float)
1948                line transparency.
1949            closed : (bool)
1950                spline is meant to be closed. The default is False.
1951
1952        Returns:
1953            a `SplineTool` object.
1954
1955        Examples:
1956            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
1957
1958            ![](https://vedo.embl.es/images/basic/spline_tool.png)
1959        """
1960        sw = addons.SplineTool(points, pc, ps, lc, ac, lw, alpha, closed, ontop, can_add_nodes)
1961        sw.interactor = self.interactor
1962        sw.on()
1963        sw.Initialize(sw.points.dataset)
1964        sw.representation.SetRenderer(self.renderer)
1965        sw.representation.SetClosedLoop(closed)
1966        sw.representation.BuildRepresentation()
1967        self.widgets.append(sw)
1968        return sw

Add a spline tool to the current plotter. Nodes of the spline can be dragged in space with the mouse. Clicking on the line itself adds an extra point. Selecting a point and pressing del removes it.

Arguments:
  • points : (Mesh, Points, array) the set of vertices forming the spline nodes.
  • pc : (str) point color. The default is 'k'.
  • ps : (str) point size. The default is 8.
  • lc : (str) line color. The default is 'r4'.
  • ac : (str) active point marker color. The default is 'g5'.
  • lw : (int) line width. The default is 2.
  • alpha : (float) line transparency.
  • closed : (bool) spline is meant to be closed. The default is False.
Returns:

a SplineTool object.

Examples:

def add_icon(self, icon, pos=3, size=0.08) -> vedo.addons.Icon:
1970    def add_icon(self, icon, pos=3, size=0.08) -> "vedo.addons.Icon":
1971        """Add an inset icon mesh into the same renderer.
1972
1973        Arguments:
1974            pos : (int, list)
1975                icon position in the range [1-4] indicating one of the 4 corners,
1976                or it can be a tuple (x,y) as a fraction of the renderer size.
1977            size : (float)
1978                size of the square inset.
1979
1980        Examples:
1981            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
1982        """
1983        iconw = addons.Icon(icon, pos, size)
1984
1985        iconw.SetInteractor(self.interactor)
1986        iconw.EnabledOn()
1987        iconw.InteractiveOff()
1988        self.widgets.append(iconw)
1989        return iconw

Add an inset icon mesh into the same renderer.

Arguments:
  • pos : (int, list) icon position in the range [1-4] indicating one of the 4 corners, or it can be a tuple (x,y) as a fraction of the renderer size.
  • size : (float) size of the square inset.
Examples:
def add_global_axes(self, axtype=None, c=None) -> Self:
1991    def add_global_axes(self, axtype=None, c=None) -> Self:
1992        """Draw axes on scene. Available axes types:
1993
1994        Arguments:
1995            axtype : (int)
1996                - 0,  no axes,
1997                - 1,  draw three gray grid walls
1998                - 2,  show cartesian axes from (0,0,0)
1999                - 3,  show positive range of cartesian axes from (0,0,0)
2000                - 4,  show a triad at bottom left
2001                - 5,  show a cube at bottom left
2002                - 6,  mark the corners of the bounding box
2003                - 7,  draw a 3D ruler at each side of the cartesian axes
2004                - 8,  show the vtkCubeAxesActor object
2005                - 9,  show the bounding box outLine
2006                - 10, show three circles representing the maximum bounding box
2007                - 11, show a large grid on the x-y plane
2008                - 12, show polar axes
2009                - 13, draw a simple ruler at the bottom of the window
2010
2011            Axis type-1 can be fully customized by passing a dictionary axes=dict().
2012
2013        Example:
2014            ```python
2015            from vedo import Box, show
2016            b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1)
2017            show(
2018                b,
2019                axes={
2020                    "xtitle": "Some long variable [a.u.]",
2021                    "number_of_divisions": 4,
2022                    # ...
2023                },
2024            )
2025            ```
2026
2027        Examples:
2028            - [custom_axes1.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes1.py)
2029            - [custom_axes2.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes2.py)
2030            - [custom_axes3.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes3.py)
2031            - [custom_axes4.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes4.py)
2032
2033            <img src="https://user-images.githubusercontent.com/32848391/72752870-ab7d5280-3bc3-11ea-8911-9ace00211e23.png" width="600">
2034        """
2035        addons.add_global_axes(axtype, c)
2036        return self

Draw axes on scene. Available axes types:

Arguments:
  • axtype : (int)
    • 0, no axes,
    • 1, draw three gray grid walls
    • 2, show cartesian axes from (0,0,0)
    • 3, show positive range of cartesian axes from (0,0,0)
    • 4, show a triad at bottom left
    • 5, show a cube at bottom left
    • 6, mark the corners of the bounding box
    • 7, draw a 3D ruler at each side of the cartesian axes
    • 8, show the vtkCubeAxesActor object
    • 9, show the bounding box outLine
    • 10, show three circles representing the maximum bounding box
    • 11, show a large grid on the x-y plane
    • 12, show polar axes
    • 13, draw a simple ruler at the bottom of the window
  • Axis type-1 can be fully customized by passing a dictionary axes=dict().
Example:
from vedo import Box, show
b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1)
show(
    b,
    axes={
        "xtitle": "Some long variable [a.u.]",
        "number_of_divisions": 4,
        # ...
    },
)
Examples:

def add_legend_box(self, **kwargs) -> vedo.addons.LegendBox:
2038    def add_legend_box(self, **kwargs) -> "vedo.addons.LegendBox":
2039        """Add a legend to the top right.
2040
2041        Examples:
2042            - [legendbox.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/basic/legendbox.py),
2043            - [flag_labels1.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels1.py)
2044            - [flag_labels2.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels2.py)
2045        """
2046        acts = self.get_meshes()
2047        lb = addons.LegendBox(acts, **kwargs)
2048        self.add(lb)
2049        return lb

Add a legend to the top right.

Examples:
def add_hint( self, obj, text='', c='k', bg='yellow9', font='Calco', size=18, justify=0, angle=0, delay=250) -> Optional[vtkmodules.vtkInteractionWidgets.vtkBalloonWidget]:
2051    def add_hint(
2052        self,
2053        obj,
2054        text="",
2055        c="k",
2056        bg="yellow9",
2057        font="Calco",
2058        size=18,
2059        justify=0,
2060        angle=0,
2061        delay=250,
2062    ) -> Union[vtki.vtkBalloonWidget, None]:
2063        """
2064        Create a pop-up hint style message when hovering an object.
2065        Use `add_hint(obj, False)` to disable a hinting a specific object.
2066        Use `add_hint(None)` to disable all hints.
2067
2068        Arguments:
2069            obj : (Mesh, Points)
2070                the object to associate the pop-up to
2071            text : (str)
2072                string description of the pop-up
2073            delay : (int)
2074                milliseconds to wait before pop-up occurs
2075        """
2076        if self.offscreen or not self.interactor:
2077            return None
2078
2079        if vedo.vtk_version[:2] == (9, 0) and "Linux" in vedo.sys_platform:
2080            # Linux vtk9.0 is bugged
2081            vedo.logger.warning(
2082                f"add_hint() is not available on Linux platforms for vtk{vedo.vtk_version}."
2083            )
2084            return None
2085
2086        if obj is None:
2087            self.hint_widget.EnabledOff()
2088            self.hint_widget.SetInteractor(None)
2089            self.hint_widget = None
2090            return self.hint_widget
2091
2092        if text is False and self.hint_widget:
2093            self.hint_widget.RemoveBalloon(obj)
2094            return self.hint_widget
2095
2096        if text == "":
2097            if obj.name:
2098                text = obj.name
2099            elif obj.filename:
2100                text = obj.filename
2101            else:
2102                return None
2103
2104        if not self.hint_widget:
2105            self.hint_widget = vtki.vtkBalloonWidget()
2106
2107            rep = self.hint_widget.GetRepresentation()
2108            rep.SetBalloonLayoutToImageRight()
2109
2110            trep = rep.GetTextProperty()
2111            trep.SetFontFamily(vtki.VTK_FONT_FILE)
2112            trep.SetFontFile(utils.get_font_path(font))
2113            trep.SetFontSize(size)
2114            trep.SetColor(vedo.get_color(c))
2115            trep.SetBackgroundColor(vedo.get_color(bg))
2116            trep.SetShadow(0)
2117            trep.SetJustification(justify)
2118            trep.UseTightBoundingBoxOn()
2119
2120            self.hint_widget.ManagesCursorOff()
2121            self.hint_widget.SetTimerDuration(delay)
2122            self.hint_widget.SetInteractor(self.interactor)
2123            if angle:
2124                trep.SetOrientation(angle)
2125                trep.SetBackgroundOpacity(0)
2126            # else:
2127            #     trep.SetBackgroundOpacity(0.5) # doesnt work well
2128            self.hint_widget.SetRepresentation(rep)
2129            self.widgets.append(self.hint_widget)
2130            self.hint_widget.EnabledOn()
2131
2132        bst = self.hint_widget.GetBalloonString(obj.actor)
2133        if bst:
2134            self.hint_widget.UpdateBalloonString(obj.actor, text)
2135        else:
2136            self.hint_widget.AddBalloon(obj.actor, text)
2137
2138        return self.hint_widget

Create a pop-up hint style message when hovering an object. Use add_hint(obj, False) to disable a hinting a specific object. Use add_hint(None) to disable all hints.

Arguments:
  • obj : (Mesh, Points) the object to associate the pop-up to
  • text : (str) string description of the pop-up
  • delay : (int) milliseconds to wait before pop-up occurs
def add_shadows(self) -> Self:
2140    def add_shadows(self) -> Self:
2141        """Add shadows at the current renderer."""
2142        if self.renderer:
2143            shadows = vtki.new("ShadowMapPass")
2144            seq = vtki.new("SequencePass")
2145            passes = vtki.new("RenderPassCollection")
2146            passes.AddItem(shadows.GetShadowMapBakerPass())
2147            passes.AddItem(shadows)
2148            seq.SetPasses(passes)
2149            camerapass = vtki.new("CameraPass")
2150            camerapass.SetDelegatePass(seq)
2151            self.renderer.SetPass(camerapass)
2152        return self

Add shadows at the current renderer.

def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
2154    def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
2155        """
2156        Screen Space Ambient Occlusion.
2157
2158        For every pixel on the screen, the pixel shader samples the depth values around
2159        the current pixel and tries to compute the amount of occlusion from each of the sampled
2160        points.
2161
2162        Arguments:
2163            radius : (float)
2164                radius of influence in absolute units
2165            bias : (float)
2166                bias of the normals
2167            blur : (bool)
2168                add a blurring to the sampled positions
2169            samples : (int)
2170                number of samples to probe
2171
2172        Examples:
2173            - [ssao.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ssao.py)
2174
2175            ![](https://vedo.embl.es/images/basic/ssao.jpg)
2176        """
2177        lights = vtki.new("LightsPass")
2178
2179        opaque = vtki.new("OpaquePass")
2180
2181        ssaoCam = vtki.new("CameraPass")
2182        ssaoCam.SetDelegatePass(opaque)
2183
2184        ssao = vtki.new("SSAOPass")
2185        ssao.SetRadius(radius)
2186        ssao.SetBias(bias)
2187        ssao.SetBlur(blur)
2188        ssao.SetKernelSize(samples)
2189        ssao.SetDelegatePass(ssaoCam)
2190
2191        translucent = vtki.new("TranslucentPass")
2192
2193        volpass = vtki.new("VolumetricPass")
2194        ddp = vtki.new("DualDepthPeelingPass")
2195        ddp.SetTranslucentPass(translucent)
2196        ddp.SetVolumetricPass(volpass)
2197
2198        over = vtki.new("OverlayPass")
2199
2200        collection = vtki.new("RenderPassCollection")
2201        collection.AddItem(lights)
2202        collection.AddItem(ssao)
2203        collection.AddItem(ddp)
2204        collection.AddItem(over)
2205
2206        sequence = vtki.new("SequencePass")
2207        sequence.SetPasses(collection)
2208
2209        cam = vtki.new("CameraPass")
2210        cam.SetDelegatePass(sequence)
2211
2212        self.renderer.SetPass(cam)
2213        return self

Screen Space Ambient Occlusion.

For every pixel on the screen, the pixel shader samples the depth values around the current pixel and tries to compute the amount of occlusion from each of the sampled points.

Arguments:
  • radius : (float) radius of influence in absolute units
  • bias : (float) bias of the normals
  • blur : (bool) add a blurring to the sampled positions
  • samples : (int) number of samples to probe
Examples:

def add_depth_of_field(self, autofocus=True) -> Self:
2215    def add_depth_of_field(self, autofocus=True) -> Self:
2216        """Add a depth of field effect in the scene."""
2217        lights = vtki.new("LightsPass")
2218
2219        opaque = vtki.new("OpaquePass")
2220
2221        dofCam = vtki.new("CameraPass")
2222        dofCam.SetDelegatePass(opaque)
2223
2224        dof = vtki.new("DepthOfFieldPass")
2225        dof.SetAutomaticFocalDistance(autofocus)
2226        dof.SetDelegatePass(dofCam)
2227
2228        collection = vtki.new("RenderPassCollection")
2229        collection.AddItem(lights)
2230        collection.AddItem(dof)
2231
2232        sequence = vtki.new("SequencePass")
2233        sequence.SetPasses(collection)
2234
2235        cam = vtki.new("CameraPass")
2236        cam.SetDelegatePass(sequence)
2237
2238        self.renderer.SetPass(cam)
2239        return self

Add a depth of field effect in the scene.

def add_renderer_frame( self, c=None, alpha=None, lw=None, padding=None) -> vedo.addons.RendererFrame:
2270    def add_renderer_frame(self, c=None, alpha=None, lw=None, padding=None) -> "vedo.addons.RendererFrame":
2271        """
2272        Add a frame to the renderer subwindow.
2273
2274        Arguments:
2275            c : (color)
2276                color name or index
2277            alpha : (float)
2278                opacity level
2279            lw : (int)
2280                line width in pixels.
2281            padding : (float)
2282                padding space in pixels.
2283        """
2284        if c is None:  # automatic black or white
2285            c = (0.9, 0.9, 0.9)
2286            if self.renderer:
2287                if np.sum(self.renderer.GetBackground()) > 1.5:
2288                    c = (0.1, 0.1, 0.1)
2289        renf = addons.RendererFrame(c, alpha, lw, padding)
2290        if renf:
2291            self.renderer.AddActor(renf)
2292        return renf

Add a frame to the renderer subwindow.

Arguments:
  • c : (color) color name or index
  • alpha : (float) opacity level
  • lw : (int) line width in pixels.
  • padding : (float) padding space in pixels.
def add_hover_legend( self, at=None, c=None, pos='bottom-left', font='Calco', s=0.75, bg='auto', alpha=0.1, maxlength=24, use_info=False) -> int:
2294    def add_hover_legend(
2295        self,
2296        at=None,
2297        c=None,
2298        pos="bottom-left",
2299        font="Calco",
2300        s=0.75,
2301        bg="auto",
2302        alpha=0.1,
2303        maxlength=24,
2304        use_info=False,
2305    ) -> int:
2306        """
2307        Add a legend with 2D text which is triggered by hovering the mouse on an object.
2308
2309        The created text object are stored in `plotter.hover_legends`.
2310
2311        Returns:
2312            the id of the callback function.
2313
2314        Arguments:
2315            c : (color)
2316                Text color. If None then black or white is chosen automatically
2317            pos : (str)
2318                text positioning
2319            font : (str)
2320                text font type. Check [available fonts here](https://vedo.embl.es/fonts).
2321            s : (float)
2322                text size scale
2323            bg : (color)
2324                background color of the 2D box containing the text
2325            alpha : (float)
2326                box transparency
2327            maxlength : (int)
2328                maximum number of characters per line
2329            use_info : (bool)
2330                visualize the content of the `obj.info` attribute
2331
2332        Examples:
2333            - [hover_legend.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/hover_legend.py)
2334            - [earthquake_browser.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/earthquake_browser.py)
2335
2336            ![](https://vedo.embl.es/images/pyplot/earthquake_browser.jpg)
2337        """
2338        hoverlegend = vedo.shapes.Text2D(pos=pos, font=font, c=c, s=s, alpha=alpha, bg=bg)
2339
2340        if at is None:
2341            at = self.renderers.index(self.renderer)
2342
2343        def _legfunc(evt):
2344            if not evt.object or not self.renderer or at != evt.at:
2345                if hoverlegend.mapper.GetInput():  # clear and return
2346                    hoverlegend.mapper.SetInput("")
2347                    self.render()
2348                return
2349
2350            if use_info:
2351                if hasattr(evt.object, "info"):
2352                    t = str(evt.object.info)
2353                else:
2354                    return
2355            else:
2356                t, tp = "", ""
2357                if evt.isMesh:
2358                    tp = "Mesh "
2359                elif evt.isPoints:
2360                    tp = "Points "
2361                elif evt.isVolume:
2362                    tp = "Volume "
2363                elif evt.isImage:
2364                    tp = "Image "
2365                elif evt.isAssembly:
2366                    tp = "Assembly "
2367                else:
2368                    return
2369
2370                if evt.isAssembly:
2371                    if not evt.object.name:
2372                        t += f"Assembly object of {len(evt.object.unpack())} parts\n"
2373                    else:
2374                        t += f"Assembly name: {evt.object.name} ({len(evt.object.unpack())} parts)\n"
2375                else:
2376                    if evt.object.name:
2377                        t += f"{tp}name"
2378                        if evt.isPoints:
2379                            t += "  "
2380                        if evt.isMesh:
2381                            t += "  "
2382                        t += f": {evt.object.name[:maxlength]}".ljust(maxlength) + "\n"
2383
2384                if evt.object.filename:
2385                    t += f"{tp}filename: "
2386                    t += f"{os.path.basename(evt.object.filename[-maxlength:])}".ljust(maxlength)
2387                    t += "\n"
2388                    if not evt.object.file_size:
2389                        evt.object.file_size, evt.object.created = vedo.file_io.file_info(evt.object.filename)
2390                    if evt.object.file_size:
2391                        t += "             : "
2392                        sz, created = evt.object.file_size, evt.object.created
2393                        t += f"{created[4:-5]} ({sz})" + "\n"
2394
2395                if evt.isPoints:
2396                    indata = evt.object.dataset
2397                    if indata.GetNumberOfPoints():
2398                        t += (
2399                            f"#points/cells: {indata.GetNumberOfPoints()}"
2400                            f" / {indata.GetNumberOfCells()}"
2401                        )
2402                    pdata = indata.GetPointData()
2403                    cdata = indata.GetCellData()
2404                    if pdata.GetScalars() and pdata.GetScalars().GetName():
2405                        t += f"\nPoint array  : {pdata.GetScalars().GetName()}"
2406                        if pdata.GetScalars().GetName() == evt.object.mapper.GetArrayName():
2407                            t += " *"
2408                    if cdata.GetScalars() and cdata.GetScalars().GetName():
2409                        t += f"\nCell  array  : {cdata.GetScalars().GetName()}"
2410                        if cdata.GetScalars().GetName() == evt.object.mapper.GetArrayName():
2411                            t += " *"
2412
2413                if evt.isImage:
2414                    t = f"{os.path.basename(evt.object.filename[:maxlength+10])}".ljust(maxlength+10)
2415                    t += f"\nImage shape: {evt.object.shape}"
2416                    pcol = self.color_picker(evt.picked2d)
2417                    t += f"\nPixel color: {vedo.colors.rgb2hex(pcol/255)} {pcol}"
2418
2419            # change box color if needed in 'auto' mode
2420            if evt.isPoints and "auto" in str(bg):
2421                actcol = evt.object.properties.GetColor()
2422                if hoverlegend.mapper.GetTextProperty().GetBackgroundColor() != actcol:
2423                    hoverlegend.mapper.GetTextProperty().SetBackgroundColor(actcol)
2424
2425            # adapt to changes in bg color
2426            bgcol = self.renderers[at].GetBackground()
2427            _bgcol = c
2428            if _bgcol is None:  # automatic black or white
2429                _bgcol = (0.9, 0.9, 0.9)
2430                if sum(bgcol) > 1.5:
2431                    _bgcol = (0.1, 0.1, 0.1)
2432                if len(set(_bgcol).intersection(bgcol)) < 3:
2433                    hoverlegend.color(_bgcol)
2434
2435            if hoverlegend.mapper.GetInput() != t:
2436                hoverlegend.mapper.SetInput(t)
2437                self.interactor.Render()
2438            
2439            # print("ABORT", idcall, hoverlegend.actor.GetCommand(idcall))
2440            # hoverlegend.actor.GetCommand(idcall).AbortFlagOn()
2441
2442        self.add(hoverlegend, at=at)
2443        self.hover_legends.append(hoverlegend)
2444        idcall = self.add_callback("MouseMove", _legfunc)
2445        return idcall

Add a legend with 2D text which is triggered by hovering the mouse on an object.

The created text object are stored in plotter.hover_legends.

Returns:

the id of the callback function.

Arguments:
  • c : (color) Text color. If None then black or white is chosen automatically
  • pos : (str) text positioning
  • font : (str) text font type. Check available fonts here.
  • s : (float) text size scale
  • bg : (color) background color of the 2D box containing the text
  • alpha : (float) box transparency
  • maxlength : (int) maximum number of characters per line
  • use_info : (bool) visualize the content of the obj.info attribute
Examples:

def add_scale_indicator( self, pos=(0.7, 0.05), s=0.02, length=2, lw=4, c='k1', alpha=1, units='', gap=0.05) -> Optional[vedo.visual.Actor2D]:
2447    def add_scale_indicator(
2448        self,
2449        pos=(0.7, 0.05),
2450        s=0.02,
2451        length=2,
2452        lw=4,
2453        c="k1",
2454        alpha=1,
2455        units="",
2456        gap=0.05,
2457    ) -> Union["vedo.visual.Actor2D", None]:
2458        """
2459        Add a Scale Indicator. Only works in parallel mode (no perspective).
2460
2461        Arguments:
2462            pos : (list)
2463                fractional (x,y) position on the screen.
2464            s : (float)
2465                size of the text.
2466            length : (float)
2467                length of the line.
2468            units : (str)
2469                string to show units.
2470            gap : (float)
2471                separation of line and text.
2472
2473        Example:
2474            ```python
2475            from vedo import settings, Cube, Plotter
2476            settings.use_parallel_projection = True # or else it does not make sense!
2477            cube = Cube().alpha(0.2)
2478            plt = Plotter(size=(900,600), axes=dict(xtitle='x (um)'))
2479            plt.add_scale_indicator(units='um', c='blue4')
2480            plt.show(cube, "Scale indicator with units").close()
2481            ```
2482            ![](https://vedo.embl.es/images/feats/scale_indicator.png)
2483        """
2484        # Note that this cannot go in addons.py
2485        # because it needs callbacks and window size
2486        if not self.interactor:
2487            return None
2488
2489        ppoints = vtki.vtkPoints()  # Generate the polyline
2490        psqr = [[0.0, gap], [length / 10, gap]]
2491        dd = psqr[1][0] - psqr[0][0]
2492        for i, pt in enumerate(psqr):
2493            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2494        lines = vtki.vtkCellArray()
2495        lines.InsertNextCell(len(psqr))
2496        for i in range(len(psqr)):
2497            lines.InsertCellPoint(i)
2498        pd = vtki.vtkPolyData()
2499        pd.SetPoints(ppoints)
2500        pd.SetLines(lines)
2501
2502        wsx, wsy = self.window.GetSize()
2503        if not self.camera.GetParallelProjection():
2504            vedo.logger.warning("add_scale_indicator called with use_parallel_projection OFF. Skip.")
2505            return None
2506
2507        rlabel = vtki.new("VectorText")
2508        rlabel.SetText("scale")
2509        tf = vtki.new("TransformPolyDataFilter")
2510        tf.SetInputConnection(rlabel.GetOutputPort())
2511        t = vtki.vtkTransform()
2512        t.Scale(s * wsy / wsx, s, 1)
2513        tf.SetTransform(t)
2514
2515        app = vtki.new("AppendPolyData")
2516        app.AddInputConnection(tf.GetOutputPort())
2517        app.AddInputData(pd)
2518
2519        mapper = vtki.new("PolyDataMapper2D")
2520        mapper.SetInputConnection(app.GetOutputPort())
2521        cs = vtki.vtkCoordinate()
2522        cs.SetCoordinateSystem(1)
2523        mapper.SetTransformCoordinate(cs)
2524
2525        fractor = vedo.visual.Actor2D()
2526        csys = fractor.GetPositionCoordinate()
2527        csys.SetCoordinateSystem(3)
2528        fractor.SetPosition(pos)
2529        fractor.SetMapper(mapper)
2530        fractor.GetProperty().SetColor(vedo.get_color(c))
2531        fractor.GetProperty().SetOpacity(alpha)
2532        fractor.GetProperty().SetLineWidth(lw)
2533        fractor.GetProperty().SetDisplayLocationToForeground()
2534
2535        def sifunc(iren, ev):
2536            wsx, wsy = self.window.GetSize()
2537            ps = self.camera.GetParallelScale()
2538            newtxt = utils.precision(ps / wsy * wsx * length * dd, 3)
2539            if units:
2540                newtxt += " " + units
2541            if rlabel.GetText() != newtxt:
2542                rlabel.SetText(newtxt)
2543
2544        self.renderer.AddActor(fractor)
2545        self.interactor.AddObserver("MouseWheelBackwardEvent", sifunc)
2546        self.interactor.AddObserver("MouseWheelForwardEvent", sifunc)
2547        self.interactor.AddObserver("InteractionEvent", sifunc)
2548        sifunc(0, 0)
2549        return fractor

Add a Scale Indicator. Only works in parallel mode (no perspective).

Arguments:
  • pos : (list) fractional (x,y) position on the screen.
  • s : (float) size of the text.
  • length : (float) length of the line.
  • units : (str) string to show units.
  • gap : (float) separation of line and text.
Example:
from vedo import settings, Cube, Plotter
settings.use_parallel_projection = True # or else it does not make sense!
cube = Cube().alpha(0.2)
plt = Plotter(size=(900,600), axes=dict(xtitle='x (um)'))
plt.add_scale_indicator(units='um', c='blue4')
plt.show(cube, "Scale indicator with units")close()

def fill_event(self, ename='', pos=(), enable_picking=True) -> vedo.plotter.Event:
2551    def fill_event(self, ename="", pos=(), enable_picking=True) -> "Event":
2552        """
2553        Create an Event object with information of what was clicked.
2554
2555        If `enable_picking` is False, no picking will be performed.
2556        This can be useful to avoid double picking when using buttons.
2557        """
2558        if not self.interactor:
2559            return Event()
2560
2561        if len(pos) > 0:
2562            x, y = pos
2563            self.interactor.SetEventPosition(pos)
2564        else:
2565            x, y = self.interactor.GetEventPosition()
2566        self.renderer = self.interactor.FindPokedRenderer(x, y)
2567
2568        self.picked2d = (x, y)
2569
2570        key = self.interactor.GetKeySym()
2571
2572        if key:
2573            if "_L" in key or "_R" in key:
2574                # skip things like Shift_R
2575                key = ""  # better than None
2576            else:
2577                if self.interactor.GetShiftKey():
2578                    key = key.upper()
2579
2580                if key == "MINUS":  # fix: vtk9 is ignoring shift chars..
2581                    key = "underscore"
2582                elif key == "EQUAL":  # fix: vtk9 is ignoring shift chars..
2583                    key = "plus"
2584                elif key == "SLASH":  # fix: vtk9 is ignoring shift chars..
2585                    key = "?"
2586
2587                if self.interactor.GetControlKey():
2588                    key = "Ctrl+" + key
2589
2590                if self.interactor.GetAltKey():
2591                    key = "Alt+" + key
2592
2593        if enable_picking:
2594            if not self.picker:
2595                self.picker = vtki.vtkPropPicker()
2596
2597            self.picker.PickProp(x, y, self.renderer)
2598            actor = self.picker.GetProp3D()
2599            # Note that GetProp3D already picks Assembly
2600
2601            xp, yp = self.interactor.GetLastEventPosition()
2602            dx, dy = x - xp, y - yp
2603
2604            delta3d = np.array([0, 0, 0])
2605
2606            if actor:
2607                picked3d = np.array(self.picker.GetPickPosition())
2608
2609                try:
2610                    vobj = actor.retrieve_object()
2611                    old_pt = np.asarray(vobj.picked3d)
2612                    vobj.picked3d = picked3d
2613                    delta3d = picked3d - old_pt
2614                except (AttributeError, TypeError):
2615                    pass
2616
2617            else:
2618                picked3d = None
2619
2620            if not actor:  # try 2D
2621                actor = self.picker.GetActor2D()
2622
2623        event = Event()
2624        event.name = ename
2625        event.title = self.title
2626        event.id = -1  # will be set by the timer wrapper function
2627        event.timerid = -1  # will be set by the timer wrapper function
2628        event.priority = -1  # will be set by the timer wrapper function
2629        event.time = time.time()
2630        event.at = self.renderers.index(self.renderer)
2631        event.keypress = key
2632        if enable_picking:
2633            try:
2634                event.object = actor.retrieve_object()
2635            except AttributeError:
2636                event.object = actor
2637            try:
2638                event.actor = actor.retrieve_object()  # obsolete use object instead
2639            except AttributeError:
2640                event.actor = actor
2641            event.picked3d = picked3d
2642            event.picked2d = (x, y)
2643            event.delta2d = (dx, dy)
2644            event.angle2d = np.arctan2(dy, dx)
2645            event.speed2d = np.sqrt(dx * dx + dy * dy)
2646            event.delta3d = delta3d
2647            event.speed3d = np.sqrt(np.dot(delta3d, delta3d))
2648            event.isPoints = isinstance(event.object, vedo.Points)
2649            event.isMesh = isinstance(event.object, vedo.Mesh)
2650            event.isAssembly = isinstance(event.object, vedo.Assembly)
2651            event.isVolume = isinstance(event.object, vedo.Volume)
2652            event.isImage = isinstance(event.object, vedo.Image)
2653            event.isActor2D = isinstance(event.object, vtki.vtkActor2D)
2654        return event

Create an Event object with information of what was clicked.

If enable_picking is False, no picking will be performed. This can be useful to avoid double picking when using buttons.

def add_callback( self, event_name: str, func: Callable, priority=0.0, enable_picking=True) -> int:
2656    def add_callback(self, event_name: str, func: Callable, priority=0.0, enable_picking=True) -> int:
2657        """
2658        Add a function to be executed while show() is active.
2659
2660        Return a unique id for the callback.
2661
2662        The callback function (see example below) exposes a dictionary
2663        with the following information:
2664        - `name`: event name,
2665        - `id`: event unique identifier,
2666        - `priority`: event priority (float),
2667        - `interactor`: the interactor object,
2668        - `at`: renderer nr. where the event occurred
2669        - `keypress`: key pressed as string
2670        - `actor`: object picked by the mouse
2671        - `picked3d`: point picked in world coordinates
2672        - `picked2d`: screen coords of the mouse pointer
2673        - `delta2d`: shift wrt previous position (to calculate speed, direction)
2674        - `delta3d`: ...same but in 3D world coords
2675        - `angle2d`: angle of mouse movement on screen
2676        - `speed2d`: speed of mouse movement on screen
2677        - `speed3d`: speed of picked point in world coordinates
2678        - `isPoints`: True if of class
2679        - `isMesh`: True if of class
2680        - `isAssembly`: True if of class
2681        - `isVolume`: True if of class Volume
2682        - `isImage`: True if of class
2683
2684        If `enable_picking` is False, no picking will be performed.
2685        This can be useful to avoid double picking when using buttons.
2686
2687        Frequently used events are:
2688        - `KeyPress`, `KeyRelease`: listen to keyboard events
2689        - `LeftButtonPress`, `LeftButtonRelease`: listen to mouse clicks
2690        - `MiddleButtonPress`, `MiddleButtonRelease`
2691        - `RightButtonPress`, `RightButtonRelease`
2692        - `MouseMove`: listen to mouse pointer changing position
2693        - `MouseWheelForward`, `MouseWheelBackward`
2694        - `Enter`, `Leave`: listen to mouse entering or leaving the window
2695        - `Pick`, `StartPick`, `EndPick`: listen to object picking
2696        - `ResetCamera`, `ResetCameraClippingRange`
2697        - `Error`, `Warning`
2698        - `Char`
2699        - `Timer`
2700
2701        Check the complete list of events [here](https://vtk.org/doc/nightly/html/classvtkCommand.html).
2702
2703        Example:
2704            ```python
2705            from vedo import *
2706
2707            def func(evt):
2708                # this function is called every time the mouse moves
2709                # (evt is a dotted dictionary)
2710                if not evt.object:
2711                    return  # no hit, return
2712                print("point coords =", evt.picked3d)
2713                # print(evt) # full event dump
2714
2715            elli = Ellipsoid()
2716            plt = Plotter(axes=1)
2717            plt.add_callback('mouse hovering', func)
2718            plt.show(elli).close()
2719            ```
2720
2721        Examples:
2722            - [spline_draw1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/spline_draw1.py)
2723            - [colorlines.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorlines.py)
2724
2725                ![](https://vedo.embl.es/images/advanced/spline_draw.png)
2726
2727            - ..and many others!
2728        """
2729        from vtkmodules.util.misc import calldata_type
2730
2731        if not self.interactor:
2732            return 0
2733
2734        if vedo.settings.dry_run_mode >= 1:
2735            return 0
2736
2737        #########################################
2738        @calldata_type(vtki.VTK_INT)
2739        def _func_wrap(iren, ename, timerid=None):
2740            event = self.fill_event(ename=ename, enable_picking=enable_picking)
2741            event.timerid = timerid
2742            event.id = cid
2743            event.priority = priority
2744            self.last_event = event
2745            func(event)
2746
2747        #########################################
2748
2749        event_name = utils.get_vtk_name_event(event_name)
2750
2751        cid = self.interactor.AddObserver(event_name, _func_wrap, priority)
2752        # print(f"Registering event: {event_name} with id={cid}")
2753        return cid

Add a function to be executed while show() is active.

Return a unique id for the callback.

The callback function (see example below) exposes a dictionary with the following information:

  • name: event name,
  • id: event unique identifier,
  • priority: event priority (float),
  • interactor: the interactor object,
  • at: renderer nr. where the event occurred
  • keypress: key pressed as string
  • actor: object picked by the mouse
  • picked3d: point picked in world coordinates
  • picked2d: screen coords of the mouse pointer
  • delta2d: shift wrt previous position (to calculate speed, direction)
  • delta3d: ...same but in 3D world coords
  • angle2d: angle of mouse movement on screen
  • speed2d: speed of mouse movement on screen
  • speed3d: speed of picked point in world coordinates
  • isPoints: True if of class
  • isMesh: True if of class
  • isAssembly: True if of class
  • isVolume: True if of class Volume
  • isImage: True if of class

If enable_picking is False, no picking will be performed. This can be useful to avoid double picking when using buttons.

Frequently used events are:

  • KeyPress, KeyRelease: listen to keyboard events
  • LeftButtonPress, LeftButtonRelease: listen to mouse clicks
  • MiddleButtonPress, MiddleButtonRelease
  • RightButtonPress, RightButtonRelease
  • MouseMove: listen to mouse pointer changing position
  • MouseWheelForward, MouseWheelBackward
  • Enter, Leave: listen to mouse entering or leaving the window
  • Pick, StartPick, EndPick: listen to object picking
  • ResetCamera, ResetCameraClippingRange
  • Error, Warning
  • Char
  • Timer

Check the complete list of events here.

Example:
from vedo import *

def func(evt):
    # this function is called every time the mouse moves
    # (evt is a dotted dictionary)
    if not evt.object:
        return  # no hit, return
    print("point coords =", evt.picked3d)
    # print(evt) # full event dump

elli = Ellipsoid()
plt = Plotter(axes=1)
plt.add_callback('mouse hovering', func)
plt.show(elli)close()
Examples:
def remove_callback(self, cid: Union[int, str]) -> Self:
2755    def remove_callback(self, cid: Union[int, str]) -> Self:
2756        """
2757        Remove a callback function by its id
2758        or a whole category of callbacks by their name.
2759
2760        Arguments:
2761            cid : (int, str)
2762                Unique id of the callback.
2763                If an event name is passed all callbacks of that type are removed.
2764        """
2765        if self.interactor:
2766            if isinstance(cid, str):
2767                cid = utils.get_vtk_name_event(cid)
2768                self.interactor.RemoveObservers(cid)
2769            else:
2770                self.interactor.RemoveObserver(cid)
2771        return self

Remove a callback function by its id or a whole category of callbacks by their name.

Arguments:
  • cid : (int, str) Unique id of the callback. If an event name is passed all callbacks of that type are removed.
def remove_all_observers(self) -> Self:
2773    def remove_all_observers(self) -> Self:
2774        """
2775        Remove all observers.
2776
2777        Example:
2778        ```python
2779        from vedo import *
2780
2781        def kfunc(event):
2782            print("Key pressed:", event.keypress)
2783            if event.keypress == 'q':
2784                plt.close()
2785
2786        def rfunc(event):
2787            if event.isImage:
2788                printc("Right-clicked!", event)
2789                plt.render()
2790
2791        img = Image(dataurl+"images/embryo.jpg")
2792
2793        plt = Plotter(size=(1050, 600))
2794        plt.parallel_projection(True)
2795        plt.remove_all_observers()
2796        plt.add_callback("key press", kfunc)
2797        plt.add_callback("mouse right click", rfunc)
2798        plt.show("Right-Click Me! Press q to exit.", img)
2799        plt.close()
2800        ```
2801        """
2802        if self.interactor:
2803            self.interactor.RemoveAllObservers()
2804        return self

Remove all observers.

Example:

from vedo import *

def kfunc(event):
    print("Key pressed:", event.keypress)
    if event.keypress == 'q':
        plt.close()

def rfunc(event):
    if event.isImage:
        printc("Right-clicked!", event)
        plt.render()

img = Image(dataurl+"images/embryo.jpg")

plt = Plotter(size=(1050, 600))
plt.parallel_projection(True)
plt.remove_all_observers()
plt.add_callback("key press", kfunc)
plt.add_callback("mouse right click", rfunc)
plt.show("Right-Click Me! Press q to exit.", img)
plt.close()
def timer_callback(self, action: str, timer_id=None, dt=1, one_shot=False) -> int:
2806    def timer_callback(self, action: str, timer_id=None, dt=1, one_shot=False) -> int:
2807        """
2808        Start or stop an existing timer.
2809
2810        Arguments:
2811            action : (str)
2812                Either "create"/"start" or "destroy"/"stop"
2813            timer_id : (int)
2814                When stopping the timer, the ID of the timer as returned when created
2815            dt : (int)
2816                time in milliseconds between each repeated call
2817            one_shot : (bool)
2818                create a one shot timer of prescribed duration instead of a repeating one
2819
2820        Examples:
2821            - [timer_callback1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback1.py)
2822            - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
2823
2824            ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
2825        """
2826        if action in ("create", "start"):
2827            if timer_id is not None:
2828                vedo.logger.warning("you set a timer_id but it will be ignored.")
2829            if one_shot:
2830                timer_id = self.interactor.CreateOneShotTimer(dt)
2831            else:
2832                timer_id = self.interactor.CreateRepeatingTimer(dt)
2833            return timer_id
2834
2835        elif action in ("destroy", "stop"):
2836            if timer_id is not None:
2837                self.interactor.DestroyTimer(timer_id)
2838            else:
2839                vedo.logger.warning("please set a timer_id. Cannot stop timer.")
2840        else:
2841            e = f"in timer_callback(). Cannot understand action: {action}\n"
2842            e += " allowed actions are: ['start', 'stop']. Skipped."
2843            vedo.logger.error(e)
2844        return timer_id

Start or stop an existing timer.

Arguments:
  • action : (str) Either "create"/"start" or "destroy"/"stop"
  • timer_id : (int) When stopping the timer, the ID of the timer as returned when created
  • dt : (int) time in milliseconds between each repeated call
  • one_shot : (bool) create a one shot timer of prescribed duration instead of a repeating one
Examples:

def add_observer(self, event_name: str, func: Callable, priority=0.0) -> int:
2846    def add_observer(self, event_name: str, func: Callable, priority=0.0) -> int:
2847        """
2848        Add a callback function that will be called when an event occurs.
2849        Consider using `add_callback()` instead.
2850        """
2851        if not self.interactor:
2852            return -1
2853        event_name = utils.get_vtk_name_event(event_name)
2854        idd = self.interactor.AddObserver(event_name, func, priority)
2855        return idd

Add a callback function that will be called when an event occurs. Consider using add_callback() instead.

def compute_world_coordinate( self, pos2d: MutableSequence[float], at=None, objs=(), bounds=(), offset=None, pixeltol=None, worldtol=None) -> numpy.ndarray:
2857    def compute_world_coordinate(
2858        self,
2859        pos2d: MutableSequence[float],
2860        at=None,
2861        objs=(),
2862        bounds=(),
2863        offset=None,
2864        pixeltol=None,
2865        worldtol=None,
2866    ) -> np.ndarray:
2867        """
2868        Transform a 2D point on the screen into a 3D point inside the rendering scene.
2869        If a set of meshes is passed then points are placed onto these.
2870
2871        Arguments:
2872            pos2d : (list)
2873                2D screen coordinates point.
2874            at : (int)
2875                renderer number.
2876            objs : (list)
2877                list of Mesh objects to project the point onto.
2878            bounds : (list)
2879                specify a bounding box as [xmin,xmax, ymin,ymax, zmin,zmax].
2880            offset : (float)
2881                specify an offset value.
2882            pixeltol : (int)
2883                screen tolerance in pixels.
2884            worldtol : (float)
2885                world coordinates tolerance.
2886
2887        Returns:
2888            numpy array, the point in 3D world coordinates.
2889
2890        Examples:
2891            - [cut_freehand.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/cut_freehand.py)
2892            - [mousehover3.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mousehover3.py)
2893
2894            ![](https://vedo.embl.es/images/basic/mousehover3.jpg)
2895        """
2896        if at is not None:
2897            renderer = self.renderers[at]
2898        else:
2899            renderer = self.renderer
2900
2901        if not objs:
2902            pp = vtki.vtkFocalPlanePointPlacer()
2903        else:
2904            pps = vtki.vtkPolygonalSurfacePointPlacer()
2905            for ob in objs:
2906                pps.AddProp(ob.actor)
2907            pp = pps # type: ignore
2908
2909        if len(bounds) == 6:
2910            pp.SetPointBounds(bounds)
2911        if pixeltol:
2912            pp.SetPixelTolerance(pixeltol)
2913        if worldtol:
2914            pp.SetWorldTolerance(worldtol)
2915        if offset:
2916            pp.SetOffset(offset)
2917
2918        worldPos: MutableSequence[float] = [0, 0, 0]
2919        worldOrient: MutableSequence[float] = [0, 0, 0, 0, 0, 0, 0, 0, 0]
2920        pp.ComputeWorldPosition(renderer, pos2d, worldPos, worldOrient)
2921        # validw = pp.ValidateWorldPosition(worldPos, worldOrient)
2922        # validd = pp.ValidateDisplayPosition(renderer, pos2d)
2923        return np.array(worldPos)

Transform a 2D point on the screen into a 3D point inside the rendering scene. If a set of meshes is passed then points are placed onto these.

Arguments:
  • pos2d : (list) 2D screen coordinates point.
  • at : (int) renderer number.
  • objs : (list) list of Mesh objects to project the point onto.
  • bounds : (list) specify a bounding box as [xmin,xmax, ymin,ymax, zmin,zmax].
  • offset : (float) specify an offset value.
  • pixeltol : (int) screen tolerance in pixels.
  • worldtol : (float) world coordinates tolerance.
Returns:

numpy array, the point in 3D world coordinates.

Examples:

def compute_screen_coordinates(self, obj, full_window=False) -> numpy.ndarray:
2925    def compute_screen_coordinates(self, obj, full_window=False) -> np.ndarray:
2926        """
2927        Given a 3D points in the current renderer (or full window),
2928        find the screen pixel coordinates.
2929
2930        Example:
2931            ```python
2932            from vedo import *
2933
2934            elli = Ellipsoid().point_size(5)
2935
2936            plt = Plotter()
2937            plt.show(elli, "Press q to continue and print the info")
2938
2939            xyscreen = plt.compute_screen_coordinates(elli)
2940            print('xyscreen coords:', xyscreen)
2941
2942            # simulate an event happening at one point
2943            event = plt.fill_event(pos=xyscreen[123])
2944            print(event)
2945            ```
2946        """
2947        try:
2948            obj = obj.vertices
2949        except AttributeError:
2950            pass
2951
2952        if utils.is_sequence(obj):
2953            pts = obj
2954        p2d = []
2955        cs = vtki.vtkCoordinate()
2956        cs.SetCoordinateSystemToWorld()
2957        cs.SetViewport(self.renderer)
2958        for p in pts:
2959            cs.SetValue(p)
2960            if full_window:
2961                p2d.append(cs.GetComputedDisplayValue(self.renderer))
2962            else:
2963                p2d.append(cs.GetComputedViewportValue(self.renderer))
2964        return np.array(p2d, dtype=int)

Given a 3D points in the current renderer (or full window), find the screen pixel coordinates.

Example:
from vedo import *

elli = Ellipsoid().point_size(5)

plt = Plotter()
plt.show(elli, "Press q to continue and print the info")

xyscreen = plt.compute_screen_coordinates(elli)
print('xyscreen coords:', xyscreen)

# simulate an event happening at one point
event = plt.fill_event(pos=xyscreen[123])
print(event)
def pick_area(self, pos1, pos2, at=None) -> vedo.mesh.Mesh:
2966    def pick_area(self, pos1, pos2, at=None) -> "vedo.Mesh":
2967        """
2968        Pick all objects within a box defined by two corner points in 2D screen coordinates.
2969
2970        Returns a frustum Mesh that contains the visible field of view.
2971        This can be used to select objects in a scene or select vertices.
2972
2973        Example:
2974            ```python
2975            from vedo import *
2976
2977            settings.enable_default_mouse_callbacks = False
2978
2979            def mode_select(objs):
2980                print("Selected objects:", objs)
2981                d0 = mode.start_x, mode.start_y # display coords
2982                d1 = mode.end_x, mode.end_y
2983
2984                frustum = plt.pick_area(d0, d1)
2985                col = np.random.randint(0, 10)
2986                infru = frustum.inside_points(mesh)
2987                infru.point_size(10).color(col)
2988                plt.add(frustum, infru).render()
2989
2990            mesh = Mesh(dataurl+"cow.vtk")
2991            mesh.color("k5").linewidth(1)
2992
2993            mode = interactor_modes.BlenderStyle()
2994            mode.callback_select = mode_select
2995
2996            plt = Plotter().user_mode(mode)
2997            plt.show(mesh, axes=1)
2998            ```
2999        """
3000        if at is not None:
3001            ren = self.renderers[at]
3002        else:
3003            ren = self.renderer
3004        area_picker = vtki.vtkAreaPicker()
3005        area_picker.AreaPick(pos1[0], pos1[1], pos2[0], pos2[1], ren)
3006        planes = area_picker.GetFrustum()
3007
3008        fru = vtki.new("FrustumSource")
3009        fru.SetPlanes(planes)
3010        fru.ShowLinesOff()
3011        fru.Update()
3012
3013        afru = vedo.Mesh(fru.GetOutput())
3014        afru.alpha(0.1).lw(1).pickable(False)
3015        afru.name = "Frustum"
3016        return afru

Pick all objects within a box defined by two corner points in 2D screen coordinates.

Returns a frustum Mesh that contains the visible field of view. This can be used to select objects in a scene or select vertices.

Example:
from vedo import *

settings.enable_default_mouse_callbacks = False

def mode_select(objs):
    print("Selected objects:", objs)
    d0 = mode.start_x, mode.start_y # display coords
    d1 = mode.end_x, mode.end_y

    frustum = plt.pick_area(d0, d1)
    col = np.random.randint(0, 10)
    infru = frustum.inside_points(mesh)
    infru.point_size(10).color(col)
    plt.add(frustum, infru).render()

mesh = Mesh(dataurl+"cow.vtk")
mesh.color("k5").linewidth(1)

mode = interactor_modes.BlenderStyle()
mode.callback_select = mode_select

plt = Plotter().user_mode(mode)
plt.show(mesh, axes=1)
def show( self, *objects, at=None, axes=None, resetcam=None, zoom=False, interactive=None, viewup='', azimuth=0.0, elevation=0.0, roll=0.0, camera=None, mode=None, rate=None, bg=None, bg2=None, size=None, title=None, screenshot='') -> Any:
3139    def show(
3140        self,
3141        *objects,
3142        at=None,
3143        axes=None,
3144        resetcam=None,
3145        zoom=False,
3146        interactive=None,
3147        viewup="",
3148        azimuth=0.0,
3149        elevation=0.0,
3150        roll=0.0,
3151        camera=None,
3152        mode=None,
3153        rate=None,
3154        bg=None,
3155        bg2=None,
3156        size=None,
3157        title=None,
3158        screenshot="",
3159    ) -> Any:
3160        """
3161        Render a list of objects.
3162
3163        Arguments:
3164            at : (int)
3165                number of the renderer to plot to, in case of more than one exists
3166
3167            axes : (int)
3168                axis type-1 can be fully customized by passing a dictionary.
3169                Check `addons.Axes()` for the full list of options.
3170                set the type of axes to be shown:
3171                - 0,  no axes
3172                - 1,  draw three gray grid walls
3173                - 2,  show cartesian axes from (0,0,0)
3174                - 3,  show positive range of cartesian axes from (0,0,0)
3175                - 4,  show a triad at bottom left
3176                - 5,  show a cube at bottom left
3177                - 6,  mark the corners of the bounding box
3178                - 7,  draw a 3D ruler at each side of the cartesian axes
3179                - 8,  show the `vtkCubeAxesActor` object
3180                - 9,  show the bounding box outLine
3181                - 10, show three circles representing the maximum bounding box
3182                - 11, show a large grid on the x-y plane
3183                - 12, show polar axes
3184                - 13, draw a simple ruler at the bottom of the window
3185
3186            azimuth/elevation/roll : (float)
3187                move camera accordingly the specified value
3188
3189            viewup: str, list
3190                either `['x', 'y', 'z']` or a vector to set vertical direction
3191
3192            resetcam : (bool)
3193                re-adjust camera position to fit objects
3194
3195            camera : (dict, vtkCamera)
3196                camera parameters can further be specified with a dictionary
3197                assigned to the `camera` keyword (E.g. `show(camera={'pos':(1,2,3), 'thickness':1000,})`):
3198                - pos, `(list)`,  the position of the camera in world coordinates
3199                - focal_point `(list)`, the focal point of the camera in world coordinates
3200                - viewup `(list)`, the view up direction for the camera
3201                - distance `(float)`, set the focal point to the specified distance from the camera position.
3202                - clipping_range `(float)`, distance of the near and far clipping planes along the direction of projection.
3203                - parallel_scale `(float)`, scaling used for a parallel projection, i.e. the height of the viewport
3204                in world-coordinate distances. The default is 1.
3205                Note that the "scale" parameter works as an "inverse scale", larger numbers produce smaller images.
3206                This method has no effect in perspective projection mode.
3207
3208                - thickness `(float)`, set the distance between clipping planes. This method adjusts the far clipping
3209                plane to be set a distance 'thickness' beyond the near clipping plane.
3210
3211                - view_angle `(float)`, the camera view angle, which is the angular height of the camera view
3212                measured in degrees. The default angle is 30 degrees.
3213                This method has no effect in parallel projection mode.
3214                The formula for setting the angle up for perfect perspective viewing is:
3215                angle = 2*atan((h/2)/d) where h is the height of the RenderWindow
3216                (measured by holding a ruler up to your screen) and d is the distance from your eyes to the screen.
3217
3218            interactive : (bool)
3219                pause and interact with window (True) or continue execution (False)
3220
3221            rate : (float)
3222                maximum rate of `show()` in Hertz
3223
3224            mode : (int, str)
3225                set the type of interaction:
3226                - 0 = TrackballCamera [default]
3227                - 1 = TrackballActor
3228                - 2 = JoystickCamera
3229                - 3 = JoystickActor
3230                - 4 = Flight
3231                - 5 = RubberBand2D
3232                - 6 = RubberBand3D
3233                - 7 = RubberBandZoom
3234                - 8 = Terrain
3235                - 9 = Unicam
3236                - 10 = Image
3237                - Check out `vedo.interaction_modes` for more options.
3238
3239            bg : (str, list)
3240                background color in RGB format, or string name
3241
3242            bg2 : (str, list)
3243                second background color to create a gradient background
3244
3245            size : (str, list)
3246                size of the window, e.g. size="fullscreen", or size=[600,400]
3247
3248            title : (str)
3249                window title text
3250
3251            screenshot : (str)
3252                save a screenshot of the window to file
3253        """
3254
3255        if vedo.settings.dry_run_mode >= 2:
3256            return self
3257
3258        if self.wx_widget:
3259            return self
3260
3261        if self.renderers:  # in case of notebooks
3262
3263            if at is None:
3264                at = self.renderers.index(self.renderer)
3265
3266            else:
3267
3268                if at >= len(self.renderers):
3269                    t = f"trying to show(at={at}) but only {len(self.renderers)} renderers exist"
3270                    vedo.logger.error(t)
3271                    return self
3272
3273                self.renderer = self.renderers[at]
3274
3275        if title is not None:
3276            self.title = title
3277
3278        if size is not None:
3279            self.size = size
3280            if self.size[0] == "f":  # full screen
3281                self.size = "fullscreen"
3282                self.window.SetFullScreen(True)
3283                self.window.BordersOn()
3284            else:
3285                self.window.SetSize(int(self.size[0]), int(self.size[1]))
3286
3287        if vedo.settings.default_backend == "vtk":
3288            if str(bg).endswith(".hdr"):
3289                self._add_skybox(bg)
3290            else:
3291                if bg is not None:
3292                    self.backgrcol = vedo.get_color(bg)
3293                    self.renderer.SetBackground(self.backgrcol)
3294                if bg2 is not None:
3295                    self.renderer.GradientBackgroundOn()
3296                    self.renderer.SetBackground2(vedo.get_color(bg2))
3297
3298        if axes is not None:
3299            if isinstance(axes, vedo.Assembly):  # user passing show(..., axes=myaxes)
3300                objects = list(objects)
3301                objects.append(axes)  # move it into the list of normal things to show
3302                axes = 0
3303            self.axes = axes
3304
3305        if interactive is not None:
3306            self._interactive = interactive
3307        if self.offscreen:
3308            self._interactive = False
3309
3310        # camera stuff
3311        if resetcam is not None:
3312            self.resetcam = resetcam
3313
3314        if camera is not None:
3315            self.resetcam = False
3316            viewup = ""
3317            if isinstance(camera, vtki.vtkCamera):
3318                cameracopy = vtki.vtkCamera()
3319                cameracopy.DeepCopy(camera)
3320                self.camera = cameracopy
3321            else:
3322                self.camera = utils.camera_from_dict(camera)
3323
3324        self.add(objects)
3325
3326        # Backend ###############################################################
3327        if vedo.settings.default_backend in ["k3d"]:
3328            return backends.get_notebook_backend(self.objects)
3329        #########################################################################
3330
3331        for ia in utils.flatten(objects):
3332            try:
3333                # fix gray color labels and title to white or black
3334                ltc = np.array(ia.scalarbar.GetLabelTextProperty().GetColor())
3335                if np.linalg.norm(ltc - (0.5, 0.5, 0.5)) / 3 < 0.05:
3336                    c = (0.9, 0.9, 0.9)
3337                    if np.sum(self.renderer.GetBackground()) > 1.5:
3338                        c = (0.1, 0.1, 0.1)
3339                    ia.scalarbar.GetLabelTextProperty().SetColor(c)
3340                    ia.scalarbar.GetTitleTextProperty().SetColor(c)
3341            except AttributeError:
3342                pass
3343
3344        if self.sharecam:
3345            for r in self.renderers:
3346                r.SetActiveCamera(self.camera)
3347
3348        if self.axes is not None:
3349            if viewup != "2d" or self.axes in [1, 8] or isinstance(self.axes, dict):
3350                bns = self.renderer.ComputeVisiblePropBounds()
3351                addons.add_global_axes(self.axes, bounds=bns)
3352
3353        # Backend ###############################################################
3354        if vedo.settings.default_backend in ["ipyvtk", "trame"]:
3355            return backends.get_notebook_backend()
3356        #########################################################################
3357
3358        if self.resetcam:
3359            self.renderer.ResetCamera()
3360
3361        if len(self.renderers) > 1:
3362            self.add_renderer_frame()
3363
3364        if vedo.settings.default_backend == "2d" and not zoom:
3365            zoom = "tightest"
3366
3367        if zoom:
3368            if zoom == "tight":
3369                self.reset_camera(tight=0.04)
3370            elif zoom == "tightest":
3371                self.reset_camera(tight=0.0001)
3372            else:
3373                self.camera.Zoom(zoom)
3374        if elevation:
3375            self.camera.Elevation(elevation)
3376        if azimuth:
3377            self.camera.Azimuth(azimuth)
3378        if roll:
3379            self.camera.Roll(roll)
3380
3381        if len(viewup) > 0:
3382            b = self.renderer.ComputeVisiblePropBounds()
3383            cm = np.array([(b[1] + b[0])/2, (b[3] + b[2])/2, (b[5] + b[4])/2])
3384            sz = np.array([(b[1] - b[0]), (b[3] - b[2]), (b[5] - b[4])])
3385            if viewup == "x":
3386                sz = np.linalg.norm(sz)
3387                self.camera.SetViewUp([1, 0, 0])
3388                self.camera.SetPosition(cm + sz)
3389            elif viewup == "y":
3390                sz = np.linalg.norm(sz)
3391                self.camera.SetViewUp([0, 1, 0])
3392                self.camera.SetPosition(cm + sz)
3393            elif viewup == "z":
3394                sz = np.array([(b[1]-b[0])*0.7, -(b[3]-b[2])*1.0, (b[5]-b[4])*1.2])
3395                self.camera.SetViewUp([0, 0, 1])
3396                self.camera.SetPosition(cm + 2 * sz)
3397            elif utils.is_sequence(viewup):
3398                sz = np.linalg.norm(sz)
3399                self.camera.SetViewUp(viewup)
3400                cpos = np.cross([0, 1, 0], viewup)
3401                self.camera.SetPosition(cm - 2 * sz * cpos)
3402
3403        self.renderer.ResetCameraClippingRange()
3404
3405        self.initialize_interactor()
3406
3407        if vedo.settings.immediate_rendering:
3408            self.window.Render()  ##################### <-------------- Render
3409
3410        if self.interactor:  # can be offscreen or not the vtk backend..
3411
3412            self.window.SetWindowName(self.title)
3413
3414            # pic = vedo.Image(vedo.dataurl+'images/vtk_logo.png')
3415            # pic = vedo.Image('/home/musy/Downloads/icons8-3d-96.png')
3416            # print(pic.dataset)# Array 0 name PNGImage
3417            # self.window.SetIcon(pic.dataset)
3418
3419            try:
3420                # Needs "pip install pyobjc" on Mac OSX
3421                if (
3422                    self._cocoa_initialized is False
3423                    and "Darwin" in vedo.sys_platform
3424                    and not self.offscreen
3425                ):
3426                    self._cocoa_initialized = True
3427                    from Cocoa import NSRunningApplication, NSApplicationActivateIgnoringOtherApps # type: ignore
3428                    pid = os.getpid()
3429                    x = NSRunningApplication.runningApplicationWithProcessIdentifier_(int(pid))
3430                    x.activateWithOptions_(NSApplicationActivateIgnoringOtherApps)
3431            except:
3432                # vedo.logger.debug("On Mac OSX try: pip install pyobjc")
3433                pass
3434
3435            # Set the interaction style
3436            if mode is not None:
3437                self.user_mode(mode)
3438            if self.qt_widget and mode is None:
3439                self.user_mode(0)
3440
3441            if screenshot:
3442                self.screenshot(screenshot)
3443
3444            if self._interactive:
3445                self.interactor.Start()
3446                if self._must_close_now:
3447                    self.interactor.GetRenderWindow().Finalize()
3448                    self.interactor.TerminateApp()
3449                    self.camera = None
3450                    self.renderer = None
3451                    self.renderers = []
3452                    self.window = None
3453                    self.interactor = None
3454                return self
3455
3456            if rate:
3457                if self.clock is None:  # set clock and limit rate
3458                    self._clockt0 = time.time()
3459                    self.clock = 0.0
3460                else:
3461                    t = time.time() - self._clockt0
3462                    elapsed = t - self.clock
3463                    mint = 1.0 / rate
3464                    if elapsed < mint:
3465                        time.sleep(mint - elapsed)
3466                    self.clock = time.time() - self._clockt0
3467
3468        # 2d ####################################################################
3469        if vedo.settings.default_backend == "2d":
3470            return backends.get_notebook_backend()
3471        #########################################################################
3472
3473        return self

Render a list of objects.

Arguments:
  • at : (int) number of the renderer to plot to, in case of more than one exists
  • axes : (int) axis type-1 can be fully customized by passing a dictionary. Check addons.Axes() for the full list of options. set the type of axes to be shown:
    • 0, no axes
    • 1, draw three gray grid walls
    • 2, show cartesian axes from (0,0,0)
    • 3, show positive range of cartesian axes from (0,0,0)
    • 4, show a triad at bottom left
    • 5, show a cube at bottom left
    • 6, mark the corners of the bounding box
    • 7, draw a 3D ruler at each side of the cartesian axes
    • 8, show the vtkCubeAxesActor object
    • 9, show the bounding box outLine
    • 10, show three circles representing the maximum bounding box
    • 11, show a large grid on the x-y plane
    • 12, show polar axes
    • 13, draw a simple ruler at the bottom of the window
  • azimuth/elevation/roll : (float) move camera accordingly the specified value
  • viewup: str, list either ['x', 'y', 'z'] or a vector to set vertical direction
  • resetcam : (bool) re-adjust camera position to fit objects
  • camera : (dict, vtkCamera) camera parameters can further be specified with a dictionary assigned to the camera keyword (E.g. show(camera={'pos':(1,2,3), 'thickness':1000,})):

    • pos, (list), the position of the camera in world coordinates
    • focal_point (list), the focal point of the camera in world coordinates
    • viewup (list), the view up direction for the camera
    • distance (float), set the focal point to the specified distance from the camera position.
    • clipping_range (float), distance of the near and far clipping planes along the direction of projection.
    • parallel_scale (float), scaling used for a parallel projection, i.e. the height of the viewport in world-coordinate distances. The default is 1. Note that the "scale" parameter works as an "inverse scale", larger numbers produce smaller images. This method has no effect in perspective projection mode.

    • thickness (float), set the distance between clipping planes. This method adjusts the far clipping plane to be set a distance 'thickness' beyond the near clipping plane.

    • view_angle (float), the camera view angle, which is the angular height of the camera view measured in degrees. The default angle is 30 degrees. This method has no effect in parallel projection mode. The formula for setting the angle up for perfect perspective viewing is: angle = 2*atan((h/2)/d) where h is the height of the RenderWindow (measured by holding a ruler up to your screen) and d is the distance from your eyes to the screen.

  • interactive : (bool) pause and interact with window (True) or continue execution (False)
  • rate : (float) maximum rate of show() in Hertz
  • mode : (int, str) set the type of interaction:
    • 0 = TrackballCamera [default]
    • 1 = TrackballActor
    • 2 = JoystickCamera
    • 3 = JoystickActor
    • 4 = Flight
    • 5 = RubberBand2D
    • 6 = RubberBand3D
    • 7 = RubberBandZoom
    • 8 = Terrain
    • 9 = Unicam
    • 10 = Image
    • Check out vedo.interaction_modes for more options.
  • bg : (str, list) background color in RGB format, or string name
  • bg2 : (str, list) second background color to create a gradient background
  • size : (str, list) size of the window, e.g. size="fullscreen", or size=[600,400]
  • title : (str) window title text
  • screenshot : (str) save a screenshot of the window to file
def add_inset( self, *objects, **options) -> Optional[vtkmodules.vtkInteractionWidgets.vtkOrientationMarkerWidget]:
3476    def add_inset(self, *objects, **options) -> Union[vtki.vtkOrientationMarkerWidget, None]:
3477        """Add a draggable inset space into a renderer.
3478
3479        Arguments:
3480            at : (int)
3481                specify the renderer number
3482            pos : (list)
3483                icon position in the range [1-4] indicating one of the 4 corners,
3484                or it can be a tuple (x,y) as a fraction of the renderer size.
3485            size : (float)
3486                size of the square inset
3487            draggable : (bool)
3488                if True the subrenderer space can be dragged around
3489            c : (color)
3490                color of the inset frame when dragged
3491
3492        Examples:
3493            - [inset.py](https://github.com/marcomusy/vedo/tree/master/examples/other/inset.py)
3494
3495            ![](https://user-images.githubusercontent.com/32848391/56758560-3c3f1300-6797-11e9-9b33-49f5a4876039.jpg)
3496        """
3497        if not self.interactor:
3498            return None
3499
3500        if not self.renderer:
3501            vedo.logger.warning("call add_inset() only after first rendering of the scene.")
3502            return None
3503
3504        options = dict(options)
3505        pos = options.pop("pos", 0)
3506        size = options.pop("size", 0.1)
3507        c = options.pop("c", "lb")
3508        at = options.pop("at", None)
3509        draggable = options.pop("draggable", True)
3510
3511        r, g, b = vedo.get_color(c)
3512        widget = vtki.vtkOrientationMarkerWidget()
3513        widget.SetOutlineColor(r, g, b)
3514        if len(objects) == 1:
3515            widget.SetOrientationMarker(objects[0].actor)
3516        else:
3517            widget.SetOrientationMarker(vedo.Assembly(objects))
3518
3519        widget.SetInteractor(self.interactor)
3520
3521        if utils.is_sequence(pos):
3522            widget.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
3523        else:
3524            if pos < 2:
3525                widget.SetViewport(0, 1 - 2 * size, size * 2, 1)
3526            elif pos == 2:
3527                widget.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
3528            elif pos == 3:
3529                widget.SetViewport(0, 0, size * 2, size * 2)
3530            elif pos == 4:
3531                widget.SetViewport(1 - 2 * size, 0, 1, size * 2)
3532        widget.EnabledOn()
3533        widget.SetInteractive(draggable)
3534        if at is not None and at < len(self.renderers):
3535            widget.SetCurrentRenderer(self.renderers[at])
3536        else:
3537            widget.SetCurrentRenderer(self.renderer)
3538        self.widgets.append(widget)
3539        return widget

Add a draggable inset space into a renderer.

Arguments:
  • at : (int) specify the renderer number
  • pos : (list) icon position in the range [1-4] indicating one of the 4 corners, or it can be a tuple (x,y) as a fraction of the renderer size.
  • size : (float) size of the square inset
  • draggable : (bool) if True the subrenderer space can be dragged around
  • c : (color) color of the inset frame when dragged
Examples:

def clear(self, at=None, deep=False) -> Self:
3541    def clear(self, at=None, deep=False) -> Self:
3542        """Clear the scene from all meshes and volumes."""
3543        if at is not None:
3544            renderer = self.renderers[at]
3545        else:
3546            renderer = self.renderer
3547        if not renderer:
3548            return self
3549
3550        if deep:
3551            renderer.RemoveAllViewProps()
3552        else:
3553            for ob in set(
3554                self.get_meshes()
3555                + self.get_volumes()
3556                + self.objects
3557                + self.axes_instances
3558            ):
3559                if isinstance(ob, vedo.shapes.Text2D):
3560                    continue
3561                self.remove(ob)
3562                try:
3563                    if ob.scalarbar:
3564                        self.remove(ob.scalarbar)
3565                except AttributeError:
3566                    pass
3567        return self

Clear the scene from all meshes and volumes.

def break_interaction(self) -> Self:
3569    def break_interaction(self) -> Self:
3570        """Break window interaction and return to the python execution flow"""
3571        if self.interactor:
3572            self.check_actors_trasform()
3573            self.interactor.ExitCallback()
3574        return self

Break window interaction and return to the python execution flow

def freeze(self, value=True) -> Self:
3576    def freeze(self, value=True) -> Self:
3577        """Freeze the current renderer. Use this with `sharecam=False`."""
3578        if not self.interactor:
3579            return self
3580        if not self.renderer:
3581            return self
3582        self.renderer.SetInteractive(not value)
3583        return self

Freeze the current renderer. Use this with sharecam=False.

def user_mode(self, mode) -> Self:
3585    def user_mode(self, mode) -> Self:
3586        """
3587        Modify the user interaction mode.
3588
3589        Examples:
3590            ```python
3591            from vedo import *
3592            mode = interactor_modes.MousePan()
3593            mesh = Mesh(dataurl+"cow.vtk")
3594            plt = Plotter().user_mode(mode)
3595            plt.show(mesh, axes=1)
3596           ```
3597        See also:
3598        [VTK interactor styles](https://vtk.org/doc/nightly/html/classvtkInteractorStyle.html)
3599        """
3600        if not self.interactor:
3601            return self
3602        
3603        curr_style = self.interactor.GetInteractorStyle().GetClassName()
3604        # print("Current style:", curr_style)
3605        if curr_style.endswith("Actor"):
3606            self.check_actors_trasform()
3607
3608        if isinstance(mode, (str, int)):
3609            # Set the style of interaction
3610            # see https://vtk.org/doc/nightly/html/classvtkInteractorStyle.html
3611            if   mode in (0, "TrackballCamera"):
3612                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTrackballCamera"))
3613                self.interactor.RemoveObservers("CharEvent")
3614            elif mode in (1, "TrackballActor"):
3615                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTrackballActor"))
3616            elif mode in (2, "JoystickCamera"):
3617                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleJoystickCamera"))
3618            elif mode in (3, "JoystickActor"):
3619                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleJoystickActor"))
3620            elif mode in (4, "Flight"):
3621                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleFlight"))
3622            elif mode in (5, "RubberBand2D"):
3623                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBand2D"))
3624            elif mode in (6, "RubberBand3D"):
3625                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBand3D"))
3626            elif mode in (7, "RubberBandZoom"):
3627                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBandZoom"))
3628            elif mode in (8, "Terrain"):
3629                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTerrain"))
3630            elif mode in (9, "Unicam"):
3631                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleUnicam"))
3632            elif mode in (10, "Image", "image", "2d"):
3633                astyle = vtki.new("InteractorStyleImage")
3634                astyle.SetInteractionModeToImage3D()
3635                self.interactor.SetInteractorStyle(astyle)
3636            else:
3637                vedo.logger.warning(f"Unknown interaction mode: {mode}")
3638
3639        elif isinstance(mode, vtki.vtkInteractorStyleUser):
3640            # set a custom interactor style
3641            if hasattr(mode, "interactor"):
3642                mode.interactor = self.interactor
3643                mode.renderer = self.renderer # type: ignore
3644            mode.SetInteractor(self.interactor)
3645            mode.SetDefaultRenderer(self.renderer)
3646            self.interactor.SetInteractorStyle(mode)
3647
3648        return self

Modify the user interaction mode.

Examples:
 from vedo import *
 mode = interactor_modes.MousePan()
 mesh = Mesh(dataurl+"cow.vtk")
 plt = Plotter().user_mode(mode)
 plt.show(mesh, axes=1)

See also: VTK interactor styles

def close(self) -> Self:
3650    def close(self) -> Self:
3651        """Close the plotter."""
3652        # https://examples.vtk.org/site/Cxx/Visualization/CloseWindow/
3653        vedo.last_figure = None
3654        self.last_event = None
3655        self.sliders = []
3656        self.buttons = []
3657        self.widgets = []
3658        self.hover_legends = []
3659        self.background_renderer = None
3660        self._extralight = None
3661
3662        self.hint_widget = None
3663        self.cutter_widget = None
3664
3665        if vedo.settings.dry_run_mode >= 2:
3666            return self
3667        
3668        if not hasattr(self, "window"):
3669            return self
3670        if not self.window:
3671            return self
3672        if not hasattr(self, "interactor"):
3673            return self
3674        if not self.interactor:
3675            return self
3676
3677        ###################################################
3678        try:
3679            if "Darwin" in vedo.sys_platform:
3680                self.interactor.ProcessEvents()
3681        except:
3682            pass
3683
3684        self._must_close_now = True
3685
3686        if vedo.plotter_instance == self:
3687            vedo.plotter_instance = None
3688
3689        if self.interactor and self._interactive:
3690            self.break_interaction()
3691        elif self._must_close_now:
3692            # dont call ExitCallback here
3693            if self.interactor:
3694                self.break_interaction()
3695                self.interactor.GetRenderWindow().Finalize()
3696                self.interactor.TerminateApp()
3697            self.camera = None
3698            self.renderer = None
3699            self.renderers = []
3700            self.window = None
3701            self.interactor = None
3702        return self

Close the plotter.

camera
3704    @property
3705    def camera(self):
3706        """Return the current active camera."""
3707        if self.renderer:
3708            return self.renderer.GetActiveCamera()

Return the current active camera.

def screenshot(self, filename='screenshot.png', scale=1, asarray=False) -> Any:
3717    def screenshot(self, filename="screenshot.png", scale=1, asarray=False) -> Any:
3718        """
3719        Take a screenshot of the Plotter window.
3720
3721        Arguments:
3722            scale : (int)
3723                set image magnification as an integer multiplicating factor
3724            asarray : (bool)
3725                return a numpy array of the image instead of writing a file
3726
3727        Warning:
3728            If you get black screenshots try to set `interactive=False` in `show()`
3729            then call `screenshot()` and `plt.interactive()` afterwards.
3730
3731        Example:
3732            ```py
3733            from vedo import *
3734            sphere = Sphere().linewidth(1)
3735            plt = show(sphere, interactive=False)
3736            plt.screenshot('image.png')
3737            plt.interactive()
3738            plt.close()
3739            ```
3740
3741        Example:
3742            ```py
3743            from vedo import *
3744            sphere = Sphere().linewidth(1)
3745            plt = show(sphere, interactive=False)
3746            plt.screenshot('anotherimage.png')
3747            plt.interactive()
3748            plt.close()
3749            ```
3750        """
3751        return vedo.file_io.screenshot(filename, scale, asarray)

Take a screenshot of the Plotter window.

Arguments:
  • scale : (int) set image magnification as an integer multiplicating factor
  • asarray : (bool) return a numpy array of the image instead of writing a file
Warning:

If you get black screenshots try to set interactive=False in show() then call screenshot() and plt.interactive() afterwards.

Example:
from vedo import *
sphere = Sphere().linewidth(1)
plt = show(sphere, interactive=False)
plt.screenshot('image.png')
plt.interactive()
plt.close()
Example:
from vedo import *
sphere = Sphere().linewidth(1)
plt = show(sphere, interactive=False)
plt.screenshot('anotherimage.png')
plt.interactive()
plt.close()
def toimage(self, scale=1) -> vedo.image.Image:
3753    def toimage(self, scale=1) -> "vedo.image.Image":
3754        """
3755        Generate a `Image` object from the current rendering window.
3756
3757        Arguments:
3758            scale : (int)
3759                set image magnification as an integer multiplicating factor
3760        """
3761        if vedo.settings.screeshot_large_image:
3762            w2if = vtki.new("RenderLargeImage")
3763            w2if.SetInput(self.renderer)
3764            w2if.SetMagnification(scale)
3765        else:
3766            w2if = vtki.new("WindowToImageFilter")
3767            w2if.SetInput(self.window)
3768            if hasattr(w2if, "SetScale"):
3769                w2if.SetScale(scale, scale)
3770            if vedo.settings.screenshot_transparent_background:
3771                w2if.SetInputBufferTypeToRGBA()
3772            w2if.ReadFrontBufferOff()  # read from the back buffer
3773        w2if.Update()
3774        return vedo.image.Image(w2if.GetOutput())

Generate a Image object from the current rendering window.

Arguments:
  • scale : (int) set image magnification as an integer multiplicating factor
def export(self, filename='scene.npz', binary=False) -> Self:
3776    def export(self, filename="scene.npz", binary=False) -> Self:
3777        """
3778        Export scene to file to HTML, X3D or Numpy file.
3779
3780        Examples:
3781            - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
3782            - [export_numpy.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_numpy.py)
3783        """
3784        vedo.file_io.export_window(filename, binary=binary)
3785        return self

Export scene to file to HTML, X3D or Numpy file.

Examples:
def color_picker(self, xy, verbose=False):
3787    def color_picker(self, xy, verbose=False):
3788        """Pick color of specific (x,y) pixel on the screen."""
3789        w2if = vtki.new("WindowToImageFilter")
3790        w2if.SetInput(self.window)
3791        w2if.ReadFrontBufferOff()
3792        w2if.Update()
3793        nx, ny = self.window.GetSize()
3794        varr = w2if.GetOutput().GetPointData().GetScalars()
3795
3796        arr = utils.vtk2numpy(varr).reshape(ny, nx, 3)
3797        x, y = int(xy[0]), int(xy[1])
3798        if y < ny and x < nx:
3799
3800            rgb = arr[y, x]
3801
3802            if verbose:
3803                vedo.printc(":rainbow:Pixel", [x, y], "has RGB[", end="")
3804                vedo.printc("█", c=[rgb[0], 0, 0], end="")
3805                vedo.printc("█", c=[0, rgb[1], 0], end="")
3806                vedo.printc("█", c=[0, 0, rgb[2]], end="")
3807                vedo.printc("] = ", end="")
3808                cnm = vedo.get_color_name(rgb)
3809                if np.sum(rgb) < 150:
3810                    vedo.printc(
3811                        rgb.tolist(),
3812                        vedo.colors.rgb2hex(np.array(rgb) / 255),
3813                        c="w",
3814                        bc=rgb,
3815                        invert=1,
3816                        end="",
3817                    )
3818                    vedo.printc("  -> " + cnm, invert=1, c="w")
3819                else:
3820                    vedo.printc(
3821                        rgb.tolist(),
3822                        vedo.colors.rgb2hex(np.array(rgb) / 255),
3823                        c=rgb,
3824                        end="",
3825                    )
3826                    vedo.printc("  -> " + cnm, c=cnm)
3827
3828            return rgb
3829
3830        return None

Pick color of specific (x,y) pixel on the screen.

def show( *objects, at=None, shape=(1, 1), N=None, pos=(0, 0), size='auto', screensize='auto', title='vedo', bg='white', bg2=None, axes=None, interactive=None, offscreen=False, sharecam=True, resetcam=True, zoom=None, viewup='', azimuth=0.0, elevation=0.0, roll=0.0, camera=None, mode=None, screenshot='', new=False) -> Optional[Self]:
118def show(
119    *objects,
120    at=None,
121    shape=(1, 1),
122    N=None,
123    pos=(0, 0),
124    size="auto",
125    screensize="auto",
126    title="vedo",
127    bg="white",
128    bg2=None,
129    axes=None,
130    interactive=None,
131    offscreen=False,
132    sharecam=True,
133    resetcam=True,
134    zoom=None,
135    viewup="",
136    azimuth=0.0,
137    elevation=0.0,
138    roll=0.0,
139    camera=None,
140    mode=None,
141    screenshot="",
142    new=False,
143) -> Union[Self, None]:
144    """
145    Create on the fly an instance of class Plotter and show the object(s) provided.
146
147    Arguments:
148        at : (int)
149            number of the renderer to plot to, in case of more than one exists
150        shape : (list, str)
151            Number of sub-render windows inside of the main window. E.g.:
152            specify two across with shape=(2,1) and a two by two grid
153            with shape=(2, 2). By default there is only one renderer.
154
155            Can also accept a shape as string descriptor. E.g.:
156            - shape="3|1" means 3 plots on the left and 1 on the right,
157            - shape="4/2" means 4 plots on top of 2 at bottom.
158        N : (int)
159            number of desired sub-render windows arranged automatically in a grid
160        pos : (list)
161            position coordinates of the top-left corner of the rendering window
162            on the screen
163        size : (list)
164            size of the rendering window
165        screensize : (list)
166            physical size of the monitor screen
167        title : (str)
168            window title
169        bg : (color)
170            background color or specify jpg image file name with path
171        bg2 : (color)
172            background color of a gradient towards the top
173        axes : (int)
174            set the type of axes to be shown:
175            - 0,  no axes
176            - 1,  draw three gray grid walls
177            - 2,  show cartesian axes from (0,0,0)
178            - 3,  show positive range of cartesian axes from (0,0,0)
179            - 4,  show a triad at bottom left
180            - 5,  show a cube at bottom left
181            - 6,  mark the corners of the bounding box
182            - 7,  draw a 3D ruler at each side of the cartesian axes
183            - 8,  show the `vtkCubeAxesActor` object
184            - 9,  show the bounding box outLine
185            - 10, show three circles representing the maximum bounding box
186            - 11, show a large grid on the x-y plane
187            - 12, show polar axes
188            - 13, draw a simple ruler at the bottom of the window
189            - 14: draw a `CameraOrientationWidget`
190
191            Axis type-1 can be fully customized by passing a dictionary.
192            Check `vedo.addons.Axes()` for the full list of options.
193        azimuth/elevation/roll : (float)
194            move camera accordingly the specified value
195        viewup : (str, list)
196            either `['x', 'y', 'z']` or a vector to set vertical direction
197        resetcam : (bool)
198            re-adjust camera position to fit objects
199        camera : (dict, vtkCamera)
200            camera parameters can further be specified with a dictionary
201            assigned to the `camera` keyword (E.g. `show(camera={'pos':(1,2,3), 'thickness':1000,})`):
202            - **pos** (list),  the position of the camera in world coordinates
203            - **focal_point** (list), the focal point of the camera in world coordinates
204            - **viewup** (list), the view up direction for the camera
205            - **distance** (float), set the focal point to the specified distance from the camera position.
206            - **clipping_range** (float), distance of the near and far clipping planes along the direction of projection.
207            - **parallel_scale** (float),
208            scaling used for a parallel projection, i.e. the height of the viewport
209            in world-coordinate distances. The default is 1. Note that the "scale" parameter works as
210            an "inverse scale", larger numbers produce smaller images.
211            This method has no effect in perspective projection mode.
212            - **thickness** (float),
213            set the distance between clipping planes. This method adjusts the far clipping
214            plane to be set a distance 'thickness' beyond the near clipping plane.
215            - **view_angle** (float),
216            the camera view angle, which is the angular height of the camera view
217            measured in degrees. The default angle is 30 degrees.
218            This method has no effect in parallel projection mode.
219            The formula for setting the angle up for perfect perspective viewing is:
220            angle = 2*atan((h/2)/d) where h is the height of the RenderWindow
221            (measured by holding a ruler up to your screen) and d is the distance
222            from your eyes to the screen.
223        interactive : (bool)
224            pause and interact with window (True) or continue execution (False)
225        rate : (float)
226            maximum rate of `show()` in Hertz
227        mode : (int, str)
228            set the type of interaction:
229            - 0 = TrackballCamera [default]
230            - 1 = TrackballActor
231            - 2 = JoystickCamera
232            - 3 = JoystickActor
233            - 4 = Flight
234            - 5 = RubberBand2D
235            - 6 = RubberBand3D
236            - 7 = RubberBandZoom
237            - 8 = Terrain
238            - 9 = Unicam
239            - 10 = Image
240        new : (bool)
241            if set to `True`, a call to show will instantiate
242            a new Plotter object (a new window) instead of reusing the first created.
243            If new is `True`, but the existing plotter was instantiated with a different
244            argument for `offscreen`, `new` is ignored and a new Plotter is created anyway.
245    """
246    if len(objects) == 0:
247        objects = None
248    elif len(objects) == 1:
249        objects = objects[0]
250    else:
251        objects = utils.flatten(objects)
252
253    # If a plotter instance is already present, check if the offscreen argument
254    # is the same as the one requested by the user. If not, create a new
255    # plotter instance (see https://github.com/marcomusy/vedo/issues/1026)
256    if vedo.plotter_instance and vedo.plotter_instance.offscreen != offscreen:
257        new = True
258
259    if vedo.plotter_instance and not new:  # Plotter exists
260        plt = vedo.plotter_instance
261
262    else:  # Plotter must be created
263
264        if utils.is_sequence(at):  # user passed a sequence for "at"
265
266            if not utils.is_sequence(objects):
267                vedo.logger.error("in show() input must be a list.")
268                raise RuntimeError()
269            if len(at) != len(objects):
270                vedo.logger.error("in show() lists 'input' and 'at' must have equal lengths")
271                raise RuntimeError()
272            if shape == (1, 1) and N is None:
273                N = max(at) + 1
274
275        elif at is None and (N or shape != (1, 1)):
276
277            if not utils.is_sequence(objects):
278                e = "in show(), N or shape is set, but input is not a sequence\n"
279                e += "              you may need to specify e.g. at=0"
280                vedo.logger.error(e)
281                raise RuntimeError()
282            at = list(range(len(objects)))
283
284        plt = Plotter(
285            shape=shape,
286            N=N,
287            pos=pos,
288            size=size,
289            screensize=screensize,
290            title=title,
291            axes=axes,
292            sharecam=sharecam,
293            resetcam=resetcam,
294            interactive=interactive,
295            offscreen=offscreen,
296            bg=bg,
297            bg2=bg2,
298        )
299
300    if vedo.settings.dry_run_mode >= 2:
301        return plt
302
303    # use _plt_to_return because plt.show() can return a k3d plot
304    _plt_to_return = None
305
306    if utils.is_sequence(at):
307
308        for i, act in enumerate(objects):
309            _plt_to_return = plt.show(
310                act,
311                at=i,
312                zoom=zoom,
313                resetcam=resetcam,
314                viewup=viewup,
315                azimuth=azimuth,
316                elevation=elevation,
317                roll=roll,
318                camera=camera,
319                interactive=False,
320                mode=mode,
321                screenshot=screenshot,
322                bg=bg,
323                bg2=bg2,
324                axes=axes,
325            )
326
327        if (
328            interactive
329            or len(at) == N
330            or (isinstance(shape[0], int) and len(at) == shape[0] * shape[1])
331        ):
332            # note that shape can be a string
333            if plt.interactor and not offscreen and (interactive is None or interactive):
334                plt.interactor.Start()
335                if plt._must_close_now:
336                    plt.interactor.GetRenderWindow().Finalize()
337                    plt.interactor.TerminateApp()
338                    plt.interactor = None
339                    plt.window = None
340                    plt.renderer = None
341                    plt.renderers = []
342                    plt.camera = None
343                    
344    else:
345
346        _plt_to_return = plt.show(
347            objects,
348            at=at,
349            zoom=zoom,
350            resetcam=resetcam,
351            viewup=viewup,
352            azimuth=azimuth,
353            elevation=elevation,
354            roll=roll,
355            camera=camera,
356            interactive=interactive,
357            mode=mode,
358            screenshot=screenshot,
359            bg=bg,
360            bg2=bg2,
361            axes=axes,
362        )
363
364    return _plt_to_return

Create on the fly an instance of class Plotter and show the object(s) provided.

Arguments:
  • at : (int) number of the renderer to plot to, in case of more than one exists
  • shape : (list, str) Number of sub-render windows inside of the main window. E.g.: specify two across with shape=(2,1) and a two by two grid with shape=(2, 2). By default there is only one renderer.

    Can also accept a shape as string descriptor. E.g.:

    • shape="3|1" means 3 plots on the left and 1 on the right,
    • shape="4/2" means 4 plots on top of 2 at bottom.
  • N : (int) number of desired sub-render windows arranged automatically in a grid
  • pos : (list) position coordinates of the top-left corner of the rendering window on the screen
  • size : (list) size of the rendering window
  • screensize : (list) physical size of the monitor screen
  • title : (str) window title
  • bg : (color) background color or specify jpg image file name with path
  • bg2 : (color) background color of a gradient towards the top
  • axes : (int) set the type of axes to be shown:

    • 0, no axes
    • 1, draw three gray grid walls
    • 2, show cartesian axes from (0,0,0)
    • 3, show positive range of cartesian axes from (0,0,0)
    • 4, show a triad at bottom left
    • 5, show a cube at bottom left
    • 6, mark the corners of the bounding box
    • 7, draw a 3D ruler at each side of the cartesian axes
    • 8, show the vtkCubeAxesActor object
    • 9, show the bounding box outLine
    • 10, show three circles representing the maximum bounding box
    • 11, show a large grid on the x-y plane
    • 12, show polar axes
    • 13, draw a simple ruler at the bottom of the window
    • 14: draw a CameraOrientationWidget

    Axis type-1 can be fully customized by passing a dictionary. Check vedo.addons.Axes() for the full list of options.

  • azimuth/elevation/roll : (float) move camera accordingly the specified value
  • viewup : (str, list) either ['x', 'y', 'z'] or a vector to set vertical direction
  • resetcam : (bool) re-adjust camera position to fit objects
  • camera : (dict, vtkCamera) camera parameters can further be specified with a dictionary assigned to the camera keyword (E.g. show(camera={'pos':(1,2,3), 'thickness':1000,})):
    • pos (list), the position of the camera in world coordinates
    • focal_point (list), the focal point of the camera in world coordinates
    • viewup (list), the view up direction for the camera
    • distance (float), set the focal point to the specified distance from the camera position.
    • clipping_range (float), distance of the near and far clipping planes along the direction of projection.
    • parallel_scale (float), scaling used for a parallel projection, i.e. the height of the viewport in world-coordinate distances. The default is 1. Note that the "scale" parameter works as an "inverse scale", larger numbers produce smaller images. This method has no effect in perspective projection mode.
    • thickness (float), set the distance between clipping planes. This method adjusts the far clipping plane to be set a distance 'thickness' beyond the near clipping plane.
    • view_angle (float), the camera view angle, which is the angular height of the camera view measured in degrees. The default angle is 30 degrees. This method has no effect in parallel projection mode. The formula for setting the angle up for perfect perspective viewing is: angle = 2*atan((h/2)/d) where h is the height of the RenderWindow (measured by holding a ruler up to your screen) and d is the distance from your eyes to the screen.
  • interactive : (bool) pause and interact with window (True) or continue execution (False)
  • rate : (float) maximum rate of show() in Hertz
  • mode : (int, str) set the type of interaction:
    • 0 = TrackballCamera [default]
    • 1 = TrackballActor
    • 2 = JoystickCamera
    • 3 = JoystickActor
    • 4 = Flight
    • 5 = RubberBand2D
    • 6 = RubberBand3D
    • 7 = RubberBandZoom
    • 8 = Terrain
    • 9 = Unicam
    • 10 = Image
  • new : (bool) if set to True, a call to show will instantiate a new Plotter object (a new window) instead of reusing the first created. If new is True, but the existing plotter was instantiated with a different argument for offscreen, new is ignored and a new Plotter is created anyway.
def close() -> None:
367def close() -> None:
368    """Close the last created Plotter instance if it exists."""
369    if not vedo.plotter_instance:
370        return
371    vedo.plotter_instance.close()
372    return

Close the last created Plotter instance if it exists.