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

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

def record(self, filename='') -> str:
1538    def record(self, filename="") -> str:
1539        """
1540        Record camera, mouse, keystrokes and all other events.
1541        Recording can be toggled on/off by pressing key "R".
1542
1543        Arguments:
1544            filename : (str)
1545                ascii file to store events.
1546                The default is `vedo.settings.cache_directory+"vedo/recorded_events.log"`.
1547
1548        Returns:
1549            a string descriptor of events.
1550
1551        Examples:
1552            - [record_play.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/record_play.py)
1553        """
1554        if vedo.settings.dry_run_mode >= 1:
1555            return ""
1556        if not self.interactor:
1557            vedo.logger.warning("Cannot record events, no interactor defined.")
1558            return ""
1559        erec = vtki.new("InteractorEventRecorder")
1560        erec.SetInteractor(self.interactor)
1561        if not filename:
1562            if not os.path.exists(vedo.settings.cache_directory):
1563                os.makedirs(vedo.settings.cache_directory)
1564            home_dir = os.path.expanduser("~")
1565            filename = os.path.join(
1566                home_dir, vedo.settings.cache_directory, "vedo", "recorded_events.log")
1567            print("Events will be recorded in", filename)
1568        erec.SetFileName(filename)
1569        erec.SetKeyPressActivationValue("R")
1570        erec.EnabledOn()
1571        erec.Record()
1572        self.interactor.Start()
1573        erec.Stop()
1574        erec.EnabledOff()
1575        with open(filename, "r", encoding="UTF-8") as fl:
1576            events = fl.read()
1577        erec = None
1578        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:
1580    def play(self, recorded_events="", repeats=0) -> Self:
1581        """
1582        Play camera, mouse, keystrokes and all other events.
1583
1584        Arguments:
1585            events : (str)
1586                file o string of events.
1587                The default is `vedo.settings.cache_directory+"vedo/recorded_events.log"`.
1588            repeats : (int)
1589                number of extra repeats of the same events. The default is 0.
1590
1591        Examples:
1592            - [record_play.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/record_play.py)
1593        """
1594        if vedo.settings.dry_run_mode >= 1:
1595            return self
1596        if not self.interactor:
1597            vedo.logger.warning("Cannot play events, no interactor defined.")
1598            return self
1599
1600        erec = vtki.new("InteractorEventRecorder")
1601        erec.SetInteractor(self.interactor)
1602
1603        if not recorded_events:
1604            home_dir = os.path.expanduser("~")
1605            recorded_events = os.path.join(
1606                home_dir, vedo.settings.cache_directory, "vedo", "recorded_events.log")
1607
1608        if recorded_events.endswith(".log"):
1609            erec.ReadFromInputStringOff()
1610            erec.SetFileName(recorded_events)
1611        else:
1612            erec.ReadFromInputStringOn()
1613            erec.SetInputString(recorded_events)
1614
1615        erec.Play()
1616        for _ in range(repeats):
1617            erec.Rewind()
1618            erec.Play()
1619        erec.EnabledOff()
1620        erec = None
1621        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:
1623    def parallel_projection(self, value=True, at=None) -> Self:
1624        """
1625        Use parallel projection `at` a specified renderer.
1626        Object is seen from "infinite" distance, e.i. remove any perspective effects.
1627        An input value equal to -1 will toggle it on/off.
1628        """
1629        if at is not None:
1630            r = self.renderers[at]
1631        else:
1632            r = self.renderer
1633        if value == -1:
1634            val = r.GetActiveCamera().GetParallelProjection()
1635            value = not val
1636        r.GetActiveCamera().SetParallelProjection(value)
1637        r.Modified()
1638        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:
1640    def render_hidden_lines(self, value=True) -> Self:
1641        """Remove hidden lines when in wireframe mode."""
1642        self.renderer.SetUseHiddenLineRemoval(not value)
1643        return self

Remove hidden lines when in wireframe mode.

def fov(self, angle: float) -> Self:
1645    def fov(self, angle: float) -> Self:
1646        """
1647        Set the field of view angle for the camera.
1648        This is the angle of the camera frustum in the horizontal direction.
1649        High values will result in a wide-angle lens (fish-eye effect),
1650        and low values will result in a telephoto lens.
1651
1652        Default value is 30 degrees.
1653        """
1654        self.renderer.GetActiveCamera().UseHorizontalViewAngleOn()
1655        self.renderer.GetActiveCamera().SetViewAngle(angle)
1656        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:
1658    def zoom(self, zoom: float) -> Self:
1659        """Apply a zooming factor for the current camera view"""
1660        self.renderer.GetActiveCamera().Zoom(zoom)
1661        return self

Apply a zooming factor for the current camera view

def azimuth(self, angle: float) -> Self:
1663    def azimuth(self, angle: float) -> Self:
1664        """Rotate camera around the view up vector."""
1665        self.renderer.GetActiveCamera().Azimuth(angle)
1666        return self

Rotate camera around the view up vector.

def elevation(self, angle: float) -> Self:
1668    def elevation(self, angle: float) -> Self:
1669        """Rotate the camera around the cross product of the negative
1670        of the direction of projection and the view up vector."""
1671        self.renderer.GetActiveCamera().Elevation(angle)
1672        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:
1674    def roll(self, angle: float) -> Self:
1675        """Roll the camera about the direction of projection."""
1676        self.renderer.GetActiveCamera().Roll(angle)
1677        return self

Roll the camera about the direction of projection.

def dolly(self, value: float) -> Self:
1679    def dolly(self, value: float) -> Self:
1680        """Move the camera towards (value>0) or away from (value<0) the focal point."""
1681        self.renderer.GetActiveCamera().Dolly(value)
1682        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:
1685    def add_slider(
1686        self,
1687        sliderfunc,
1688        xmin,
1689        xmax,
1690        value=None,
1691        pos=4,
1692        title="",
1693        font="Calco",
1694        title_size=1,
1695        c=None,
1696        alpha=1,
1697        show_value=True,
1698        delayed=False,
1699        **options,
1700    ) -> "vedo.addons.Slider2D":
1701        """
1702        Add a `vedo.addons.Slider2D` which can call an external custom function.
1703
1704        Arguments:
1705            sliderfunc : (Callable)
1706                external function to be called by the widget
1707            xmin : (float)
1708                lower value of the slider
1709            xmax : (float)
1710                upper value
1711            value : (float)
1712                current value
1713            pos : (list, str)
1714                position corner number: horizontal [1-5] or vertical [11-15]
1715                it can also be specified by corners coordinates [(x1,y1), (x2,y2)]
1716                and also by a string descriptor (eg. "bottom-left")
1717            title : (str)
1718                title text
1719            font : (str)
1720                title font face. Check [available fonts here](https://vedo.embl.es/fonts).
1721            title_size : (float)
1722                title text scale [1.0]
1723            show_value : (bool)
1724                if True current value is shown
1725            delayed : (bool)
1726                if True the callback is delayed until when the mouse button is released
1727            alpha : (float)
1728                opacity of the scalar bar texts
1729            slider_length : (float)
1730                slider length
1731            slider_width : (float)
1732                slider width
1733            end_cap_length : (float)
1734                length of the end cap
1735            end_cap_width : (float)
1736                width of the end cap
1737            tube_width : (float)
1738                width of the tube
1739            title_height : (float)
1740                width of the title
1741            tformat : (str)
1742                format of the title
1743
1744        Examples:
1745            - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py)
1746            - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py)
1747
1748            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
1749        """
1750        if c is None:  # automatic black or white
1751            c = (0.8, 0.8, 0.8)
1752            if np.sum(vedo.get_color(self.backgrcol)) > 1.5:
1753                c = (0.2, 0.2, 0.2)
1754        else:
1755            c = vedo.get_color(c)
1756
1757        slider2d = addons.Slider2D(
1758            sliderfunc,
1759            xmin,
1760            xmax,
1761            value,
1762            pos,
1763            title,
1764            font,
1765            title_size,
1766            c,
1767            alpha,
1768            show_value,
1769            delayed,
1770            **options,
1771        )
1772
1773        if self.renderer:
1774            slider2d.renderer = self.renderer
1775            if self.interactor:
1776                slider2d.interactor = self.interactor
1777                slider2d.on()
1778                self.sliders.append([slider2d, sliderfunc])
1779        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:
1781    def add_slider3d(
1782        self,
1783        sliderfunc,
1784        pos1,
1785        pos2,
1786        xmin,
1787        xmax,
1788        value=None,
1789        s=0.03,
1790        t=1,
1791        title="",
1792        rotation=0.0,
1793        c=None,
1794        show_value=True,
1795    ) -> "vedo.addons.Slider3D":
1796        """
1797        Add a 3D slider widget which can call an external custom function.
1798
1799        Arguments:
1800            sliderfunc : (function)
1801                external function to be called by the widget
1802            pos1 : (list)
1803                first position 3D coordinates
1804            pos2 : (list)
1805                second position coordinates
1806            xmin : (float)
1807                lower value
1808            xmax : (float)
1809                upper value
1810            value : (float)
1811                initial value
1812            s : (float)
1813                label scaling factor
1814            t : (float)
1815                tube scaling factor
1816            title : (str)
1817                title text
1818            c : (color)
1819                slider color
1820            rotation : (float)
1821                title rotation around slider axis
1822            show_value : (bool)
1823                if True current value is shown
1824
1825        Examples:
1826            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
1827
1828            ![](https://user-images.githubusercontent.com/32848391/52859555-4efcf200-312d-11e9-9290-6988c8295163.png)
1829        """
1830        if c is None:  # automatic black or white
1831            c = (0.8, 0.8, 0.8)
1832            if np.sum(vedo.get_color(self.backgrcol)) > 1.5:
1833                c = (0.2, 0.2, 0.2)
1834        else:
1835            c = vedo.get_color(c)
1836
1837        slider3d = addons.Slider3D(
1838            sliderfunc,
1839            pos1,
1840            pos2,
1841            xmin,
1842            xmax,
1843            value,
1844            s,
1845            t,
1846            title,
1847            rotation,
1848            c,
1849            show_value,
1850        )
1851        slider3d.renderer = self.renderer
1852        slider3d.interactor = self.interactor
1853        slider3d.on()
1854        self.sliders.append([slider3d, sliderfunc])
1855        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]:
1857    def add_button(
1858        self,
1859        fnc=None,
1860        states=("On", "Off"),
1861        c=("w", "w"),
1862        bc=("green4", "red4"),
1863        pos=(0.7, 0.1),
1864        size=24,
1865        font="Courier",
1866        bold=True,
1867        italic=False,
1868        alpha=1,
1869        angle=0,
1870    ) -> Union["vedo.addons.Button", None]:
1871        """
1872        Add a button to the renderer window.
1873
1874        Arguments:
1875            states : (list)
1876                a list of possible states, e.g. ['On', 'Off']
1877            c : (list)
1878                a list of colors for each state
1879            bc : (list)
1880                a list of background colors for each state
1881            pos : (list)
1882                2D position from left-bottom corner
1883            size : (float)
1884                size of button font
1885            font : (str)
1886                font type. Check [available fonts here](https://vedo.embl.es/fonts).
1887            bold : (bool)
1888                bold font face (False)
1889            italic : (bool)
1890                italic font face (False)
1891            alpha : (float)
1892                opacity level
1893            angle : (float)
1894                anticlockwise rotation in degrees
1895
1896        Returns:
1897            `vedo.addons.Button` object.
1898
1899        Examples:
1900            - [buttons1.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/buttons1.py)
1901            - [buttons2.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/buttons2.py)
1902
1903            ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
1904        """
1905        if self.interactor:
1906            bu = addons.Button(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle)
1907            self.renderer.AddActor2D(bu)
1908            self.buttons.append(bu)
1909            # bu.function_id = self.add_callback("LeftButtonPress", bu.function) # not good
1910            bu.function_id = bu.add_observer("pick", bu.function, priority=10)
1911            return bu
1912        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:
1914    def add_spline_tool(
1915        self, points, pc="k", ps=8, lc="r4", ac="g5",
1916        lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True,
1917    ) -> "vedo.addons.SplineTool":
1918        """
1919        Add a spline tool to the current plotter.
1920        Nodes of the spline can be dragged in space with the mouse.
1921        Clicking on the line itself adds an extra point.
1922        Selecting a point and pressing del removes it.
1923
1924        Arguments:
1925            points : (Mesh, Points, array)
1926                the set of vertices forming the spline nodes.
1927            pc : (str)
1928                point color. The default is 'k'.
1929            ps : (str)
1930                point size. The default is 8.
1931            lc : (str)
1932                line color. The default is 'r4'.
1933            ac : (str)
1934                active point marker color. The default is 'g5'.
1935            lw : (int)
1936                line width. The default is 2.
1937            alpha : (float)
1938                line transparency.
1939            closed : (bool)
1940                spline is meant to be closed. The default is False.
1941
1942        Returns:
1943            a `SplineTool` object.
1944
1945        Examples:
1946            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
1947
1948            ![](https://vedo.embl.es/images/basic/spline_tool.png)
1949        """
1950        sw = addons.SplineTool(points, pc, ps, lc, ac, lw, alpha, closed, ontop, can_add_nodes)
1951        sw.interactor = self.interactor
1952        sw.on()
1953        sw.Initialize(sw.points.dataset)
1954        sw.representation.SetRenderer(self.renderer)
1955        sw.representation.SetClosedLoop(closed)
1956        sw.representation.BuildRepresentation()
1957        self.widgets.append(sw)
1958        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:
1960    def add_icon(self, icon, pos=3, size=0.08) -> "vedo.addons.Icon":
1961        """Add an inset icon mesh into the same renderer.
1962
1963        Arguments:
1964            pos : (int, list)
1965                icon position in the range [1-4] indicating one of the 4 corners,
1966                or it can be a tuple (x,y) as a fraction of the renderer size.
1967            size : (float)
1968                size of the square inset.
1969
1970        Examples:
1971            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
1972        """
1973        iconw = addons.Icon(icon, pos, size)
1974
1975        iconw.SetInteractor(self.interactor)
1976        iconw.EnabledOn()
1977        iconw.InteractiveOff()
1978        self.widgets.append(iconw)
1979        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:
1981    def add_global_axes(self, axtype=None, c=None) -> Self:
1982        """Draw axes on scene. Available axes types:
1983
1984        Arguments:
1985            axtype : (int)
1986                - 0,  no axes,
1987                - 1,  draw three gray grid walls
1988                - 2,  show cartesian axes from (0,0,0)
1989                - 3,  show positive range of cartesian axes from (0,0,0)
1990                - 4,  show a triad at bottom left
1991                - 5,  show a cube at bottom left
1992                - 6,  mark the corners of the bounding box
1993                - 7,  draw a 3D ruler at each side of the cartesian axes
1994                - 8,  show the vtkCubeAxesActor object
1995                - 9,  show the bounding box outLine
1996                - 10, show three circles representing the maximum bounding box
1997                - 11, show a large grid on the x-y plane
1998                - 12, show polar axes
1999                - 13, draw a simple ruler at the bottom of the window
2000
2001            Axis type-1 can be fully customized by passing a dictionary axes=dict().
2002
2003        Example:
2004            ```python
2005            from vedo import Box, show
2006            b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1)
2007            show(
2008                b,
2009                axes={
2010                    "xtitle": "Some long variable [a.u.]",
2011                    "number_of_divisions": 4,
2012                    # ...
2013                },
2014            )
2015            ```
2016
2017        Examples:
2018            - [custom_axes1.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes1.py)
2019            - [custom_axes2.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes2.py)
2020            - [custom_axes3.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes3.py)
2021            - [custom_axes4.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes4.py)
2022
2023            <img src="https://user-images.githubusercontent.com/32848391/72752870-ab7d5280-3bc3-11ea-8911-9ace00211e23.png" width="600">
2024        """
2025        addons.add_global_axes(axtype, c)
2026        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:
2028    def add_legend_box(self, **kwargs) -> "vedo.addons.LegendBox":
2029        """Add a legend to the top right.
2030
2031        Examples:
2032            - [legendbox.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/basic/legendbox.py),
2033            - [flag_labels1.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels1.py)
2034            - [flag_labels2.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels2.py)
2035        """
2036        acts = self.get_meshes()
2037        lb = addons.LegendBox(acts, **kwargs)
2038        self.add(lb)
2039        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]:
2041    def add_hint(
2042        self,
2043        obj,
2044        text="",
2045        c="k",
2046        bg="yellow9",
2047        font="Calco",
2048        size=18,
2049        justify=0,
2050        angle=0,
2051        delay=250,
2052    ) -> Union[vtki.vtkBalloonWidget, None]:
2053        """
2054        Create a pop-up hint style message when hovering an object.
2055        Use `add_hint(obj, False)` to disable a hinting a specific object.
2056        Use `add_hint(None)` to disable all hints.
2057
2058        Arguments:
2059            obj : (Mesh, Points)
2060                the object to associate the pop-up to
2061            text : (str)
2062                string description of the pop-up
2063            delay : (int)
2064                milliseconds to wait before pop-up occurs
2065        """
2066        if self.offscreen or not self.interactor:
2067            return None
2068
2069        if vedo.vtk_version[:2] == (9, 0) and "Linux" in vedo.sys_platform:
2070            # Linux vtk9.0 is bugged
2071            vedo.logger.warning(
2072                f"add_hint() is not available on Linux platforms for vtk{vedo.vtk_version}."
2073            )
2074            return None
2075
2076        if obj is None:
2077            self.hint_widget.EnabledOff()
2078            self.hint_widget.SetInteractor(None)
2079            self.hint_widget = None
2080            return self.hint_widget
2081
2082        if text is False and self.hint_widget:
2083            self.hint_widget.RemoveBalloon(obj)
2084            return self.hint_widget
2085
2086        if text == "":
2087            if obj.name:
2088                text = obj.name
2089            elif obj.filename:
2090                text = obj.filename
2091            else:
2092                return None
2093
2094        if not self.hint_widget:
2095            self.hint_widget = vtki.vtkBalloonWidget()
2096
2097            rep = self.hint_widget.GetRepresentation()
2098            rep.SetBalloonLayoutToImageRight()
2099
2100            trep = rep.GetTextProperty()
2101            trep.SetFontFamily(vtki.VTK_FONT_FILE)
2102            trep.SetFontFile(utils.get_font_path(font))
2103            trep.SetFontSize(size)
2104            trep.SetColor(vedo.get_color(c))
2105            trep.SetBackgroundColor(vedo.get_color(bg))
2106            trep.SetShadow(0)
2107            trep.SetJustification(justify)
2108            trep.UseTightBoundingBoxOn()
2109
2110            self.hint_widget.ManagesCursorOff()
2111            self.hint_widget.SetTimerDuration(delay)
2112            self.hint_widget.SetInteractor(self.interactor)
2113            if angle:
2114                trep.SetOrientation(angle)
2115                trep.SetBackgroundOpacity(0)
2116            # else:
2117            #     trep.SetBackgroundOpacity(0.5) # doesnt work well
2118            self.hint_widget.SetRepresentation(rep)
2119            self.widgets.append(self.hint_widget)
2120            self.hint_widget.EnabledOn()
2121
2122        bst = self.hint_widget.GetBalloonString(obj.actor)
2123        if bst:
2124            self.hint_widget.UpdateBalloonString(obj.actor, text)
2125        else:
2126            self.hint_widget.AddBalloon(obj.actor, text)
2127
2128        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:
2130    def add_shadows(self) -> Self:
2131        """Add shadows at the current renderer."""
2132        if self.renderer:
2133            shadows = vtki.new("ShadowMapPass")
2134            seq = vtki.new("SequencePass")
2135            passes = vtki.new("RenderPassCollection")
2136            passes.AddItem(shadows.GetShadowMapBakerPass())
2137            passes.AddItem(shadows)
2138            seq.SetPasses(passes)
2139            camerapass = vtki.new("CameraPass")
2140            camerapass.SetDelegatePass(seq)
2141            self.renderer.SetPass(camerapass)
2142        return self

Add shadows at the current renderer.

def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
2144    def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
2145        """
2146        Screen Space Ambient Occlusion.
2147
2148        For every pixel on the screen, the pixel shader samples the depth values around
2149        the current pixel and tries to compute the amount of occlusion from each of the sampled
2150        points.
2151
2152        Arguments:
2153            radius : (float)
2154                radius of influence in absolute units
2155            bias : (float)
2156                bias of the normals
2157            blur : (bool)
2158                add a blurring to the sampled positions
2159            samples : (int)
2160                number of samples to probe
2161
2162        Examples:
2163            - [ssao.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ssao.py)
2164
2165            ![](https://vedo.embl.es/images/basic/ssao.jpg)
2166        """
2167        lights = vtki.new("LightsPass")
2168
2169        opaque = vtki.new("OpaquePass")
2170
2171        ssaoCam = vtki.new("CameraPass")
2172        ssaoCam.SetDelegatePass(opaque)
2173
2174        ssao = vtki.new("SSAOPass")
2175        ssao.SetRadius(radius)
2176        ssao.SetBias(bias)
2177        ssao.SetBlur(blur)
2178        ssao.SetKernelSize(samples)
2179        ssao.SetDelegatePass(ssaoCam)
2180
2181        translucent = vtki.new("TranslucentPass")
2182
2183        volpass = vtki.new("VolumetricPass")
2184        ddp = vtki.new("DualDepthPeelingPass")
2185        ddp.SetTranslucentPass(translucent)
2186        ddp.SetVolumetricPass(volpass)
2187
2188        over = vtki.new("OverlayPass")
2189
2190        collection = vtki.new("RenderPassCollection")
2191        collection.AddItem(lights)
2192        collection.AddItem(ssao)
2193        collection.AddItem(ddp)
2194        collection.AddItem(over)
2195
2196        sequence = vtki.new("SequencePass")
2197        sequence.SetPasses(collection)
2198
2199        cam = vtki.new("CameraPass")
2200        cam.SetDelegatePass(sequence)
2201
2202        self.renderer.SetPass(cam)
2203        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:
2205    def add_depth_of_field(self, autofocus=True) -> Self:
2206        """Add a depth of field effect in the scene."""
2207        lights = vtki.new("LightsPass")
2208
2209        opaque = vtki.new("OpaquePass")
2210
2211        dofCam = vtki.new("CameraPass")
2212        dofCam.SetDelegatePass(opaque)
2213
2214        dof = vtki.new("DepthOfFieldPass")
2215        dof.SetAutomaticFocalDistance(autofocus)
2216        dof.SetDelegatePass(dofCam)
2217
2218        collection = vtki.new("RenderPassCollection")
2219        collection.AddItem(lights)
2220        collection.AddItem(dof)
2221
2222        sequence = vtki.new("SequencePass")
2223        sequence.SetPasses(collection)
2224
2225        cam = vtki.new("CameraPass")
2226        cam.SetDelegatePass(sequence)
2227
2228        self.renderer.SetPass(cam)
2229        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:
2260    def add_renderer_frame(self, c=None, alpha=None, lw=None, padding=None) -> "vedo.addons.RendererFrame":
2261        """
2262        Add a frame to the renderer subwindow.
2263
2264        Arguments:
2265            c : (color)
2266                color name or index
2267            alpha : (float)
2268                opacity level
2269            lw : (int)
2270                line width in pixels.
2271            padding : (float)
2272                padding space in pixels.
2273        """
2274        if c is None:  # automatic black or white
2275            c = (0.9, 0.9, 0.9)
2276            if self.renderer:
2277                if np.sum(self.renderer.GetBackground()) > 1.5:
2278                    c = (0.1, 0.1, 0.1)
2279        renf = addons.RendererFrame(c, alpha, lw, padding)
2280        if renf:
2281            self.renderer.AddActor(renf)
2282        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:
2284    def add_hover_legend(
2285        self,
2286        at=None,
2287        c=None,
2288        pos="bottom-left",
2289        font="Calco",
2290        s=0.75,
2291        bg="auto",
2292        alpha=0.1,
2293        maxlength=24,
2294        use_info=False,
2295    ) -> int:
2296        """
2297        Add a legend with 2D text which is triggered by hovering the mouse on an object.
2298
2299        The created text object are stored in `plotter.hover_legends`.
2300
2301        Returns:
2302            the id of the callback function.
2303
2304        Arguments:
2305            c : (color)
2306                Text color. If None then black or white is chosen automatically
2307            pos : (str)
2308                text positioning
2309            font : (str)
2310                text font type. Check [available fonts here](https://vedo.embl.es/fonts).
2311            s : (float)
2312                text size scale
2313            bg : (color)
2314                background color of the 2D box containing the text
2315            alpha : (float)
2316                box transparency
2317            maxlength : (int)
2318                maximum number of characters per line
2319            use_info : (bool)
2320                visualize the content of the `obj.info` attribute
2321
2322        Examples:
2323            - [hover_legend.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/hover_legend.py)
2324            - [earthquake_browser.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/earthquake_browser.py)
2325
2326            ![](https://vedo.embl.es/images/pyplot/earthquake_browser.jpg)
2327        """
2328        hoverlegend = vedo.shapes.Text2D(pos=pos, font=font, c=c, s=s, alpha=alpha, bg=bg)
2329
2330        if at is None:
2331            at = self.renderers.index(self.renderer)
2332
2333        def _legfunc(evt):
2334            if not evt.object or not self.renderer or at != evt.at:
2335                if hoverlegend.mapper.GetInput():  # clear and return
2336                    hoverlegend.mapper.SetInput("")
2337                    self.render()
2338                return
2339
2340            if use_info:
2341                if hasattr(evt.object, "info"):
2342                    t = str(evt.object.info)
2343                else:
2344                    return
2345            else:
2346                t, tp = "", ""
2347                if evt.isMesh:
2348                    tp = "Mesh "
2349                elif evt.isPoints:
2350                    tp = "Points "
2351                elif evt.isVolume:
2352                    tp = "Volume "
2353                elif evt.isImage:
2354                    tp = "Image "
2355                elif evt.isAssembly:
2356                    tp = "Assembly "
2357                else:
2358                    return
2359
2360                if evt.isAssembly:
2361                    if not evt.object.name:
2362                        t += f"Assembly object of {len(evt.object.unpack())} parts\n"
2363                    else:
2364                        t += f"Assembly name: {evt.object.name} ({len(evt.object.unpack())} parts)\n"
2365                else:
2366                    if evt.object.name:
2367                        t += f"{tp}name"
2368                        if evt.isPoints:
2369                            t += "  "
2370                        if evt.isMesh:
2371                            t += "  "
2372                        t += f": {evt.object.name[:maxlength]}".ljust(maxlength) + "\n"
2373
2374                if evt.object.filename:
2375                    t += f"{tp}filename: "
2376                    t += f"{os.path.basename(evt.object.filename[-maxlength:])}".ljust(maxlength)
2377                    t += "\n"
2378                    if not evt.object.file_size:
2379                        evt.object.file_size, evt.object.created = vedo.file_io.file_info(evt.object.filename)
2380                    if evt.object.file_size:
2381                        t += "             : "
2382                        sz, created = evt.object.file_size, evt.object.created
2383                        t += f"{created[4:-5]} ({sz})" + "\n"
2384
2385                if evt.isPoints:
2386                    indata = evt.object.dataset
2387                    if indata.GetNumberOfPoints():
2388                        t += (
2389                            f"#points/cells: {indata.GetNumberOfPoints()}"
2390                            f" / {indata.GetNumberOfCells()}"
2391                        )
2392                    pdata = indata.GetPointData()
2393                    cdata = indata.GetCellData()
2394                    if pdata.GetScalars() and pdata.GetScalars().GetName():
2395                        t += f"\nPoint array  : {pdata.GetScalars().GetName()}"
2396                        if pdata.GetScalars().GetName() == evt.object.mapper.GetArrayName():
2397                            t += " *"
2398                    if cdata.GetScalars() and cdata.GetScalars().GetName():
2399                        t += f"\nCell  array  : {cdata.GetScalars().GetName()}"
2400                        if cdata.GetScalars().GetName() == evt.object.mapper.GetArrayName():
2401                            t += " *"
2402
2403                if evt.isImage:
2404                    t = f"{os.path.basename(evt.object.filename[:maxlength+10])}".ljust(maxlength+10)
2405                    t += f"\nImage shape: {evt.object.shape}"
2406                    pcol = self.color_picker(evt.picked2d)
2407                    t += f"\nPixel color: {vedo.colors.rgb2hex(pcol/255)} {pcol}"
2408
2409            # change box color if needed in 'auto' mode
2410            if evt.isPoints and "auto" in str(bg):
2411                actcol = evt.object.properties.GetColor()
2412                if hoverlegend.mapper.GetTextProperty().GetBackgroundColor() != actcol:
2413                    hoverlegend.mapper.GetTextProperty().SetBackgroundColor(actcol)
2414
2415            # adapt to changes in bg color
2416            bgcol = self.renderers[at].GetBackground()
2417            _bgcol = c
2418            if _bgcol is None:  # automatic black or white
2419                _bgcol = (0.9, 0.9, 0.9)
2420                if sum(bgcol) > 1.5:
2421                    _bgcol = (0.1, 0.1, 0.1)
2422                if len(set(_bgcol).intersection(bgcol)) < 3:
2423                    hoverlegend.color(_bgcol)
2424
2425            if hoverlegend.mapper.GetInput() != t:
2426                hoverlegend.mapper.SetInput(t)
2427                self.interactor.Render()
2428            
2429            # print("ABORT", idcall, hoverlegend.actor.GetCommand(idcall))
2430            # hoverlegend.actor.GetCommand(idcall).AbortFlagOn()
2431
2432        self.add(hoverlegend, at=at)
2433        self.hover_legends.append(hoverlegend)
2434        idcall = self.add_callback("MouseMove", _legfunc)
2435        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]:
2437    def add_scale_indicator(
2438        self,
2439        pos=(0.7, 0.05),
2440        s=0.02,
2441        length=2,
2442        lw=4,
2443        c="k1",
2444        alpha=1,
2445        units="",
2446        gap=0.05,
2447    ) -> Union["vedo.visual.Actor2D", None]:
2448        """
2449        Add a Scale Indicator. Only works in parallel mode (no perspective).
2450
2451        Arguments:
2452            pos : (list)
2453                fractional (x,y) position on the screen.
2454            s : (float)
2455                size of the text.
2456            length : (float)
2457                length of the line.
2458            units : (str)
2459                string to show units.
2460            gap : (float)
2461                separation of line and text.
2462
2463        Example:
2464            ```python
2465            from vedo import settings, Cube, Plotter
2466            settings.use_parallel_projection = True # or else it does not make sense!
2467            cube = Cube().alpha(0.2)
2468            plt = Plotter(size=(900,600), axes=dict(xtitle='x (um)'))
2469            plt.add_scale_indicator(units='um', c='blue4')
2470            plt.show(cube, "Scale indicator with units").close()
2471            ```
2472            ![](https://vedo.embl.es/images/feats/scale_indicator.png)
2473        """
2474        # Note that this cannot go in addons.py
2475        # because it needs callbacks and window size
2476        if not self.interactor:
2477            return None
2478
2479        ppoints = vtki.vtkPoints()  # Generate the polyline
2480        psqr = [[0.0, gap], [length / 10, gap]]
2481        dd = psqr[1][0] - psqr[0][0]
2482        for i, pt in enumerate(psqr):
2483            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2484        lines = vtki.vtkCellArray()
2485        lines.InsertNextCell(len(psqr))
2486        for i in range(len(psqr)):
2487            lines.InsertCellPoint(i)
2488        pd = vtki.vtkPolyData()
2489        pd.SetPoints(ppoints)
2490        pd.SetLines(lines)
2491
2492        wsx, wsy = self.window.GetSize()
2493        if not self.camera.GetParallelProjection():
2494            vedo.logger.warning("add_scale_indicator called with use_parallel_projection OFF. Skip.")
2495            return None
2496
2497        rlabel = vtki.new("VectorText")
2498        rlabel.SetText("scale")
2499        tf = vtki.new("TransformPolyDataFilter")
2500        tf.SetInputConnection(rlabel.GetOutputPort())
2501        t = vtki.vtkTransform()
2502        t.Scale(s * wsy / wsx, s, 1)
2503        tf.SetTransform(t)
2504
2505        app = vtki.new("AppendPolyData")
2506        app.AddInputConnection(tf.GetOutputPort())
2507        app.AddInputData(pd)
2508
2509        mapper = vtki.new("PolyDataMapper2D")
2510        mapper.SetInputConnection(app.GetOutputPort())
2511        cs = vtki.vtkCoordinate()
2512        cs.SetCoordinateSystem(1)
2513        mapper.SetTransformCoordinate(cs)
2514
2515        fractor = vedo.visual.Actor2D()
2516        csys = fractor.GetPositionCoordinate()
2517        csys.SetCoordinateSystem(3)
2518        fractor.SetPosition(pos)
2519        fractor.SetMapper(mapper)
2520        fractor.GetProperty().SetColor(vedo.get_color(c))
2521        fractor.GetProperty().SetOpacity(alpha)
2522        fractor.GetProperty().SetLineWidth(lw)
2523        fractor.GetProperty().SetDisplayLocationToForeground()
2524
2525        def sifunc(iren, ev):
2526            wsx, wsy = self.window.GetSize()
2527            ps = self.camera.GetParallelScale()
2528            newtxt = utils.precision(ps / wsy * wsx * length * dd, 3)
2529            if units:
2530                newtxt += " " + units
2531            if rlabel.GetText() != newtxt:
2532                rlabel.SetText(newtxt)
2533
2534        self.renderer.AddActor(fractor)
2535        self.interactor.AddObserver("MouseWheelBackwardEvent", sifunc)
2536        self.interactor.AddObserver("MouseWheelForwardEvent", sifunc)
2537        self.interactor.AddObserver("InteractionEvent", sifunc)
2538        sifunc(0, 0)
2539        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:
2541    def fill_event(self, ename="", pos=(), enable_picking=True) -> "Event":
2542        """
2543        Create an Event object with information of what was clicked.
2544
2545        If `enable_picking` is False, no picking will be performed.
2546        This can be useful to avoid double picking when using buttons.
2547        """
2548        if not self.interactor:
2549            return Event()
2550
2551        if len(pos) > 0:
2552            x, y = pos
2553            self.interactor.SetEventPosition(pos)
2554        else:
2555            x, y = self.interactor.GetEventPosition()
2556        self.renderer = self.interactor.FindPokedRenderer(x, y)
2557
2558        self.picked2d = (x, y)
2559
2560        key = self.interactor.GetKeySym()
2561
2562        if key:
2563            if "_L" in key or "_R" in key:
2564                # skip things like Shift_R
2565                key = ""  # better than None
2566            else:
2567                if self.interactor.GetShiftKey():
2568                    key = key.upper()
2569
2570                if key == "MINUS":  # fix: vtk9 is ignoring shift chars..
2571                    key = "underscore"
2572                elif key == "EQUAL":  # fix: vtk9 is ignoring shift chars..
2573                    key = "plus"
2574                elif key == "SLASH":  # fix: vtk9 is ignoring shift chars..
2575                    key = "?"
2576
2577                if self.interactor.GetControlKey():
2578                    key = "Ctrl+" + key
2579
2580                if self.interactor.GetAltKey():
2581                    key = "Alt+" + key
2582
2583        if enable_picking:
2584            if not self.picker:
2585                self.picker = vtki.vtkPropPicker()
2586
2587            self.picker.PickProp(x, y, self.renderer)
2588            actor = self.picker.GetProp3D()
2589            # Note that GetProp3D already picks Assembly
2590
2591            xp, yp = self.interactor.GetLastEventPosition()
2592            dx, dy = x - xp, y - yp
2593
2594            delta3d = np.array([0, 0, 0])
2595
2596            if actor:
2597                picked3d = np.array(self.picker.GetPickPosition())
2598
2599                try:
2600                    vobj = actor.retrieve_object()
2601                    old_pt = np.asarray(vobj.picked3d)
2602                    vobj.picked3d = picked3d
2603                    delta3d = picked3d - old_pt
2604                except (AttributeError, TypeError):
2605                    pass
2606
2607            else:
2608                picked3d = None
2609
2610            if not actor:  # try 2D
2611                actor = self.picker.GetActor2D()
2612
2613        event = Event()
2614        event.name = ename
2615        event.title = self.title
2616        event.id = -1  # will be set by the timer wrapper function
2617        event.timerid = -1  # will be set by the timer wrapper function
2618        event.priority = -1  # will be set by the timer wrapper function
2619        event.time = time.time()
2620        event.at = self.renderers.index(self.renderer)
2621        event.keypress = key
2622        if enable_picking:
2623            try:
2624                event.object = actor.retrieve_object()
2625            except AttributeError:
2626                event.object = actor
2627            try:
2628                event.actor = actor.retrieve_object()  # obsolete use object instead
2629            except AttributeError:
2630                event.actor = actor
2631            event.picked3d = picked3d
2632            event.picked2d = (x, y)
2633            event.delta2d = (dx, dy)
2634            event.angle2d = np.arctan2(dy, dx)
2635            event.speed2d = np.sqrt(dx * dx + dy * dy)
2636            event.delta3d = delta3d
2637            event.speed3d = np.sqrt(np.dot(delta3d, delta3d))
2638            event.isPoints = isinstance(event.object, vedo.Points)
2639            event.isMesh = isinstance(event.object, vedo.Mesh)
2640            event.isAssembly = isinstance(event.object, vedo.Assembly)
2641            event.isVolume = isinstance(event.object, vedo.Volume)
2642            event.isImage = isinstance(event.object, vedo.Image)
2643            event.isActor2D = isinstance(event.object, vtki.vtkActor2D)
2644        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:
2646    def add_callback(self, event_name: str, func: Callable, priority=0.0, enable_picking=True) -> int:
2647        """
2648        Add a function to be executed while show() is active.
2649
2650        Return a unique id for the callback.
2651
2652        The callback function (see example below) exposes a dictionary
2653        with the following information:
2654        - `name`: event name,
2655        - `id`: event unique identifier,
2656        - `priority`: event priority (float),
2657        - `interactor`: the interactor object,
2658        - `at`: renderer nr. where the event occurred
2659        - `keypress`: key pressed as string
2660        - `actor`: object picked by the mouse
2661        - `picked3d`: point picked in world coordinates
2662        - `picked2d`: screen coords of the mouse pointer
2663        - `delta2d`: shift wrt previous position (to calculate speed, direction)
2664        - `delta3d`: ...same but in 3D world coords
2665        - `angle2d`: angle of mouse movement on screen
2666        - `speed2d`: speed of mouse movement on screen
2667        - `speed3d`: speed of picked point in world coordinates
2668        - `isPoints`: True if of class
2669        - `isMesh`: True if of class
2670        - `isAssembly`: True if of class
2671        - `isVolume`: True if of class Volume
2672        - `isImage`: True if of class
2673
2674        If `enable_picking` is False, no picking will be performed.
2675        This can be useful to avoid double picking when using buttons.
2676
2677        Frequently used events are:
2678        - `KeyPress`, `KeyRelease`: listen to keyboard events
2679        - `LeftButtonPress`, `LeftButtonRelease`: listen to mouse clicks
2680        - `MiddleButtonPress`, `MiddleButtonRelease`
2681        - `RightButtonPress`, `RightButtonRelease`
2682        - `MouseMove`: listen to mouse pointer changing position
2683        - `MouseWheelForward`, `MouseWheelBackward`
2684        - `Enter`, `Leave`: listen to mouse entering or leaving the window
2685        - `Pick`, `StartPick`, `EndPick`: listen to object picking
2686        - `ResetCamera`, `ResetCameraClippingRange`
2687        - `Error`, `Warning`
2688        - `Char`
2689        - `Timer`
2690
2691        Check the complete list of events [here](https://vtk.org/doc/nightly/html/classvtkCommand.html).
2692
2693        Example:
2694            ```python
2695            from vedo import *
2696
2697            def func(evt):
2698                # this function is called every time the mouse moves
2699                # (evt is a dotted dictionary)
2700                if not evt.object:
2701                    return  # no hit, return
2702                print("point coords =", evt.picked3d)
2703                # print(evt) # full event dump
2704
2705            elli = Ellipsoid()
2706            plt = Plotter(axes=1)
2707            plt.add_callback('mouse hovering', func)
2708            plt.show(elli).close()
2709            ```
2710
2711        Examples:
2712            - [spline_draw.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/spline_draw.py)
2713            - [colorlines.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorlines.py)
2714
2715                ![](https://vedo.embl.es/images/advanced/spline_draw.png)
2716
2717            - ..and many others!
2718        """
2719        from vtkmodules.util.misc import calldata_type
2720
2721        if not self.interactor:
2722            return 0
2723
2724        if vedo.settings.dry_run_mode >= 1:
2725            return 0
2726
2727        #########################################
2728        @calldata_type(vtki.VTK_INT)
2729        def _func_wrap(iren, ename, timerid=None):
2730            event = self.fill_event(ename=ename, enable_picking=enable_picking)
2731            event.timerid = timerid
2732            event.id = cid
2733            event.priority = priority
2734            self.last_event = event
2735            func(event)
2736
2737        #########################################
2738
2739        event_name = utils.get_vtk_name_event(event_name)
2740
2741        cid = self.interactor.AddObserver(event_name, _func_wrap, priority)
2742        # print(f"Registering event: {event_name} with id={cid}")
2743        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:
2745    def remove_callback(self, cid: Union[int, str]) -> Self:
2746        """
2747        Remove a callback function by its id
2748        or a whole category of callbacks by their name.
2749
2750        Arguments:
2751            cid : (int, str)
2752                Unique id of the callback.
2753                If an event name is passed all callbacks of that type are removed.
2754        """
2755        if self.interactor:
2756            if isinstance(cid, str):
2757                cid = utils.get_vtk_name_event(cid)
2758                self.interactor.RemoveObservers(cid)
2759            else:
2760                self.interactor.RemoveObserver(cid)
2761        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:
2763    def remove_all_observers(self) -> Self:
2764        """
2765        Remove all observers.
2766
2767        Example:
2768        ```python
2769        from vedo import *
2770
2771        def kfunc(event):
2772            print("Key pressed:", event.keypress)
2773            if event.keypress == 'q':
2774                plt.close()
2775
2776        def rfunc(event):
2777            if event.isImage:
2778                printc("Right-clicked!", event)
2779                plt.render()
2780
2781        img = Image(dataurl+"images/embryo.jpg")
2782
2783        plt = Plotter(size=(1050, 600))
2784        plt.parallel_projection(True)
2785        plt.remove_all_observers()
2786        plt.add_callback("key press", kfunc)
2787        plt.add_callback("mouse right click", rfunc)
2788        plt.show("Right-Click Me! Press q to exit.", img)
2789        plt.close()
2790        ```
2791        """
2792        if self.interactor:
2793            self.interactor.RemoveAllObservers()
2794        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:
2796    def timer_callback(self, action: str, timer_id=None, dt=1, one_shot=False) -> int:
2797        """
2798        Start or stop an existing timer.
2799
2800        Arguments:
2801            action : (str)
2802                Either "create"/"start" or "destroy"/"stop"
2803            timer_id : (int)
2804                When stopping the timer, the ID of the timer as returned when created
2805            dt : (int)
2806                time in milliseconds between each repeated call
2807            one_shot : (bool)
2808                create a one shot timer of prescribed duration instead of a repeating one
2809
2810        Examples:
2811            - [timer_callback1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback1.py)
2812            - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
2813
2814            ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
2815        """
2816        if action in ("create", "start"):
2817            if timer_id is not None:
2818                vedo.logger.warning("you set a timer_id but it will be ignored.")
2819            if one_shot:
2820                timer_id = self.interactor.CreateOneShotTimer(dt)
2821            else:
2822                timer_id = self.interactor.CreateRepeatingTimer(dt)
2823            return timer_id
2824
2825        elif action in ("destroy", "stop"):
2826            if timer_id is not None:
2827                self.interactor.DestroyTimer(timer_id)
2828            else:
2829                vedo.logger.warning("please set a timer_id. Cannot stop timer.")
2830        else:
2831            e = f"in timer_callback(). Cannot understand action: {action}\n"
2832            e += " allowed actions are: ['start', 'stop']. Skipped."
2833            vedo.logger.error(e)
2834        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:
2836    def add_observer(self, event_name: str, func: Callable, priority=0.0) -> int:
2837        """
2838        Add a callback function that will be called when an event occurs.
2839        Consider using `add_callback()` instead.
2840        """
2841        if not self.interactor:
2842            return -1
2843        event_name = utils.get_vtk_name_event(event_name)
2844        idd = self.interactor.AddObserver(event_name, func, priority)
2845        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:
2847    def compute_world_coordinate(
2848        self,
2849        pos2d: MutableSequence[float],
2850        at=None,
2851        objs=(),
2852        bounds=(),
2853        offset=None,
2854        pixeltol=None,
2855        worldtol=None,
2856    ) -> np.ndarray:
2857        """
2858        Transform a 2D point on the screen into a 3D point inside the rendering scene.
2859        If a set of meshes is passed then points are placed onto these.
2860
2861        Arguments:
2862            pos2d : (list)
2863                2D screen coordinates point.
2864            at : (int)
2865                renderer number.
2866            objs : (list)
2867                list of Mesh objects to project the point onto.
2868            bounds : (list)
2869                specify a bounding box as [xmin,xmax, ymin,ymax, zmin,zmax].
2870            offset : (float)
2871                specify an offset value.
2872            pixeltol : (int)
2873                screen tolerance in pixels.
2874            worldtol : (float)
2875                world coordinates tolerance.
2876
2877        Returns:
2878            numpy array, the point in 3D world coordinates.
2879
2880        Examples:
2881            - [cut_freehand.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/cut_freehand.py)
2882            - [mousehover3.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mousehover3.py)
2883
2884            ![](https://vedo.embl.es/images/basic/mousehover3.jpg)
2885        """
2886        if at is not None:
2887            renderer = self.renderers[at]
2888        else:
2889            renderer = self.renderer
2890
2891        if not objs:
2892            pp = vtki.vtkFocalPlanePointPlacer()
2893        else:
2894            pps = vtki.vtkPolygonalSurfacePointPlacer()
2895            for ob in objs:
2896                pps.AddProp(ob.actor)
2897            pp = pps # type: ignore
2898
2899        if len(bounds) == 6:
2900            pp.SetPointBounds(bounds)
2901        if pixeltol:
2902            pp.SetPixelTolerance(pixeltol)
2903        if worldtol:
2904            pp.SetWorldTolerance(worldtol)
2905        if offset:
2906            pp.SetOffset(offset)
2907
2908        worldPos: MutableSequence[float] = [0, 0, 0]
2909        worldOrient: MutableSequence[float] = [0, 0, 0, 0, 0, 0, 0, 0, 0]
2910        pp.ComputeWorldPosition(renderer, pos2d, worldPos, worldOrient)
2911        # validw = pp.ValidateWorldPosition(worldPos, worldOrient)
2912        # validd = pp.ValidateDisplayPosition(renderer, pos2d)
2913        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:
2915    def compute_screen_coordinates(self, obj, full_window=False) -> np.ndarray:
2916        """
2917        Given a 3D points in the current renderer (or full window),
2918        find the screen pixel coordinates.
2919
2920        Example:
2921            ```python
2922            from vedo import *
2923
2924            elli = Ellipsoid().point_size(5)
2925
2926            plt = Plotter()
2927            plt.show(elli, "Press q to continue and print the info")
2928
2929            xyscreen = plt.compute_screen_coordinates(elli)
2930            print('xyscreen coords:', xyscreen)
2931
2932            # simulate an event happening at one point
2933            event = plt.fill_event(pos=xyscreen[123])
2934            print(event)
2935            ```
2936        """
2937        try:
2938            obj = obj.vertices
2939        except AttributeError:
2940            pass
2941
2942        if utils.is_sequence(obj):
2943            pts = obj
2944        p2d = []
2945        cs = vtki.vtkCoordinate()
2946        cs.SetCoordinateSystemToWorld()
2947        cs.SetViewport(self.renderer)
2948        for p in pts:
2949            cs.SetValue(p)
2950            if full_window:
2951                p2d.append(cs.GetComputedDisplayValue(self.renderer))
2952            else:
2953                p2d.append(cs.GetComputedViewportValue(self.renderer))
2954        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:
2956    def pick_area(self, pos1, pos2, at=None) -> "vedo.Mesh":
2957        """
2958        Pick all objects within a box defined by two corner points in 2D screen coordinates.
2959
2960        Returns a frustum Mesh that contains the visible field of view.
2961        This can be used to select objects in a scene or select vertices.
2962
2963        Example:
2964            ```python
2965            from vedo import *
2966
2967            settings.enable_default_mouse_callbacks = False
2968
2969            def mode_select(objs):
2970                print("Selected objects:", objs)
2971                d0 = mode.start_x, mode.start_y # display coords
2972                d1 = mode.end_x, mode.end_y
2973
2974                frustum = plt.pick_area(d0, d1)
2975                col = np.random.randint(0, 10)
2976                infru = frustum.inside_points(mesh)
2977                infru.point_size(10).color(col)
2978                plt.add(frustum, infru).render()
2979
2980            mesh = Mesh(dataurl+"cow.vtk")
2981            mesh.color("k5").linewidth(1)
2982
2983            mode = interactor_modes.BlenderStyle()
2984            mode.callback_select = mode_select
2985
2986            plt = Plotter().user_mode(mode)
2987            plt.show(mesh, axes=1)
2988            ```
2989        """
2990        if at is not None:
2991            ren = self.renderers[at]
2992        else:
2993            ren = self.renderer
2994        area_picker = vtki.vtkAreaPicker()
2995        area_picker.AreaPick(pos1[0], pos1[1], pos2[0], pos2[1], ren)
2996        planes = area_picker.GetFrustum()
2997
2998        fru = vtki.new("FrustumSource")
2999        fru.SetPlanes(planes)
3000        fru.ShowLinesOff()
3001        fru.Update()
3002
3003        afru = vedo.Mesh(fru.GetOutput())
3004        afru.alpha(0.1).lw(1).pickable(False)
3005        afru.name = "Frustum"
3006        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:
3129    def show(
3130        self,
3131        *objects,
3132        at=None,
3133        axes=None,
3134        resetcam=None,
3135        zoom=False,
3136        interactive=None,
3137        viewup="",
3138        azimuth=0.0,
3139        elevation=0.0,
3140        roll=0.0,
3141        camera=None,
3142        mode=None,
3143        rate=None,
3144        bg=None,
3145        bg2=None,
3146        size=None,
3147        title=None,
3148        screenshot="",
3149    ) -> Any:
3150        """
3151        Render a list of objects.
3152
3153        Arguments:
3154            at : (int)
3155                number of the renderer to plot to, in case of more than one exists
3156
3157            axes : (int)
3158                axis type-1 can be fully customized by passing a dictionary.
3159                Check `addons.Axes()` for the full list of options.
3160                set the type of axes to be shown:
3161                - 0,  no axes
3162                - 1,  draw three gray grid walls
3163                - 2,  show cartesian axes from (0,0,0)
3164                - 3,  show positive range of cartesian axes from (0,0,0)
3165                - 4,  show a triad at bottom left
3166                - 5,  show a cube at bottom left
3167                - 6,  mark the corners of the bounding box
3168                - 7,  draw a 3D ruler at each side of the cartesian axes
3169                - 8,  show the `vtkCubeAxesActor` object
3170                - 9,  show the bounding box outLine
3171                - 10, show three circles representing the maximum bounding box
3172                - 11, show a large grid on the x-y plane
3173                - 12, show polar axes
3174                - 13, draw a simple ruler at the bottom of the window
3175
3176            azimuth/elevation/roll : (float)
3177                move camera accordingly the specified value
3178
3179            viewup: str, list
3180                either `['x', 'y', 'z']` or a vector to set vertical direction
3181
3182            resetcam : (bool)
3183                re-adjust camera position to fit objects
3184
3185            camera : (dict, vtkCamera)
3186                camera parameters can further be specified with a dictionary
3187                assigned to the `camera` keyword (E.g. `show(camera={'pos':(1,2,3), 'thickness':1000,})`):
3188                - pos, `(list)`,  the position of the camera in world coordinates
3189                - focal_point `(list)`, the focal point of the camera in world coordinates
3190                - viewup `(list)`, the view up direction for the camera
3191                - distance `(float)`, set the focal point to the specified distance from the camera position.
3192                - clipping_range `(float)`, distance of the near and far clipping planes along the direction of projection.
3193                - parallel_scale `(float)`, scaling used for a parallel projection, i.e. the height of the viewport
3194                in world-coordinate distances. The default is 1.
3195                Note that the "scale" parameter works as an "inverse scale", larger numbers produce smaller images.
3196                This method has no effect in perspective projection mode.
3197
3198                - thickness `(float)`, set the distance between clipping planes. This method adjusts the far clipping
3199                plane to be set a distance 'thickness' beyond the near clipping plane.
3200
3201                - view_angle `(float)`, the camera view angle, which is the angular height of the camera view
3202                measured in degrees. The default angle is 30 degrees.
3203                This method has no effect in parallel projection mode.
3204                The formula for setting the angle up for perfect perspective viewing is:
3205                angle = 2*atan((h/2)/d) where h is the height of the RenderWindow
3206                (measured by holding a ruler up to your screen) and d is the distance from your eyes to the screen.
3207
3208            interactive : (bool)
3209                pause and interact with window (True) or continue execution (False)
3210
3211            rate : (float)
3212                maximum rate of `show()` in Hertz
3213
3214            mode : (int, str)
3215                set the type of interaction:
3216                - 0 = TrackballCamera [default]
3217                - 1 = TrackballActor
3218                - 2 = JoystickCamera
3219                - 3 = JoystickActor
3220                - 4 = Flight
3221                - 5 = RubberBand2D
3222                - 6 = RubberBand3D
3223                - 7 = RubberBandZoom
3224                - 8 = Terrain
3225                - 9 = Unicam
3226                - 10 = Image
3227                - Check out `vedo.interaction_modes` for more options.
3228
3229            bg : (str, list)
3230                background color in RGB format, or string name
3231
3232            bg2 : (str, list)
3233                second background color to create a gradient background
3234
3235            size : (str, list)
3236                size of the window, e.g. size="fullscreen", or size=[600,400]
3237
3238            title : (str)
3239                window title text
3240
3241            screenshot : (str)
3242                save a screenshot of the window to file
3243        """
3244
3245        if vedo.settings.dry_run_mode >= 2:
3246            return self
3247
3248        if self.wx_widget:
3249            return self
3250
3251        if self.renderers:  # in case of notebooks
3252
3253            if at is None:
3254                at = self.renderers.index(self.renderer)
3255
3256            else:
3257
3258                if at >= len(self.renderers):
3259                    t = f"trying to show(at={at}) but only {len(self.renderers)} renderers exist"
3260                    vedo.logger.error(t)
3261                    return self
3262
3263                self.renderer = self.renderers[at]
3264
3265        if title is not None:
3266            self.title = title
3267
3268        if size is not None:
3269            self.size = size
3270            if self.size[0] == "f":  # full screen
3271                self.size = "fullscreen"
3272                self.window.SetFullScreen(True)
3273                self.window.BordersOn()
3274            else:
3275                self.window.SetSize(int(self.size[0]), int(self.size[1]))
3276
3277        if vedo.settings.default_backend == "vtk":
3278            if str(bg).endswith(".hdr"):
3279                self._add_skybox(bg)
3280            else:
3281                if bg is not None:
3282                    self.backgrcol = vedo.get_color(bg)
3283                    self.renderer.SetBackground(self.backgrcol)
3284                if bg2 is not None:
3285                    self.renderer.GradientBackgroundOn()
3286                    self.renderer.SetBackground2(vedo.get_color(bg2))
3287
3288        if axes is not None:
3289            if isinstance(axes, vedo.Assembly):  # user passing show(..., axes=myaxes)
3290                objects = list(objects)
3291                objects.append(axes)  # move it into the list of normal things to show
3292                axes = 0
3293            self.axes = axes
3294
3295        if interactive is not None:
3296            self._interactive = interactive
3297        if self.offscreen:
3298            self._interactive = False
3299
3300        # camera stuff
3301        if resetcam is not None:
3302            self.resetcam = resetcam
3303
3304        if camera is not None:
3305            self.resetcam = False
3306            viewup = ""
3307            if isinstance(camera, vtki.vtkCamera):
3308                cameracopy = vtki.vtkCamera()
3309                cameracopy.DeepCopy(camera)
3310                self.camera = cameracopy
3311            else:
3312                self.camera = utils.camera_from_dict(camera)
3313
3314        self.add(objects)
3315
3316        # Backend ###############################################################
3317        if vedo.settings.default_backend in ["k3d"]:
3318            return backends.get_notebook_backend(self.objects)
3319        #########################################################################
3320
3321        for ia in utils.flatten(objects):
3322            try:
3323                # fix gray color labels and title to white or black
3324                ltc = np.array(ia.scalarbar.GetLabelTextProperty().GetColor())
3325                if np.linalg.norm(ltc - (0.5, 0.5, 0.5)) / 3 < 0.05:
3326                    c = (0.9, 0.9, 0.9)
3327                    if np.sum(self.renderer.GetBackground()) > 1.5:
3328                        c = (0.1, 0.1, 0.1)
3329                    ia.scalarbar.GetLabelTextProperty().SetColor(c)
3330                    ia.scalarbar.GetTitleTextProperty().SetColor(c)
3331            except AttributeError:
3332                pass
3333
3334        if self.sharecam:
3335            for r in self.renderers:
3336                r.SetActiveCamera(self.camera)
3337
3338        if self.axes is not None:
3339            if viewup != "2d" or self.axes in [1, 8] or isinstance(self.axes, dict):
3340                bns = self.renderer.ComputeVisiblePropBounds()
3341                addons.add_global_axes(self.axes, bounds=bns)
3342
3343        # Backend ###############################################################
3344        if vedo.settings.default_backend in ["ipyvtk", "trame"]:
3345            return backends.get_notebook_backend()
3346        #########################################################################
3347
3348        if self.resetcam:
3349            self.renderer.ResetCamera()
3350
3351        if len(self.renderers) > 1:
3352            self.add_renderer_frame()
3353
3354        if vedo.settings.default_backend == "2d" and not zoom:
3355            zoom = "tightest"
3356
3357        if zoom:
3358            if zoom == "tight":
3359                self.reset_camera(tight=0.04)
3360            elif zoom == "tightest":
3361                self.reset_camera(tight=0.0001)
3362            else:
3363                self.camera.Zoom(zoom)
3364        if elevation:
3365            self.camera.Elevation(elevation)
3366        if azimuth:
3367            self.camera.Azimuth(azimuth)
3368        if roll:
3369            self.camera.Roll(roll)
3370
3371        if len(viewup) > 0:
3372            b = self.renderer.ComputeVisiblePropBounds()
3373            cm = np.array([(b[1] + b[0])/2, (b[3] + b[2])/2, (b[5] + b[4])/2])
3374            sz = np.array([(b[1] - b[0]), (b[3] - b[2]), (b[5] - b[4])])
3375            if viewup == "x":
3376                sz = np.linalg.norm(sz)
3377                self.camera.SetViewUp([1, 0, 0])
3378                self.camera.SetPosition(cm + sz)
3379            elif viewup == "y":
3380                sz = np.linalg.norm(sz)
3381                self.camera.SetViewUp([0, 1, 0])
3382                self.camera.SetPosition(cm + sz)
3383            elif viewup == "z":
3384                sz = np.array([(b[1]-b[0])*0.7, -(b[3]-b[2])*1.0, (b[5]-b[4])*1.2])
3385                self.camera.SetViewUp([0, 0, 1])
3386                self.camera.SetPosition(cm + 2 * sz)
3387            elif utils.is_sequence(viewup):
3388                sz = np.linalg.norm(sz)
3389                self.camera.SetViewUp(viewup)
3390                cpos = np.cross([0, 1, 0], viewup)
3391                self.camera.SetPosition(cm - 2 * sz * cpos)
3392
3393        self.renderer.ResetCameraClippingRange()
3394
3395        self.initialize_interactor()
3396
3397        if vedo.settings.immediate_rendering:
3398            self.window.Render()  ##################### <-------------- Render
3399
3400        if self.interactor:  # can be offscreen or not the vtk backend..
3401
3402            self.window.SetWindowName(self.title)
3403
3404            # pic = vedo.Image(vedo.dataurl+'images/vtk_logo.png')
3405            # pic = vedo.Image('/home/musy/Downloads/icons8-3d-96.png')
3406            # print(pic.dataset)# Array 0 name PNGImage
3407            # self.window.SetIcon(pic.dataset)
3408
3409            try:
3410                # Needs "pip install pyobjc" on Mac OSX
3411                if (
3412                    self._cocoa_initialized is False
3413                    and "Darwin" in vedo.sys_platform
3414                    and not self.offscreen
3415                ):
3416                    self._cocoa_initialized = True
3417                    from Cocoa import NSRunningApplication, NSApplicationActivateIgnoringOtherApps # type: ignore
3418                    pid = os.getpid()
3419                    x = NSRunningApplication.runningApplicationWithProcessIdentifier_(int(pid))
3420                    x.activateWithOptions_(NSApplicationActivateIgnoringOtherApps)
3421            except:
3422                # vedo.logger.debug("On Mac OSX try: pip install pyobjc")
3423                pass
3424
3425            # Set the interaction style
3426            if mode is not None:
3427                self.user_mode(mode)
3428            if self.qt_widget and mode is None:
3429                self.user_mode(0)
3430
3431            if screenshot:
3432                self.screenshot(screenshot)
3433
3434            if self._interactive:
3435                self.interactor.Start()
3436                if self._must_close_now:
3437                    self.interactor.GetRenderWindow().Finalize()
3438                    self.interactor.TerminateApp()
3439                    self.camera = None
3440                    self.renderer = None
3441                    self.renderers = []
3442                    self.window = None
3443                    self.interactor = None
3444                return self
3445
3446            if rate:
3447                if self.clock is None:  # set clock and limit rate
3448                    self._clockt0 = time.time()
3449                    self.clock = 0.0
3450                else:
3451                    t = time.time() - self._clockt0
3452                    elapsed = t - self.clock
3453                    mint = 1.0 / rate
3454                    if elapsed < mint:
3455                        time.sleep(mint - elapsed)
3456                    self.clock = time.time() - self._clockt0
3457
3458        # 2d ####################################################################
3459        if vedo.settings.default_backend == "2d":
3460            return backends.get_notebook_backend()
3461        #########################################################################
3462
3463        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]:
3466    def add_inset(self, *objects, **options) -> Union[vtki.vtkOrientationMarkerWidget, None]:
3467        """Add a draggable inset space into a renderer.
3468
3469        Arguments:
3470            at : (int)
3471                specify the renderer number
3472            pos : (list)
3473                icon position in the range [1-4] indicating one of the 4 corners,
3474                or it can be a tuple (x,y) as a fraction of the renderer size.
3475            size : (float)
3476                size of the square inset
3477            draggable : (bool)
3478                if True the subrenderer space can be dragged around
3479            c : (color)
3480                color of the inset frame when dragged
3481
3482        Examples:
3483            - [inset.py](https://github.com/marcomusy/vedo/tree/master/examples/other/inset.py)
3484
3485            ![](https://user-images.githubusercontent.com/32848391/56758560-3c3f1300-6797-11e9-9b33-49f5a4876039.jpg)
3486        """
3487        if not self.interactor:
3488            return None
3489
3490        if not self.renderer:
3491            vedo.logger.warning("call add_inset() only after first rendering of the scene.")
3492            return None
3493
3494        options = dict(options)
3495        pos = options.pop("pos", 0)
3496        size = options.pop("size", 0.1)
3497        c = options.pop("c", "lb")
3498        at = options.pop("at", None)
3499        draggable = options.pop("draggable", True)
3500
3501        r, g, b = vedo.get_color(c)
3502        widget = vtki.vtkOrientationMarkerWidget()
3503        widget.SetOutlineColor(r, g, b)
3504        if len(objects) == 1:
3505            widget.SetOrientationMarker(objects[0].actor)
3506        else:
3507            widget.SetOrientationMarker(vedo.Assembly(objects))
3508
3509        widget.SetInteractor(self.interactor)
3510
3511        if utils.is_sequence(pos):
3512            widget.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
3513        else:
3514            if pos < 2:
3515                widget.SetViewport(0, 1 - 2 * size, size * 2, 1)
3516            elif pos == 2:
3517                widget.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
3518            elif pos == 3:
3519                widget.SetViewport(0, 0, size * 2, size * 2)
3520            elif pos == 4:
3521                widget.SetViewport(1 - 2 * size, 0, 1, size * 2)
3522        widget.EnabledOn()
3523        widget.SetInteractive(draggable)
3524        if at is not None and at < len(self.renderers):
3525            widget.SetCurrentRenderer(self.renderers[at])
3526        else:
3527            widget.SetCurrentRenderer(self.renderer)
3528        self.widgets.append(widget)
3529        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:
3531    def clear(self, at=None, deep=False) -> Self:
3532        """Clear the scene from all meshes and volumes."""
3533        if at is not None:
3534            renderer = self.renderers[at]
3535        else:
3536            renderer = self.renderer
3537        if not renderer:
3538            return self
3539
3540        if deep:
3541            renderer.RemoveAllViewProps()
3542        else:
3543            for ob in set(
3544                self.get_meshes()
3545                + self.get_volumes()
3546                + self.objects
3547                + self.axes_instances
3548            ):
3549                if isinstance(ob, vedo.shapes.Text2D):
3550                    continue
3551                self.remove(ob)
3552                try:
3553                    if ob.scalarbar:
3554                        self.remove(ob.scalarbar)
3555                except AttributeError:
3556                    pass
3557        return self

Clear the scene from all meshes and volumes.

def break_interaction(self) -> Self:
3559    def break_interaction(self) -> Self:
3560        """Break window interaction and return to the python execution flow"""
3561        if self.interactor:
3562            self.check_actors_trasform()
3563            self.interactor.ExitCallback()
3564        return self

Break window interaction and return to the python execution flow

def freeze(self, value=True) -> Self:
3566    def freeze(self, value=True) -> Self:
3567        """Freeze the current renderer. Use this with `sharecam=False`."""
3568        if not self.interactor:
3569            return self
3570        if not self.renderer:
3571            return self
3572        self.renderer.SetInteractive(not value)
3573        return self

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

def user_mode(self, mode) -> Self:
3575    def user_mode(self, mode) -> Self:
3576        """
3577        Modify the user interaction mode.
3578
3579        Examples:
3580            ```python
3581            from vedo import *
3582            mode = interactor_modes.MousePan()
3583            mesh = Mesh(dataurl+"cow.vtk")
3584            plt = Plotter().user_mode(mode)
3585            plt.show(mesh, axes=1)
3586           ```
3587        See also:
3588        [VTK interactor styles](https://vtk.org/doc/nightly/html/classvtkInteractorStyle.html)
3589        """
3590        if not self.interactor:
3591            return self
3592        
3593        curr_style = self.interactor.GetInteractorStyle().GetClassName()
3594        # print("Current style:", curr_style)
3595        if curr_style.endswith("Actor"):
3596            self.check_actors_trasform()
3597
3598        if isinstance(mode, (str, int)):
3599            # Set the style of interaction
3600            # see https://vtk.org/doc/nightly/html/classvtkInteractorStyle.html
3601            if   mode in (0, "TrackballCamera"):
3602                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTrackballCamera"))
3603                self.interactor.RemoveObservers("CharEvent")
3604            elif mode in (1, "TrackballActor"):
3605                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTrackballActor"))
3606            elif mode in (2, "JoystickCamera"):
3607                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleJoystickCamera"))
3608            elif mode in (3, "JoystickActor"):
3609                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleJoystickActor"))
3610            elif mode in (4, "Flight"):
3611                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleFlight"))
3612            elif mode in (5, "RubberBand2D"):
3613                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBand2D"))
3614            elif mode in (6, "RubberBand3D"):
3615                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBand3D"))
3616            elif mode in (7, "RubberBandZoom"):
3617                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBandZoom"))
3618            elif mode in (8, "Terrain"):
3619                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTerrain"))
3620            elif mode in (9, "Unicam"):
3621                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleUnicam"))
3622            elif mode in (10, "Image", "image", "2d"):
3623                astyle = vtki.new("InteractorStyleImage")
3624                astyle.SetInteractionModeToImage3D()
3625                self.interactor.SetInteractorStyle(astyle)
3626            else:
3627                vedo.logger.warning(f"Unknown interaction mode: {mode}")
3628
3629        elif isinstance(mode, vtki.vtkInteractorStyleUser):
3630            # set a custom interactor style
3631            if hasattr(mode, "interactor"):
3632                mode.interactor = self.interactor
3633                mode.renderer = self.renderer # type: ignore
3634            mode.SetInteractor(self.interactor)
3635            mode.SetDefaultRenderer(self.renderer)
3636            self.interactor.SetInteractorStyle(mode)
3637
3638        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:
3640    def close(self) -> Self:
3641        """Close the plotter."""
3642        # https://examples.vtk.org/site/Cxx/Visualization/CloseWindow/
3643        vedo.last_figure = None
3644        self.last_event = None
3645        self.sliders = []
3646        self.buttons = []
3647        self.widgets = []
3648        self.hover_legends = []
3649        self.background_renderer = None
3650        self._extralight = None
3651
3652        self.hint_widget = None
3653        self.cutter_widget = None
3654
3655        if vedo.settings.dry_run_mode >= 2:
3656            return self
3657        
3658        if not hasattr(self, "window"):
3659            return self
3660        if not self.window:
3661            return self
3662        if not hasattr(self, "interactor"):
3663            return self
3664        if not self.interactor:
3665            return self
3666
3667        ###################################################
3668        try:
3669            if "Darwin" in vedo.sys_platform:
3670                self.interactor.ProcessEvents()
3671        except:
3672            pass
3673
3674        self._must_close_now = True
3675
3676        if vedo.plotter_instance == self:
3677            vedo.plotter_instance = None
3678
3679        if self.interactor and self._interactive:
3680            self.break_interaction()
3681        elif self._must_close_now:
3682            # dont call ExitCallback here
3683            if self.interactor:
3684                self.break_interaction()
3685                self.interactor.GetRenderWindow().Finalize()
3686                self.interactor.TerminateApp()
3687            self.camera = None
3688            self.renderer = None
3689            self.renderers = []
3690            self.window = None
3691            self.interactor = None
3692        return self

Close the plotter.

camera
3694    @property
3695    def camera(self):
3696        """Return the current active camera."""
3697        if self.renderer:
3698            return self.renderer.GetActiveCamera()

Return the current active camera.

def screenshot(self, filename='screenshot.png', scale=1, asarray=False) -> Any:
3707    def screenshot(self, filename="screenshot.png", scale=1, asarray=False) -> Any:
3708        """
3709        Take a screenshot of the Plotter window.
3710
3711        Arguments:
3712            scale : (int)
3713                set image magnification as an integer multiplicating factor
3714            asarray : (bool)
3715                return a numpy array of the image instead of writing a file
3716
3717        Warning:
3718            If you get black screenshots try to set `interactive=False` in `show()`
3719            then call `screenshot()` and `plt.interactive()` afterwards.
3720
3721        Example:
3722            ```py
3723            from vedo import *
3724            sphere = Sphere().linewidth(1)
3725            plt = show(sphere, interactive=False)
3726            plt.screenshot('image.png')
3727            plt.interactive()
3728            plt.close()
3729            ```
3730
3731        Example:
3732            ```py
3733            from vedo import *
3734            sphere = Sphere().linewidth(1)
3735            plt = show(sphere, interactive=False)
3736            plt.screenshot('anotherimage.png')
3737            plt.interactive()
3738            plt.close()
3739            ```
3740        """
3741        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:
3743    def toimage(self, scale=1) -> "vedo.image.Image":
3744        """
3745        Generate a `Image` object from the current rendering window.
3746
3747        Arguments:
3748            scale : (int)
3749                set image magnification as an integer multiplicating factor
3750        """
3751        if vedo.settings.screeshot_large_image:
3752            w2if = vtki.new("RenderLargeImage")
3753            w2if.SetInput(self.renderer)
3754            w2if.SetMagnification(scale)
3755        else:
3756            w2if = vtki.new("WindowToImageFilter")
3757            w2if.SetInput(self.window)
3758            if hasattr(w2if, "SetScale"):
3759                w2if.SetScale(scale, scale)
3760            if vedo.settings.screenshot_transparent_background:
3761                w2if.SetInputBufferTypeToRGBA()
3762            w2if.ReadFrontBufferOff()  # read from the back buffer
3763        w2if.Update()
3764        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:
3766    def export(self, filename="scene.npz", binary=False) -> Self:
3767        """
3768        Export scene to file to HTML, X3D or Numpy file.
3769
3770        Examples:
3771            - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
3772            - [export_numpy.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_numpy.py)
3773        """
3774        vedo.file_io.export_window(filename, binary=binary)
3775        return self

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

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