vedo.plotter

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

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

Main class to manage objects.

Plotter( shape=(1, 1), N=None, pos=(0, 0), size='auto', screensize='auto', title='vedo', bg='white', bg2=None, axes=None, sharecam=True, resetcam=True, interactive=None, offscreen=False, qt_widget=None, wx_widget=None)
379    def __init__(
380        self,
381        shape=(1, 1),
382        N=None,
383        pos=(0, 0),
384        size="auto",
385        screensize="auto",
386        title="vedo",
387        bg="white",
388        bg2=None,
389        axes=None,
390        sharecam=True,
391        resetcam=True,
392        interactive=None,
393        offscreen=False,
394        qt_widget=None,
395        wx_widget=None,
396    ):
397        """
398        Arguments:
399            shape : (str, list)
400                shape of the grid of renderers in format (rows, columns). Ignored if N is specified.
401            N : (int)
402                number of desired renderers arranged in a grid automatically.
403            pos : (list)
404                (x,y) position in pixels of top-left corner of the rendering window on the screen
405            size : (str, list)
406                size of the rendering window. If 'auto', guess it based on screensize.
407            screensize : (list)
408                physical size of the monitor screen in pixels
409            bg : (color, str)
410                background color or specify jpg image file name with path
411            bg2 : (color)
412                background color of a gradient towards the top
413            title : (str)
414                window title
415
416            axes : (int)
417
418                Note that Axes type-1 can be fully customized by passing a dictionary `axes=dict()`.
419                Check out `vedo.addons.Axes()` for the available options.
420
421                    - 0,  no axes
422                    - 1,  draw three gray grid walls
423                    - 2,  show cartesian axes from (0,0,0)
424                    - 3,  show positive range of cartesian axes from (0,0,0)
425                    - 4,  show a triad at bottom left
426                    - 5,  show a cube at bottom left
427                    - 6,  mark the corners of the bounding box
428                    - 7,  draw a 3D ruler at each side of the cartesian axes
429                    - 8,  show the VTK CubeAxesActor object
430                    - 9,  show the bounding box outLine
431                    - 10, show three circles representing the maximum bounding box
432                    - 11, show a large grid on the x-y plane (use with zoom=8)
433                    - 12, show polar axes
434                    - 13, draw a simple ruler at the bottom of the window
435                    - 14: draw a camera orientation widget
436
437            sharecam : (bool)
438                if False each renderer will have an independent camera
439            interactive : (bool)
440                if True will stop after show() to allow interaction with the 3d scene
441            offscreen : (bool)
442                if True will not show the rendering window
443            qt_widget : (QVTKRenderWindowInteractor)
444                render in a Qt-Widget using an QVTKRenderWindowInteractor.
445                See examples `qt_windows[1,2,3].py` and `qt_cutter.py`.
446        """
447        vedo.plotter_instance = self
448
449        if interactive is None:
450            interactive = bool(N in (0, 1, None) and shape == (1, 1))
451        self._interactive = interactive
452        # print("interactive", interactive, N, shape)
453
454        self.objects = []           # list of objects to be shown
455        self.clicked_object = None  # holds the object that has been clicked
456        self.clicked_actor = None   # holds the actor that has been clicked
457
458        self.shape = shape   # nr. of subwindows in grid
459        self.axes = axes     # show axes type nr.
460        self.title = title   # window title
461        self.size = size     # window size
462        self.backgrcol = bg  # used also by backend notebooks
463
464        self.offscreen= offscreen
465        self.resetcam = resetcam
466        self.sharecam = sharecam  # share the same camera if multiple renderers
467        self.pos      = pos       # used by vedo.file_io
468
469        self.picker   = None  # hold the vtkPicker object
470        self.picked2d = None  # 2d coords of a clicked point on the rendering window
471        self.picked3d = None  # 3d coords of a clicked point on an actor
472
473        self.qt_widget = qt_widget  # QVTKRenderWindowInteractor
474        self.wx_widget = wx_widget  # wxVTKRenderWindowInteractor
475        self.interactor = None
476        self.window = None
477        self.renderer = None
478        self.renderers = []  # list of renderers
479
480        # mostly internal stuff:
481        self.hover_legends = []
482        self.justremoved = None
483        self.axes_instances = []
484        self.clock = 0
485        self.sliders = []
486        self.buttons = []
487        self.widgets = []
488        self.cutter_widget = None
489        self.hint_widget = None
490        self.background_renderer = None
491        self.last_event = None
492        self.skybox = None
493        self._icol = 0
494        self._clockt0 = time.time()
495        self._extralight = None
496        self._cocoa_initialized = False
497        self._cocoa_process_events = True  # make one call in show()
498        self._must_close_now = False
499
500        #####################################################################
501        if vedo.settings.default_backend == "2d":
502            self.offscreen = True
503            if self.size == "auto":
504                self.size = (800, 600)
505
506        elif vedo.settings.default_backend == "k3d":
507            if self.size == "auto":
508                self.size = (1000, 1000)
509            ####################################
510            return  ############################
511            ####################################
512
513        #############################################################
514        if screensize == "auto":
515            screensize = (2160, 1440)  # TODO: get actual screen size
516
517        # build the rendering window:
518        self.window = vtki.vtkRenderWindow()
519
520        self.window.GlobalWarningDisplayOff()
521
522        if self.title == "vedo":  # check if dev version
523            if "dev" in vedo.__version__:
524                self.title = f"vedo ({vedo.__version__})"
525        self.window.SetWindowName(self.title)
526
527        # more vedo.settings
528        if vedo.settings.use_depth_peeling:
529            self.window.SetAlphaBitPlanes(vedo.settings.alpha_bit_planes)
530        self.window.SetMultiSamples(vedo.settings.multi_samples)
531
532        self.window.SetPolygonSmoothing(vedo.settings.polygon_smoothing)
533        self.window.SetLineSmoothing(vedo.settings.line_smoothing)
534        self.window.SetPointSmoothing(vedo.settings.point_smoothing)
535
536
537        #############################################################
538        if N:  # N = number of renderers. Find out the best
539
540            if shape != (1, 1):  # arrangement based on minimum nr. of empty renderers
541                vedo.logger.warning("having set N, shape is ignored.")
542
543            x, y = screensize
544            nx = int(np.sqrt(int(N * y / x) + 1))
545            ny = int(np.sqrt(int(N * x / y) + 1))
546            lm = [
547                (nx, ny),
548                (nx, ny + 1),
549                (nx - 1, ny),
550                (nx + 1, ny),
551                (nx, ny - 1),
552                (nx - 1, ny + 1),
553                (nx + 1, ny - 1),
554                (nx + 1, ny + 1),
555                (nx - 1, ny - 1),
556            ]
557            ind, minl = 0, 1000
558            for i, m in enumerate(lm):
559                l = m[0] * m[1]
560                if N <= l < minl:
561                    ind = i
562                    minl = l
563            shape = lm[ind]
564
565        ##################################################
566        if isinstance(shape, str):
567
568            if "|" in shape:
569                if self.size == "auto":
570                    self.size = (800, 1200)
571                n = int(shape.split("|")[0])
572                m = int(shape.split("|")[1])
573                rangen = reversed(range(n))
574                rangem = reversed(range(m))
575            else:
576                if self.size == "auto":
577                    self.size = (1200, 800)
578                m = int(shape.split("/")[0])
579                n = int(shape.split("/")[1])
580                rangen = range(n)
581                rangem = range(m)
582
583            if n >= m:
584                xsplit = m / (n + m)
585            else:
586                xsplit = 1 - n / (n + m)
587            if vedo.settings.window_splitting_position:
588                xsplit = vedo.settings.window_splitting_position
589
590            for i in rangen:
591                arenderer = vtki.vtkRenderer()
592                if "|" in shape:
593                    arenderer.SetViewport(0, i / n, xsplit, (i + 1) / n)
594                else:
595                    arenderer.SetViewport(i / n, 0, (i + 1) / n, xsplit)
596                self.renderers.append(arenderer)
597
598            for i in rangem:
599                arenderer = vtki.vtkRenderer()
600
601                if "|" in shape:
602                    arenderer.SetViewport(xsplit, i / m, 1, (i + 1) / m)
603                else:
604                    arenderer.SetViewport(i / m, xsplit, (i + 1) / m, 1)
605                self.renderers.append(arenderer)
606
607            for r in self.renderers:
608                r.SetLightFollowCamera(vedo.settings.light_follows_camera)
609
610                r.SetUseDepthPeeling(vedo.settings.use_depth_peeling)
611                # r.SetUseDepthPeelingForVolumes(vedo.settings.use_depth_peeling)
612                if vedo.settings.use_depth_peeling:
613                    r.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels)
614                    r.SetOcclusionRatio(vedo.settings.occlusion_ratio)
615                r.SetUseFXAA(vedo.settings.use_fxaa)
616                r.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer)
617
618                r.SetBackground(vedo.get_color(self.backgrcol))
619
620                self.axes_instances.append(None)
621
622            self.shape = (n + m,)
623
624        elif utils.is_sequence(shape) and isinstance(shape[0], dict):
625            # passing a sequence of dicts for renderers specifications
626
627            if self.size == "auto":
628                self.size = (1000, 800)
629
630            for rd in shape:
631                x0, y0 = rd["bottomleft"]
632                x1, y1 = rd["topright"]
633                bg_ = rd.pop("bg", "white")
634                bg2_ = rd.pop("bg2", None)
635
636                arenderer = vtki.vtkRenderer()
637                arenderer.SetLightFollowCamera(vedo.settings.light_follows_camera)
638
639                arenderer.SetUseDepthPeeling(vedo.settings.use_depth_peeling)
640                # arenderer.SetUseDepthPeelingForVolumes(vedo.settings.use_depth_peeling)
641                if vedo.settings.use_depth_peeling:
642                    arenderer.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels)
643                    arenderer.SetOcclusionRatio(vedo.settings.occlusion_ratio)
644                arenderer.SetUseFXAA(vedo.settings.use_fxaa)
645                arenderer.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer)
646
647                arenderer.SetViewport(x0, y0, x1, y1)
648                arenderer.SetBackground(vedo.get_color(bg_))
649                if bg2_:
650                    arenderer.GradientBackgroundOn()
651                    arenderer.SetBackground2(vedo.get_color(bg2_))
652
653                self.renderers.append(arenderer)
654                self.axes_instances.append(None)
655
656            self.shape = (len(shape),)
657
658        else:
659
660            if isinstance(self.size, str) and self.size == "auto":
661                # figure out a reasonable window size
662                f = 1.5
663                x, y = screensize
664                xs = y / f * shape[1]  # because y<x
665                ys = y / f * shape[0]
666                if xs > x / f:  # shrink
667                    xs = x / f
668                    ys = xs / shape[1] * shape[0]
669                if ys > y / f:
670                    ys = y / f
671                    xs = ys / shape[0] * shape[1]
672                self.size = (int(xs), int(ys))
673                if shape == (1, 1):
674                    self.size = (int(y / f), int(y / f))  # because y<x
675            else:
676                self.size = (self.size[0], self.size[1])
677
678            try:
679                image_actor = None
680                bgname = str(self.backgrcol).lower()
681                if ".jpg" in bgname or ".jpeg" in bgname or ".png" in bgname:
682                    self.window.SetNumberOfLayers(2)
683                    self.background_renderer = vtki.vtkRenderer()
684                    self.background_renderer.SetLayer(0)
685                    self.background_renderer.InteractiveOff()
686                    self.background_renderer.SetBackground(vedo.get_color(bg2))
687                    image_actor = vedo.Image(self.backgrcol).actor
688                    self.window.AddRenderer(self.background_renderer)
689                    self.background_renderer.AddActor(image_actor)
690            except AttributeError:
691                pass
692
693            for i in reversed(range(shape[0])):
694                for j in range(shape[1]):
695                    arenderer = vtki.vtkRenderer()
696                    arenderer.SetLightFollowCamera(vedo.settings.light_follows_camera)
697                    arenderer.SetTwoSidedLighting(vedo.settings.two_sided_lighting)
698
699                    arenderer.SetUseDepthPeeling(vedo.settings.use_depth_peeling)
700                    if vedo.settings.use_depth_peeling:
701                        arenderer.SetMaximumNumberOfPeels(vedo.settings.max_number_of_peels)
702                        arenderer.SetOcclusionRatio(vedo.settings.occlusion_ratio)
703                    arenderer.SetUseFXAA(vedo.settings.use_fxaa)
704                    arenderer.SetPreserveDepthBuffer(vedo.settings.preserve_depth_buffer)
705
706                    if image_actor:
707                        arenderer.SetLayer(1)
708
709                    arenderer.SetBackground(vedo.get_color(self.backgrcol))
710                    if bg2:
711                        arenderer.GradientBackgroundOn()
712                        arenderer.SetBackground2(vedo.get_color(bg2))
713
714                    x0 = i / shape[0]
715                    y0 = j / shape[1]
716                    x1 = (i + 1) / shape[0]
717                    y1 = (j + 1) / shape[1]
718                    arenderer.SetViewport(y0, x0, y1, x1)
719                    self.renderers.append(arenderer)
720                    self.axes_instances.append(None)
721            self.shape = shape
722
723        if self.renderers:
724            self.renderer = self.renderers[0]
725            self.camera.SetParallelProjection(vedo.settings.use_parallel_projection)
726
727        #########################################################
728        if self.qt_widget or self.wx_widget:
729            if self.qt_widget:
730                self.window = self.qt_widget.GetRenderWindow()  # overwrite
731            else:
732                self.window = self.wx_widget.GetRenderWindow()
733            self.interactor = self.window.GetInteractor()
734
735        #########################################################
736        for r in self.renderers:
737            self.window.AddRenderer(r)
738            # set the background gradient if any
739            if vedo.settings.background_gradient_orientation > 0:
740                try:
741                    modes = [
742                        vtki.vtkViewport.GradientModes.VTK_GRADIENT_VERTICAL,
743                        vtki.vtkViewport.GradientModes.VTK_GRADIENT_HORIZONTAL,
744                        vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_SIDE,
745                        vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_CORNER,
746                    ]
747                    r.SetGradientMode(modes[vedo.settings.background_gradient_orientation])
748                    r.GradientBackgroundOn()
749                except AttributeError:
750                    pass
751
752        # Backend ####################################################
753        if vedo.settings.default_backend in ["panel", "trame", "k3d"]:
754            return  ################
755            ########################
756
757        #########################################################
758        if self.qt_widget or self.wx_widget:
759            self.interactor.SetRenderWindow(self.window)
760            if vedo.settings.enable_default_keyboard_callbacks:
761                self.interactor.AddObserver("KeyPressEvent", self._default_keypress)
762            if vedo.settings.enable_default_mouse_callbacks:
763                self.interactor.AddObserver("LeftButtonPressEvent", self._default_mouseleftclick)
764            return  ################
765            ########################
766
767        if self.size[0] == "f":  # full screen
768            self.size = "fullscreen"
769            self.window.SetFullScreen(True)
770            self.window.BordersOn()
771        else:
772            self.window.SetSize(int(self.size[0]), int(self.size[1]))
773
774        if self.offscreen:
775            if self.axes in (4, 5, 8, 12, 14):
776                self.axes = 0  # does not work with those
777            self.window.SetOffScreenRendering(True)
778            self.interactor = None
779            self._interactive = False
780            return  ################
781            ########################
782
783        self.window.SetPosition(pos)
784
785        #########################################################
786        self.interactor = vtki.vtkRenderWindowInteractor()
787
788        self.interactor.SetRenderWindow(self.window)
789        vsty = vtki.new("InteractorStyleTrackballCamera")
790        self.interactor.SetInteractorStyle(vsty)
791        self.interactor.RemoveObservers("CharEvent")
792
793        if vedo.settings.enable_default_keyboard_callbacks:
794            self.interactor.AddObserver("KeyPressEvent", self._default_keypress)
795        if vedo.settings.enable_default_mouse_callbacks:
796            self.interactor.AddObserver("LeftButtonPressEvent", self._default_mouseleftclick)
Arguments:
  • shape : (str, list) shape of the grid of renderers in format (rows, columns). Ignored if N is specified.
  • N : (int) number of desired renderers arranged in a grid automatically.
  • pos : (list) (x,y) position in pixels of top-left corner of the rendering window on the screen
  • size : (str, list) size of the rendering window. If 'auto', guess it based on screensize.
  • screensize : (list) physical size of the monitor screen in pixels
  • bg : (color, str) background color or specify jpg image file name with path
  • bg2 : (color) background color of a gradient towards the top
  • title : (str) window title
  • axes : (int)

    Note that Axes type-1 can be fully customized by passing a dictionary axes=dict(). Check out vedo.addons.Axes() for the available options.

    - 0,  no axes
    - 1,  draw three gray grid walls
    - 2,  show cartesian axes from (0,0,0)
    - 3,  show positive range of cartesian axes from (0,0,0)
    - 4,  show a triad at bottom left
    - 5,  show a cube at bottom left
    - 6,  mark the corners of the bounding box
    - 7,  draw a 3D ruler at each side of the cartesian axes
    - 8,  show the VTK CubeAxesActor object
    - 9,  show the bounding box outLine
    - 10, show three circles representing the maximum bounding box
    - 11, show a large grid on the x-y plane (use with zoom=8)
    - 12, show polar axes
    - 13, draw a simple ruler at the bottom of the window
    - 14: draw a camera orientation widget
    
  • sharecam : (bool) if False each renderer will have an independent camera
  • interactive : (bool) if True will stop after show() to allow interaction with the 3d scene
  • offscreen : (bool) if True will not show the rendering window
  • qt_widget : (QVTKRenderWindowInteractor) render in a Qt-Widget using an QVTKRenderWindowInteractor. See examples qt_windows[1,2,3].py and qt_cutter.py.
def print(self):
868    def print(self):
869        """Print information about the current instance."""
870        print(self.__str__())
871        return self

Print information about the current instance.

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

Initialize the interactor if not already initialized.

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

Process all pending events.

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

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

def add(self, *objs, at=None) -> Self:
933    def add(self, *objs, at=None) -> Self:
934        """
935        Append the input objects to the internal list of objects to be shown.
936
937        Arguments:
938            at : (int)
939                add the object at the specified renderer
940        """
941        if at is not None:
942            ren = self.renderers[at]
943        else:
944            ren = self.renderer
945
946        objs = utils.flatten(objs)
947        for ob in objs:
948            if ob and ob not in self.objects:
949                self.objects.append(ob)
950
951        acts = self._scan_input_return_acts(objs)
952
953        for a in acts:
954
955            if ren:
956                if isinstance(a, vedo.addons.BaseCutter):
957                    a.add_to(self)  # from cutters
958                    continue
959
960                if isinstance(a, vtki.vtkLight):
961                    ren.AddLight(a)
962                    continue
963
964                try:
965                    ren.AddActor(a)
966                except TypeError:
967                    ren.AddActor(a.actor)
968
969                try:
970                    ir = self.renderers.index(ren)
971                    a.rendered_at.add(ir) # might not have rendered_at
972                except (AttributeError, ValueError):
973                    pass
974
975                if isinstance(a, vtki.vtkFollower):
976                    a.SetCamera(self.camera)
977                elif isinstance(a, vedo.visual.LightKit):
978                    a.lightkit.AddLightsToRenderer(ren)
979
980        return self

Append the input objects to the internal list of objects to be shown.

Arguments:
  • at : (int) add the object at the specified renderer
def remove(self, *objs, at=None) -> Self:
 982    def remove(self, *objs, at=None) -> Self:
 983        """
 984        Remove input object to the internal list of objects to be shown.
 985
 986        Objects to be removed can be referenced by their assigned name,
 987
 988        Arguments:
 989            at : (int)
 990                remove the object at the specified renderer
 991        """
 992        # TODO and you can also use wildcards like `*` and `?`.
 993        if at is not None:
 994            ren = self.renderers[at]
 995        else:
 996            ren = self.renderer
 997
 998        objs = [ob for ob in utils.flatten(objs) if ob]
 999
1000        has_str = False
1001        for ob in objs:
1002            if isinstance(ob, str):
1003                has_str = True
1004                break
1005
1006        has_actor = False
1007        for ob in objs:
1008            if hasattr(ob, "actor") and ob.actor:
1009                has_actor = True
1010                break
1011
1012        if has_str or has_actor:
1013            # need to get the actors to search for
1014            for a in self.get_actors(include_non_pickables=True):
1015                # print("PARSING", [a])
1016                try:
1017                    if (a.name and a.name in objs) or a in objs:
1018                        objs.append(a)
1019                    # if a.name:
1020                    #     bools = [utils.parse_pattern(ob, a.name)[0] for ob in objs]
1021                    #     if any(bools) or a in objs:
1022                    #         objs.append(a)
1023                    #     print('a.name',a.name, objs,any(bools))
1024                except AttributeError:  # no .name
1025                    # passing the actor so get back the object with .retrieve_object()
1026                    try:
1027                        vobj = a.retrieve_object()
1028                        if (vobj.name and vobj.name in objs) or vobj in objs:
1029                            # print('vobj.name', vobj.name)
1030                            objs.append(vobj)
1031                    except AttributeError:
1032                        pass
1033
1034        if ren is None:
1035            return self
1036        ir = self.renderers.index(ren)
1037
1038        ids = []
1039        for ob in set(objs):
1040
1041            # will remove it from internal list if possible
1042            try:
1043                idx = self.objects.index(ob)
1044                ids.append(idx)
1045            except ValueError:
1046                pass
1047
1048            if ren:  ### remove it from the renderer
1049
1050                if isinstance(ob, vedo.addons.BaseCutter):
1051                    ob.remove_from(self)  # from cutters
1052                    continue
1053
1054                try:
1055                    ren.RemoveActor(ob)
1056                except TypeError:
1057                    try:
1058                        ren.RemoveActor(ob.actor)
1059                    except AttributeError:
1060                        pass
1061
1062                if hasattr(ob, "rendered_at"):
1063                    ob.rendered_at.discard(ir)
1064
1065                if hasattr(ob, "scalarbar") and ob.scalarbar:
1066                    ren.RemoveActor(ob.scalarbar)
1067                if hasattr(ob, "_caption") and ob._caption:
1068                    ren.RemoveActor(ob._caption)
1069                if hasattr(ob, "shadows") and ob.shadows:
1070                    for sha in ob.shadows:
1071                        ren.RemoveActor(sha.actor)
1072                if hasattr(ob, "trail") and ob.trail:
1073                    ren.RemoveActor(ob.trail.actor)
1074                    ob.trail_points = []
1075                    if hasattr(ob.trail, "shadows") and ob.trail.shadows:
1076                        for sha in ob.trail.shadows:
1077                            ren.RemoveActor(sha.actor)
1078
1079                elif isinstance(ob, vedo.visual.LightKit):
1080                    ob.lightkit.RemoveLightsFromRenderer(ren)
1081
1082        # for i in ids: # WRONG way of doing it!
1083        #     del self.objects[i]
1084        # instead we do:
1085        self.objects = [ele for i, ele in enumerate(self.objects) if i not in ids]
1086        return self

Remove input object to the internal list of objects to be shown.

Objects to be removed can be referenced by their assigned name,

Arguments:
  • at : (int) remove the object at the specified renderer
actors
1088    @property
1089    def actors(self):
1090        """Return the list of actors."""
1091        return [ob.actor for ob in self.objects if hasattr(ob, "actor")]

Return the list of actors.

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

Remove all the present lights in the current renderer.

def pop(self, at=None) -> Self:
1099    def pop(self, at=None) -> Self:
1100        """
1101        Remove the last added object from the rendering window.
1102        This method is typically used in loops or callback functions.
1103        """
1104        if at is not None and not isinstance(at, int):
1105            # wrong usage pitfall
1106            vedo.logger.error("argument of pop() must be an integer")
1107            raise RuntimeError()
1108
1109        if self.objects:
1110            self.remove(self.objects[-1], at)
1111        return self

Remove the last added object from the rendering window. This method is typically used in loops or callback functions.

def render(self, resetcam=False) -> Self:
1113    def render(self, resetcam=False) -> Self:
1114        """Render the scene. This method is typically used in loops or callback functions."""
1115
1116        if vedo.settings.dry_run_mode >= 2:
1117            return self
1118
1119        if not self.window:
1120            return self
1121
1122        self.initialize_interactor()
1123
1124        if resetcam:
1125            self.renderer.ResetCamera()
1126
1127        self.window.Render()
1128
1129        if self._cocoa_process_events and self.interactor and self.interactor.GetInitialized():
1130            if "Darwin" in vedo.sys_platform and not self.offscreen:
1131                self.interactor.ProcessEvents()
1132                self._cocoa_process_events = False
1133        return self

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

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

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

def use_depth_peeling(self, at=None, value=True) -> Self:
1159    def use_depth_peeling(self, at=None, value=True) -> Self:
1160        """
1161        Specify whether use depth peeling algorithm at this specific renderer
1162        Call this method before the first rendering.
1163        """
1164        if at is None:
1165            ren = self.renderer
1166        else:
1167            ren = self.renderers[at]
1168        ren.SetUseDepthPeeling(value)
1169        return self

Specify whether use depth peeling algorithm at this specific renderer Call this method before the first rendering.

def background(self, c1=None, c2=None, at=None, mode=0) -> Union[Self, numpy.ndarray]:
1171    def background(self, c1=None, c2=None, at=None, mode=0) -> Union[Self, "np.ndarray"]:
1172        """Set the color of the background for the current renderer.
1173        A different renderer index can be specified by keyword `at`.
1174
1175        Arguments:
1176            c1 : (list)
1177                background main color.
1178            c2 : (list)
1179                background color for the upper part of the window.
1180            at : (int)
1181                renderer index.
1182            mode : (int)
1183                background mode (needs vtk version >= 9.3)
1184                    0 = vertical,
1185                    1 = horizontal,
1186                    2 = radial farthest side,
1187                    3 = radia farthest corner.
1188        """
1189        if not self.renderers:
1190            return self
1191        if at is None:
1192            r = self.renderer
1193        else:
1194            r = self.renderers[at]
1195
1196        if c1 is None and c2 is None:
1197            return np.array(r.GetBackground())
1198
1199        if r:
1200            if c1 is not None:
1201                r.SetBackground(vedo.get_color(c1))
1202            if c2 is not None:
1203                r.GradientBackgroundOn()
1204                r.SetBackground2(vedo.get_color(c2))
1205                if mode:
1206                    try:  # only works with vtk>=9.3
1207                        modes = [
1208                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_VERTICAL,
1209                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_HORIZONTAL,
1210                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_SIDE,
1211                            vtki.vtkViewport.GradientModes.VTK_GRADIENT_RADIAL_VIEWPORT_FARTHEST_CORNER,
1212                        ]
1213                        r.SetGradientMode(modes[mode])
1214                    except AttributeError:
1215                        pass
1216
1217            else:
1218                r.GradientBackgroundOff()
1219        return self

Set the color of the background for the current renderer. A different renderer index can be specified by keyword at.

Arguments:
  • c1 : (list) background main color.
  • c2 : (list) background color for the upper part of the window.
  • at : (int) renderer index.
  • mode : (int) background mode (needs vtk version >= 9.3) 0 = vertical, 1 = horizontal, 2 = radial farthest side, 3 = radia farthest corner.
def get_meshes( self, at=None, include_non_pickables=False, unpack_assemblies=True) -> list:
1222    def get_meshes(self, at=None, include_non_pickables=False, unpack_assemblies=True) -> list:
1223        """
1224        Return a list of Meshes from the specified renderer.
1225
1226        Arguments:
1227            at : (int)
1228                specify which renderer to look at.
1229            include_non_pickables : (bool)
1230                include non-pickable objects
1231            unpack_assemblies : (bool)
1232                unpack assemblies into their components
1233        """
1234        if at is None:
1235            renderer = self.renderer
1236            at = self.renderers.index(renderer)
1237        elif isinstance(at, int):
1238            renderer = self.renderers[at]
1239
1240        has_global_axes = False
1241        if isinstance(self.axes_instances[at], vedo.Assembly):
1242            has_global_axes = True
1243
1244        if unpack_assemblies:
1245            acs = renderer.GetActors()
1246        else:
1247            acs = renderer.GetViewProps()
1248
1249        objs = []
1250        acs.InitTraversal()
1251        for _ in range(acs.GetNumberOfItems()):
1252
1253            if unpack_assemblies:
1254                a = acs.GetNextItem()
1255            else:
1256                a = acs.GetNextProp()
1257
1258            if isinstance(a, vtki.vtkVolume):
1259                continue
1260
1261            if include_non_pickables or a.GetPickable():
1262                if a == self.axes_instances[at]:
1263                    continue
1264                if has_global_axes and a in self.axes_instances[at].actors:
1265                    continue
1266                try:
1267                    objs.append(a.retrieve_object())
1268                except AttributeError:
1269                    pass
1270        return objs

Return a list of Meshes from the specified renderer.

Arguments:
  • at : (int) specify which renderer to look at.
  • include_non_pickables : (bool) include non-pickable objects
  • unpack_assemblies : (bool) unpack assemblies into their components
def get_volumes(self, at=None, include_non_pickables=False) -> list:
1272    def get_volumes(self, at=None, include_non_pickables=False) -> list:
1273        """
1274        Return a list of Volumes from the specified renderer.
1275
1276        Arguments:
1277            at : (int)
1278                specify which renderer to look at
1279            include_non_pickables : (bool)
1280                include non-pickable objects
1281        """
1282        if at is None:
1283            renderer = self.renderer
1284            at = self.renderers.index(renderer)
1285        elif isinstance(at, int):
1286            renderer = self.renderers[at]
1287
1288        vols = []
1289        acs = renderer.GetVolumes()
1290        acs.InitTraversal()
1291        for _ in range(acs.GetNumberOfItems()):
1292            a = acs.GetNextItem()
1293            if include_non_pickables or a.GetPickable():
1294                try:
1295                    vols.append(a.retrieve_object())
1296                except AttributeError:
1297                    pass
1298        return vols

Return a list of Volumes from the specified renderer.

Arguments:
  • at : (int) specify which renderer to look at
  • include_non_pickables : (bool) include non-pickable objects
def get_actors(self, at=None, include_non_pickables=False) -> list:
1300    def get_actors(self, at=None, include_non_pickables=False) -> list:
1301        """
1302        Return a list of Volumes from the specified renderer.
1303
1304        Arguments:
1305            at : (int)
1306                specify which renderer to look at
1307            include_non_pickables : (bool)
1308                include non-pickable objects
1309        """
1310        if at is None:
1311            renderer = self.renderer
1312            if renderer is None:
1313                return []
1314            at = self.renderers.index(renderer)
1315        elif isinstance(at, int):
1316            renderer = self.renderers[at]
1317
1318        acts = []
1319        acs = renderer.GetViewProps()
1320        acs.InitTraversal()
1321        for _ in range(acs.GetNumberOfItems()):
1322            a = acs.GetNextProp()
1323            if include_non_pickables or a.GetPickable():
1324                acts.append(a)
1325        return acts

Return a list of Volumes from the specified renderer.

Arguments:
  • at : (int) specify which renderer to look at
  • include_non_pickables : (bool) include non-pickable objects
def check_actors_trasform(self, at=None) -> Self:
1327    def check_actors_trasform(self, at=None) -> Self:
1328        """
1329        Reset the transformation matrix of all actors at specified renderer.
1330        This is only useful when actors have been moved/rotated/scaled manually
1331        in an already rendered scene using interactors like
1332        'TrackballActor' or 'JoystickActor'.
1333        """
1334        # see issue https://github.com/marcomusy/vedo/issues/1046
1335        for a in self.get_actors(at=at, include_non_pickables=True):
1336            try:
1337                M = a.GetMatrix()
1338            except AttributeError:
1339                continue
1340            if M and not M.IsIdentity():
1341                try:
1342                    a.retrieve_object().apply_transform_from_actor()
1343                    # vedo.logger.info(
1344                    #     f"object '{a.retrieve_object().name}' "
1345                    #     "was manually moved. Updated to its current position."
1346                    # )
1347                except AttributeError:
1348                    pass
1349        return self

Reset the transformation matrix of all actors at specified renderer. This is only useful when actors have been moved/rotated/scaled manually in an already rendered scene using interactors like 'TrackballActor' or 'JoystickActor'.

def reset_camera(self, tight=None) -> Self:
1351    def reset_camera(self, tight=None) -> Self:
1352        """
1353        Reset the camera position and zooming.
1354        If tight (float) is specified the zooming reserves a padding space
1355        in the xy-plane expressed in percent of the average size.
1356        """
1357        if tight is None:
1358            self.renderer.ResetCamera()
1359        else:
1360            x0, x1, y0, y1, z0, z1 = self.renderer.ComputeVisiblePropBounds()
1361            cam = self.camera
1362
1363            self.renderer.ComputeAspect()
1364            aspect = self.renderer.GetAspect()
1365            angle = np.pi * cam.GetViewAngle() / 180.0
1366            dx = x1 - x0
1367            dy = y1 - y0
1368            dist = max(dx / aspect[0], dy) / np.tan(angle / 2) / 2
1369
1370            cam.SetViewUp(0, 1, 0)
1371            cam.SetPosition(x0 + dx / 2, y0 + dy / 2, dist * (1 + tight))
1372            cam.SetFocalPoint(x0 + dx / 2, y0 + dy / 2, 0)
1373            if cam.GetParallelProjection():
1374                ps = max(dx / aspect[0], dy) / 2
1375                cam.SetParallelScale(ps * (1 + tight))
1376            self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
1377        return self

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

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

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

def reset_viewup(self, smooth=True) -> Self:
1390    def reset_viewup(self, smooth=True) -> Self:
1391        """
1392        Reset the orientation of the camera to the closest orthogonal direction and view-up.
1393        """
1394        vbb = addons.compute_visible_bounds()[0]
1395        x0, x1, y0, y1, z0, z1 = vbb
1396        mx, my, mz = (x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2
1397        d = self.camera.GetDistance()
1398
1399        viewups = np.array(
1400            [(0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)]
1401        )
1402        positions = np.array(
1403            [
1404                (mx, my, mz + d),
1405                (mx, my, mz - d),
1406                (mx, my + d, mz),
1407                (mx, my - d, mz),
1408                (mx + d, my, mz),
1409                (mx - d, my, mz),
1410            ]
1411        )
1412
1413        vu = np.array(self.camera.GetViewUp())
1414        vui = np.argmin(np.linalg.norm(viewups - vu, axis=1))
1415
1416        poc = np.array(self.camera.GetPosition())
1417        foc = np.array(self.camera.GetFocalPoint())
1418        a = poc - foc
1419        b = positions - foc
1420        a = a / np.linalg.norm(a)
1421        b = b.T * (1 / np.linalg.norm(b, axis=1))
1422        pui = np.argmin(np.linalg.norm(b.T - a, axis=1))
1423
1424        if smooth:
1425            outtimes = np.linspace(0, 1, num=11, endpoint=True)
1426            for t in outtimes:
1427                vv = vu * (1 - t) + viewups[vui] * t
1428                pp = poc * (1 - t) + positions[pui] * t
1429                ff = foc * (1 - t) + np.array([mx, my, mz]) * t
1430                self.camera.SetViewUp(vv)
1431                self.camera.SetPosition(pp)
1432                self.camera.SetFocalPoint(ff)
1433                self.render()
1434
1435            # interpolator does not respect parallel view...:
1436            # cam1 = dict(
1437            #     pos=poc,
1438            #     viewup=vu,
1439            #     focal_point=(mx,my,mz),
1440            #     clipping_range=self.camera.GetClippingRange()
1441            # )
1442            # # cam1 = self.camera
1443            # cam2 = dict(
1444            #     pos=positions[pui],
1445            #     viewup=viewups[vui],
1446            #     focal_point=(mx,my,mz),
1447            #     clipping_range=self.camera.GetClippingRange()
1448            # )
1449            # vcams = self.move_camera([cam1, cam2], output_times=outtimes, smooth=0)
1450            # for c in vcams:
1451            #     self.renderer.SetActiveCamera(c)
1452            #     self.render()
1453        else:
1454
1455            self.camera.SetViewUp(viewups[vui])
1456            self.camera.SetPosition(positions[pui])
1457            self.camera.SetFocalPoint(mx, my, mz)
1458
1459        self.renderer.ResetCameraClippingRange()
1460
1461        # vbb, _, _, _ = addons.compute_visible_bounds()
1462        # x0,x1, y0,y1, z0,z1 = vbb
1463        # self.renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
1464        self.render()
1465        return self

Reset the orientation of the camera to the closest orthogonal direction and view-up.

def move_camera(self, cameras, t=0, times=(), smooth=True, output_times=()) -> list:
1467    def move_camera(self, cameras, t=0, times=(), smooth=True, output_times=()) -> list:
1468        """
1469        Takes as input two cameras set camera at an interpolated position:
1470
1471        Cameras can be vtkCamera or dictionaries in format:
1472
1473            `dict(pos=..., focal_point=..., viewup=..., distance=..., clipping_range=...)`
1474
1475        Press `shift-C` key in interactive mode to dump a python snipplet
1476        of parameters for the current camera view.
1477        """
1478        nc = len(cameras)
1479        if len(times) == 0:
1480            times = np.linspace(0, 1, num=nc, endpoint=True)
1481
1482        assert len(times) == nc
1483
1484        cin = vtki.new("CameraInterpolator")
1485
1486        # cin.SetInterpolationTypeToLinear() # buggy?
1487        if nc > 2 and smooth:
1488            cin.SetInterpolationTypeToSpline()
1489
1490        for i, cam in enumerate(cameras):
1491            vcam = cam
1492            if isinstance(cam, dict):
1493                vcam = utils.camera_from_dict(cam)
1494            cin.AddCamera(times[i], vcam)
1495
1496        mint, maxt = cin.GetMinimumT(), cin.GetMaximumT()
1497        rng = maxt - mint
1498
1499        if len(output_times) == 0:
1500            cin.InterpolateCamera(t * rng, self.camera)
1501            return [self.camera]
1502        else:
1503            vcams = []
1504            for tt in output_times:
1505                c = vtki.vtkCamera()
1506                cin.InterpolateCamera(tt * rng, c)
1507                vcams.append(c)
1508            return vcams

Takes as input two cameras set camera at an interpolated position:

Cameras can be vtkCamera or dictionaries in format:

dict(pos=..., focal_point=..., viewup=..., distance=..., clipping_range=...)

Press shift-C key in interactive mode to dump a python snipplet of parameters for the current camera view.

def fly_to(self, point) -> Self:
1510    def fly_to(self, point) -> Self:
1511        """
1512        Fly camera to the specified point.
1513
1514        Arguments:
1515            point : (list)
1516                point in space to place camera.
1517
1518        Example:
1519            ```python
1520            from vedo import *
1521            cone = Cone()
1522            plt = Plotter(axes=1)
1523            plt.show(cone)
1524            plt.fly_to([1,0,0])
1525            plt.interactive().close()
1526            ```
1527        """
1528        if self.interactor:
1529            self.resetcam = False
1530            self.interactor.FlyTo(self.renderer, point)
1531        return self

Fly camera to the specified point.

Arguments:
  • point : (list) point in space to place camera.
Example:
from vedo import *
cone = Cone()
plt = Plotter(axes=1)
plt.show(cone)
plt.fly_to([1,0,0])
plt.interactive()close()
def look_at(self, plane='xy') -> Self:
1533    def look_at(self, plane="xy") -> Self:
1534        """Move the camera so that it looks at the specified cartesian plane"""
1535        cam = self.renderer.GetActiveCamera()
1536        fp = np.array(cam.GetFocalPoint())
1537        p = np.array(cam.GetPosition())
1538        dist = np.linalg.norm(fp - p)
1539        plane = plane.lower()
1540        if "x" in plane and "y" in plane:
1541            cam.SetPosition(fp[0], fp[1], fp[2] + dist)
1542            cam.SetViewUp(0.0, 1.0, 0.0)
1543        elif "x" in plane and "z" in plane:
1544            cam.SetPosition(fp[0], fp[1] - dist, fp[2])
1545            cam.SetViewUp(0.0, 0.0, 1.0)
1546        elif "y" in plane and "z" in plane:
1547            cam.SetPosition(fp[0] + dist, fp[1], fp[2])
1548            cam.SetViewUp(0.0, 0.0, 1.0)
1549        else:
1550            vedo.logger.error(f"in plotter.look() cannot understand argument {plane}")
1551        return self

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

def record(self, filename='') -> str:
1553    def record(self, filename="") -> str:
1554        """
1555        Record camera, mouse, keystrokes and all other events.
1556        Recording can be toggled on/off by pressing key "R".
1557
1558        Arguments:
1559            filename : (str)
1560                ascii file to store events.
1561                The default is `vedo.settings.cache_directory+"vedo/recorded_events.log"`.
1562
1563        Returns:
1564            a string descriptor of events.
1565
1566        Examples:
1567            - [record_play.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/record_play.py)
1568        """
1569        if vedo.settings.dry_run_mode >= 1:
1570            return ""
1571        if not self.interactor:
1572            vedo.logger.warning("Cannot record events, no interactor defined.")
1573            return ""
1574        erec = vtki.new("InteractorEventRecorder")
1575        erec.SetInteractor(self.interactor)
1576        if not filename:
1577            if not os.path.exists(vedo.settings.cache_directory):
1578                os.makedirs(vedo.settings.cache_directory)
1579            home_dir = os.path.expanduser("~")
1580            filename = os.path.join(
1581                home_dir, vedo.settings.cache_directory, "vedo", "recorded_events.log")
1582            print("Events will be recorded in", filename)
1583        erec.SetFileName(filename)
1584        erec.SetKeyPressActivationValue("R")
1585        erec.EnabledOn()
1586        erec.Record()
1587        self.interactor.Start()
1588        erec.Stop()
1589        erec.EnabledOff()
1590        with open(filename, "r", encoding="UTF-8") as fl:
1591            events = fl.read()
1592        erec = None
1593        return events

Record camera, mouse, keystrokes and all other events. Recording can be toggled on/off by pressing key "R".

Arguments:
  • filename : (str) ascii file to store events. The default is vedo.settings.cache_directory+"vedo/recorded_events.log".
Returns:

a string descriptor of events.

Examples:
def play(self, recorded_events='', repeats=0) -> Self:
1595    def play(self, recorded_events="", repeats=0) -> Self:
1596        """
1597        Play camera, mouse, keystrokes and all other events.
1598
1599        Arguments:
1600            events : (str)
1601                file o string of events.
1602                The default is `vedo.settings.cache_directory+"vedo/recorded_events.log"`.
1603            repeats : (int)
1604                number of extra repeats of the same events. The default is 0.
1605
1606        Examples:
1607            - [record_play.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/record_play.py)
1608        """
1609        if vedo.settings.dry_run_mode >= 1:
1610            return self
1611        if not self.interactor:
1612            vedo.logger.warning("Cannot play events, no interactor defined.")
1613            return self
1614
1615        erec = vtki.new("InteractorEventRecorder")
1616        erec.SetInteractor(self.interactor)
1617
1618        if not recorded_events:
1619            home_dir = os.path.expanduser("~")
1620            recorded_events = os.path.join(
1621                home_dir, vedo.settings.cache_directory, "vedo", "recorded_events.log")
1622
1623        if recorded_events.endswith(".log"):
1624            erec.ReadFromInputStringOff()
1625            erec.SetFileName(recorded_events)
1626        else:
1627            erec.ReadFromInputStringOn()
1628            erec.SetInputString(recorded_events)
1629
1630        erec.Play()
1631        for _ in range(repeats):
1632            erec.Rewind()
1633            erec.Play()
1634        erec.EnabledOff()
1635        erec = None
1636        return self

Play camera, mouse, keystrokes and all other events.

Arguments:
  • events : (str) file o string of events. The default is vedo.settings.cache_directory+"vedo/recorded_events.log".
  • repeats : (int) number of extra repeats of the same events. The default is 0.
Examples:
def parallel_projection(self, value=True, at=None) -> Self:
1638    def parallel_projection(self, value=True, at=None) -> Self:
1639        """
1640        Use parallel projection `at` a specified renderer.
1641        Object is seen from "infinite" distance, e.i. remove any perspective effects.
1642        An input value equal to -1 will toggle it on/off.
1643        """
1644        if at is not None:
1645            r = self.renderers[at]
1646        else:
1647            r = self.renderer
1648        if value == -1:
1649            val = r.GetActiveCamera().GetParallelProjection()
1650            value = not val
1651        r.GetActiveCamera().SetParallelProjection(value)
1652        r.Modified()
1653        return self

Use parallel projection at a specified renderer. Object is seen from "infinite" distance, e.i. remove any perspective effects. An input value equal to -1 will toggle it on/off.

def render_hidden_lines(self, value=True) -> Self:
1655    def render_hidden_lines(self, value=True) -> Self:
1656        """Remove hidden lines when in wireframe mode."""
1657        self.renderer.SetUseHiddenLineRemoval(not value)
1658        return self

Remove hidden lines when in wireframe mode.

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

Set the field of view angle for the camera. This is the angle of the camera frustum in the horizontal direction. High values will result in a wide-angle lens (fish-eye effect), and low values will result in a telephoto lens.

Default value is 30 degrees.

def zoom(self, zoom: float) -> Self:
1673    def zoom(self, zoom: float) -> Self:
1674        """Apply a zooming factor for the current camera view"""
1675        self.renderer.GetActiveCamera().Zoom(zoom)
1676        return self

Apply a zooming factor for the current camera view

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

Rotate camera around the view up vector.

def elevation(self, angle: float) -> Self:
1683    def elevation(self, angle: float) -> Self:
1684        """Rotate the camera around the cross product of the negative
1685        of the direction of projection and the view up vector."""
1686        self.renderer.GetActiveCamera().Elevation(angle)
1687        return self

Rotate the camera around the cross product of the negative of the direction of projection and the view up vector.

def roll(self, angle: float) -> Self:
1689    def roll(self, angle: float) -> Self:
1690        """Roll the camera about the direction of projection."""
1691        self.renderer.GetActiveCamera().Roll(angle)
1692        return self

Roll the camera about the direction of projection.

def dolly(self, value: float) -> Self:
1694    def dolly(self, value: float) -> Self:
1695        """Move the camera towards (value>0) or away from (value<0) the focal point."""
1696        self.renderer.GetActiveCamera().Dolly(value)
1697        return self

Move the camera towards (value>0) or away from (value<0) the focal point.

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

Add a vedo.addons.Slider2D which can call an external custom function.

Arguments:
  • sliderfunc : (Callable) external function to be called by the widget
  • xmin : (float) lower value of the slider
  • xmax : (float) upper value
  • value : (float) current value
  • pos : (list, str) position corner number: horizontal [1-5] or vertical [11-15] it can also be specified by corners coordinates [(x1,y1), (x2,y2)] and also by a string descriptor (eg. "bottom-left")
  • title : (str) title text
  • font : (str) title font face. Check available fonts here.
  • title_size : (float) title text scale [1.0]
  • show_value : (bool) if True current value is shown
  • delayed : (bool) if True the callback is delayed until when the mouse button is released
  • alpha : (float) opacity of the scalar bar texts
  • slider_length : (float) slider length
  • slider_width : (float) slider width
  • end_cap_length : (float) length of the end cap
  • end_cap_width : (float) width of the end cap
  • tube_width : (float) width of the tube
  • title_height : (float) width of the title
  • tformat : (str) format of the title
Examples:

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

Add a 3D slider widget which can call an external custom function.

Arguments:
  • sliderfunc : (function) external function to be called by the widget
  • pos1 : (list) first position 3D coordinates
  • pos2 : (list) second position coordinates
  • xmin : (float) lower value
  • xmax : (float) upper value
  • value : (float) initial value
  • s : (float) label scaling factor
  • t : (float) tube scaling factor
  • title : (str) title text
  • c : (color) slider color
  • rotation : (float) title rotation around slider axis
  • show_value : (bool) if True current value is shown
Examples:

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

Add a button to the renderer window.

Arguments:
  • states : (list) a list of possible states, e.g. ['On', 'Off']
  • c : (list) a list of colors for each state
  • bc : (list) a list of background colors for each state
  • pos : (list) 2D position from left-bottom corner
  • size : (float) size of button font
  • font : (str) font type. Check available fonts here.
  • bold : (bool) bold font face (False)
  • italic : (bool) italic font face (False)
  • alpha : (float) opacity level
  • angle : (float) anticlockwise rotation in degrees
Returns:

vedo.addons.Button object.

Examples:

def add_spline_tool( self, points, pc='k', ps=8, lc='r4', ac='g5', lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True) -> vedo.addons.SplineTool:
1929    def add_spline_tool(
1930        self, points, pc="k", ps=8, lc="r4", ac="g5",
1931        lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True,
1932    ) -> "vedo.addons.SplineTool":
1933        """
1934        Add a spline tool to the current plotter.
1935        Nodes of the spline can be dragged in space with the mouse.
1936        Clicking on the line itself adds an extra point.
1937        Selecting a point and pressing del removes it.
1938
1939        Arguments:
1940            points : (Mesh, Points, array)
1941                the set of coordinates forming the spline nodes.
1942            pc : (str)
1943                point color. The default is 'k'.
1944            ps : (str)
1945                point size. The default is 8.
1946            lc : (str)
1947                line color. The default is 'r4'.
1948            ac : (str)
1949                active point marker color. The default is 'g5'.
1950            lw : (int)
1951                line width. The default is 2.
1952            alpha : (float)
1953                line transparency.
1954            closed : (bool)
1955                spline is meant to be closed. The default is False.
1956
1957        Returns:
1958            a `SplineTool` object.
1959
1960        Examples:
1961            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
1962
1963            ![](https://vedo.embl.es/images/basic/spline_tool.png)
1964        """
1965        sw = addons.SplineTool(points, pc, ps, lc, ac, lw, alpha, closed, ontop, can_add_nodes)
1966        sw.interactor = self.interactor
1967        sw.on()
1968        sw.Initialize(sw.points.dataset)
1969        sw.representation.SetRenderer(self.renderer)
1970        sw.representation.SetClosedLoop(closed)
1971        sw.representation.BuildRepresentation()
1972        self.widgets.append(sw)
1973        return sw

Add a spline tool to the current plotter. Nodes of the spline can be dragged in space with the mouse. Clicking on the line itself adds an extra point. Selecting a point and pressing del removes it.

Arguments:
  • points : (Mesh, Points, array) the set of coordinates forming the spline nodes.
  • pc : (str) point color. The default is 'k'.
  • ps : (str) point size. The default is 8.
  • lc : (str) line color. The default is 'r4'.
  • ac : (str) active point marker color. The default is 'g5'.
  • lw : (int) line width. The default is 2.
  • alpha : (float) line transparency.
  • closed : (bool) spline is meant to be closed. The default is False.
Returns:

a SplineTool object.

Examples:

def add_icon(self, icon, pos=3, size=0.08) -> vedo.addons.Icon:
1975    def add_icon(self, icon, pos=3, size=0.08) -> "vedo.addons.Icon":
1976        """Add an inset icon mesh into the same renderer.
1977
1978        Arguments:
1979            pos : (int, list)
1980                icon position in the range [1-4] indicating one of the 4 corners,
1981                or it can be a tuple (x,y) as a fraction of the renderer size.
1982            size : (float)
1983                size of the square inset.
1984
1985        Examples:
1986            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
1987        """
1988        iconw = addons.Icon(icon, pos, size)
1989
1990        iconw.SetInteractor(self.interactor)
1991        iconw.EnabledOn()
1992        iconw.InteractiveOff()
1993        self.widgets.append(iconw)
1994        return iconw

Add an inset icon mesh into the same renderer.

Arguments:
  • pos : (int, list) icon position in the range [1-4] indicating one of the 4 corners, or it can be a tuple (x,y) as a fraction of the renderer size.
  • size : (float) size of the square inset.
Examples:
def add_global_axes(self, axtype=None, c=None) -> Self:
1996    def add_global_axes(self, axtype=None, c=None) -> Self:
1997        """Draw axes on scene. Available axes types:
1998
1999        Arguments:
2000            axtype : (int)
2001                - 0,  no axes,
2002                - 1,  draw three gray grid walls
2003                - 2,  show cartesian axes from (0,0,0)
2004                - 3,  show positive range of cartesian axes from (0,0,0)
2005                - 4,  show a triad at bottom left
2006                - 5,  show a cube at bottom left
2007                - 6,  mark the corners of the bounding box
2008                - 7,  draw a 3D ruler at each side of the cartesian axes
2009                - 8,  show the vtkCubeAxesActor object
2010                - 9,  show the bounding box outLine
2011                - 10, show three circles representing the maximum bounding box
2012                - 11, show a large grid on the x-y plane
2013                - 12, show polar axes
2014                - 13, draw a simple ruler at the bottom of the window
2015
2016            Axis type-1 can be fully customized by passing a dictionary axes=dict().
2017
2018        Example:
2019            ```python
2020            from vedo import Box, show
2021            b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1)
2022            show(
2023                b,
2024                axes={
2025                    "xtitle": "Some long variable [a.u.]",
2026                    "number_of_divisions": 4,
2027                    # ...
2028                },
2029            )
2030            ```
2031
2032        Examples:
2033            - [custom_axes1.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes1.py)
2034            - [custom_axes2.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes2.py)
2035            - [custom_axes3.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes3.py)
2036            - [custom_axes4.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes4.py)
2037
2038            <img src="https://user-images.githubusercontent.com/32848391/72752870-ab7d5280-3bc3-11ea-8911-9ace00211e23.png" width="600">
2039        """
2040        addons.add_global_axes(axtype, c)
2041        return self

Draw axes on scene. Available axes types:

Arguments:
  • axtype : (int)
    • 0, no axes,
    • 1, draw three gray grid walls
    • 2, show cartesian axes from (0,0,0)
    • 3, show positive range of cartesian axes from (0,0,0)
    • 4, show a triad at bottom left
    • 5, show a cube at bottom left
    • 6, mark the corners of the bounding box
    • 7, draw a 3D ruler at each side of the cartesian axes
    • 8, show the vtkCubeAxesActor object
    • 9, show the bounding box outLine
    • 10, show three circles representing the maximum bounding box
    • 11, show a large grid on the x-y plane
    • 12, show polar axes
    • 13, draw a simple ruler at the bottom of the window
  • Axis type-1 can be fully customized by passing a dictionary axes=dict().
Example:
from vedo import Box, show
b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1)
show(
    b,
    axes={
        "xtitle": "Some long variable [a.u.]",
        "number_of_divisions": 4,
        # ...
    },
)
Examples:

def add_legend_box(self, **kwargs) -> vedo.addons.LegendBox:
2043    def add_legend_box(self, **kwargs) -> "vedo.addons.LegendBox":
2044        """Add a legend to the top right.
2045
2046        Examples:
2047            - [legendbox.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/basic/legendbox.py),
2048            - [flag_labels1.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels1.py)
2049            - [flag_labels2.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels2.py)
2050        """
2051        acts = self.get_meshes()
2052        lb = addons.LegendBox(acts, **kwargs)
2053        self.add(lb)
2054        return lb

Add a legend to the top right.

Examples:
def add_hint( self, obj, text='', c='k', bg='yellow9', font='Calco', size=18, justify=0, angle=0, delay=250) -> Optional[vtkmodules.vtkInteractionWidgets.vtkBalloonWidget]:
2056    def add_hint(
2057        self,
2058        obj,
2059        text="",
2060        c="k",
2061        bg="yellow9",
2062        font="Calco",
2063        size=18,
2064        justify=0,
2065        angle=0,
2066        delay=250,
2067    ) -> Union[vtki.vtkBalloonWidget, None]:
2068        """
2069        Create a pop-up hint style message when hovering an object.
2070        Use `add_hint(obj, False)` to disable a hinting a specific object.
2071        Use `add_hint(None)` to disable all hints.
2072
2073        Arguments:
2074            obj : (Mesh, Points)
2075                the object to associate the pop-up to
2076            text : (str)
2077                string description of the pop-up
2078            delay : (int)
2079                milliseconds to wait before pop-up occurs
2080        """
2081        if self.offscreen or not self.interactor:
2082            return None
2083
2084        if vedo.vtk_version[:2] == (9, 0) and "Linux" in vedo.sys_platform:
2085            # Linux vtk9.0 is bugged
2086            vedo.logger.warning(
2087                f"add_hint() is not available on Linux platforms for vtk{vedo.vtk_version}."
2088            )
2089            return None
2090
2091        if obj is None:
2092            self.hint_widget.EnabledOff()
2093            self.hint_widget.SetInteractor(None)
2094            self.hint_widget = None
2095            return self.hint_widget
2096
2097        if text is False and self.hint_widget:
2098            self.hint_widget.RemoveBalloon(obj)
2099            return self.hint_widget
2100
2101        if text == "":
2102            if obj.name:
2103                text = obj.name
2104            elif obj.filename:
2105                text = obj.filename
2106            else:
2107                return None
2108
2109        if not self.hint_widget:
2110            self.hint_widget = vtki.vtkBalloonWidget()
2111
2112            rep = self.hint_widget.GetRepresentation()
2113            rep.SetBalloonLayoutToImageRight()
2114
2115            trep = rep.GetTextProperty()
2116            trep.SetFontFamily(vtki.VTK_FONT_FILE)
2117            trep.SetFontFile(utils.get_font_path(font))
2118            trep.SetFontSize(size)
2119            trep.SetColor(vedo.get_color(c))
2120            trep.SetBackgroundColor(vedo.get_color(bg))
2121            trep.SetShadow(0)
2122            trep.SetJustification(justify)
2123            trep.UseTightBoundingBoxOn()
2124
2125            self.hint_widget.ManagesCursorOff()
2126            self.hint_widget.SetTimerDuration(delay)
2127            self.hint_widget.SetInteractor(self.interactor)
2128            if angle:
2129                trep.SetOrientation(angle)
2130                trep.SetBackgroundOpacity(0)
2131            # else:
2132            #     trep.SetBackgroundOpacity(0.5) # doesnt work well
2133            self.hint_widget.SetRepresentation(rep)
2134            self.widgets.append(self.hint_widget)
2135            self.hint_widget.EnabledOn()
2136
2137        bst = self.hint_widget.GetBalloonString(obj.actor)
2138        if bst:
2139            self.hint_widget.UpdateBalloonString(obj.actor, text)
2140        else:
2141            self.hint_widget.AddBalloon(obj.actor, text)
2142
2143        return self.hint_widget

Create a pop-up hint style message when hovering an object. Use add_hint(obj, False) to disable a hinting a specific object. Use add_hint(None) to disable all hints.

Arguments:
  • obj : (Mesh, Points) the object to associate the pop-up to
  • text : (str) string description of the pop-up
  • delay : (int) milliseconds to wait before pop-up occurs
def add_shadows(self) -> Self:
2145    def add_shadows(self) -> Self:
2146        """Add shadows at the current renderer."""
2147        if self.renderer:
2148            shadows = vtki.new("ShadowMapPass")
2149            seq = vtki.new("SequencePass")
2150            passes = vtki.new("RenderPassCollection")
2151            passes.AddItem(shadows.GetShadowMapBakerPass())
2152            passes.AddItem(shadows)
2153            seq.SetPasses(passes)
2154            camerapass = vtki.new("CameraPass")
2155            camerapass.SetDelegatePass(seq)
2156            self.renderer.SetPass(camerapass)
2157        return self

Add shadows at the current renderer.

def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
2159    def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> Self:
2160        """
2161        Screen Space Ambient Occlusion.
2162
2163        For every pixel on the screen, the pixel shader samples the depth values around
2164        the current pixel and tries to compute the amount of occlusion from each of the sampled
2165        points.
2166
2167        Arguments:
2168            radius : (float)
2169                radius of influence in absolute units
2170            bias : (float)
2171                bias of the normals
2172            blur : (bool)
2173                add a blurring to the sampled positions
2174            samples : (int)
2175                number of samples to probe
2176
2177        Examples:
2178            - [ssao.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ssao.py)
2179
2180            ![](https://vedo.embl.es/images/basic/ssao.jpg)
2181        """
2182        lights = vtki.new("LightsPass")
2183
2184        opaque = vtki.new("OpaquePass")
2185
2186        ssaoCam = vtki.new("CameraPass")
2187        ssaoCam.SetDelegatePass(opaque)
2188
2189        ssao = vtki.new("SSAOPass")
2190        ssao.SetRadius(radius)
2191        ssao.SetBias(bias)
2192        ssao.SetBlur(blur)
2193        ssao.SetKernelSize(samples)
2194        ssao.SetDelegatePass(ssaoCam)
2195
2196        translucent = vtki.new("TranslucentPass")
2197
2198        volpass = vtki.new("VolumetricPass")
2199        ddp = vtki.new("DualDepthPeelingPass")
2200        ddp.SetTranslucentPass(translucent)
2201        ddp.SetVolumetricPass(volpass)
2202
2203        over = vtki.new("OverlayPass")
2204
2205        collection = vtki.new("RenderPassCollection")
2206        collection.AddItem(lights)
2207        collection.AddItem(ssao)
2208        collection.AddItem(ddp)
2209        collection.AddItem(over)
2210
2211        sequence = vtki.new("SequencePass")
2212        sequence.SetPasses(collection)
2213
2214        cam = vtki.new("CameraPass")
2215        cam.SetDelegatePass(sequence)
2216
2217        self.renderer.SetPass(cam)
2218        return self

Screen Space Ambient Occlusion.

For every pixel on the screen, the pixel shader samples the depth values around the current pixel and tries to compute the amount of occlusion from each of the sampled points.

Arguments:
  • radius : (float) radius of influence in absolute units
  • bias : (float) bias of the normals
  • blur : (bool) add a blurring to the sampled positions
  • samples : (int) number of samples to probe
Examples:

def add_depth_of_field(self, autofocus=True) -> Self:
2220    def add_depth_of_field(self, autofocus=True) -> Self:
2221        """Add a depth of field effect in the scene."""
2222        lights = vtki.new("LightsPass")
2223
2224        opaque = vtki.new("OpaquePass")
2225
2226        dofCam = vtki.new("CameraPass")
2227        dofCam.SetDelegatePass(opaque)
2228
2229        dof = vtki.new("DepthOfFieldPass")
2230        dof.SetAutomaticFocalDistance(autofocus)
2231        dof.SetDelegatePass(dofCam)
2232
2233        collection = vtki.new("RenderPassCollection")
2234        collection.AddItem(lights)
2235        collection.AddItem(dof)
2236
2237        sequence = vtki.new("SequencePass")
2238        sequence.SetPasses(collection)
2239
2240        cam = vtki.new("CameraPass")
2241        cam.SetDelegatePass(sequence)
2242
2243        self.renderer.SetPass(cam)
2244        return self

Add a depth of field effect in the scene.

def add_renderer_frame( self, c=None, alpha=None, lw=None, padding=None) -> vedo.addons.RendererFrame:
2275    def add_renderer_frame(self, c=None, alpha=None, lw=None, padding=None) -> "vedo.addons.RendererFrame":
2276        """
2277        Add a frame to the renderer subwindow.
2278
2279        Arguments:
2280            c : (color)
2281                color name or index
2282            alpha : (float)
2283                opacity level
2284            lw : (int)
2285                line width in pixels.
2286            padding : (float)
2287                padding space in pixels.
2288        """
2289        if c is None:  # automatic black or white
2290            c = (0.9, 0.9, 0.9)
2291            if self.renderer:
2292                if np.sum(self.renderer.GetBackground()) > 1.5:
2293                    c = (0.1, 0.1, 0.1)
2294        renf = addons.RendererFrame(c, alpha, lw, padding)
2295        if renf:
2296            self.renderer.AddActor(renf)
2297        return renf

Add a frame to the renderer subwindow.

Arguments:
  • c : (color) color name or index
  • alpha : (float) opacity level
  • lw : (int) line width in pixels.
  • padding : (float) padding space in pixels.
def add_hover_legend( self, at=None, c=None, pos='bottom-left', font='Calco', s=0.75, bg='auto', alpha=0.1, maxlength=24, use_info=False) -> int:
2299    def add_hover_legend(
2300        self,
2301        at=None,
2302        c=None,
2303        pos="bottom-left",
2304        font="Calco",
2305        s=0.75,
2306        bg="auto",
2307        alpha=0.1,
2308        maxlength=24,
2309        use_info=False,
2310    ) -> int:
2311        """
2312        Add a legend with 2D text which is triggered by hovering the mouse on an object.
2313
2314        The created text object are stored in `plotter.hover_legends`.
2315
2316        Returns:
2317            the id of the callback function.
2318
2319        Arguments:
2320            c : (color)
2321                Text color. If None then black or white is chosen automatically
2322            pos : (str)
2323                text positioning
2324            font : (str)
2325                text font type. Check [available fonts here](https://vedo.embl.es/fonts).
2326            s : (float)
2327                text size scale
2328            bg : (color)
2329                background color of the 2D box containing the text
2330            alpha : (float)
2331                box transparency
2332            maxlength : (int)
2333                maximum number of characters per line
2334            use_info : (bool)
2335                visualize the content of the `obj.info` attribute
2336
2337        Examples:
2338            - [hover_legend.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/hover_legend.py)
2339            - [earthquake_browser.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/earthquake_browser.py)
2340
2341            ![](https://vedo.embl.es/images/pyplot/earthquake_browser.jpg)
2342        """
2343        hoverlegend = vedo.shapes.Text2D(pos=pos, font=font, c=c, s=s, alpha=alpha, bg=bg)
2344
2345        if at is None:
2346            at = self.renderers.index(self.renderer)
2347
2348        def _legfunc(evt):
2349            if not evt.object or not self.renderer or at != evt.at:
2350                if hoverlegend.mapper.GetInput():  # clear and return
2351                    hoverlegend.mapper.SetInput("")
2352                    self.render()
2353                return
2354
2355            if use_info:
2356                if hasattr(evt.object, "info"):
2357                    t = str(evt.object.info)
2358                else:
2359                    return
2360            else:
2361                t, tp = "", ""
2362                if evt.isMesh:
2363                    tp = "Mesh "
2364                elif evt.isPoints:
2365                    tp = "Points "
2366                elif evt.isVolume:
2367                    tp = "Volume "
2368                elif evt.isImage:
2369                    tp = "Image "
2370                elif evt.isAssembly:
2371                    tp = "Assembly "
2372                else:
2373                    return
2374
2375                if evt.isAssembly:
2376                    if not evt.object.name:
2377                        t += f"Assembly object of {len(evt.object.unpack())} parts\n"
2378                    else:
2379                        t += f"Assembly name: {evt.object.name} ({len(evt.object.unpack())} parts)\n"
2380                else:
2381                    if evt.object.name:
2382                        t += f"{tp}name"
2383                        if evt.isPoints:
2384                            t += "  "
2385                        if evt.isMesh:
2386                            t += "  "
2387                        t += f": {evt.object.name[:maxlength]}".ljust(maxlength) + "\n"
2388
2389                if evt.object.filename:
2390                    t += f"{tp}filename: "
2391                    t += f"{os.path.basename(evt.object.filename[-maxlength:])}".ljust(maxlength)
2392                    t += "\n"
2393                    if not evt.object.file_size:
2394                        evt.object.file_size, evt.object.created = vedo.file_io.file_info(evt.object.filename)
2395                    if evt.object.file_size:
2396                        t += "             : "
2397                        sz, created = evt.object.file_size, evt.object.created
2398                        t += f"{created[4:-5]} ({sz})" + "\n"
2399
2400                if evt.isPoints:
2401                    indata = evt.object.dataset
2402                    if indata.GetNumberOfPoints():
2403                        t += (
2404                            f"#points/cells: {indata.GetNumberOfPoints()}"
2405                            f" / {indata.GetNumberOfCells()}"
2406                        )
2407                    pdata = indata.GetPointData()
2408                    cdata = indata.GetCellData()
2409                    if pdata.GetScalars() and pdata.GetScalars().GetName():
2410                        t += f"\nPoint array  : {pdata.GetScalars().GetName()}"
2411                        if pdata.GetScalars().GetName() == evt.object.mapper.GetArrayName():
2412                            t += " *"
2413                    if cdata.GetScalars() and cdata.GetScalars().GetName():
2414                        t += f"\nCell  array  : {cdata.GetScalars().GetName()}"
2415                        if cdata.GetScalars().GetName() == evt.object.mapper.GetArrayName():
2416                            t += " *"
2417
2418                if evt.isImage:
2419                    t = f"{os.path.basename(evt.object.filename[:maxlength+10])}".ljust(maxlength+10)
2420                    t += f"\nImage shape: {evt.object.shape}"
2421                    pcol = self.color_picker(evt.picked2d)
2422                    t += f"\nPixel color: {vedo.colors.rgb2hex(pcol/255)} {pcol}"
2423
2424            # change box color if needed in 'auto' mode
2425            if evt.isPoints and "auto" in str(bg):
2426                actcol = evt.object.properties.GetColor()
2427                if hoverlegend.mapper.GetTextProperty().GetBackgroundColor() != actcol:
2428                    hoverlegend.mapper.GetTextProperty().SetBackgroundColor(actcol)
2429
2430            # adapt to changes in bg color
2431            bgcol = self.renderers[at].GetBackground()
2432            _bgcol = c
2433            if _bgcol is None:  # automatic black or white
2434                _bgcol = (0.9, 0.9, 0.9)
2435                if sum(bgcol) > 1.5:
2436                    _bgcol = (0.1, 0.1, 0.1)
2437                if len(set(_bgcol).intersection(bgcol)) < 3:
2438                    hoverlegend.color(_bgcol)
2439
2440            if hoverlegend.mapper.GetInput() != t:
2441                hoverlegend.mapper.SetInput(t)
2442                self.interactor.Render()
2443            
2444            # print("ABORT", idcall, hoverlegend.actor.GetCommand(idcall))
2445            # hoverlegend.actor.GetCommand(idcall).AbortFlagOn()
2446
2447        self.add(hoverlegend, at=at)
2448        self.hover_legends.append(hoverlegend)
2449        idcall = self.add_callback("MouseMove", _legfunc)
2450        return idcall

Add a legend with 2D text which is triggered by hovering the mouse on an object.

The created text object are stored in plotter.hover_legends.

Returns:

the id of the callback function.

Arguments:
  • c : (color) Text color. If None then black or white is chosen automatically
  • pos : (str) text positioning
  • font : (str) text font type. Check available fonts here.
  • s : (float) text size scale
  • bg : (color) background color of the 2D box containing the text
  • alpha : (float) box transparency
  • maxlength : (int) maximum number of characters per line
  • use_info : (bool) visualize the content of the obj.info attribute
Examples:

def add_scale_indicator( self, pos=(0.7, 0.05), s=0.02, length=2, lw=4, c='k1', alpha=1, units='', gap=0.05) -> Optional[vedo.visual.Actor2D]:
2452    def add_scale_indicator(
2453        self,
2454        pos=(0.7, 0.05),
2455        s=0.02,
2456        length=2,
2457        lw=4,
2458        c="k1",
2459        alpha=1,
2460        units="",
2461        gap=0.05,
2462    ) -> Union["vedo.visual.Actor2D", None]:
2463        """
2464        Add a Scale Indicator. Only works in parallel mode (no perspective).
2465
2466        Arguments:
2467            pos : (list)
2468                fractional (x,y) position on the screen.
2469            s : (float)
2470                size of the text.
2471            length : (float)
2472                length of the line.
2473            units : (str)
2474                string to show units.
2475            gap : (float)
2476                separation of line and text.
2477
2478        Example:
2479            ```python
2480            from vedo import settings, Cube, Plotter
2481            settings.use_parallel_projection = True # or else it does not make sense!
2482            cube = Cube().alpha(0.2)
2483            plt = Plotter(size=(900,600), axes=dict(xtitle='x (um)'))
2484            plt.add_scale_indicator(units='um', c='blue4')
2485            plt.show(cube, "Scale indicator with units").close()
2486            ```
2487            ![](https://vedo.embl.es/images/feats/scale_indicator.png)
2488        """
2489        # Note that this cannot go in addons.py
2490        # because it needs callbacks and window size
2491        if not self.interactor:
2492            return None
2493
2494        ppoints = vtki.vtkPoints()  # Generate the polyline
2495        psqr = [[0.0, gap], [length / 10, gap]]
2496        dd = psqr[1][0] - psqr[0][0]
2497        for i, pt in enumerate(psqr):
2498            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2499        lines = vtki.vtkCellArray()
2500        lines.InsertNextCell(len(psqr))
2501        for i in range(len(psqr)):
2502            lines.InsertCellPoint(i)
2503        pd = vtki.vtkPolyData()
2504        pd.SetPoints(ppoints)
2505        pd.SetLines(lines)
2506
2507        wsx, wsy = self.window.GetSize()
2508        if not self.camera.GetParallelProjection():
2509            vedo.logger.warning("add_scale_indicator called with use_parallel_projection OFF. Skip.")
2510            return None
2511
2512        rlabel = vtki.new("VectorText")
2513        rlabel.SetText("scale")
2514        tf = vtki.new("TransformPolyDataFilter")
2515        tf.SetInputConnection(rlabel.GetOutputPort())
2516        t = vtki.vtkTransform()
2517        t.Scale(s * wsy / wsx, s, 1)
2518        tf.SetTransform(t)
2519
2520        app = vtki.new("AppendPolyData")
2521        app.AddInputConnection(tf.GetOutputPort())
2522        app.AddInputData(pd)
2523
2524        mapper = vtki.new("PolyDataMapper2D")
2525        mapper.SetInputConnection(app.GetOutputPort())
2526        cs = vtki.vtkCoordinate()
2527        cs.SetCoordinateSystem(1)
2528        mapper.SetTransformCoordinate(cs)
2529
2530        fractor = vedo.visual.Actor2D()
2531        csys = fractor.GetPositionCoordinate()
2532        csys.SetCoordinateSystem(3)
2533        fractor.SetPosition(pos)
2534        fractor.SetMapper(mapper)
2535        fractor.GetProperty().SetColor(vedo.get_color(c))
2536        fractor.GetProperty().SetOpacity(alpha)
2537        fractor.GetProperty().SetLineWidth(lw)
2538        fractor.GetProperty().SetDisplayLocationToForeground()
2539
2540        def sifunc(iren, ev):
2541            wsx, wsy = self.window.GetSize()
2542            ps = self.camera.GetParallelScale()
2543            newtxt = utils.precision(ps / wsy * wsx * length * dd, 3)
2544            if units:
2545                newtxt += " " + units
2546            if rlabel.GetText() != newtxt:
2547                rlabel.SetText(newtxt)
2548
2549        self.renderer.AddActor(fractor)
2550        self.interactor.AddObserver("MouseWheelBackwardEvent", sifunc)
2551        self.interactor.AddObserver("MouseWheelForwardEvent", sifunc)
2552        self.interactor.AddObserver("InteractionEvent", sifunc)
2553        sifunc(0, 0)
2554        return fractor

Add a Scale Indicator. Only works in parallel mode (no perspective).

Arguments:
  • pos : (list) fractional (x,y) position on the screen.
  • s : (float) size of the text.
  • length : (float) length of the line.
  • units : (str) string to show units.
  • gap : (float) separation of line and text.
Example:
from vedo import settings, Cube, Plotter
settings.use_parallel_projection = True # or else it does not make sense!
cube = Cube().alpha(0.2)
plt = Plotter(size=(900,600), axes=dict(xtitle='x (um)'))
plt.add_scale_indicator(units='um', c='blue4')
plt.show(cube, "Scale indicator with units")close()

def fill_event(self, ename='', pos=(), enable_picking=True) -> vedo.plotter.Event:
2556    def fill_event(self, ename="", pos=(), enable_picking=True) -> "Event":
2557        """
2558        Create an Event object with information of what was clicked.
2559
2560        If `enable_picking` is False, no picking will be performed.
2561        This can be useful to avoid double picking when using buttons.
2562        """
2563        if not self.interactor:
2564            return Event()
2565
2566        if len(pos) > 0:
2567            x, y = pos
2568            self.interactor.SetEventPosition(pos)
2569        else:
2570            x, y = self.interactor.GetEventPosition()
2571        self.renderer = self.interactor.FindPokedRenderer(x, y)
2572
2573        self.picked2d = (x, y)
2574
2575        key = self.interactor.GetKeySym()
2576
2577        if key:
2578            if "_L" in key or "_R" in key:
2579                # skip things like Shift_R
2580                key = ""  # better than None
2581            else:
2582                if self.interactor.GetShiftKey():
2583                    key = key.upper()
2584
2585                if key == "MINUS":  # fix: vtk9 is ignoring shift chars..
2586                    key = "underscore"
2587                elif key == "EQUAL":  # fix: vtk9 is ignoring shift chars..
2588                    key = "plus"
2589                elif key == "SLASH":  # fix: vtk9 is ignoring shift chars..
2590                    key = "?"
2591
2592                if self.interactor.GetControlKey():
2593                    key = "Ctrl+" + key
2594
2595                if self.interactor.GetAltKey():
2596                    key = "Alt+" + key
2597
2598        if enable_picking:
2599            if not self.picker:
2600                self.picker = vtki.vtkPropPicker()
2601
2602            self.picker.PickProp(x, y, self.renderer)
2603            actor = self.picker.GetProp3D()
2604            # Note that GetProp3D already picks Assembly
2605
2606            xp, yp = self.interactor.GetLastEventPosition()
2607            dx, dy = x - xp, y - yp
2608
2609            delta3d = np.array([0, 0, 0])
2610
2611            if actor:
2612                picked3d = np.array(self.picker.GetPickPosition())
2613
2614                try:
2615                    vobj = actor.retrieve_object()
2616                    old_pt = np.asarray(vobj.picked3d)
2617                    vobj.picked3d = picked3d
2618                    delta3d = picked3d - old_pt
2619                except (AttributeError, TypeError):
2620                    pass
2621
2622            else:
2623                picked3d = None
2624
2625            if not actor:  # try 2D
2626                actor = self.picker.GetActor2D()
2627
2628        event = Event()
2629        event.name = ename
2630        event.title = self.title
2631        event.id = -1  # will be set by the timer wrapper function
2632        event.timerid = -1  # will be set by the timer wrapper function
2633        event.priority = -1  # will be set by the timer wrapper function
2634        event.time = time.time()
2635        event.at = self.renderers.index(self.renderer)
2636        event.keypress = key
2637        if enable_picking:
2638            try:
2639                event.object = actor.retrieve_object()
2640            except AttributeError:
2641                event.object = actor
2642            try:
2643                event.actor = actor.retrieve_object()  # obsolete use object instead
2644            except AttributeError:
2645                event.actor = actor
2646            event.picked3d = picked3d
2647            event.picked2d = (x, y)
2648            event.delta2d = (dx, dy)
2649            event.angle2d = np.arctan2(dy, dx)
2650            event.speed2d = np.sqrt(dx * dx + dy * dy)
2651            event.delta3d = delta3d
2652            event.speed3d = np.sqrt(np.dot(delta3d, delta3d))
2653            event.isPoints = isinstance(event.object, vedo.Points)
2654            event.isMesh = isinstance(event.object, vedo.Mesh)
2655            event.isAssembly = isinstance(event.object, vedo.Assembly)
2656            event.isVolume = isinstance(event.object, vedo.Volume)
2657            event.isImage = isinstance(event.object, vedo.Image)
2658            event.isActor2D = isinstance(event.object, vtki.vtkActor2D)
2659        return event

Create an Event object with information of what was clicked.

If enable_picking is False, no picking will be performed. This can be useful to avoid double picking when using buttons.

def add_callback( self, event_name: str, func: Callable, priority=0.0, enable_picking=True) -> int:
2661    def add_callback(self, event_name: str, func: Callable, priority=0.0, enable_picking=True) -> int:
2662        """
2663        Add a function to be executed while show() is active.
2664
2665        Return a unique id for the callback.
2666
2667        The callback function (see example below) exposes a dictionary
2668        with the following information:
2669        - `name`: event name,
2670        - `id`: event unique identifier,
2671        - `priority`: event priority (float),
2672        - `interactor`: the interactor object,
2673        - `at`: renderer nr. where the event occurred
2674        - `keypress`: key pressed as string
2675        - `actor`: object picked by the mouse
2676        - `picked3d`: point picked in world coordinates
2677        - `picked2d`: screen coords of the mouse pointer
2678        - `delta2d`: shift wrt previous position (to calculate speed, direction)
2679        - `delta3d`: ...same but in 3D world coords
2680        - `angle2d`: angle of mouse movement on screen
2681        - `speed2d`: speed of mouse movement on screen
2682        - `speed3d`: speed of picked point in world coordinates
2683        - `isPoints`: True if of class
2684        - `isMesh`: True if of class
2685        - `isAssembly`: True if of class
2686        - `isVolume`: True if of class Volume
2687        - `isImage`: True if of class
2688
2689        If `enable_picking` is False, no picking will be performed.
2690        This can be useful to avoid double picking when using buttons.
2691
2692        Frequently used events are:
2693        - `KeyPress`, `KeyRelease`: listen to keyboard events
2694        - `LeftButtonPress`, `LeftButtonRelease`: listen to mouse clicks
2695        - `MiddleButtonPress`, `MiddleButtonRelease`
2696        - `RightButtonPress`, `RightButtonRelease`
2697        - `MouseMove`: listen to mouse pointer changing position
2698        - `MouseWheelForward`, `MouseWheelBackward`
2699        - `Enter`, `Leave`: listen to mouse entering or leaving the window
2700        - `Pick`, `StartPick`, `EndPick`: listen to object picking
2701        - `ResetCamera`, `ResetCameraClippingRange`
2702        - `Error`, `Warning`
2703        - `Char`
2704        - `Timer`
2705
2706        Check the complete list of events [here](https://vtk.org/doc/nightly/html/classvtkCommand.html).
2707
2708        Example:
2709            ```python
2710            from vedo import *
2711
2712            def func(evt):
2713                # this function is called every time the mouse moves
2714                # (evt is a dotted dictionary)
2715                if not evt.object:
2716                    return  # no hit, return
2717                print("point coords =", evt.picked3d)
2718                # print(evt) # full event dump
2719
2720            elli = Ellipsoid()
2721            plt = Plotter(axes=1)
2722            plt.add_callback('mouse hovering', func)
2723            plt.show(elli).close()
2724            ```
2725
2726        Examples:
2727            - [spline_draw1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/spline_draw1.py)
2728            - [colorlines.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorlines.py)
2729
2730                ![](https://vedo.embl.es/images/advanced/spline_draw.png)
2731
2732            - ..and many others!
2733        """
2734        from vtkmodules.util.misc import calldata_type
2735
2736        if not self.interactor:
2737            return 0
2738
2739        if vedo.settings.dry_run_mode >= 1:
2740            return 0
2741
2742        #########################################
2743        @calldata_type(vtki.VTK_INT)
2744        def _func_wrap(iren, ename, timerid=None):
2745            event = self.fill_event(ename=ename, enable_picking=enable_picking)
2746            event.timerid = timerid
2747            event.id = cid
2748            event.priority = priority
2749            self.last_event = event
2750            func(event)
2751
2752        #########################################
2753
2754        event_name = utils.get_vtk_name_event(event_name)
2755
2756        cid = self.interactor.AddObserver(event_name, _func_wrap, priority)
2757        # print(f"Registering event: {event_name} with id={cid}")
2758        return cid

Add a function to be executed while show() is active.

Return a unique id for the callback.

The callback function (see example below) exposes a dictionary with the following information:

  • name: event name,
  • id: event unique identifier,
  • priority: event priority (float),
  • interactor: the interactor object,
  • at: renderer nr. where the event occurred
  • keypress: key pressed as string
  • actor: object picked by the mouse
  • picked3d: point picked in world coordinates
  • picked2d: screen coords of the mouse pointer
  • delta2d: shift wrt previous position (to calculate speed, direction)
  • delta3d: ...same but in 3D world coords
  • angle2d: angle of mouse movement on screen
  • speed2d: speed of mouse movement on screen
  • speed3d: speed of picked point in world coordinates
  • isPoints: True if of class
  • isMesh: True if of class
  • isAssembly: True if of class
  • isVolume: True if of class Volume
  • isImage: True if of class

If enable_picking is False, no picking will be performed. This can be useful to avoid double picking when using buttons.

Frequently used events are:

  • KeyPress, KeyRelease: listen to keyboard events
  • LeftButtonPress, LeftButtonRelease: listen to mouse clicks
  • MiddleButtonPress, MiddleButtonRelease
  • RightButtonPress, RightButtonRelease
  • MouseMove: listen to mouse pointer changing position
  • MouseWheelForward, MouseWheelBackward
  • Enter, Leave: listen to mouse entering or leaving the window
  • Pick, StartPick, EndPick: listen to object picking
  • ResetCamera, ResetCameraClippingRange
  • Error, Warning
  • Char
  • Timer

Check the complete list of events here.

Example:
from vedo import *

def func(evt):
    # this function is called every time the mouse moves
    # (evt is a dotted dictionary)
    if not evt.object:
        return  # no hit, return
    print("point coords =", evt.picked3d)
    # print(evt) # full event dump

elli = Ellipsoid()
plt = Plotter(axes=1)
plt.add_callback('mouse hovering', func)
plt.show(elli)close()
Examples:
def remove_callback(self, cid: Union[int, str]) -> Self:
2760    def remove_callback(self, cid: Union[int, str]) -> Self:
2761        """
2762        Remove a callback function by its id
2763        or a whole category of callbacks by their name.
2764
2765        Arguments:
2766            cid : (int, str)
2767                Unique id of the callback.
2768                If an event name is passed all callbacks of that type are removed.
2769        """
2770        if self.interactor:
2771            if isinstance(cid, str):
2772                cid = utils.get_vtk_name_event(cid)
2773                self.interactor.RemoveObservers(cid)
2774            else:
2775                self.interactor.RemoveObserver(cid)
2776        return self

Remove a callback function by its id or a whole category of callbacks by their name.

Arguments:
  • cid : (int, str) Unique id of the callback. If an event name is passed all callbacks of that type are removed.
def remove_all_observers(self) -> Self:
2778    def remove_all_observers(self) -> Self:
2779        """
2780        Remove all observers.
2781
2782        Example:
2783        ```python
2784        from vedo import *
2785
2786        def kfunc(event):
2787            print("Key pressed:", event.keypress)
2788            if event.keypress == 'q':
2789                plt.close()
2790
2791        def rfunc(event):
2792            if event.isImage:
2793                printc("Right-clicked!", event)
2794                plt.render()
2795
2796        img = Image(dataurl+"images/embryo.jpg")
2797
2798        plt = Plotter(size=(1050, 600))
2799        plt.parallel_projection(True)
2800        plt.remove_all_observers()
2801        plt.add_callback("key press", kfunc)
2802        plt.add_callback("mouse right click", rfunc)
2803        plt.show("Right-Click Me! Press q to exit.", img)
2804        plt.close()
2805        ```
2806        """
2807        if self.interactor:
2808            self.interactor.RemoveAllObservers()
2809        return self

Remove all observers.

Example:

from vedo import *

def kfunc(event):
    print("Key pressed:", event.keypress)
    if event.keypress == 'q':
        plt.close()

def rfunc(event):
    if event.isImage:
        printc("Right-clicked!", event)
        plt.render()

img = Image(dataurl+"images/embryo.jpg")

plt = Plotter(size=(1050, 600))
plt.parallel_projection(True)
plt.remove_all_observers()
plt.add_callback("key press", kfunc)
plt.add_callback("mouse right click", rfunc)
plt.show("Right-Click Me! Press q to exit.", img)
plt.close()
def timer_callback(self, action: str, timer_id=None, dt=1, one_shot=False) -> int:
2811    def timer_callback(self, action: str, timer_id=None, dt=1, one_shot=False) -> int:
2812        """
2813        Start or stop an existing timer.
2814
2815        Arguments:
2816            action : (str)
2817                Either "create"/"start" or "destroy"/"stop"
2818            timer_id : (int)
2819                When stopping the timer, the ID of the timer as returned when created
2820            dt : (int)
2821                time in milliseconds between each repeated call
2822            one_shot : (bool)
2823                create a one shot timer of prescribed duration instead of a repeating one
2824
2825        Examples:
2826            - [timer_callback1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback1.py)
2827            - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
2828
2829            ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
2830        """
2831        if action in ("create", "start"):
2832
2833            if "Windows" in vedo.sys_platform:
2834                # otherwise on windows it gets stuck
2835                self.initialize_interactor()
2836
2837            if timer_id is not None:
2838                vedo.logger.warning("you set a timer_id but it will be ignored.")
2839            if one_shot:
2840                timer_id = self.interactor.CreateOneShotTimer(dt)
2841            else:
2842                timer_id = self.interactor.CreateRepeatingTimer(dt)
2843            return timer_id
2844
2845        elif action in ("destroy", "stop"):
2846            if timer_id is not None:
2847                self.interactor.DestroyTimer(timer_id)
2848            else:
2849                vedo.logger.warning("please set a timer_id. Cannot stop timer.")
2850        else:
2851            e = f"in timer_callback(). Cannot understand action: {action}\n"
2852            e += " allowed actions are: ['start', 'stop']. Skipped."
2853            vedo.logger.error(e)
2854        return timer_id

Start or stop an existing timer.

Arguments:
  • action : (str) Either "create"/"start" or "destroy"/"stop"
  • timer_id : (int) When stopping the timer, the ID of the timer as returned when created
  • dt : (int) time in milliseconds between each repeated call
  • one_shot : (bool) create a one shot timer of prescribed duration instead of a repeating one
Examples:

def add_observer(self, event_name: str, func: Callable, priority=0.0) -> int:
2856    def add_observer(self, event_name: str, func: Callable, priority=0.0) -> int:
2857        """
2858        Add a callback function that will be called when an event occurs.
2859        Consider using `add_callback()` instead.
2860        """
2861        if not self.interactor:
2862            return -1
2863        event_name = utils.get_vtk_name_event(event_name)
2864        idd = self.interactor.AddObserver(event_name, func, priority)
2865        return idd

Add a callback function that will be called when an event occurs. Consider using add_callback() instead.

def compute_world_coordinate( self, pos2d: MutableSequence[float], at=None, objs=(), bounds=(), offset=None, pixeltol=None, worldtol=None) -> numpy.ndarray:
2867    def compute_world_coordinate(
2868        self,
2869        pos2d: MutableSequence[float],
2870        at=None,
2871        objs=(),
2872        bounds=(),
2873        offset=None,
2874        pixeltol=None,
2875        worldtol=None,
2876    ) -> np.ndarray:
2877        """
2878        Transform a 2D point on the screen into a 3D point inside the rendering scene.
2879        If a set of meshes is passed then points are placed onto these.
2880
2881        Arguments:
2882            pos2d : (list)
2883                2D screen coordinates point.
2884            at : (int)
2885                renderer number.
2886            objs : (list)
2887                list of Mesh objects to project the point onto.
2888            bounds : (list)
2889                specify a bounding box as [xmin,xmax, ymin,ymax, zmin,zmax].
2890            offset : (float)
2891                specify an offset value.
2892            pixeltol : (int)
2893                screen tolerance in pixels.
2894            worldtol : (float)
2895                world coordinates tolerance.
2896
2897        Returns:
2898            numpy array, the point in 3D world coordinates.
2899
2900        Examples:
2901            - [cut_freehand.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/cut_freehand.py)
2902            - [mousehover3.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mousehover3.py)
2903
2904            ![](https://vedo.embl.es/images/basic/mousehover3.jpg)
2905        """
2906        if at is not None:
2907            renderer = self.renderers[at]
2908        else:
2909            renderer = self.renderer
2910
2911        if not objs:
2912            pp = vtki.vtkFocalPlanePointPlacer()
2913        else:
2914            pps = vtki.vtkPolygonalSurfacePointPlacer()
2915            for ob in objs:
2916                pps.AddProp(ob.actor)
2917            pp = pps # type: ignore
2918
2919        if len(bounds) == 6:
2920            pp.SetPointBounds(bounds)
2921        if pixeltol:
2922            pp.SetPixelTolerance(pixeltol)
2923        if worldtol:
2924            pp.SetWorldTolerance(worldtol)
2925        if offset:
2926            pp.SetOffset(offset)
2927
2928        worldPos: MutableSequence[float] = [0, 0, 0]
2929        worldOrient: MutableSequence[float] = [0, 0, 0, 0, 0, 0, 0, 0, 0]
2930        pp.ComputeWorldPosition(renderer, pos2d, worldPos, worldOrient)
2931        # validw = pp.ValidateWorldPosition(worldPos, worldOrient)
2932        # validd = pp.ValidateDisplayPosition(renderer, pos2d)
2933        return np.array(worldPos)

Transform a 2D point on the screen into a 3D point inside the rendering scene. If a set of meshes is passed then points are placed onto these.

Arguments:
  • pos2d : (list) 2D screen coordinates point.
  • at : (int) renderer number.
  • objs : (list) list of Mesh objects to project the point onto.
  • bounds : (list) specify a bounding box as [xmin,xmax, ymin,ymax, zmin,zmax].
  • offset : (float) specify an offset value.
  • pixeltol : (int) screen tolerance in pixels.
  • worldtol : (float) world coordinates tolerance.
Returns:

numpy array, the point in 3D world coordinates.

Examples:

def compute_screen_coordinates(self, obj, full_window=False) -> numpy.ndarray:
2935    def compute_screen_coordinates(self, obj, full_window=False) -> np.ndarray:
2936        """
2937        Given a 3D points in the current renderer (or full window),
2938        find the screen pixel coordinates.
2939
2940        Example:
2941            ```python
2942            from vedo import *
2943
2944            elli = Ellipsoid().point_size(5)
2945
2946            plt = Plotter()
2947            plt.show(elli, "Press q to continue and print the info")
2948
2949            xyscreen = plt.compute_screen_coordinates(elli)
2950            print('xyscreen coords:', xyscreen)
2951
2952            # simulate an event happening at one point
2953            event = plt.fill_event(pos=xyscreen[123])
2954            print(event)
2955            ```
2956        """
2957        try:
2958            obj = obj.coordinates
2959        except AttributeError:
2960            pass
2961
2962        if utils.is_sequence(obj):
2963            pts = obj
2964        p2d = []
2965        cs = vtki.vtkCoordinate()
2966        cs.SetCoordinateSystemToWorld()
2967        cs.SetViewport(self.renderer)
2968        for p in pts:
2969            cs.SetValue(p)
2970            if full_window:
2971                p2d.append(cs.GetComputedDisplayValue(self.renderer))
2972            else:
2973                p2d.append(cs.GetComputedViewportValue(self.renderer))
2974        return np.array(p2d, dtype=int)

Given a 3D points in the current renderer (or full window), find the screen pixel coordinates.

Example:
from vedo import *

elli = Ellipsoid().point_size(5)

plt = Plotter()
plt.show(elli, "Press q to continue and print the info")

xyscreen = plt.compute_screen_coordinates(elli)
print('xyscreen coords:', xyscreen)

# simulate an event happening at one point
event = plt.fill_event(pos=xyscreen[123])
print(event)
def pick_area(self, pos1, pos2, at=None) -> vedo.mesh.Mesh:
2976    def pick_area(self, pos1, pos2, at=None) -> "vedo.Mesh":
2977        """
2978        Pick all objects within a box defined by two corner points in 2D screen coordinates.
2979
2980        Returns a frustum Mesh that contains the visible field of view.
2981        This can be used to select objects in a scene or select vertices.
2982
2983        Example:
2984            ```python
2985            from vedo import *
2986
2987            settings.enable_default_mouse_callbacks = False
2988
2989            def mode_select(objs):
2990                print("Selected objects:", objs)
2991                d0 = mode.start_x, mode.start_y # display coords
2992                d1 = mode.end_x, mode.end_y
2993
2994                frustum = plt.pick_area(d0, d1)
2995                col = np.random.randint(0, 10)
2996                infru = frustum.inside_points(mesh)
2997                infru.point_size(10).color(col)
2998                plt.add(frustum, infru).render()
2999
3000            mesh = Mesh(dataurl+"cow.vtk")
3001            mesh.color("k5").linewidth(1)
3002
3003            mode = interactor_modes.BlenderStyle()
3004            mode.callback_select = mode_select
3005
3006            plt = Plotter().user_mode(mode)
3007            plt.show(mesh, axes=1)
3008            ```
3009        """
3010        if at is not None:
3011            ren = self.renderers[at]
3012        else:
3013            ren = self.renderer
3014        area_picker = vtki.vtkAreaPicker()
3015        area_picker.AreaPick(pos1[0], pos1[1], pos2[0], pos2[1], ren)
3016        planes = area_picker.GetFrustum()
3017
3018        fru = vtki.new("FrustumSource")
3019        fru.SetPlanes(planes)
3020        fru.ShowLinesOff()
3021        fru.Update()
3022
3023        afru = vedo.Mesh(fru.GetOutput())
3024        afru.alpha(0.1).lw(1).pickable(False)
3025        afru.name = "Frustum"
3026        return afru

Pick all objects within a box defined by two corner points in 2D screen coordinates.

Returns a frustum Mesh that contains the visible field of view. This can be used to select objects in a scene or select vertices.

Example:
from vedo import *

settings.enable_default_mouse_callbacks = False

def mode_select(objs):
    print("Selected objects:", objs)
    d0 = mode.start_x, mode.start_y # display coords
    d1 = mode.end_x, mode.end_y

    frustum = plt.pick_area(d0, d1)
    col = np.random.randint(0, 10)
    infru = frustum.inside_points(mesh)
    infru.point_size(10).color(col)
    plt.add(frustum, infru).render()

mesh = Mesh(dataurl+"cow.vtk")
mesh.color("k5").linewidth(1)

mode = interactor_modes.BlenderStyle()
mode.callback_select = mode_select

plt = Plotter().user_mode(mode)
plt.show(mesh, axes=1)
def show( self, *objects, at=None, axes=None, resetcam=None, zoom=False, interactive=None, viewup='', azimuth=0.0, elevation=0.0, roll=0.0, camera=None, mode=None, rate=None, bg=None, bg2=None, size=None, title=None, screenshot='') -> Any:
3149    def show(
3150        self,
3151        *objects,
3152        at=None,
3153        axes=None,
3154        resetcam=None,
3155        zoom=False,
3156        interactive=None,
3157        viewup="",
3158        azimuth=0.0,
3159        elevation=0.0,
3160        roll=0.0,
3161        camera=None,
3162        mode=None,
3163        rate=None,
3164        bg=None,
3165        bg2=None,
3166        size=None,
3167        title=None,
3168        screenshot="",
3169    ) -> Any:
3170        """
3171        Render a list of objects.
3172
3173        Arguments:
3174            at : (int)
3175                number of the renderer to plot to, in case of more than one exists
3176
3177            axes : (int)
3178                axis type-1 can be fully customized by passing a dictionary.
3179                Check `addons.Axes()` for the full list of options.
3180                set the type of axes to be shown:
3181                - 0,  no axes
3182                - 1,  draw three gray grid walls
3183                - 2,  show cartesian axes from (0,0,0)
3184                - 3,  show positive range of cartesian axes from (0,0,0)
3185                - 4,  show a triad at bottom left
3186                - 5,  show a cube at bottom left
3187                - 6,  mark the corners of the bounding box
3188                - 7,  draw a 3D ruler at each side of the cartesian axes
3189                - 8,  show the `vtkCubeAxesActor` object
3190                - 9,  show the bounding box outLine
3191                - 10, show three circles representing the maximum bounding box
3192                - 11, show a large grid on the x-y plane
3193                - 12, show polar axes
3194                - 13, draw a simple ruler at the bottom of the window
3195
3196            azimuth/elevation/roll : (float)
3197                move camera accordingly the specified value
3198
3199            viewup: str, list
3200                either `['x', 'y', 'z']` or a vector to set vertical direction
3201
3202            resetcam : (bool)
3203                re-adjust camera position to fit objects
3204
3205            camera : (dict, vtkCamera)
3206                camera parameters can further be specified with a dictionary
3207                assigned to the `camera` keyword (E.g. `show(camera={'pos':(1,2,3), 'thickness':1000,})`):
3208                - pos, `(list)`,  the position of the camera in world coordinates
3209                - focal_point `(list)`, the focal point of the camera in world coordinates
3210                - viewup `(list)`, the view up direction for the camera
3211                - distance `(float)`, set the focal point to the specified distance from the camera position.
3212                - clipping_range `(float)`, distance of the near and far clipping planes along the direction of projection.
3213                - parallel_scale `(float)`, scaling used for a parallel projection, i.e. the height of the viewport
3214                in world-coordinate distances. The default is 1.
3215                Note that the "scale" parameter works as an "inverse scale", larger numbers produce smaller images.
3216                This method has no effect in perspective projection mode.
3217
3218                - thickness `(float)`, set the distance between clipping planes. This method adjusts the far clipping
3219                plane to be set a distance 'thickness' beyond the near clipping plane.
3220
3221                - view_angle `(float)`, the camera view angle, which is the angular height of the camera view
3222                measured in degrees. The default angle is 30 degrees.
3223                This method has no effect in parallel projection mode.
3224                The formula for setting the angle up for perfect perspective viewing is:
3225                angle = 2*atan((h/2)/d) where h is the height of the RenderWindow
3226                (measured by holding a ruler up to your screen) and d is the distance from your eyes to the screen.
3227
3228            interactive : (bool)
3229                pause and interact with window (True) or continue execution (False)
3230
3231            rate : (float)
3232                maximum rate of `show()` in Hertz
3233
3234            mode : (int, str)
3235                set the type of interaction:
3236                - 0 = TrackballCamera [default]
3237                - 1 = TrackballActor
3238                - 2 = JoystickCamera
3239                - 3 = JoystickActor
3240                - 4 = Flight
3241                - 5 = RubberBand2D
3242                - 6 = RubberBand3D
3243                - 7 = RubberBandZoom
3244                - 8 = Terrain
3245                - 9 = Unicam
3246                - 10 = Image
3247                - Check out `vedo.interaction_modes` for more options.
3248
3249            bg : (str, list)
3250                background color in RGB format, or string name
3251
3252            bg2 : (str, list)
3253                second background color to create a gradient background
3254
3255            size : (str, list)
3256                size of the window, e.g. size="fullscreen", or size=[600,400]
3257
3258            title : (str)
3259                window title text
3260
3261            screenshot : (str)
3262                save a screenshot of the window to file
3263        """
3264
3265        if vedo.settings.dry_run_mode >= 2:
3266            return self
3267
3268        if self.wx_widget:
3269            return self
3270
3271        if self.renderers:  # in case of notebooks
3272
3273            if at is None:
3274                at = self.renderers.index(self.renderer)
3275
3276            else:
3277
3278                if at >= len(self.renderers):
3279                    t = f"trying to show(at={at}) but only {len(self.renderers)} renderers exist"
3280                    vedo.logger.error(t)
3281                    return self
3282
3283                self.renderer = self.renderers[at]
3284
3285        if title is not None:
3286            self.title = title
3287
3288        if size is not None:
3289            self.size = size
3290            if self.size[0] == "f":  # full screen
3291                self.size = "fullscreen"
3292                self.window.SetFullScreen(True)
3293                self.window.BordersOn()
3294            else:
3295                self.window.SetSize(int(self.size[0]), int(self.size[1]))
3296
3297        if vedo.settings.default_backend == "vtk":
3298            if str(bg).endswith(".hdr"):
3299                self._add_skybox(bg)
3300            else:
3301                if bg is not None:
3302                    self.backgrcol = vedo.get_color(bg)
3303                    self.renderer.SetBackground(self.backgrcol)
3304                if bg2 is not None:
3305                    self.renderer.GradientBackgroundOn()
3306                    self.renderer.SetBackground2(vedo.get_color(bg2))
3307
3308        if axes is not None:
3309            if isinstance(axes, vedo.Assembly):  # user passing show(..., axes=myaxes)
3310                objects = list(objects)
3311                objects.append(axes)  # move it into the list of normal things to show
3312                axes = 0
3313            self.axes = axes
3314
3315        if interactive is not None:
3316            self._interactive = interactive
3317        if self.offscreen:
3318            self._interactive = False
3319
3320        # camera stuff
3321        if resetcam is not None:
3322            self.resetcam = resetcam
3323
3324        if camera is not None:
3325            self.resetcam = False
3326            viewup = ""
3327            if isinstance(camera, vtki.vtkCamera):
3328                cameracopy = vtki.vtkCamera()
3329                cameracopy.DeepCopy(camera)
3330                self.camera = cameracopy
3331            else:
3332                self.camera = utils.camera_from_dict(camera)
3333
3334        self.add(objects)
3335
3336        # Backend ###############################################################
3337        if vedo.settings.default_backend in ["k3d", "panel"]:
3338            return backends.get_notebook_backend(self.objects)
3339        #########################################################################
3340
3341        for ia in utils.flatten(objects):
3342            try:
3343                # fix gray color labels and title to white or black
3344                ltc = np.array(ia.scalarbar.GetLabelTextProperty().GetColor())
3345                if np.linalg.norm(ltc - (0.5, 0.5, 0.5)) / 3 < 0.05:
3346                    c = (0.9, 0.9, 0.9)
3347                    if np.sum(self.renderer.GetBackground()) > 1.5:
3348                        c = (0.1, 0.1, 0.1)
3349                    ia.scalarbar.GetLabelTextProperty().SetColor(c)
3350                    ia.scalarbar.GetTitleTextProperty().SetColor(c)
3351            except AttributeError:
3352                pass
3353
3354        if self.sharecam:
3355            for r in self.renderers:
3356                r.SetActiveCamera(self.camera)
3357
3358        if self.axes is not None:
3359            if viewup != "2d" or self.axes in [1, 8] or isinstance(self.axes, dict):
3360                bns = self.renderer.ComputeVisiblePropBounds()
3361                addons.add_global_axes(self.axes, bounds=bns)
3362
3363        # Backend ###############################################################
3364        if vedo.settings.default_backend in ["ipyvtk", "trame"]:
3365            return backends.get_notebook_backend()
3366        #########################################################################
3367
3368        if self.resetcam:
3369            self.renderer.ResetCamera()
3370
3371        if len(self.renderers) > 1:
3372            self.add_renderer_frame()
3373
3374        if vedo.settings.default_backend == "2d" and not zoom:
3375            zoom = "tightest"
3376
3377        if zoom:
3378            if zoom == "tight":
3379                self.reset_camera(tight=0.04)
3380            elif zoom == "tightest":
3381                self.reset_camera(tight=0.0001)
3382            else:
3383                self.camera.Zoom(zoom)
3384        if elevation:
3385            self.camera.Elevation(elevation)
3386        if azimuth:
3387            self.camera.Azimuth(azimuth)
3388        if roll:
3389            self.camera.Roll(roll)
3390
3391        if len(viewup) > 0:
3392            b = self.renderer.ComputeVisiblePropBounds()
3393            cm = np.array([(b[1] + b[0])/2, (b[3] + b[2])/2, (b[5] + b[4])/2])
3394            sz = np.array([(b[1] - b[0]), (b[3] - b[2]), (b[5] - b[4])])
3395            if viewup == "x":
3396                sz = np.linalg.norm(sz)
3397                self.camera.SetViewUp([1, 0, 0])
3398                self.camera.SetPosition(cm + sz)
3399            elif viewup == "y":
3400                sz = np.linalg.norm(sz)
3401                self.camera.SetViewUp([0, 1, 0])
3402                self.camera.SetPosition(cm + sz)
3403            elif viewup == "z":
3404                sz = np.array([(b[1]-b[0])*0.7, -(b[3]-b[2])*1.0, (b[5]-b[4])*1.2])
3405                self.camera.SetViewUp([0, 0, 1])
3406                self.camera.SetPosition(cm + 2 * sz)
3407            elif utils.is_sequence(viewup):
3408                sz = np.linalg.norm(sz)
3409                self.camera.SetViewUp(viewup)
3410                cpos = np.cross([0, 1, 0], viewup)
3411                self.camera.SetPosition(cm - 2 * sz * cpos)
3412
3413        self.renderer.ResetCameraClippingRange()
3414
3415        self.initialize_interactor()
3416
3417        if vedo.settings.immediate_rendering:
3418            self.window.Render()  ##################### <-------------- Render
3419
3420        if self.interactor:  # can be offscreen or not the vtk backend..
3421
3422            self.window.SetWindowName(self.title)
3423
3424            # pic = vedo.Image(vedo.dataurl+'images/vtk_logo.png')
3425            # pic = vedo.Image('/home/musy/Downloads/icons8-3d-96.png')
3426            # print(pic.dataset)# Array 0 name PNGImage
3427            # self.window.SetIcon(pic.dataset)
3428
3429            try:
3430                # Needs "pip install pyobjc" on Mac OSX
3431                if (
3432                    self._cocoa_initialized is False
3433                    and "Darwin" in vedo.sys_platform
3434                    and not self.offscreen
3435                ):
3436                    self._cocoa_initialized = True
3437                    from Cocoa import NSRunningApplication, NSApplicationActivateIgnoringOtherApps # type: ignore
3438                    pid = os.getpid()
3439                    x = NSRunningApplication.runningApplicationWithProcessIdentifier_(int(pid))
3440                    x.activateWithOptions_(NSApplicationActivateIgnoringOtherApps)
3441            except:
3442                # vedo.logger.debug("On Mac OSX try: pip install pyobjc")
3443                pass
3444
3445            # Set the interaction style
3446            if mode is not None:
3447                self.user_mode(mode)
3448            if self.qt_widget and mode is None:
3449                self.user_mode(0)
3450
3451            if screenshot:
3452                self.screenshot(screenshot)
3453
3454            if self._interactive:
3455                self.interactor.Start()
3456                if self._must_close_now:
3457                    self.interactor.GetRenderWindow().Finalize()
3458                    self.interactor.TerminateApp()
3459                    self.camera = None
3460                    self.renderer = None
3461                    self.renderers = []
3462                    self.window = None
3463                    self.interactor = None
3464                return self
3465
3466            if rate:
3467                if self.clock is None:  # set clock and limit rate
3468                    self._clockt0 = time.time()
3469                    self.clock = 0.0
3470                else:
3471                    t = time.time() - self._clockt0
3472                    elapsed = t - self.clock
3473                    mint = 1.0 / rate
3474                    if elapsed < mint:
3475                        time.sleep(mint - elapsed)
3476                    self.clock = time.time() - self._clockt0
3477
3478        # 2d ####################################################################
3479        if vedo.settings.default_backend in ["2d"]:
3480            return backends.get_notebook_backend()
3481        #########################################################################
3482
3483        return self

Render a list of objects.

Arguments:
  • at : (int) number of the renderer to plot to, in case of more than one exists
  • axes : (int) axis type-1 can be fully customized by passing a dictionary. Check addons.Axes() for the full list of options. set the type of axes to be shown:
    • 0, no axes
    • 1, draw three gray grid walls
    • 2, show cartesian axes from (0,0,0)
    • 3, show positive range of cartesian axes from (0,0,0)
    • 4, show a triad at bottom left
    • 5, show a cube at bottom left
    • 6, mark the corners of the bounding box
    • 7, draw a 3D ruler at each side of the cartesian axes
    • 8, show the vtkCubeAxesActor object
    • 9, show the bounding box outLine
    • 10, show three circles representing the maximum bounding box
    • 11, show a large grid on the x-y plane
    • 12, show polar axes
    • 13, draw a simple ruler at the bottom of the window
  • azimuth/elevation/roll : (float) move camera accordingly the specified value
  • viewup: str, list either ['x', 'y', 'z'] or a vector to set vertical direction
  • resetcam : (bool) re-adjust camera position to fit objects
  • camera : (dict, vtkCamera) camera parameters can further be specified with a dictionary assigned to the camera keyword (E.g. show(camera={'pos':(1,2,3), 'thickness':1000,})):

    • pos, (list), the position of the camera in world coordinates
    • focal_point (list), the focal point of the camera in world coordinates
    • viewup (list), the view up direction for the camera
    • distance (float), set the focal point to the specified distance from the camera position.
    • clipping_range (float), distance of the near and far clipping planes along the direction of projection.
    • parallel_scale (float), scaling used for a parallel projection, i.e. the height of the viewport in world-coordinate distances. The default is 1. Note that the "scale" parameter works as an "inverse scale", larger numbers produce smaller images. This method has no effect in perspective projection mode.

    • thickness (float), set the distance between clipping planes. This method adjusts the far clipping plane to be set a distance 'thickness' beyond the near clipping plane.

    • view_angle (float), the camera view angle, which is the angular height of the camera view measured in degrees. The default angle is 30 degrees. This method has no effect in parallel projection mode. The formula for setting the angle up for perfect perspective viewing is: angle = 2*atan((h/2)/d) where h is the height of the RenderWindow (measured by holding a ruler up to your screen) and d is the distance from your eyes to the screen.

  • interactive : (bool) pause and interact with window (True) or continue execution (False)
  • rate : (float) maximum rate of show() in Hertz
  • mode : (int, str) set the type of interaction:
    • 0 = TrackballCamera [default]
    • 1 = TrackballActor
    • 2 = JoystickCamera
    • 3 = JoystickActor
    • 4 = Flight
    • 5 = RubberBand2D
    • 6 = RubberBand3D
    • 7 = RubberBandZoom
    • 8 = Terrain
    • 9 = Unicam
    • 10 = Image
    • Check out vedo.interaction_modes for more options.
  • bg : (str, list) background color in RGB format, or string name
  • bg2 : (str, list) second background color to create a gradient background
  • size : (str, list) size of the window, e.g. size="fullscreen", or size=[600,400]
  • title : (str) window title text
  • screenshot : (str) save a screenshot of the window to file
def add_inset( self, *objects, **options) -> Optional[vtkmodules.vtkInteractionWidgets.vtkOrientationMarkerWidget]:
3486    def add_inset(self, *objects, **options) -> Union[vtki.vtkOrientationMarkerWidget, None]:
3487        """Add a draggable inset space into a renderer.
3488
3489        Arguments:
3490            at : (int)
3491                specify the renderer number
3492            pos : (list)
3493                icon position in the range [1-4] indicating one of the 4 corners,
3494                or it can be a tuple (x,y) as a fraction of the renderer size.
3495            size : (float)
3496                size of the square inset
3497            draggable : (bool)
3498                if True the subrenderer space can be dragged around
3499            c : (color)
3500                color of the inset frame when dragged
3501
3502        Examples:
3503            - [inset.py](https://github.com/marcomusy/vedo/tree/master/examples/other/inset.py)
3504
3505            ![](https://user-images.githubusercontent.com/32848391/56758560-3c3f1300-6797-11e9-9b33-49f5a4876039.jpg)
3506        """
3507        if not self.interactor:
3508            return None
3509
3510        if not self.renderer:
3511            vedo.logger.warning("call add_inset() only after first rendering of the scene.")
3512            return None
3513
3514        options = dict(options)
3515        pos = options.pop("pos", 0)
3516        size = options.pop("size", 0.1)
3517        c = options.pop("c", "lb")
3518        at = options.pop("at", None)
3519        draggable = options.pop("draggable", True)
3520
3521        r, g, b = vedo.get_color(c)
3522        widget = vtki.vtkOrientationMarkerWidget()
3523        widget.SetOutlineColor(r, g, b)
3524        if len(objects) == 1:
3525            widget.SetOrientationMarker(objects[0].actor)
3526        else:
3527            widget.SetOrientationMarker(vedo.Assembly(objects))
3528
3529        widget.SetInteractor(self.interactor)
3530
3531        if utils.is_sequence(pos):
3532            widget.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
3533        else:
3534            if pos < 2:
3535                widget.SetViewport(0, 1 - 2 * size, size * 2, 1)
3536            elif pos == 2:
3537                widget.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
3538            elif pos == 3:
3539                widget.SetViewport(0, 0, size * 2, size * 2)
3540            elif pos == 4:
3541                widget.SetViewport(1 - 2 * size, 0, 1, size * 2)
3542        widget.EnabledOn()
3543        widget.SetInteractive(draggable)
3544        if at is not None and at < len(self.renderers):
3545            widget.SetCurrentRenderer(self.renderers[at])
3546        else:
3547            widget.SetCurrentRenderer(self.renderer)
3548        self.widgets.append(widget)
3549        return widget

Add a draggable inset space into a renderer.

Arguments:
  • at : (int) specify the renderer number
  • pos : (list) icon position in the range [1-4] indicating one of the 4 corners, or it can be a tuple (x,y) as a fraction of the renderer size.
  • size : (float) size of the square inset
  • draggable : (bool) if True the subrenderer space can be dragged around
  • c : (color) color of the inset frame when dragged
Examples:

def clear(self, at=None, deep=False) -> Self:
3551    def clear(self, at=None, deep=False) -> Self:
3552        """Clear the scene from all meshes and volumes."""
3553        if at is not None:
3554            renderer = self.renderers[at]
3555        else:
3556            renderer = self.renderer
3557        if not renderer:
3558            return self
3559
3560        if deep:
3561            renderer.RemoveAllViewProps()
3562        else:
3563            for ob in set(
3564                self.get_meshes()
3565                + self.get_volumes()
3566                + self.objects
3567                + self.axes_instances
3568            ):
3569                if isinstance(ob, vedo.shapes.Text2D):
3570                    continue
3571                self.remove(ob)
3572                try:
3573                    if ob.scalarbar:
3574                        self.remove(ob.scalarbar)
3575                except AttributeError:
3576                    pass
3577        return self

Clear the scene from all meshes and volumes.

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

Break window interaction and return to the python execution flow

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

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

def user_mode(self, mode) -> Self:
3595    def user_mode(self, mode) -> Self:
3596        """
3597        Modify the user interaction mode.
3598
3599        Examples:
3600            ```python
3601            from vedo import *
3602            mode = interactor_modes.MousePan()
3603            mesh = Mesh(dataurl+"cow.vtk")
3604            plt = Plotter().user_mode(mode)
3605            plt.show(mesh, axes=1)
3606           ```
3607        See also:
3608        [VTK interactor styles](https://vtk.org/doc/nightly/html/classvtkInteractorStyle.html)
3609        """
3610        if not self.interactor:
3611            return self
3612        
3613        curr_style = self.interactor.GetInteractorStyle().GetClassName()
3614        # print("Current style:", curr_style)
3615        if curr_style.endswith("Actor"):
3616            self.check_actors_trasform()
3617
3618        if isinstance(mode, (str, int)):
3619            # Set the style of interaction
3620            # see https://vtk.org/doc/nightly/html/classvtkInteractorStyle.html
3621            if   mode in (0, "TrackballCamera"):
3622                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTrackballCamera"))
3623                self.interactor.RemoveObservers("CharEvent")
3624            elif mode in (1, "TrackballActor"):
3625                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTrackballActor"))
3626            elif mode in (2, "JoystickCamera"):
3627                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleJoystickCamera"))
3628            elif mode in (3, "JoystickActor"):
3629                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleJoystickActor"))
3630            elif mode in (4, "Flight"):
3631                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleFlight"))
3632            elif mode in (5, "RubberBand2D"):
3633                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBand2D"))
3634            elif mode in (6, "RubberBand3D"):
3635                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBand3D"))
3636            elif mode in (7, "RubberBandZoom"):
3637                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleRubberBandZoom"))
3638            elif mode in (8, "Terrain"):
3639                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleTerrain"))
3640            elif mode in (9, "Unicam"):
3641                self.interactor.SetInteractorStyle(vtki.new("InteractorStyleUnicam"))
3642            elif mode in (10, "Image", "image", "2d"):
3643                astyle = vtki.new("InteractorStyleImage")
3644                astyle.SetInteractionModeToImage3D()
3645                self.interactor.SetInteractorStyle(astyle)
3646            else:
3647                vedo.logger.warning(f"Unknown interaction mode: {mode}")
3648
3649        elif isinstance(mode, vtki.vtkInteractorStyleUser):
3650            # set a custom interactor style
3651            if hasattr(mode, "interactor"):
3652                mode.interactor = self.interactor
3653                mode.renderer = self.renderer # type: ignore
3654            mode.SetInteractor(self.interactor)
3655            mode.SetDefaultRenderer(self.renderer)
3656            self.interactor.SetInteractorStyle(mode)
3657
3658        return self

Modify the user interaction mode.

Examples:
 from vedo import *
 mode = interactor_modes.MousePan()
 mesh = Mesh(dataurl+"cow.vtk")
 plt = Plotter().user_mode(mode)
 plt.show(mesh, axes=1)

See also: VTK interactor styles

def close(self) -> Self:
3660    def close(self) -> Self:
3661        """Close the plotter."""
3662        # https://examples.vtk.org/site/Cxx/Visualization/CloseWindow/
3663        vedo.last_figure = None
3664        self.last_event = None
3665        self.sliders = []
3666        self.buttons = []
3667        self.widgets = []
3668        self.hover_legends = []
3669        self.background_renderer = None
3670        self._extralight = None
3671
3672        self.hint_widget = None
3673        self.cutter_widget = None
3674
3675        if vedo.settings.dry_run_mode >= 2:
3676            return self
3677        
3678        if not hasattr(self, "window"):
3679            return self
3680        if not self.window:
3681            return self
3682        if not hasattr(self, "interactor"):
3683            return self
3684        if not self.interactor:
3685            return self
3686
3687        ###################################################
3688        try:
3689            if "Darwin" in vedo.sys_platform:
3690                self.interactor.ProcessEvents()
3691        except:
3692            pass
3693
3694        self._must_close_now = True
3695
3696        if vedo.plotter_instance == self:
3697            vedo.plotter_instance = None
3698
3699        if self.interactor and self._interactive:
3700            self.break_interaction()
3701        elif self._must_close_now:
3702            # dont call ExitCallback here
3703            if self.interactor:
3704                self.break_interaction()
3705                self.interactor.GetRenderWindow().Finalize()
3706                self.interactor.TerminateApp()
3707            self.camera = None
3708            self.renderer = None
3709            self.renderers = []
3710            self.window = None
3711            self.interactor = None
3712        return self

Close the plotter.

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

Return the current active camera.

def screenshot(self, filename='screenshot.png', scale=1, asarray=False) -> Any:
3727    def screenshot(self, filename="screenshot.png", scale=1, asarray=False) -> Any:
3728        """
3729        Take a screenshot of the Plotter window.
3730
3731        Arguments:
3732            scale : (int)
3733                set image magnification as an integer multiplicating factor
3734            asarray : (bool)
3735                return a numpy array of the image instead of writing a file
3736
3737        Warning:
3738            If you get black screenshots try to set `interactive=False` in `show()`
3739            then call `screenshot()` and `plt.interactive()` afterwards.
3740
3741        Example:
3742            ```py
3743            from vedo import *
3744            sphere = Sphere().linewidth(1)
3745            plt = show(sphere, interactive=False)
3746            plt.screenshot('image.png')
3747            plt.interactive()
3748            plt.close()
3749            ```
3750
3751        Example:
3752            ```py
3753            from vedo import *
3754            sphere = Sphere().linewidth(1)
3755            plt = show(sphere, interactive=False)
3756            plt.screenshot('anotherimage.png')
3757            plt.interactive()
3758            plt.close()
3759            ```
3760        """
3761        return vedo.file_io.screenshot(filename, scale, asarray)

Take a screenshot of the Plotter window.

Arguments:
  • scale : (int) set image magnification as an integer multiplicating factor
  • asarray : (bool) return a numpy array of the image instead of writing a file
Warning:

If you get black screenshots try to set interactive=False in show() then call screenshot() and plt.interactive() afterwards.

Example:
from vedo import *
sphere = Sphere().linewidth(1)
plt = show(sphere, interactive=False)
plt.screenshot('image.png')
plt.interactive()
plt.close()
Example:
from vedo import *
sphere = Sphere().linewidth(1)
plt = show(sphere, interactive=False)
plt.screenshot('anotherimage.png')
plt.interactive()
plt.close()
def toimage(self, scale=1) -> vedo.image.Image:
3763    def toimage(self, scale=1) -> "vedo.image.Image":
3764        """
3765        Generate a `Image` object from the current rendering window.
3766
3767        Arguments:
3768            scale : (int)
3769                set image magnification as an integer multiplicating factor
3770        """
3771        if vedo.settings.screeshot_large_image:
3772            w2if = vtki.new("RenderLargeImage")
3773            w2if.SetInput(self.renderer)
3774            w2if.SetMagnification(scale)
3775        else:
3776            w2if = vtki.new("WindowToImageFilter")
3777            w2if.SetInput(self.window)
3778            if hasattr(w2if, "SetScale"):
3779                w2if.SetScale(scale, scale)
3780            if vedo.settings.screenshot_transparent_background:
3781                w2if.SetInputBufferTypeToRGBA()
3782            w2if.ReadFrontBufferOff()  # read from the back buffer
3783        w2if.Update()
3784        return vedo.image.Image(w2if.GetOutput())

Generate a Image object from the current rendering window.

Arguments:
  • scale : (int) set image magnification as an integer multiplicating factor
def export(self, filename='scene.npz', binary=False) -> Self:
3786    def export(self, filename="scene.npz", binary=False) -> Self:
3787        """
3788        Export scene to file to HTML, X3D or Numpy file.
3789
3790        Examples:
3791            - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
3792            - [export_numpy.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_numpy.py)
3793        """
3794        vedo.file_io.export_window(filename, binary=binary)
3795        return self

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

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

Pick color of specific (x,y) pixel on the screen.

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

Create on the fly an instance of class Plotter and show the object(s) provided.

Arguments:
  • at : (int) number of the renderer to plot to, in case of more than one exists
  • shape : (list, str) Number of sub-render windows inside of the main window. E.g.: specify two across with shape=(2,1) and a two by two grid with shape=(2, 2). By default there is only one renderer.

    Can also accept a shape as string descriptor. E.g.:

    • shape="3|1" means 3 plots on the left and 1 on the right,
    • shape="4/2" means 4 plots on top of 2 at bottom.
  • N : (int) number of desired sub-render windows arranged automatically in a grid
  • pos : (list) position coordinates of the top-left corner of the rendering window on the screen
  • size : (list) size of the rendering window
  • screensize : (list) physical size of the monitor screen
  • title : (str) window title
  • bg : (color) background color or specify jpg image file name with path
  • bg2 : (color) background color of a gradient towards the top
  • axes : (int) set the type of axes to be shown:

    • 0, no axes
    • 1, draw three gray grid walls
    • 2, show cartesian axes from (0,0,0)
    • 3, show positive range of cartesian axes from (0,0,0)
    • 4, show a triad at bottom left
    • 5, show a cube at bottom left
    • 6, mark the corners of the bounding box
    • 7, draw a 3D ruler at each side of the cartesian axes
    • 8, show the vtkCubeAxesActor object
    • 9, show the bounding box outLine
    • 10, show three circles representing the maximum bounding box
    • 11, show a large grid on the x-y plane
    • 12, show polar axes
    • 13, draw a simple ruler at the bottom of the window
    • 14: draw a CameraOrientationWidget

    Axis type-1 can be fully customized by passing a dictionary. Check vedo.addons.Axes() for the full list of options.

  • azimuth/elevation/roll : (float) move camera accordingly the specified value
  • viewup : (str, list) either ['x', 'y', 'z'] or a vector to set vertical direction
  • resetcam : (bool) re-adjust camera position to fit objects
  • camera : (dict, vtkCamera) camera parameters can further be specified with a dictionary assigned to the camera keyword (E.g. show(camera={'pos':(1,2,3), 'thickness':1000,})):
    • pos (list), the position of the camera in world coordinates
    • focal_point (list), the focal point of the camera in world coordinates
    • viewup (list), the view up direction for the camera
    • distance (float), set the focal point to the specified distance from the camera position.
    • clipping_range (float), distance of the near and far clipping planes along the direction of projection.
    • parallel_scale (float), scaling used for a parallel projection, i.e. the height of the viewport in world-coordinate distances. The default is 1. Note that the "scale" parameter works as an "inverse scale", larger numbers produce smaller images. This method has no effect in perspective projection mode.
    • thickness (float), set the distance between clipping planes. This method adjusts the far clipping plane to be set a distance 'thickness' beyond the near clipping plane.
    • view_angle (float), the camera view angle, which is the angular height of the camera view measured in degrees. The default angle is 30 degrees. This method has no effect in parallel projection mode. The formula for setting the angle up for perfect perspective viewing is: angle = 2*atan((h/2)/d) where h is the height of the RenderWindow (measured by holding a ruler up to your screen) and d is the distance from your eyes to the screen.
  • interactive : (bool) pause and interact with window (True) or continue execution (False)
  • rate : (float) maximum rate of show() in Hertz
  • mode : (int, str) set the type of interaction:
    • 0 = TrackballCamera [default]
    • 1 = TrackballActor
    • 2 = JoystickCamera
    • 3 = JoystickActor
    • 4 = Flight
    • 5 = RubberBand2D
    • 6 = RubberBand3D
    • 7 = RubberBandZoom
    • 8 = Terrain
    • 9 = Unicam
    • 10 = Image
  • new : (bool) if set to True, a call to show will instantiate a new Plotter object (a new window) instead of reusing the first created. If new is True, but the existing plotter was instantiated with a different argument for offscreen, new is ignored and a new Plotter is created anyway.
def close() -> None:
367def close() -> None:
368    """Close the last created Plotter instance if it exists."""
369    if not vedo.plotter_instance:
370        return
371    vedo.plotter_instance.close()
372    return

Close the last created Plotter instance if it exists.