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

Print information about the current instance.

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

Initialize the interactor if not already initialized.

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

Process all pending events.

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

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

def add(self, *objs, at=None) -> Self:
933    def add(self, *objs, at=None) -> Self:
934        """
935        Append the input objects to the internal list of objects to be shown.
936
937        Arguments:
938            at : (int)
939                add the object at the specified renderer
940        """
941        if at is not None:
942            ren = self.renderers[at]
943        else:
944            ren = self.renderer
945
946        objs = utils.flatten(objs)
947        for ob in objs:
948            if ob and ob not in self.objects:
949                self.objects.append(ob)
950
951        acts = self._scan_input_return_acts(objs)
952
953        for a in acts:
954
955            if ren:
956                if isinstance(a, vedo.addons.BaseCutter):
957                    a.add_to(self)  # from cutters
958                    continue
959
960                if isinstance(a, vtki.vtkLight):
961                    ren.AddLight(a)
962                    continue
963
964                try:
965                    ren.AddActor(a)
966                except TypeError:
967                    ren.AddActor(a.actor)
968
969                try:
970                    ir = self.renderers.index(ren)
971                    a.rendered_at.add(ir) # might not have rendered_at
972                except (AttributeError, ValueError):
973                    pass
974
975                if isinstance(a, vtki.vtkFollower):
976                    a.SetCamera(self.camera)
977                elif isinstance(a, vedo.visual.LightKit):
978                    a.lightkit.AddLightsToRenderer(ren)
979
980        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:
 982    def remove(self, *objs, at=None) -> Self:
 983        """
 984        Remove input object to the internal list of objects to be shown.
 985
 986        Objects to be removed can be referenced by their assigned name,
 987
 988        Arguments:
 989            at : (int)
 990                remove the object at the specified renderer
 991        """
 992        # TODO and you can also use wildcards like `*` and `?`.
 993        if at is not None:
 994            ren = self.renderers[at]
 995        else:
 996            ren = self.renderer
 997
 998        objs = [ob for ob in utils.flatten(objs) if ob]
 999
1000        has_str = False
1001        for ob in objs:
1002            if isinstance(ob, str):
1003                has_str = True
1004                break
1005
1006        has_actor = False
1007        for ob in objs:
1008            if hasattr(ob, "actor") and ob.actor:
1009                has_actor = True
1010                break
1011
1012        if has_str or has_actor:
1013            # need to get the actors to search for
1014            for a in self.get_actors(include_non_pickables=True):
1015                # print("PARSING", [a])
1016                try:
1017                    if (a.name and a.name in objs) or a in objs:
1018                        objs.append(a)
1019                    # if a.name:
1020                    #     bools = [utils.parse_pattern(ob, a.name)[0] for ob in objs]
1021                    #     if any(bools) or a in objs:
1022                    #         objs.append(a)
1023                    #     print('a.name',a.name, objs,any(bools))
1024                except AttributeError:  # no .name
1025                    # passing the actor so get back the object with .retrieve_object()
1026                    try:
1027                        vobj = a.retrieve_object()
1028                        if (vobj.name and vobj.name in objs) or vobj in objs:
1029                            # print('vobj.name', vobj.name)
1030                            objs.append(vobj)
1031                    except AttributeError:
1032                        pass
1033
1034        if ren is None:
1035            return self
1036        ir = self.renderers.index(ren)
1037
1038        ids = []
1039        for ob in set(objs):
1040
1041            # will remove it from internal list if possible
1042            try:
1043                idx = self.objects.index(ob)
1044                ids.append(idx)
1045            except ValueError:
1046                pass
1047
1048            if ren:  ### remove it from the renderer
1049
1050                if isinstance(ob, vedo.addons.BaseCutter):
1051                    ob.remove_from(self)  # from cutters
1052                    continue
1053
1054                try:
1055                    ren.RemoveActor(ob)
1056                except TypeError:
1057                    try:
1058                        ren.RemoveActor(ob.actor)
1059                    except AttributeError:
1060                        pass
1061
1062                if hasattr(ob, "rendered_at"):
1063                    ob.rendered_at.discard(ir)
1064
1065                if hasattr(ob, "scalarbar") and ob.scalarbar:
1066                    ren.RemoveActor(ob.scalarbar)
1067                if hasattr(ob, "_caption") and ob._caption:
1068                    ren.RemoveActor(ob._caption)
1069                if hasattr(ob, "shadows") and ob.shadows:
1070                    for sha in ob.shadows:
1071                        ren.RemoveActor(sha.actor)
1072                if hasattr(ob, "trail") and ob.trail:
1073                    ren.RemoveActor(ob.trail.actor)
1074                    ob.trail_points = []
1075                    if hasattr(ob.trail, "shadows") and ob.trail.shadows:
1076                        for sha in ob.trail.shadows:
1077                            ren.RemoveActor(sha.actor)
1078
1079                elif isinstance(ob, vedo.visual.LightKit):
1080                    ob.lightkit.RemoveLightsFromRenderer(ren)
1081
1082        # for i in ids: # WRONG way of doing it!
1083        #     del self.objects[i]
1084        # instead we do:
1085        self.objects = [ele for i, ele in enumerate(self.objects) if i not in ids]
1086        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
1088    @property
1089    def actors(self):
1090        """Return the list of actors."""
1091        return [ob.actor for ob in self.objects if hasattr(ob, "actor")]

Return the list of actors.

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

Remove all the present lights in the current renderer.

def pop(self, at=None) -> Self:
1099    def pop(self, at=None) -> Self:
1100        """
1101        Remove the last added object from the rendering window.
1102        This method is typically used in loops or callback functions.
1103        """
1104        if at is not None and not isinstance(at, int):
1105            # wrong usage pitfall
1106            vedo.logger.error("argument of pop() must be an integer")
1107            raise RuntimeError()
1108
1109        if self.objects:
1110            self.remove(self.objects[-1], at)
1111        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:
1113    def render(self, resetcam=False) -> Self:
1114        """Render the scene. This method is typically used in loops or callback functions."""
1115
1116        if vedo.settings.dry_run_mode >= 2:
1117            return self
1118
1119        if not self.window:
1120            return self
1121
1122        self.initialize_interactor()
1123
1124        if resetcam:
1125            self.renderer.ResetCamera()
1126
1127        self.window.Render()
1128
1129        if self._cocoa_process_events and self.interactor and self.interactor.GetInitialized():
1130            if "Darwin" in vedo.sys_platform and not self.offscreen:
1131                self.interactor.ProcessEvents()
1132                self._cocoa_process_events = False
1133        return self

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

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

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

def use_depth_peeling(self, at=None, value=True) -> Self:
1159    def use_depth_peeling(self, at=None, value=True) -> Self:
1160        """
1161        Specify whether use depth peeling algorithm at this specific renderer
1162        Call this method before the first rendering.
1163        """
1164        if at is None:
1165            ren = self.renderer
1166        else:
1167            ren = self.renderers[at]
1168        ren.SetUseDepthPeeling(value)
1169        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]:
1171    def background(self, c1=None, c2=None, at=None, mode=0) -> Union[Self, "np.ndarray"]:
1172        """Set the color of the background for the current renderer.
1173        A different renderer index can be specified by keyword `at`.
1174
1175        Arguments:
1176            c1 : (list)
1177                background main color.
1178            c2 : (list)
1179                background color for the upper part of the window.
1180            at : (int)
1181                renderer index.
1182            mode : (int)
1183                background mode (needs vtk version >= 9.3)
1184                    0 = vertical,
1185                    1 = horizontal,
1186                    2 = radial farthest side,
1187                    3 = radia farthest corner.
1188        """
1189        if not self.renderers:
1190            return self
1191        if at is None:
1192            r = self.renderer
1193        else:
1194            r = self.renderers[at]
1195
1196        if c1 is None and c2 is None:
1197            return np.array(r.GetBackground())
1198
1199        if r:
1200            if c1 is not None:
1201                r.SetBackground(vedo.get_color(c1))
1202            if c2 is not None:
1203                r.GradientBackgroundOn()
1204                r.SetBackground2(vedo.get_color(c2))
1205                if mode:
1206                    try:  # only works with vtk>=9.3
1207                        modes = [
1208                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_VERTICAL,
1209                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_HORIZONTAL,
1210                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_SIDE,
1211                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_CORNER,
1212                        ]
1213                        r.SetGradientMode(modes[vedo.settings.background_gradient_orientation])
1214                    except AttributeError:
1215                        pass
1216
1217            else:
1218                r.GradientBackgroundOff()
1219        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:
1222    def get_meshes(self, at=None, include_non_pickables=False, unpack_assemblies=True) -> list:
1223        """
1224        Return a list of Meshes from the specified renderer.
1225
1226        Arguments:
1227            at : (int)
1228                specify which renderer to look at.
1229            include_non_pickables : (bool)
1230                include non-pickable objects
1231            unpack_assemblies : (bool)
1232                unpack assemblies into their components
1233        """
1234        if at is None:
1235            renderer = self.renderer
1236            at = self.renderers.index(renderer)
1237        elif isinstance(at, int):
1238            renderer = self.renderers[at]
1239
1240        has_global_axes = False
1241        if isinstance(self.axes_instances[at], vedo.Assembly):
1242            has_global_axes = True
1243
1244        if unpack_assemblies:
1245            acs = renderer.GetActors()
1246        else:
1247            acs = renderer.GetViewProps()
1248
1249        objs = []
1250        acs.InitTraversal()
1251        for _ in range(acs.GetNumberOfItems()):
1252
1253            if unpack_assemblies:
1254                a = acs.GetNextItem()
1255            else:
1256                a = acs.GetNextProp()
1257
1258            if isinstance(a, vtki.vtkVolume):
1259                continue
1260
1261            if include_non_pickables or a.GetPickable():
1262                if a == self.axes_instances[at]:
1263                    continue
1264                if has_global_axes and a in self.axes_instances[at].actors:
1265                    continue
1266                try:
1267                    objs.append(a.retrieve_object())
1268                except AttributeError:
1269                    pass
1270        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:
1272    def get_volumes(self, at=None, include_non_pickables=False) -> list:
1273        """
1274        Return a list of Volumes from the specified renderer.
1275
1276        Arguments:
1277            at : (int)
1278                specify which renderer to look at
1279            include_non_pickables : (bool)
1280                include non-pickable objects
1281        """
1282        if at is None:
1283            renderer = self.renderer
1284            at = self.renderers.index(renderer)
1285        elif isinstance(at, int):
1286            renderer = self.renderers[at]
1287
1288        vols = []
1289        acs = renderer.GetVolumes()
1290        acs.InitTraversal()
1291        for _ in range(acs.GetNumberOfItems()):
1292            a = acs.GetNextItem()
1293            if include_non_pickables or a.GetPickable():
1294                try:
1295                    vols.append(a.retrieve_object())
1296                except AttributeError:
1297                    pass
1298        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:
1300    def get_actors(self, at=None, include_non_pickables=False) -> list:
1301        """
1302        Return a list of Volumes from the specified renderer.
1303
1304        Arguments:
1305            at : (int)
1306                specify which renderer to look at
1307            include_non_pickables : (bool)
1308                include non-pickable objects
1309        """
1310        if at is None:
1311            renderer = self.renderer
1312            if renderer is None:
1313                return []
1314            at = self.renderers.index(renderer)
1315        elif isinstance(at, int):
1316            renderer = self.renderers[at]
1317
1318        acts = []
1319        acs = renderer.GetViewProps()
1320        acs.InitTraversal()
1321        for _ in range(acs.GetNumberOfItems()):
1322            a = acs.GetNextProp()
1323            if include_non_pickables or a.GetPickable():
1324                acts.append(a)
1325        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:
1327    def check_actors_trasform(self, at=None) -> Self:
1328        """
1329        Reset the transformation matrix of all actors at specified renderer.
1330        This is only useful when actors have been moved/rotated/scaled manually
1331        in an already rendered scene using interactors like
1332        'TrackballActor' or 'JoystickActor'.
1333        """
1334        # see issue https://github.com/marcomusy/vedo/issues/1046
1335        for a in self.get_actors(at=at, include_non_pickables=True):
1336            try:
1337                M = a.GetMatrix()
1338            except AttributeError:
1339                continue
1340            if M and not M.IsIdentity():
1341                try:
1342                    a.retrieve_object().apply_transform_from_actor()
1343                    # vedo.logger.info(
1344                    #     f"object '{a.retrieve_object().name}' "
1345                    #     "was manually moved. Updated to its current position."
1346                    # )
1347                except AttributeError:
1348                    pass
1349        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:
1351    def reset_camera(self, tight=None) -> Self:
1352        """
1353        Reset the camera position and zooming.
1354        If tight (float) is specified the zooming reserves a padding space
1355        in the xy-plane expressed in percent of the average size.
1356        """
1357        if tight is None:
1358            self.renderer.ResetCamera()
1359        else:
1360            x0, x1, y0, y1, z0, z1 = self.renderer.ComputeVisiblePropBounds()
1361            cam = self.camera
1362
1363            self.renderer.ComputeAspect()
1364            aspect = self.renderer.GetAspect()
1365            angle = np.pi * cam.GetViewAngle() / 180.0
1366            dx = x1 - x0
1367            dy = y1 - y0
1368            dist = max(dx / aspect[0], dy) / np.tan(angle / 2) / 2
1369
1370            cam.SetViewUp(0, 1, 0)
1371            cam.SetPosition(x0 + dx / 2, y0 + dy / 2, dist * (1 + tight))
1372            cam.SetFocalPoint(x0 + dx / 2, y0 + dy / 2, 0)
1373            if cam.GetParallelProjection():
1374                ps = max(dx / aspect[0], dy) / 2
1375                cam.SetParallelScale(ps * (1 + tight))
1376            self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
1377        return self

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

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

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

def reset_viewup(self, smooth=True) -> Self:
1390    def reset_viewup(self, smooth=True) -> Self:
1391        """
1392        Reset the orientation of the camera to the closest orthogonal direction and view-up.
1393        """
1394        vbb = addons.compute_visible_bounds()[0]
1395        x0, x1, y0, y1, z0, z1 = vbb
1396        mx, my, mz = (x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2
1397        d = self.camera.GetDistance()
1398
1399        viewups = np.array(
1400            [(0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)]
1401        )
1402        positions = np.array(
1403            [
1404                (mx, my, mz + d),
1405                (mx, my, mz - d),
1406                (mx, my + d, mz),
1407                (mx, my - d, mz),
1408                (mx + d, my, mz),
1409                (mx - d, my, mz),
1410            ]
1411        )
1412
1413        vu = np.array(self.camera.GetViewUp())
1414        vui = np.argmin(np.linalg.norm(viewups - vu, axis=1))
1415
1416        poc = np.array(self.camera.GetPosition())
1417        foc = np.array(self.camera.GetFocalPoint())
1418        a = poc - foc
1419        b = positions - foc
1420        a = a / np.linalg.norm(a)
1421        b = b.T * (1 / np.linalg.norm(b, axis=1))
1422        pui = np.argmin(np.linalg.norm(b.T - a, axis=1))
1423
1424        if smooth:
1425            outtimes = np.linspace(0, 1, num=11, endpoint=True)
1426            for t in outtimes:
1427                vv = vu * (1 - t) + viewups[vui] * t
1428                pp = poc * (1 - t) + positions[pui] * t
1429                ff = foc * (1 - t) + np.array([mx, my, mz]) * t
1430                self.camera.SetViewUp(vv)
1431                self.camera.SetPosition(pp)
1432                self.camera.SetFocalPoint(ff)
1433                self.render()
1434
1435            # interpolator does not respect parallel view...:
1436            # cam1 = dict(
1437            #     pos=poc,
1438            #     viewup=vu,
1439            #     focal_point=(mx,my,mz),
1440            #     clipping_range=self.camera.GetClippingRange()
1441            # )
1442            # # cam1 = self.camera
1443            # cam2 = dict(
1444            #     pos=positions[pui],
1445            #     viewup=viewups[vui],
1446            #     focal_point=(mx,my,mz),
1447            #     clipping_range=self.camera.GetClippingRange()
1448            # )
1449            # vcams = self.move_camera([cam1, cam2], output_times=outtimes, smooth=0)
1450            # for c in vcams:
1451            #     self.renderer.SetActiveCamera(c)
1452            #     self.render()
1453        else:
1454
1455            self.camera.SetViewUp(viewups[vui])
1456            self.camera.SetPosition(positions[pui])
1457            self.camera.SetFocalPoint(mx, my, mz)
1458
1459        self.renderer.ResetCameraClippingRange()
1460
1461        # vbb, _, _, _ = addons.compute_visible_bounds()
1462        # x0,x1, y0,y1, z0,z1 = vbb
1463        # self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
1464        self.render()
1465        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:
1467    def move_camera(self, cameras, t=0, times=(), smooth=True, output_times=()) -> list:
1468        """
1469        Takes as input two cameras set camera at an interpolated position:
1470
1471        Cameras can be vtkCamera or dictionaries in format:
1472
1473            `dict(pos=..., focal_point=..., viewup=..., distance=..., clipping_range=...)`
1474
1475        Press `shift-C` key in interactive mode to dump a python snipplet
1476        of parameters for the current camera view.
1477        """
1478        nc = len(cameras)
1479        if len(times) == 0:
1480            times = np.linspace(0, 1, num=nc, endpoint=True)
1481
1482        assert len(times) == nc
1483
1484        cin = vtki.new("CameraInterpolator")
1485
1486        # cin.SetInterpolationTypeToLinear() # buggy?
1487        if nc > 2 and smooth:
1488            cin.SetInterpolationTypeToSpline()
1489
1490        for i, cam in enumerate(cameras):
1491            vcam = cam
1492            if isinstance(cam, dict):
1493                vcam = utils.camera_from_dict(cam)
1494            cin.AddCamera(times[i], vcam)
1495
1496        mint, maxt = cin.GetMinimumT(), cin.GetMaximumT()
1497        rng = maxt - mint
1498
1499        if len(output_times) == 0:
1500            cin.InterpolateCamera(t * rng, self.camera)
1501            return [self.camera]
1502        else:
1503            vcams = []
1504            for tt in output_times:
1505                c = vtki.vtkCamera()
1506                cin.InterpolateCamera(tt * rng, c)
1507                vcams.append(c)
1508            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:
1510    def fly_to(self, point) -> Self:
1511        """
1512        Fly camera to the specified point.
1513
1514        Arguments:
1515            point : (list)
1516                point in space to place camera.
1517
1518        Example:
1519            ```python
1520            from vedo import *
1521            cone = Cone()
1522            plt = Plotter(axes=1)
1523            plt.show(cone)
1524            plt.fly_to([1,0,0])
1525            plt.interactive().close()
1526            ```
1527        """
1528        if self.interactor:
1529            self.resetcam = False
1530            self.interactor.FlyTo(self.renderer, point)
1531        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:
1533    def look_at(self, plane="xy") -> Self:
1534        """Move the camera so that it looks at the specified cartesian plane"""
1535        cam = self.renderer.GetActiveCamera()
1536        fp = np.array(cam.GetFocalPoint())
1537        p = np.array(cam.GetPosition())
1538        dist = np.linalg.norm(fp - p)
1539        plane = plane.lower()
1540        if "x" in plane and "y" in plane:
1541            cam.SetPosition(fp[0], fp[1], fp[2] + dist)
1542            cam.SetViewUp(0.0, 1.0, 0.0)
1543        elif "x" in plane and "z" in plane:
1544            cam.SetPosition(fp[0], fp[1] - dist, fp[2])
1545            cam.SetViewUp(0.0, 0.0, 1.0)
1546        elif "y" in plane and "z" in plane:
1547            cam.SetPosition(fp[0] + dist, fp[1], fp[2])
1548            cam.SetViewUp(0.0, 0.0, 1.0)
1549        else:
1550            vedo.logger.error(f"in plotter.look() cannot understand argument {plane}")
1551        return self

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

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

Remove hidden lines when in wireframe mode.

def fov(self, angle: float) -> Self:
1660    def fov(self, angle: float) -> Self:
1661        """
1662        Set the field of view angle for the camera.
1663        This is the angle of the camera frustum in the horizontal direction.
1664        High values will result in a wide-angle lens (fish-eye effect),
1665        and low values will result in a telephoto lens.
1666
1667        Default value is 30 degrees.
1668        """
1669        self.renderer.GetActiveCamera().UseHorizontalViewAngleOn()
1670        self.renderer.GetActiveCamera().SetViewAngle(angle)
1671        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:
1673    def zoom(self, zoom: float) -> Self:
1674        """Apply a zooming factor for the current camera view"""
1675        self.renderer.GetActiveCamera().Zoom(zoom)
1676        return self

Apply a zooming factor for the current camera view

def azimuth(self, angle: float) -> Self:
1678    def azimuth(self, angle: float) -> Self:
1679        """Rotate camera around the view up vector."""
1680        self.renderer.GetActiveCamera().Azimuth(angle)
1681        return self

Rotate camera around the view up vector.

def elevation(self, angle: float) -> Self:
1683    def elevation(self, angle: float) -> Self:
1684        """Rotate the camera around the cross product of the negative
1685        of the direction of projection and the view up vector."""
1686        self.renderer.GetActiveCamera().Elevation(angle)
1687        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:
1689    def roll(self, angle: float) -> Self:
1690        """Roll the camera about the direction of projection."""
1691        self.renderer.GetActiveCamera().Roll(angle)
1692        return self

Roll the camera about the direction of projection.

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

Add shadows at the current renderer.

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

Clear the scene from all meshes and volumes.

def break_interaction(self) -> Self:
3574    def break_interaction(self) -> Self:
3575        """Break window interaction and return to the python execution flow"""
3576        if self.interactor:
3577            self.check_actors_trasform()
3578            self.interactor.ExitCallback()
3579        return self

Break window interaction and return to the python execution flow

def freeze(self, value=True) -> Self:
3581    def freeze(self, value=True) -> Self:
3582        """Freeze the current renderer. Use this with `sharecam=False`."""
3583        if not self.interactor:
3584            return self
3585        if not self.renderer:
3586            return self
3587        self.renderer.SetInteractive(not value)
3588        return self

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

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

Close the plotter.

camera
3709    @property
3710    def camera(self):
3711        """Return the current active camera."""
3712        if self.renderer:
3713            return self.renderer.GetActiveCamera()

Return the current active camera.

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

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

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