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

Print information about the current instance.

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

Initialize the interactor if not already initialized.

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

Process all pending events.

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

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

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

Return the list of actors.

def remove_lights(self) -> Plotter:
1083    def remove_lights(self) -> "Plotter":
1084        """Remove all the present lights in the current renderer."""
1085        if self.renderer:
1086            self.renderer.RemoveAllLights()
1087        return self

Remove all the present lights in the current renderer.

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

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

def interactive(self) -> Plotter:
1125    def interactive(self) -> "Plotter":
1126        """
1127        Start window interaction.
1128        Analogous to `show(..., interactive=True)`.
1129        """
1130        if vedo.settings.dry_run_mode >= 1:
1131            return self
1132        self.initialize_interactor()
1133        if self.interactor:
1134            # print("self.interactor.Start()")
1135            self.interactor.Start()
1136            # print("self.interactor.Start() done")
1137            if self._must_close_now:
1138                # print("self.interactor.TerminateApp()")
1139                self.interactor.GetRenderWindow().Finalize()
1140                self.interactor.TerminateApp()
1141                self.interactor = None
1142                self.window = None
1143                self.renderer = None
1144                self.renderers = []
1145                self.camera = None
1146        return self

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

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

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

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

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

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

Remove hidden lines when in wireframe mode.

def fov(self, angle: float) -> Plotter:
1637    def fov(self, angle: float) -> "Plotter":
1638        """
1639        Set the field of view angle for the camera.
1640        This is the angle of the camera frustum in the horizontal direction.
1641        High values will result in a wide-angle lens (fish-eye effect),
1642        and low values will result in a telephoto lens.
1643
1644        Default value is 30 degrees.
1645        """
1646        self.renderer.GetActiveCamera().UseHorizontalViewAngleOn()
1647        self.renderer.GetActiveCamera().SetViewAngle(angle)
1648        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) -> Plotter:
1650    def zoom(self, zoom: float) -> "Plotter":
1651        """Apply a zooming factor for the current camera view"""
1652        self.renderer.GetActiveCamera().Zoom(zoom)
1653        return self

Apply a zooming factor for the current camera view

def azimuth(self, angle: float) -> Plotter:
1655    def azimuth(self, angle: float) -> "Plotter":
1656        """Rotate camera around the view up vector."""
1657        self.renderer.GetActiveCamera().Azimuth(angle)
1658        return self

Rotate camera around the view up vector.

def elevation(self, angle: float) -> Plotter:
1660    def elevation(self, angle: float) -> "Plotter":
1661        """Rotate the camera around the cross product of the negative
1662        of the direction of projection and the view up vector."""
1663        self.renderer.GetActiveCamera().Elevation(angle)
1664        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) -> Plotter:
1666    def roll(self, angle: float) -> "Plotter":
1667        """Roll the camera about the direction of projection."""
1668        self.renderer.GetActiveCamera().Roll(angle)
1669        return self

Roll the camera about the direction of projection.

def dolly(self, value: float) -> Plotter:
1671    def dolly(self, value: float) -> "Plotter":
1672        """Move the camera towards (value>0) or away from (value<0) the focal point."""
1673        self.renderer.GetActiveCamera().Dolly(value)
1674        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:
1677    def add_slider(
1678        self,
1679        sliderfunc,
1680        xmin,
1681        xmax,
1682        value=None,
1683        pos=4,
1684        title="",
1685        font="Calco",
1686        title_size=1,
1687        c=None,
1688        alpha=1,
1689        show_value=True,
1690        delayed=False,
1691        **options,
1692    ) -> "vedo.addons.Slider2D":
1693        """
1694        Add a `vedo.addons.Slider2D` which can call an external custom function.
1695
1696        Arguments:
1697            sliderfunc : (Callable)
1698                external function to be called by the widget
1699            xmin : (float)
1700                lower value of the slider
1701            xmax : (float)
1702                upper value
1703            value : (float)
1704                current value
1705            pos : (list, str)
1706                position corner number: horizontal [1-5] or vertical [11-15]
1707                it can also be specified by corners coordinates [(x1,y1), (x2,y2)]
1708                and also by a string descriptor (eg. "bottom-left")
1709            title : (str)
1710                title text
1711            font : (str)
1712                title font face. Check [available fonts here](https://vedo.embl.es/fonts).
1713            title_size : (float)
1714                title text scale [1.0]
1715            show_value : (bool)
1716                if True current value is shown
1717            delayed : (bool)
1718                if True the callback is delayed until when the mouse button is released
1719            alpha : (float)
1720                opacity of the scalar bar texts
1721            slider_length : (float)
1722                slider length
1723            slider_width : (float)
1724                slider width
1725            end_cap_length : (float)
1726                length of the end cap
1727            end_cap_width : (float)
1728                width of the end cap
1729            tube_width : (float)
1730                width of the tube
1731            title_height : (float)
1732                width of the title
1733            tformat : (str)
1734                format of the title
1735
1736        Examples:
1737            - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py)
1738            - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py)
1739
1740            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
1741        """
1742        if c is None:  # automatic black or white
1743            c = (0.8, 0.8, 0.8)
1744            if np.sum(vedo.get_color(self.backgrcol)) > 1.5:
1745                c = (0.2, 0.2, 0.2)
1746        else:
1747            c = vedo.get_color(c)
1748
1749        slider2d = addons.Slider2D(
1750            sliderfunc,
1751            xmin,
1752            xmax,
1753            value,
1754            pos,
1755            title,
1756            font,
1757            title_size,
1758            c,
1759            alpha,
1760            show_value,
1761            delayed,
1762            **options,
1763        )
1764
1765        if self.renderer:
1766            slider2d.renderer = self.renderer
1767            if self.interactor:
1768                slider2d.interactor = self.interactor
1769                slider2d.on()
1770                self.sliders.append([slider2d, sliderfunc])
1771        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:
1773    def add_slider3d(
1774        self,
1775        sliderfunc,
1776        pos1,
1777        pos2,
1778        xmin,
1779        xmax,
1780        value=None,
1781        s=0.03,
1782        t=1,
1783        title="",
1784        rotation=0.0,
1785        c=None,
1786        show_value=True,
1787    ) -> "vedo.addons.Slider3D":
1788        """
1789        Add a 3D slider widget which can call an external custom function.
1790
1791        Arguments:
1792            sliderfunc : (function)
1793                external function to be called by the widget
1794            pos1 : (list)
1795                first position 3D coordinates
1796            pos2 : (list)
1797                second position coordinates
1798            xmin : (float)
1799                lower value
1800            xmax : (float)
1801                upper value
1802            value : (float)
1803                initial value
1804            s : (float)
1805                label scaling factor
1806            t : (float)
1807                tube scaling factor
1808            title : (str)
1809                title text
1810            c : (color)
1811                slider color
1812            rotation : (float)
1813                title rotation around slider axis
1814            show_value : (bool)
1815                if True current value is shown
1816
1817        Examples:
1818            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
1819
1820            ![](https://user-images.githubusercontent.com/32848391/52859555-4efcf200-312d-11e9-9290-6988c8295163.png)
1821        """
1822        if c is None:  # automatic black or white
1823            c = (0.8, 0.8, 0.8)
1824            if np.sum(vedo.get_color(self.backgrcol)) > 1.5:
1825                c = (0.2, 0.2, 0.2)
1826        else:
1827            c = vedo.get_color(c)
1828
1829        slider3d = addons.Slider3D(
1830            sliderfunc,
1831            pos1,
1832            pos2,
1833            xmin,
1834            xmax,
1835            value,
1836            s,
1837            t,
1838            title,
1839            rotation,
1840            c,
1841            show_value,
1842        )
1843        slider3d.renderer = self.renderer
1844        slider3d.interactor = self.interactor
1845        slider3d.on()
1846        self.sliders.append([slider3d, sliderfunc])
1847        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]:
1849    def add_button(
1850        self,
1851        fnc=None,
1852        states=("On", "Off"),
1853        c=("w", "w"),
1854        bc=("green4", "red4"),
1855        pos=(0.7, 0.1),
1856        size=24,
1857        font="Courier",
1858        bold=True,
1859        italic=False,
1860        alpha=1,
1861        angle=0,
1862    ) -> Union["vedo.addons.Button", None]:
1863        """
1864        Add a button to the renderer window.
1865
1866        Arguments:
1867            states : (list)
1868                a list of possible states, e.g. ['On', 'Off']
1869            c : (list)
1870                a list of colors for each state
1871            bc : (list)
1872                a list of background colors for each state
1873            pos : (list)
1874                2D position from left-bottom corner
1875            size : (float)
1876                size of button font
1877            font : (str)
1878                font type. Check [available fonts here](https://vedo.embl.es/fonts).
1879            bold : (bool)
1880                bold font face (False)
1881            italic : (bool)
1882                italic font face (False)
1883            alpha : (float)
1884                opacity level
1885            angle : (float)
1886                anticlockwise rotation in degrees
1887
1888        Returns:
1889            `vedo.addons.Button` object.
1890
1891        Examples:
1892            - [buttons1.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/buttons1.py)
1893            - [buttons2.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/buttons2.py)
1894
1895            ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
1896        """
1897        if self.interactor:
1898            bu = addons.Button(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle)
1899            self.renderer.AddActor2D(bu)
1900            self.buttons.append(bu)
1901            # bu.function_id = self.add_callback("LeftButtonPress", bu.function) # not good
1902            bu.function_id = bu.add_observer("pick", bu.function, priority=10)
1903            return bu
1904        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:
1906    def add_spline_tool(
1907        self, points, pc="k", ps=8, lc="r4", ac="g5",
1908        lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True,
1909    ) -> "vedo.addons.SplineTool":
1910        """
1911        Add a spline tool to the current plotter.
1912        Nodes of the spline can be dragged in space with the mouse.
1913        Clicking on the line itself adds an extra point.
1914        Selecting a point and pressing del removes it.
1915
1916        Arguments:
1917            points : (Mesh, Points, array)
1918                the set of vertices forming the spline nodes.
1919            pc : (str)
1920                point color. The default is 'k'.
1921            ps : (str)
1922                point size. The default is 8.
1923            lc : (str)
1924                line color. The default is 'r4'.
1925            ac : (str)
1926                active point marker color. The default is 'g5'.
1927            lw : (int)
1928                line width. The default is 2.
1929            alpha : (float)
1930                line transparency.
1931            closed : (bool)
1932                spline is meant to be closed. The default is False.
1933
1934        Returns:
1935            a `SplineTool` object.
1936
1937        Examples:
1938            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
1939
1940            ![](https://vedo.embl.es/images/basic/spline_tool.png)
1941        """
1942        sw = addons.SplineTool(points, pc, ps, lc, ac, lw, alpha, closed, ontop, can_add_nodes)
1943        sw.interactor = self.interactor
1944        sw.on()
1945        sw.Initialize(sw.points.dataset)
1946        sw.representation.SetRenderer(self.renderer)
1947        sw.representation.SetClosedLoop(closed)
1948        sw.representation.BuildRepresentation()
1949        self.widgets.append(sw)
1950        return sw

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

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

a SplineTool object.

Examples:

def add_icon(self, icon, pos=3, size=0.08) -> vedo.addons.Icon:
1952    def add_icon(self, icon, pos=3, size=0.08) -> "vedo.addons.Icon":
1953        """Add an inset icon mesh into the same renderer.
1954
1955        Arguments:
1956            pos : (int, list)
1957                icon position in the range [1-4] indicating one of the 4 corners,
1958                or it can be a tuple (x,y) as a fraction of the renderer size.
1959            size : (float)
1960                size of the square inset.
1961
1962        Examples:
1963            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
1964        """
1965        iconw = addons.Icon(icon, pos, size)
1966
1967        iconw.SetInteractor(self.interactor)
1968        iconw.EnabledOn()
1969        iconw.InteractiveOff()
1970        self.widgets.append(iconw)
1971        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) -> Plotter:
1973    def add_global_axes(self, axtype=None, c=None) -> "Plotter":
1974        """Draw axes on scene. Available axes types:
1975
1976        Arguments:
1977            axtype : (int)
1978                - 0,  no axes,
1979                - 1,  draw three gray grid walls
1980                - 2,  show cartesian axes from (0,0,0)
1981                - 3,  show positive range of cartesian axes from (0,0,0)
1982                - 4,  show a triad at bottom left
1983                - 5,  show a cube at bottom left
1984                - 6,  mark the corners of the bounding box
1985                - 7,  draw a 3D ruler at each side of the cartesian axes
1986                - 8,  show the vtkCubeAxesActor object
1987                - 9,  show the bounding box outLine
1988                - 10, show three circles representing the maximum bounding box
1989                - 11, show a large grid on the x-y plane
1990                - 12, show polar axes
1991                - 13, draw a simple ruler at the bottom of the window
1992
1993            Axis type-1 can be fully customized by passing a dictionary axes=dict().
1994
1995        Example:
1996            ```python
1997            from vedo import Box, show
1998            b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1)
1999            show(
2000                b,
2001                axes={
2002                    "xtitle": "Some long variable [a.u.]",
2003                    "number_of_divisions": 4,
2004                    # ...
2005                },
2006            )
2007            ```
2008
2009        Examples:
2010            - [custom_axes1.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes1.py)
2011            - [custom_axes2.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes2.py)
2012            - [custom_axes3.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes3.py)
2013            - [custom_axes4.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/custom_axes4.py)
2014
2015            <img src="https://user-images.githubusercontent.com/32848391/72752870-ab7d5280-3bc3-11ea-8911-9ace00211e23.png" width="600">
2016        """
2017        addons.add_global_axes(axtype, c)
2018        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:
2020    def add_legend_box(self, **kwargs) -> "vedo.addons.LegendBox":
2021        """Add a legend to the top right.
2022
2023        Examples:
2024            - [legendbox.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/basic/legendbox.py),
2025            - [flag_labels1.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels1.py)
2026            - [flag_labels2.py](https://github.com/marcomusy/vedo/blob/master/examples/examples/other/flag_labels2.py)
2027        """
2028        acts = self.get_meshes()
2029        lb = addons.LegendBox(acts, **kwargs)
2030        self.add(lb)
2031        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]:
2033    def add_hint(
2034        self,
2035        obj,
2036        text="",
2037        c="k",
2038        bg="yellow9",
2039        font="Calco",
2040        size=18,
2041        justify=0,
2042        angle=0,
2043        delay=250,
2044    ) -> Union[vtki.vtkBalloonWidget, None]:
2045        """
2046        Create a pop-up hint style message when hovering an object.
2047        Use `add_hint(obj, False)` to disable a hinting a specific object.
2048        Use `add_hint(None)` to disable all hints.
2049
2050        Arguments:
2051            obj : (Mesh, Points)
2052                the object to associate the pop-up to
2053            text : (str)
2054                string description of the pop-up
2055            delay : (int)
2056                milliseconds to wait before pop-up occurs
2057        """
2058        if self.offscreen or not self.interactor:
2059            return None
2060
2061        if vedo.vtk_version[:2] == (9, 0) and "Linux" in vedo.sys_platform:
2062            # Linux vtk9.0 is bugged
2063            vedo.logger.warning(
2064                f"add_hint() is not available on Linux platforms for vtk{vedo.vtk_version}."
2065            )
2066            return None
2067
2068        if obj is None:
2069            self.hint_widget.EnabledOff()
2070            self.hint_widget.SetInteractor(None)
2071            self.hint_widget = None
2072            return self.hint_widget
2073
2074        if text is False and self.hint_widget:
2075            self.hint_widget.RemoveBalloon(obj)
2076            return self.hint_widget
2077
2078        if text == "":
2079            if obj.name:
2080                text = obj.name
2081            elif obj.filename:
2082                text = obj.filename
2083            else:
2084                return None
2085
2086        if not self.hint_widget:
2087            self.hint_widget = vtki.vtkBalloonWidget()
2088
2089            rep = self.hint_widget.GetRepresentation()
2090            rep.SetBalloonLayoutToImageRight()
2091
2092            trep = rep.GetTextProperty()
2093            trep.SetFontFamily(vtki.VTK_FONT_FILE)
2094            trep.SetFontFile(utils.get_font_path(font))
2095            trep.SetFontSize(size)
2096            trep.SetColor(vedo.get_color(c))
2097            trep.SetBackgroundColor(vedo.get_color(bg))
2098            trep.SetShadow(0)
2099            trep.SetJustification(justify)
2100            trep.UseTightBoundingBoxOn()
2101
2102            self.hint_widget.ManagesCursorOff()
2103            self.hint_widget.SetTimerDuration(delay)
2104            self.hint_widget.SetInteractor(self.interactor)
2105            if angle:
2106                trep.SetOrientation(angle)
2107                trep.SetBackgroundOpacity(0)
2108            # else:
2109            #     trep.SetBackgroundOpacity(0.5) # doesnt work well
2110            self.hint_widget.SetRepresentation(rep)
2111            self.widgets.append(self.hint_widget)
2112            self.hint_widget.EnabledOn()
2113
2114        bst = self.hint_widget.GetBalloonString(obj.actor)
2115        if bst:
2116            self.hint_widget.UpdateBalloonString(obj.actor, text)
2117        else:
2118            self.hint_widget.AddBalloon(obj.actor, text)
2119
2120        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) -> Plotter:
2122    def add_shadows(self) -> "Plotter":
2123        """Add shadows at the current renderer."""
2124        if self.renderer:
2125            shadows = vtki.new("ShadowMapPass")
2126            seq = vtki.new("SequencePass")
2127            passes = vtki.new("RenderPassCollection")
2128            passes.AddItem(shadows.GetShadowMapBakerPass())
2129            passes.AddItem(shadows)
2130            seq.SetPasses(passes)
2131            camerapass = vtki.new("CameraPass")
2132            camerapass.SetDelegatePass(seq)
2133            self.renderer.SetPass(camerapass)
2134        return self

Add shadows at the current renderer.

def add_ambient_occlusion( self, radius: float, bias=0.01, blur=True, samples=100) -> Plotter:
2136    def add_ambient_occlusion(self, radius: float, bias=0.01, blur=True, samples=100) -> "Plotter":
2137        """
2138        Screen Space Ambient Occlusion.
2139
2140        For every pixel on the screen, the pixel shader samples the depth values around
2141        the current pixel and tries to compute the amount of occlusion from each of the sampled
2142        points.
2143
2144        Arguments:
2145            radius : (float)
2146                radius of influence in absolute units
2147            bias : (float)
2148                bias of the normals
2149            blur : (bool)
2150                add a blurring to the sampled positions
2151            samples : (int)
2152                number of samples to probe
2153
2154        Examples:
2155            - [ssao.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ssao.py)
2156
2157            ![](https://vedo.embl.es/images/basic/ssao.jpg)
2158        """
2159        lights = vtki.new("LightsPass")
2160
2161        opaque = vtki.new("OpaquePass")
2162
2163        ssaoCam = vtki.new("CameraPass")
2164        ssaoCam.SetDelegatePass(opaque)
2165
2166        ssao = vtki.new("SSAOPass")
2167        ssao.SetRadius(radius)
2168        ssao.SetBias(bias)
2169        ssao.SetBlur(blur)
2170        ssao.SetKernelSize(samples)
2171        ssao.SetDelegatePass(ssaoCam)
2172
2173        translucent = vtki.new("TranslucentPass")
2174
2175        volpass = vtki.new("VolumetricPass")
2176        ddp = vtki.new("DualDepthPeelingPass")
2177        ddp.SetTranslucentPass(translucent)
2178        ddp.SetVolumetricPass(volpass)
2179
2180        over = vtki.new("OverlayPass")
2181
2182        collection = vtki.new("RenderPassCollection")
2183        collection.AddItem(lights)
2184        collection.AddItem(ssao)
2185        collection.AddItem(ddp)
2186        collection.AddItem(over)
2187
2188        sequence = vtki.new("SequencePass")
2189        sequence.SetPasses(collection)
2190
2191        cam = vtki.new("CameraPass")
2192        cam.SetDelegatePass(sequence)
2193
2194        self.renderer.SetPass(cam)
2195        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) -> Plotter:
2197    def add_depth_of_field(self, autofocus=True) -> "Plotter":
2198        """Add a depth of field effect in the scene."""
2199        lights = vtki.new("LightsPass")
2200
2201        opaque = vtki.new("OpaquePass")
2202
2203        dofCam = vtki.new("CameraPass")
2204        dofCam.SetDelegatePass(opaque)
2205
2206        dof = vtki.new("DepthOfFieldPass")
2207        dof.SetAutomaticFocalDistance(autofocus)
2208        dof.SetDelegatePass(dofCam)
2209
2210        collection = vtki.new("RenderPassCollection")
2211        collection.AddItem(lights)
2212        collection.AddItem(dof)
2213
2214        sequence = vtki.new("SequencePass")
2215        sequence.SetPasses(collection)
2216
2217        cam = vtki.new("CameraPass")
2218        cam.SetDelegatePass(sequence)
2219
2220        self.renderer.SetPass(cam)
2221        return self

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

Clear the scene from all meshes and volumes.

def break_interaction(self) -> Plotter:
3551    def break_interaction(self) -> "Plotter":
3552        """Break window interaction and return to the python execution flow"""
3553        if self.interactor:
3554            self.check_actors_trasform()
3555            self.interactor.ExitCallback()
3556        return self

Break window interaction and return to the python execution flow

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

Close the plotter.

camera
3675    @property
3676    def camera(self):
3677        """Return the current active camera."""
3678        if self.renderer:
3679            return self.renderer.GetActiveCamera()

Return the current active camera.

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

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

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

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

Close the last created Plotter instance if it exists.