vedo.plotter

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

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