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

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

def use_depth_peeling(self, at=None, value=True) -> Self:
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

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]:
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

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:
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

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:
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

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:
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

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:
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

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:
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

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:
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

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:
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

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:
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

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:
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

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

def record(self, filename='') -> str:
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

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:
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

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:
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

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:
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

Remove hidden lines when in wireframe mode.

def fov(self, angle: float) -> Self:
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

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:
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

Apply a zooming factor for the current camera view

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

Rotate camera around the view up vector.

def elevation(self, angle: float) -> Self:
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

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:
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

Roll the camera about the direction of projection.

def dolly(self, value: float) -> Self:
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

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:
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

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:
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

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]:
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

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:
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

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:
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

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:
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

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:
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

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]:
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

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:
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

Add shadows at the current renderer.

def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
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

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:
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

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:
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

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:
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

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]:
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

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:
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

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:
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

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:
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

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:
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

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:
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

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:
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

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:
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)

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:
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)

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:
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

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:
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

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]:
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

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:
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

Clear the scene from all meshes and volumes.

def break_interaction(self) -> Self:
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

Break window interaction and return to the python execution flow

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

Close the plotter.

camera
3682    @property
3683    def camera(self):
3684        """Return the current active camera."""
3685        if self.renderer:
3686            return self.renderer.GetActiveCamera()

Return the current active camera.

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

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

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