vedo.plotter

Module containing the Plotter class for managing objects and 3D rendering.

The Plotter class is the main interface for creating visualization windows, rendering 3D scenes, handling interactions, managing renderers, cameras, and visualization settings. It provides methods for displaying meshes, creating screenshots, handling user events, and configuring the visualization pipeline.

Key functionality:

  • Create and manage multiple render windows
  • Handle user interaction via mouse and keyboard events

  • Control camera position and settings

  • Configure lighting and shading

  • Add text labels, scalar bars and other annotations

  • Export visualizations to images and movies

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

Print information about the current instance.

def initialize_interactor(self) -> Self:
903    def initialize_interactor(self) -> Self:
904        """Initialize the interactor if not already initialized."""
905        if self.offscreen:
906            return self
907        if self.interactor:
908            if not self.interactor.GetInitialized():
909                self.interactor.Initialize()
910                self.interactor.RemoveObservers("CharEvent")
911        return self

Initialize the interactor if not already initialized.

def process_events(self) -> Self:
913    def process_events(self) -> Self:
914        """Process all pending events."""
915        self.initialize_interactor()
916        if self.interactor:
917            try:
918                self.interactor.ProcessEvents()
919            except AttributeError:
920                pass
921        return self

Process all pending events.

def at(self, nren: int, yren=None) -> Self:
923    def at(self, nren: int, yren=None) -> Self:
924        """
925        Select the current renderer number as an int.
926        Can also use the `[nx, ny]` format.
927        """
928        if utils.is_sequence(nren):
929            if len(nren) == 2:
930                nren, yren = nren
931            else:
932                vedo.logger.error("at() argument must be a single number or a list of two numbers")
933                raise TypeError
934
935        if yren is not None:
936            a, b = self.shape
937            x, y = nren, yren
938            nren_orig = nren
939            nren = x * b + y
940            # print("at (", x, y, ")  -> ren", nren)
941            if nren < 0 or nren > len(self.renderers) or x >= a or y >= b:
942                vedo.logger.error(f"at({nren_orig, yren}) is malformed!")
943                raise RuntimeError
944
945        self.renderer = self.renderers[nren]
946        return self

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

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

Return the list of actors.

def remove_lights(self) -> Self:
1103    def remove_lights(self) -> Self:
1104        """Remove all the present lights in the current renderer."""
1105        if self.renderer:
1106            self.renderer.RemoveAllLights()
1107        return self

Remove all the present lights in the current renderer.

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

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

def interactive(self) -> Self:
1145    def interactive(self) -> Self:
1146        """
1147        Start window interaction.
1148        Analogous to `show(..., interactive=True)`.
1149        """
1150        if vedo.settings.dry_run_mode >= 1:
1151            return self
1152        self.initialize_interactor()
1153        if self.interactor:
1154            # print("self.interactor.Start()")
1155            self.interactor.Start()
1156            # print("self.interactor.Start() done")
1157            if self._must_close_now:
1158                # print("self.interactor.TerminateApp()")
1159                if self.interactor:
1160                    self.interactor.GetRenderWindow().Finalize()
1161                    self.interactor.TerminateApp()
1162                self.interactor = None
1163                self.window = None
1164                self.renderer = None
1165                self.renderers = []
1166                self.camera = None
1167        return self

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

def use_depth_peeling(self, at=None, value=True) -> Self:
1169    def use_depth_peeling(self, at=None, value=True) -> Self:
1170        """
1171        Specify whether use depth peeling algorithm at this specific renderer
1172        Call this method before the first rendering.
1173        """
1174        ren = self.renderer if at is None else self.renderers[at]
1175        ren.SetUseDepthPeeling(value)
1176        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]:
1178    def background(self, c1=None, c2=None, at=None, mode=0) -> Union[Self, "np.ndarray"]:
1179        """Set the color of the background for the current renderer.
1180        A different renderer index can be specified by keyword `at`.
1181
1182        Arguments:
1183            c1 : (list)
1184                background main color.
1185            c2 : (list)
1186                background color for the upper part of the window.
1187            at : (int)
1188                renderer index.
1189            mode : (int)
1190                background mode (needs vtk version >= 9.3)
1191                    0 = vertical,
1192                    1 = horizontal,
1193                    2 = radial farthest side,
1194                    3 = radia farthest corner.
1195        """
1196        if not self.renderers:
1197            return self
1198        r = self.renderer if at is None else self.renderers[at]
1199
1200        if c1 is None and c2 is None:
1201            return np.array(r.GetBackground())
1202
1203        if r:
1204            if c1 is not None:
1205                r.SetBackground(vedo.get_color(c1))
1206            if c2 is not None:
1207                r.GradientBackgroundOn()
1208                r.SetBackground2(vedo.get_color(c2))
1209                if mode:
1210                    try:  # only works with vtk>=9.3
1211                        modes = [
1212                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_VERTICAL,
1213                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_HORIZONTAL,
1214                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_SIDE,
1215                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_CORNER,
1216                        ]
1217                        r.SetGradientMode(modes[mode])
1218                    except AttributeError:
1219                        pass
1220
1221            else:
1222                r.GradientBackgroundOff()
1223        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:
1226    def get_meshes(self, at=None, include_non_pickables=False, unpack_assemblies=True) -> list:
1227        """
1228        Return a list of Meshes from the specified renderer.
1229
1230        Arguments:
1231            at : (int)
1232                specify which renderer to look at.
1233            include_non_pickables : (bool)
1234                include non-pickable objects
1235            unpack_assemblies : (bool)
1236                unpack assemblies into their components
1237        """
1238        if at is None:
1239            renderer = self.renderer
1240            at = self.renderers.index(renderer)
1241        elif isinstance(at, int):
1242            renderer = self.renderers[at]
1243
1244        has_global_axes = False
1245        if isinstance(self.axes_instances[at], vedo.Assembly):
1246            has_global_axes = True
1247
1248        if unpack_assemblies:
1249            acs = renderer.GetActors()
1250        else:
1251            acs = renderer.GetViewProps()
1252
1253        objs = []
1254        acs.InitTraversal()
1255        for _ in range(acs.GetNumberOfItems()):
1256
1257            if unpack_assemblies:
1258                a = acs.GetNextItem()
1259            else:
1260                a = acs.GetNextProp()
1261
1262            if isinstance(a, vtki.vtkVolume):
1263                continue
1264
1265            if include_non_pickables or a.GetPickable():
1266                if a == self.axes_instances[at]:
1267                    continue
1268                if has_global_axes and a in self.axes_instances[at].actors:
1269                    continue
1270                try:
1271                    objs.append(a.retrieve_object())
1272                except AttributeError:
1273                    pass
1274        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:
1276    def get_volumes(self, at=None, include_non_pickables=False) -> list:
1277        """
1278        Return a list of Volumes from the specified renderer.
1279
1280        Arguments:
1281            at : (int)
1282                specify which renderer to look at
1283            include_non_pickables : (bool)
1284                include non-pickable objects
1285        """
1286        renderer = self.renderer if at is None else 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        renderer = self.renderer if at is None else self.renderers[at]
1311        if renderer is None:
1312            return []
1313
1314        acts = []
1315        acs = renderer.GetViewProps()
1316        acs.InitTraversal()
1317        for _ in range(acs.GetNumberOfItems()):
1318            a = acs.GetNextProp()
1319            if include_non_pickables or a.GetPickable():
1320                acts.append(a)
1321        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:
1323    def check_actors_trasform(self, at=None) -> Self:
1324        """
1325        Reset the transformation matrix of all actors at specified renderer.
1326        This is only useful when actors have been moved/rotated/scaled manually
1327        in an already rendered scene using interactors like
1328        'TrackballActor' or 'JoystickActor'.
1329        """
1330        # see issue https://github.com/marcomusy/vedo/issues/1046
1331        for a in self.get_actors(at=at, include_non_pickables=True):
1332            try:
1333                M = a.GetMatrix()
1334            except AttributeError:
1335                continue
1336            if M and not M.IsIdentity():
1337                try:
1338                    a.retrieve_object().apply_transform_from_actor()
1339                    # vedo.logger.info(
1340                    #     f"object '{a.retrieve_object().name}' "
1341                    #     "was manually moved. Updated to its current position."
1342                    # )
1343                except AttributeError:
1344                    pass
1345        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:
1347    def reset_camera(self, tight=None) -> Self:
1348        """
1349        Reset the camera position and zooming.
1350        If tight (float) is specified the zooming reserves a padding space
1351        in the xy-plane expressed in percent of the average size.
1352        """
1353        if tight is None:
1354            self.renderer.ResetCamera()
1355        else:
1356            x0, x1, y0, y1, z0, z1 = self.renderer.ComputeVisiblePropBounds()
1357            cam = self.camera
1358
1359            self.renderer.ComputeAspect()
1360            aspect = self.renderer.GetAspect()
1361            angle = np.pi * cam.GetViewAngle() / 180.0
1362            dx = x1 - x0
1363            dy = y1 - y0
1364            dist = max(dx / aspect[0], dy) / np.tan(angle / 2) / 2
1365
1366            cam.SetViewUp(0, 1, 0)
1367            cam.SetPosition(x0 + dx / 2, y0 + dy / 2, dist * (1 + tight))
1368            cam.SetFocalPoint(x0 + dx / 2, y0 + dy / 2, 0)
1369            if cam.GetParallelProjection():
1370                ps = max(dx / aspect[0], dy) / 2
1371                cam.SetParallelScale(ps * (1 + tight))
1372            self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
1373        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:
1375    def reset_clipping_range(self, bounds=None) -> Self:
1376        """
1377        Reset the camera clipping range to include all visible actors.
1378        If bounds is given, it will be used instead of computing it.
1379        """
1380        if bounds is None:
1381            self.renderer.ResetCameraClippingRange()
1382        else:
1383            self.renderer.ResetCameraClippingRange(bounds)
1384        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:
1386    def reset_viewup(self, smooth=True) -> Self:
1387        """
1388        Reset the orientation of the camera to the closest orthogonal direction and view-up.
1389        """
1390        vbb = addons.compute_visible_bounds()[0]
1391        x0, x1, y0, y1, z0, z1 = vbb
1392        mx, my, mz = (x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2
1393        d = self.camera.GetDistance()
1394
1395        viewups = np.array(
1396            [(0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)]
1397        )
1398        positions = np.array(
1399            [
1400                (mx, my, mz + d),
1401                (mx, my, mz - d),
1402                (mx, my + d, mz),
1403                (mx, my - d, mz),
1404                (mx + d, my, mz),
1405                (mx - d, my, mz),
1406            ]
1407        )
1408
1409        vu = np.array(self.camera.GetViewUp())
1410        vui = np.argmin(np.linalg.norm(viewups - vu, axis=1))
1411
1412        poc = np.array(self.camera.GetPosition())
1413        foc = np.array(self.camera.GetFocalPoint())
1414        a = poc - foc
1415        b = positions - foc
1416        a = a / np.linalg.norm(a)
1417        b = b.T * (1 / np.linalg.norm(b, axis=1))
1418        pui = np.argmin(np.linalg.norm(b.T - a, axis=1))
1419
1420        if smooth:
1421            outtimes = np.linspace(0, 1, num=11, endpoint=True)
1422            for t in outtimes:
1423                vv = vu * (1 - t) + viewups[vui] * t
1424                pp = poc * (1 - t) + positions[pui] * t
1425                ff = foc * (1 - t) + np.array([mx, my, mz]) * t
1426                self.camera.SetViewUp(vv)
1427                self.camera.SetPosition(pp)
1428                self.camera.SetFocalPoint(ff)
1429                self.render()
1430
1431            # interpolator does not respect parallel view...:
1432            # cam1 = dict(
1433            #     pos=poc,
1434            #     viewup=vu,
1435            #     focal_point=(mx,my,mz),
1436            #     clipping_range=self.camera.GetClippingRange()
1437            # )
1438            # # cam1 = self.camera
1439            # cam2 = dict(
1440            #     pos=positions[pui],
1441            #     viewup=viewups[vui],
1442            #     focal_point=(mx,my,mz),
1443            #     clipping_range=self.camera.GetClippingRange()
1444            # )
1445            # vcams = self.move_camera([cam1, cam2], output_times=outtimes, smooth=0)
1446            # for c in vcams:
1447            #     self.renderer.SetActiveCamera(c)
1448            #     self.render()
1449        else:
1450
1451            self.camera.SetViewUp(viewups[vui])
1452            self.camera.SetPosition(positions[pui])
1453            self.camera.SetFocalPoint(mx, my, mz)
1454
1455        self.renderer.ResetCameraClippingRange()
1456
1457        # vbb, _, _, _ = addons.compute_visible_bounds()
1458        # x0,x1, y0,y1, z0,z1 = vbb
1459        # self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
1460        self.render()
1461        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:
1463    def move_camera(self, cameras, t=0, times=(), smooth=True, output_times=()) -> list:
1464        """
1465        Takes as input two cameras set camera at an interpolated position:
1466
1467        Cameras can be vtkCamera or dictionaries in format:
1468
1469            `dict(pos=..., focal_point=..., viewup=..., distance=..., clipping_range=...)`
1470
1471        Press `shift-C` key in interactive mode to dump a python snipplet
1472        of parameters for the current camera view.
1473        """
1474        nc = len(cameras)
1475        if len(times) == 0:
1476            times = np.linspace(0, 1, num=nc, endpoint=True)
1477
1478        assert len(times) == nc
1479
1480        cin = vtki.new("CameraInterpolator")
1481
1482        # cin.SetInterpolationTypeToLinear() # buggy?
1483        if nc > 2 and smooth:
1484            cin.SetInterpolationTypeToSpline()
1485
1486        for i, cam in enumerate(cameras):
1487            vcam = cam
1488            if isinstance(cam, dict):
1489                vcam = utils.camera_from_dict(cam)
1490            cin.AddCamera(times[i], vcam)
1491
1492        mint, maxt = cin.GetMinimumT(), cin.GetMaximumT()
1493        rng = maxt - mint
1494
1495        if len(output_times) == 0:
1496            cin.InterpolateCamera(t * rng, self.camera)
1497            return [self.camera]
1498        else:
1499            vcams = []
1500            for tt in output_times:
1501                c = vtki.vtkCamera()
1502                cin.InterpolateCamera(tt * rng, c)
1503                vcams.append(c)
1504            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:
1506    def fly_to(self, point) -> Self:
1507        """
1508        Fly camera to the specified point.
1509
1510        Arguments:
1511            point : (list)
1512                point in space to place camera.
1513
1514        Example:
1515            ```python
1516            from vedo import *
1517            cone = Cone()
1518            plt = Plotter(axes=1)
1519            plt.show(cone)
1520            plt.fly_to([1,0,0])
1521            plt.interactive().close()
1522            ```
1523        """
1524        if self.interactor:
1525            self.resetcam = False
1526            self.interactor.FlyTo(self.renderer, point)
1527        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:
1529    def look_at(self, plane="xy") -> Self:
1530        """Move the camera so that it looks at the specified cartesian plane"""
1531        cam = self.renderer.GetActiveCamera()
1532        fp = np.array(cam.GetFocalPoint())
1533        p = np.array(cam.GetPosition())
1534        dist = np.linalg.norm(fp - p)
1535        plane = plane.lower()
1536        if "x" in plane and "y" in plane:
1537            cam.SetPosition(fp[0], fp[1], fp[2] + dist)
1538            cam.SetViewUp(0.0, 1.0, 0.0)
1539        elif "x" in plane and "z" in plane:
1540            cam.SetPosition(fp[0], fp[1] - dist, fp[2])
1541            cam.SetViewUp(0.0, 0.0, 1.0)
1542        elif "y" in plane and "z" in plane:
1543            cam.SetPosition(fp[0] + dist, fp[1], fp[2])
1544            cam.SetViewUp(0.0, 0.0, 1.0)
1545        else:
1546            vedo.logger.error(f"in plotter.look() cannot understand argument {plane}")
1547        return self

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

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

Remove hidden lines when in wireframe mode.

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

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:
1667    def zoom(self, zoom: float) -> Self:
1668        """Apply a zooming factor for the current camera view"""
1669        self.renderer.GetActiveCamera().Zoom(zoom)
1670        return self

Apply a zooming factor for the current camera view

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

Rotate camera around the view up vector.

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

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:
1683    def roll(self, angle: float) -> Self:
1684        """Roll the camera about the direction of projection."""
1685        self.renderer.GetActiveCamera().Roll(angle)
1686        return self

Roll the camera about the direction of projection.

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

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

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

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

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:
1923    def add_spline_tool(
1924        self,
1925        points,
1926        pc="k",
1927        ps=8,
1928        lc="r4",
1929        ac="g5",
1930        lw=2,
1931        alpha=1,
1932        closed=False,
1933        ontop=True,
1934        can_add_nodes=True,
1935    ) -> "vedo.addons.SplineTool":
1936        """
1937        Add a spline tool to the current plotter.
1938        Nodes of the spline can be dragged in space with the mouse.
1939        Clicking on the line itself adds an extra point.
1940        Selecting a point and pressing del removes it.
1941
1942        Arguments:
1943            points : (Mesh, Points, array)
1944                the set of coordinates forming the spline nodes.
1945            pc : (str)
1946                point color. The default is 'k'.
1947            ps : (str)
1948                point size. The default is 8.
1949            lc : (str)
1950                line color. The default is 'r4'.
1951            ac : (str)
1952                active point marker color. The default is 'g5'.
1953            lw : (int)
1954                line width. The default is 2.
1955            alpha : (float)
1956                line transparency.
1957            closed : (bool)
1958                spline is meant to be closed. The default is False.
1959
1960        Returns:
1961            a `SplineTool` object.
1962
1963        Examples:
1964            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
1965
1966            ![](https://vedo.embl.es/images/basic/spline_tool.png)
1967        """
1968        sw = addons.SplineTool(points, pc, ps, lc, ac, lw, alpha, closed, ontop, can_add_nodes)
1969        sw.interactor = self.interactor
1970        sw.on()
1971        sw.Initialize(sw.points.dataset)
1972        sw.representation.SetRenderer(self.renderer)
1973        sw.representation.SetClosedLoop(closed)
1974        sw.representation.BuildRepresentation()
1975        self.widgets.append(sw)
1976        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 coordinates 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:
1978    def add_icon(self, icon, pos=3, size=0.08) -> "vedo.addons.Icon":
1979        """Add an inset icon mesh into the same renderer.
1980
1981        Arguments:
1982            pos : (int, list)
1983                icon position in the range [1-4] indicating one of the 4 corners,
1984                or it can be a tuple (x,y) as a fraction of the renderer size.
1985            size : (float)
1986                size of the square inset.
1987
1988        Examples:
1989            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
1990        """
1991        iconw = addons.Icon(icon, pos, size)
1992
1993        iconw.SetInteractor(self.interactor)
1994        iconw.EnabledOn()
1995        iconw.InteractiveOff()
1996        self.widgets.append(iconw)
1997        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:
1999    def add_global_axes(self, axtype=None, c=None) -> Self:
2000        """Draw axes on scene. Available axes types:
2001
2002        Arguments:
2003            axtype : (int)
2004                - 0,  no axes,
2005                - 1,  draw three gray grid walls
2006                - 2,  show cartesian axes from (0,0,0)
2007                - 3,  show positive range of cartesian axes from (0,0,0)
2008                - 4,  show a triad at bottom left
2009                - 5,  show a cube at bottom left
2010                - 6,  mark the corners of the bounding box
2011                - 7,  draw a 3D ruler at each side of the cartesian axes
2012                - 8,  show the vtkCubeAxesActor object
2013                - 9,  show the bounding box outLine
2014                - 10, show three circles representing the maximum bounding box
2015                - 11, show a large grid on the x-y plane
2016                - 12, show polar axes
2017                - 13, draw a simple ruler at the bottom of the window
2018
2019            Axis type-1 can be fully customized by passing a dictionary axes=dict().
2020
2021        Example:
2022            ```python
2023            from vedo import Box, show
2024            b = Box(pos=(0, 0, 0), size=(80, 90, 70)).alpha(0.1)
2025            show(
2026                b,
2027                axes={
2028                    "xtitle": "Some long variable [a.u.]",
2029                    "number_of_divisions": 4,
2030                    # ...
2031                },
2032            )
2033            ```
2034
2035        Examples:
2036            - [custom_axes1.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes1.py)
2037            - [custom_axes2.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes2.py)
2038            - [custom_axes3.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes3.py)
2039            - [custom_axes4.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes4.py)
2040
2041            <img src="https://user-images.githubusercontent.com/32848391/72752870-ab7d5280-3bc3-11ea-8911-9ace00211e23.png" width="600">
2042        """
2043        addons.add_global_axes(axtype, c)
2044        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), size=(80, 90, 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:
2046    def add_legend_box(self, **kwargs) -> "vedo.addons.LegendBox":
2047        """Add a legend to the top right.
2048
2049        Examples:
2050            - [legendbox.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/legendbox.py),
2051            - [flag_labels1.py](https://github.com/marcomusy/vedo/blob/master/examples/other/flag_labels1.py)
2052            - [flag_labels2.py](https://github.com/marcomusy/vedo/blob/master/examples/other/flag_labels2.py)
2053        """
2054        acts = self.get_meshes()
2055        lb = addons.LegendBox(acts, **kwargs)
2056        self.add(lb)
2057        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]:
2059    def add_hint(
2060        self,
2061        obj,
2062        text="",
2063        c="k",
2064        bg="yellow9",
2065        font="Calco",
2066        size=18,
2067        justify=0,
2068        angle=0,
2069        delay=250,
2070    ) -> Union[vtki.vtkBalloonWidget, None]:
2071        """
2072        Create a pop-up hint style message when hovering an object.
2073        Use `add_hint(obj, False)` to disable a hinting a specific object.
2074        Use `add_hint(None)` to disable all hints.
2075
2076        Arguments:
2077            obj : (Mesh, Points)
2078                the object to associate the pop-up to
2079            text : (str)
2080                string description of the pop-up
2081            delay : (int)
2082                milliseconds to wait before pop-up occurs
2083        """
2084        if self.offscreen or not self.interactor:
2085            return None
2086
2087        if vedo.vtk_version[:2] == (9, 0) and "Linux" in vedo.sys_platform:
2088            # Linux vtk9.0 is bugged
2089            vedo.logger.warning(
2090                f"add_hint() is not available on Linux platforms for vtk{vedo.vtk_version}."
2091            )
2092            return None
2093
2094        if obj is None:
2095            self.hint_widget.EnabledOff()
2096            self.hint_widget.SetInteractor(None)
2097            self.hint_widget = None
2098            return self.hint_widget
2099
2100        if text is False and self.hint_widget:
2101            self.hint_widget.RemoveBalloon(obj)
2102            return self.hint_widget
2103
2104        if text == "":
2105            if obj.name:
2106                text = obj.name
2107            elif obj.filename:
2108                text = obj.filename
2109            else:
2110                return None
2111
2112        if not self.hint_widget:
2113            self.hint_widget = vtki.vtkBalloonWidget()
2114
2115            rep = self.hint_widget.GetRepresentation()
2116            rep.SetBalloonLayoutToImageRight()
2117
2118            trep = rep.GetTextProperty()
2119            trep.SetFontFamily(vtki.VTK_FONT_FILE)
2120            trep.SetFontFile(utils.get_font_path(font))
2121            trep.SetFontSize(size)
2122            trep.SetColor(vedo.get_color(c))
2123            trep.SetBackgroundColor(vedo.get_color(bg))
2124            trep.SetShadow(0)
2125            trep.SetJustification(justify)
2126            trep.UseTightBoundingBoxOn()
2127
2128            self.hint_widget.ManagesCursorOff()
2129            self.hint_widget.SetTimerDuration(delay)
2130            self.hint_widget.SetInteractor(self.interactor)
2131            if angle:
2132                trep.SetOrientation(angle)
2133                trep.SetBackgroundOpacity(0)
2134            # else:
2135            #     trep.SetBackgroundOpacity(0.5) # doesnt work well
2136            self.hint_widget.SetRepresentation(rep)
2137            self.widgets.append(self.hint_widget)
2138            self.hint_widget.EnabledOn()
2139
2140        bst = self.hint_widget.GetBalloonString(obj.actor)
2141        if bst:
2142            self.hint_widget.UpdateBalloonString(obj.actor, text)
2143        else:
2144            self.hint_widget.AddBalloon(obj.actor, text)
2145
2146        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:
2148    def add_shadows(self) -> Self:
2149        """Add shadows at the current renderer."""
2150        if self.renderer:
2151            shadows = vtki.new("ShadowMapPass")
2152            seq = vtki.new("SequencePass")
2153            passes = vtki.new("RenderPassCollection")
2154            passes.AddItem(shadows.GetShadowMapBakerPass())
2155            passes.AddItem(shadows)
2156            seq.SetPasses(passes)
2157            camerapass = vtki.new("CameraPass")
2158            camerapass.SetDelegatePass(seq)
2159            self.renderer.SetPass(camerapass)
2160        return self

Add shadows at the current renderer.

def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
2162    def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
2163        """
2164        Screen Space Ambient Occlusion.
2165
2166        For every pixel on the screen, the pixel shader samples the depth values around
2167        the current pixel and tries to compute the amount of occlusion from each of the sampled
2168        points.
2169
2170        Arguments:
2171            radius : (float)
2172                radius of influence in absolute units
2173            bias : (float)
2174                bias of the normals
2175            blur : (bool)
2176                add a blurring to the sampled positions
2177            samples : (int)
2178                number of samples to probe
2179
2180        Examples:
2181            - [ssao.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ssao.py)
2182
2183            ![](https://vedo.embl.es/images/basic/ssao.jpg)
2184        """
2185        lights = vtki.new("LightsPass")
2186
2187        opaque = vtki.new("OpaquePass")
2188
2189        ssaoCam = vtki.new("CameraPass")
2190        ssaoCam.SetDelegatePass(opaque)
2191
2192        ssao = vtki.new("SSAOPass")
2193        ssao.SetRadius(radius)
2194        ssao.SetBias(bias)
2195        ssao.SetBlur(blur)
2196        ssao.SetKernelSize(samples)
2197        ssao.SetDelegatePass(ssaoCam)
2198
2199        translucent = vtki.new("TranslucentPass")
2200
2201        volpass = vtki.new("VolumetricPass")
2202        ddp = vtki.new("DualDepthPeelingPass")
2203        ddp.SetTranslucentPass(translucent)
2204        ddp.SetVolumetricPass(volpass)
2205
2206        over = vtki.new("OverlayPass")
2207
2208        collection = vtki.new("RenderPassCollection")
2209        collection.AddItem(lights)
2210        collection.AddItem(ssao)
2211        collection.AddItem(ddp)
2212        collection.AddItem(over)
2213
2214        sequence = vtki.new("SequencePass")
2215        sequence.SetPasses(collection)
2216
2217        cam = vtki.new("CameraPass")
2218        cam.SetDelegatePass(sequence)
2219
2220        self.renderer.SetPass(cam)
2221        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:
2223    def add_depth_of_field(self, autofocus=True) -> Self:
2224        """Add a depth of field effect in the scene."""
2225        lights = vtki.new("LightsPass")
2226
2227        opaque = vtki.new("OpaquePass")
2228
2229        dofCam = vtki.new("CameraPass")
2230        dofCam.SetDelegatePass(opaque)
2231
2232        dof = vtki.new("DepthOfFieldPass")
2233        dof.SetAutomaticFocalDistance(autofocus)
2234        dof.SetDelegatePass(dofCam)
2235
2236        collection = vtki.new("RenderPassCollection")
2237        collection.AddItem(lights)
2238        collection.AddItem(dof)
2239
2240        sequence = vtki.new("SequencePass")
2241        sequence.SetPasses(collection)
2242
2243        cam = vtki.new("CameraPass")
2244        cam.SetDelegatePass(sequence)
2245
2246        self.renderer.SetPass(cam)
2247        return self

Add a depth of field effect in the scene.

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

Clear the scene from all meshes and volumes.

def break_interaction(self) -> Self:
3579    def break_interaction(self) -> Self:
3580        """Break window interaction and return to the python execution flow"""
3581        if self.interactor:
3582            self.check_actors_trasform()
3583            self.interactor.ExitCallback()
3584        return self

Break window interaction and return to the python execution flow

def freeze(self, value=True) -> Self:
3586    def freeze(self, value=True) -> Self:
3587        """Freeze the current renderer. Use this with `sharecam=False`."""
3588        if not self.interactor:
3589            return self
3590        if not self.renderer:
3591            return self
3592        self.renderer.SetInteractive(not value)
3593        return self

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

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

Close the plotter.

camera
3712    @property
3713    def camera(self):
3714        """Return the current active camera."""
3715        if self.renderer:
3716            return self.renderer.GetActiveCamera()

Return the current active camera.

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

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

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

Close the last created Plotter instance if it exists.