vedo.visual

Base classes to manage visualization and apperance of objects and their properties.

   1#!/usr/bin/env python3
   2# -*- coding: utf-8 -*-
   3import os
   4from weakref import ref as weak_ref_to
   5
   6from typing import Union
   7from typing_extensions import Self
   8import numpy as np
   9
  10import vedo.vtkclasses as vtki
  11
  12import vedo
  13from vedo import colors
  14from vedo import utils
  15
  16
  17__docformat__ = "google"
  18
  19__doc__ = """
  20Base classes to manage visualization and apperance of objects and their properties.
  21"""
  22
  23__all__ = [
  24    "CommonVisual",
  25    "PointsVisual",
  26    "VolumeVisual",
  27    "MeshVisual",
  28    "ImageVisual",
  29    "Actor2D",
  30    "LightKit",
  31]
  32
  33
  34###################################################
  35class CommonVisual:
  36    """Class to manage the visual aspects common to all objects."""
  37
  38    def __init__(self):
  39        # print("init CommonVisual", type(self))
  40
  41        self.properties = None
  42
  43        self.scalarbar = None
  44        self.pipeline = None
  45
  46        self.trail = None
  47        self.trail_points = []
  48        self.trail_segment_size = 0
  49        self.trail_offset = None
  50
  51        self.shadows = []
  52
  53        self.axes = None
  54        self.picked3d = None
  55
  56        self.rendered_at = set()
  57
  58        self._ligthingnr = 0  # index of the lighting mode changed from CLI
  59        self._cmap_name = ""  # remember the cmap name for self._keypress
  60        self._caption = None
  61
  62        self.actor = None
  63
  64
  65    def print(self):
  66        """Print object info."""
  67        print(self.__str__())
  68        return self
  69
  70    @property
  71    def LUT(self) -> np.ndarray:
  72        """Return the lookup table of the object as a numpy object."""
  73        try:
  74            _lut = self.mapper.GetLookupTable()
  75            values = []
  76            for i in range(_lut.GetTable().GetNumberOfTuples()):
  77                # print("LUT i =", i, "value =", _lut.GetTableValue(i))
  78                values.append(_lut.GetTableValue(i))
  79            return np.array(values)
  80        except AttributeError:
  81            return np.array([], dtype=float)
  82
  83    @LUT.setter
  84    def LUT(self, arr):
  85        """
  86        Set the lookup table of the object from a numpy or `vtkLookupTable` object.
  87        Consider using `cmap()` or `build_lut()` instead as it allows
  88        to set the range of the LUT and to use a string name for the color map.
  89        """
  90        if isinstance(arr, vtki.vtkLookupTable):
  91            newlut = arr
  92            self.mapper.SetScalarRange(newlut.GetRange())
  93        else:
  94            newlut = vtki.vtkLookupTable()
  95            newlut.SetNumberOfTableValues(len(arr))
  96            if len(arr[0]) == 3:
  97                arr = np.insert(arr, 3, 1, axis=1)
  98            for i, v in enumerate(arr):
  99                newlut.SetTableValue(i, v)
 100            newlut.SetRange(self.mapper.GetScalarRange())
 101            # print("newlut.GetRange() =", newlut.GetRange())
 102            # print("self.mapper.GetScalarRange() =", self.mapper.GetScalarRange())
 103            newlut.Build()
 104        self.mapper.SetLookupTable(newlut)
 105        self.mapper.ScalarVisibilityOn()
 106
 107    def scalar_range(self, vmin=None, vmax=None):
 108        """Set the range of the scalar value for visualization."""
 109        if vmin is None and vmax is None:
 110            return np.array(self.mapper.GetScalarRange())
 111        if vmax is None:
 112            vmin, vmax = vmin # assume it is a list
 113        self.mapper.SetScalarRange(float(vmin), float(vmax))
 114        return self
 115
 116    def add_observer(self, event_name, func, priority=0) -> int:
 117        """Add a callback function that will be called when an event occurs."""
 118        event_name = utils.get_vtk_name_event(event_name)
 119        idd = self.actor.AddObserver(event_name, func, priority)
 120        return idd
 121
 122    def invoke_event(self, event_name) -> Self:
 123        """Invoke an event."""
 124        event_name = utils.get_vtk_name_event(event_name)
 125        self.actor.InvokeEvent(event_name)
 126        return self
 127
 128    # def abort_event(self, obs_id):
 129    #     """Abort an event."""
 130    #     cmd = self.actor.GetCommand(obs_id) # vtkCommand
 131    #     if cmd:
 132    #         cmd.AbortFlagOn()
 133    #     return self
 134
 135    def show(self, **options) -> Union["vedo.Plotter", None]:
 136        """
 137        Create on the fly an instance of class `Plotter` or use the last existing one to
 138        show one single object.
 139
 140        This method is meant as a shortcut. If more than one object needs to be visualised
 141        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
 142
 143        Returns the `Plotter` class instance.
 144        """
 145        return vedo.plotter.show(self, **options)
 146
 147    def thumbnail(self, zoom=1.25, size=(200, 200), bg="white", azimuth=0, elevation=0, axes=False) -> np.ndarray:
 148        """Build a thumbnail of the object and return it as an array."""
 149        # speed is about 20Hz for size=[200,200]
 150        ren = vtki.vtkRenderer()
 151
 152        actor = self.actor
 153        if isinstance(self, vedo.UnstructuredGrid):
 154            geo = vtki.new("GeometryFilter")
 155            geo.SetInputData(self.dataset)
 156            geo.Update()
 157            actor = vedo.Mesh(geo.GetOutput()).cmap("rainbow").actor
 158
 159        ren.AddActor(actor)
 160        if axes:
 161            axes = vedo.addons.Axes(self)
 162            ren.AddActor(axes.actor)
 163        ren.ResetCamera()
 164        cam = ren.GetActiveCamera()
 165        cam.Zoom(zoom)
 166        cam.Elevation(elevation)
 167        cam.Azimuth(azimuth)
 168
 169        ren_win = vtki.vtkRenderWindow()
 170        ren_win.SetOffScreenRendering(True)
 171        ren_win.SetSize(size)
 172        ren.SetBackground(colors.get_color(bg))
 173        ren_win.AddRenderer(ren)
 174        ren.ResetCameraClippingRange()
 175        ren_win.Render()
 176
 177        nx, ny = ren_win.GetSize()
 178        arr = vtki.vtkUnsignedCharArray()
 179        ren_win.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr)
 180        narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
 181        narr = np.ascontiguousarray(np.flip(narr, axis=0))
 182
 183        ren.RemoveActor(actor)
 184        if axes:
 185            ren.RemoveActor(axes.actor)
 186        ren_win.Finalize()
 187        del ren_win
 188        return narr
 189
 190    def pickable(self, value=None) -> Self:
 191        """Set/get the pickability property of an object."""
 192        if value is None:
 193            return self.actor.GetPickable()
 194        self.actor.SetPickable(value)
 195        return self
 196
 197    def use_bounds(self, value=True) -> Self:
 198        """
 199        Instruct the current camera to either take into account or ignore
 200        the object bounds when resetting.
 201        """
 202        self.actor.SetUseBounds(value)
 203        return self
 204
 205    def draggable(self, value=None) -> Self:  # NOT FUNCTIONAL?
 206        """Set/get the draggability property of an object."""
 207        if value is None:
 208            return self.actor.GetDragable()
 209        self.actor.SetDragable(value)
 210        return self
 211
 212    def on(self) -> Self:
 213        """Switch on  object visibility. Object is not removed."""
 214        self.actor.VisibilityOn()
 215        try:
 216            self.scalarbar.actor.VisibilityOn()
 217        except AttributeError:
 218            pass
 219        try:
 220            self.trail.actor.VisibilityOn()
 221        except AttributeError:
 222            pass
 223        try:
 224            for sh in self.shadows:
 225                sh.actor.VisibilityOn()
 226        except AttributeError:
 227            pass
 228        return self
 229
 230    def off(self) -> Self:
 231        """Switch off object visibility. Object is not removed."""
 232        self.actor.VisibilityOff()
 233        try:
 234            self.scalarbar.actor.VisibilityOff()
 235        except AttributeError:
 236            pass
 237        try:
 238            self.trail.actor.VisibilityOff()
 239        except AttributeError:
 240            pass
 241        try:
 242            for sh in self.shadows:
 243                sh.actor.VisibilityOff()
 244        except AttributeError:
 245            pass
 246        return self
 247
 248    def toggle(self) -> Self:
 249        """Toggle object visibility on/off."""
 250        v = self.actor.GetVisibility()
 251        if v:
 252            self.off()
 253        else:
 254            self.on()
 255        return self
 256
 257    def add_scalarbar(
 258        self,
 259        title="",
 260        pos=(),
 261        size=(80, 400),
 262        font_size=14,
 263        title_yoffset=15,
 264        nlabels=None,
 265        c=None,
 266        horizontal=False,
 267        use_alpha=True,
 268        label_format=":6.3g",
 269    ) -> Self:
 270        """
 271        Add a 2D scalar bar for the specified object.
 272
 273        Arguments:
 274            title : (str)
 275                scalar bar title
 276            pos : (list)
 277                position coordinates of the bottom left corner.
 278                Can also be a pair of (x,y) values in the range [0,1]
 279                to indicate the position of the bottom left and top right corners.
 280            size : (float,float)
 281                size of the scalarbar in number of pixels (width, height)
 282            font_size : (float)
 283                size of font for title and numeric labels
 284            title_yoffset : (float)
 285                vertical space offset between title and color scalarbar
 286            nlabels : (int)
 287                number of numeric labels
 288            c : (list)
 289                color of the scalar bar text
 290            horizontal : (bool)
 291                lay the scalarbar horizontally
 292            use_alpha : (bool)
 293                render transparency in the color bar itself
 294            label_format : (str)
 295                c-style format string for numeric labels
 296
 297        Examples:
 298            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
 299            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
 300        """
 301        plt = vedo.plotter_instance
 302
 303        if plt and plt.renderer:
 304            c = (0.9, 0.9, 0.9)
 305            if np.sum(plt.renderer.GetBackground()) > 1.5:
 306                c = (0.1, 0.1, 0.1)
 307            if isinstance(self.scalarbar, vtki.vtkActor):
 308                plt.renderer.RemoveActor(self.scalarbar)
 309            elif isinstance(self.scalarbar, vedo.Assembly):
 310                for a in self.scalarbar.unpack():
 311                    plt.renderer.RemoveActor(a)
 312        if c is None:
 313            c = "gray"
 314
 315        sb = vedo.addons.ScalarBar(
 316            self,
 317            title,
 318            pos,
 319            size,
 320            font_size,
 321            title_yoffset,
 322            nlabels,
 323            c,
 324            horizontal,
 325            use_alpha,
 326            label_format,
 327        )
 328        self.scalarbar = sb
 329        return self
 330
 331    def add_scalarbar3d(
 332        self,
 333        title="",
 334        pos=None,
 335        size=(0, 0),
 336        title_font="",
 337        title_xoffset=-1.2,
 338        title_yoffset=0.0,
 339        title_size=1.5,
 340        title_rotation=0.0,
 341        nlabels=9,
 342        label_font="",
 343        label_size=1,
 344        label_offset=0.375,
 345        label_rotation=0,
 346        label_format="",
 347        italic=0,
 348        c=None,
 349        draw_box=True,
 350        above_text=None,
 351        below_text=None,
 352        nan_text="NaN",
 353        categories=None,
 354    ) -> Self:
 355        """
 356        Associate a 3D scalar bar to the object and add it to the scene.
 357        The new scalarbar object (Assembly) will be accessible as obj.scalarbar
 358
 359        Arguments:
 360            size : (list)
 361                (thickness, length) of scalarbar
 362            title : (str)
 363                scalar bar title
 364            title_xoffset : (float)
 365                horizontal space btw title and color scalarbar
 366            title_yoffset : (float)
 367                vertical space offset
 368            title_size : (float)
 369                size of title wrt numeric labels
 370            title_rotation : (float)
 371                title rotation in degrees
 372            nlabels : (int)
 373                number of numeric labels
 374            label_font : (str)
 375                font type for labels
 376            label_size : (float)
 377                label scale factor
 378            label_offset : (float)
 379                space btw numeric labels and scale
 380            label_rotation : (float)
 381                label rotation in degrees
 382            label_format : (str)
 383                label format for floats and integers (e.g. `':.2f'`)
 384            draw_box : (bool)
 385                draw a box around the colorbar
 386            categories : (list)
 387                make a categorical scalarbar,
 388                the input list will have the format `[value, color, alpha, textlabel]`
 389
 390        Examples:
 391            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
 392        """
 393        plt = vedo.plotter_instance
 394        if plt and c is None:  # automatic black or white
 395            c = (0.9, 0.9, 0.9)
 396            if np.sum(vedo.get_color(plt.backgrcol)) > 1.5:
 397                c = (0.1, 0.1, 0.1)
 398        if c is None:
 399            c = (0, 0, 0)
 400        c = vedo.get_color(c)
 401
 402        self.scalarbar = vedo.addons.ScalarBar3D(
 403            self,
 404            title,
 405            pos,
 406            size,
 407            title_font,
 408            title_xoffset,
 409            title_yoffset,
 410            title_size,
 411            title_rotation,
 412            nlabels,
 413            label_font,
 414            label_size,
 415            label_offset,
 416            label_rotation,
 417            label_format,
 418            italic,
 419            c,
 420            draw_box,
 421            above_text,
 422            below_text,
 423            nan_text,
 424            categories,
 425        )
 426        return self
 427
 428    def color(self, col, alpha=None, vmin=None, vmax=None):
 429        """
 430        Assign a color or a set of colors along the range of the scalar value.
 431        A single constant color can also be assigned.
 432        Any matplotlib color map name is also accepted, e.g. `volume.color('jet')`.
 433
 434        E.g.: say that your cells scalar runs from -3 to 6,
 435        and you want -3 to show red and 1.5 violet and 6 green, then just set:
 436
 437        `volume.color(['red', 'violet', 'green'])`
 438
 439        You can also assign a specific color to a aspecific value with eg.:
 440
 441        `volume.color([(0,'red'), (0.5,'violet'), (1,'green')])`
 442
 443        Arguments:
 444            alpha : (list)
 445                use a list to specify transparencies along the scalar range
 446            vmin : (float)
 447                force the min of the scalar range to be this value
 448            vmax : (float)
 449                force the max of the scalar range to be this value
 450        """
 451        # supersedes method in Points, Mesh
 452
 453        if col is None:
 454            return self
 455        
 456        if vmin is None:
 457            vmin, _ = self.dataset.GetScalarRange()
 458        if vmax is None:
 459            _, vmax = self.dataset.GetScalarRange()
 460        ctf = self.properties.GetRGBTransferFunction()
 461        ctf.RemoveAllPoints()
 462
 463        if utils.is_sequence(col):
 464            if utils.is_sequence(col[0]) and len(col[0]) == 2:
 465                # user passing [(value1, color1), ...]
 466                for x, ci in col:
 467                    r, g, b = colors.get_color(ci)
 468                    ctf.AddRGBPoint(x, r, g, b)
 469                    # colors.printc('color at', round(x, 1),
 470                    #               'set to', colors.get_color_name((r, g, b)), bold=0)
 471            else:
 472                # user passing [color1, color2, ..]
 473                for i, ci in enumerate(col):
 474                    r, g, b = colors.get_color(ci)
 475                    x = vmin + (vmax - vmin) * i / (len(col) - 1)
 476                    ctf.AddRGBPoint(x, r, g, b)
 477        elif isinstance(col, str):
 478            if col in colors.colors.keys() or col in colors.color_nicks.keys():
 479                r, g, b = colors.get_color(col)
 480                ctf.AddRGBPoint(vmin, r, g, b)  # constant color
 481                ctf.AddRGBPoint(vmax, r, g, b)
 482            else:  # assume it's a colormap
 483                for x in np.linspace(vmin, vmax, num=64, endpoint=True):
 484                    r, g, b = colors.color_map(x, name=col, vmin=vmin, vmax=vmax)
 485                    ctf.AddRGBPoint(x, r, g, b)
 486        elif isinstance(col, int):
 487            r, g, b = colors.get_color(col)
 488            ctf.AddRGBPoint(vmin, r, g, b)  # constant color
 489            ctf.AddRGBPoint(vmax, r, g, b)
 490        elif isinstance(col, vtki.vtkLookupTable):
 491            alpha=[]
 492            nt = col.GetNumberOfTableValues()
 493            for i in range(nt):
 494                r, g, b, a = col.GetTableValue(i)
 495                x = vmin + (vmax - vmin) * i / (nt - 1)
 496                ctf.AddRGBPoint(x, r, g, b)
 497                alpha.append(a)
 498        elif hasattr(col, "resampled"): # cover the case of LinearSegmentedColormap
 499            N = col.N
 500            cs = np.array([col(i/N) for i in range(N)])
 501            alpha = cs[:,3].copy()
 502            for i, v in enumerate(cs):
 503                r, g, b, _ = v
 504                x = vmin + (vmax - vmin) * i / (N - 1)
 505                ctf.AddRGBPoint(x, r, g, b)
 506        elif hasattr(col, "to_rgba"):   # col is a matplotlib colormap
 507            for i in range(256):
 508                r, g, b, a = col(i / 255)
 509                x = vmin + (vmax - vmin) * i / 255
 510                ctf.AddRGBPoint(x, r, g, b)
 511                alpha.append(a)
 512        else:
 513            vedo.logger.warning(f"in color() unknown input type {type(col)}")
 514
 515        if alpha is not None:
 516            self.alpha(alpha, vmin=vmin, vmax=vmax)
 517        return self
 518
 519    def alpha(self, alpha, vmin=None, vmax=None) -> Self:
 520        """
 521        Assign a set of tranparencies along the range of the scalar value.
 522        A single constant value can also be assigned.
 523
 524        E.g.: say `alpha=(0.0, 0.3, 0.9, 1)` and the scalar range goes from -10 to 150.
 525        Then all cells with a value close to -10 will be completely transparent, cells at 1/4
 526        of the range will get an alpha equal to 0.3 and voxels with value close to 150
 527        will be completely opaque.
 528
 529        As a second option one can set explicit (x, alpha_x) pairs to define the transfer function.
 530
 531        E.g.: say `alpha=[(-5, 0), (35, 0.4) (123,0.9)]` and the scalar range goes from -10 to 150.
 532        Then all cells below -5 will be completely transparent, cells with a scalar value of 35
 533        will get an opacity of 40% and above 123 alpha is set to 90%.
 534        """
 535        if vmin is None:
 536            vmin, _ = self.dataset.GetScalarRange()
 537        if vmax is None:
 538            _, vmax = self.dataset.GetScalarRange()
 539        otf = self.properties.GetScalarOpacity()
 540        otf.RemoveAllPoints()
 541
 542        if utils.is_sequence(alpha):
 543            alpha = np.array(alpha)
 544            if len(alpha.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
 545                for i, al in enumerate(alpha):
 546                    xalpha = vmin + (vmax - vmin) * i / (len(alpha) - 1)
 547                    # Create transfer mapping scalar value to opacity
 548                    otf.AddPoint(xalpha, al)
 549                    # print("alpha at", round(xalpha, 1), "\tset to", al)
 550            elif len(alpha.shape) == 2:  # user passing [(x0,alpha0), ...]
 551                otf.AddPoint(vmin, alpha[0][1])
 552                for xalpha, al in alpha:
 553                    # Create transfer mapping scalar value to opacity
 554                    otf.AddPoint(xalpha, al)
 555                otf.AddPoint(vmax, alpha[-1][1])
 556
 557        else:
 558
 559            otf.AddPoint(vmin, alpha)  # constant alpha
 560            otf.AddPoint(vmax, alpha)
 561
 562        return self
 563
 564
 565########################################################################################
 566class Actor2D(vtki.vtkActor2D):
 567    """Wrapping of `vtkActor2D` class."""
 568
 569    def __init__(self, dataset=None):
 570        """Manage 2D objects."""
 571        super().__init__()
 572
 573        self.dataset = None
 574        self.name = "Actor2D"
 575        self.filename = ""
 576        self.file_size = 0
 577        self.pipeline = None
 578        self.shape = [] # for images
 579        self.coordinate = None
 580
 581        if dataset is not None:
 582            mapper = vtki.new("PolyDataMapper2D")
 583            mapper.SetInputData(dataset)
 584            self.SetMapper(mapper)
 585
 586        self.dataset = dataset
 587        self.properties = self.GetProperty()
 588
 589
 590    @property
 591    def mapper(self):
 592        """Get the internal vtkMapper."""
 593        return self.GetMapper()
 594    
 595    # not usable
 596    # @property
 597    # def properties(self):
 598    #     """Get the internal vtkProperty."""
 599    #     return self.GetProperty()
 600    
 601    # @properties.setter
 602    # def properties(self, prop):
 603    #     """Set the internal vtkProperty."""
 604    #     self.SetProperty(prop)
 605
 606    @mapper.setter
 607    def mapper(self, amapper):
 608        """Set the internal vtkMapper."""
 609        self.SetMapper(amapper)
 610
 611    def layer(self, value=None):
 612        """Set/Get the layer number in the overlay planes into which to render."""
 613        if value is None:
 614            return self.GetLayerNumber()
 615        self.SetLayerNumber(value)
 616        return self
 617
 618    def pos(self, px=None, py=None) -> Union[np.ndarray, Self]:
 619        """Set/Get the screen-coordinate position."""
 620        if isinstance(px, str):
 621            vedo.logger.error("Use string descriptors only inside the constructor")
 622            return self
 623        if px is None:
 624            return np.array(self.GetPosition(), dtype=int)
 625        if py is not None:
 626            p = [px, py]
 627        else:
 628            p = px
 629        assert len(p) == 2, "Error: len(pos) must be 2 for Actor2D"
 630        self.SetPosition(p)
 631        return self
 632
 633    def coordinate_system(self, value=None) -> Self:
 634        """
 635        Set/get the coordinate system which this coordinate is defined in.
 636
 637        The options are:
 638            0. Display
 639            1. Normalized Display
 640            2. Viewport
 641            3. Normalized Viewport
 642            4. View
 643            5. Pose
 644            6. World
 645        """
 646        coor = self.GetPositionCoordinate()
 647        if value is None:
 648            return coor.GetCoordinateSystem()
 649        coor.SetCoordinateSystem(value)
 650        return self
 651    
 652    def set_position_coordinates(self, p1, p2):
 653        """Set the position coordinates."""
 654        self.GetPositionCoordinate().SetValue(*p1)
 655        self.GetPosition2Coordinate().SetValue(*p2)
 656        return self
 657
 658    def on(self) -> Self:
 659        """Set object visibility."""
 660        self.VisibilityOn()
 661        return self
 662
 663    def off(self) -> Self:
 664        """Set object visibility."""
 665        self.VisibilityOn()
 666        return self
 667
 668    def toggle(self) -> Self:
 669        """Toggle object visibility."""
 670        self.SetVisibility(not self.GetVisibility())
 671        return self
 672
 673    def visibility(self, value=None) -> bool:
 674        """Get/Set object visibility."""
 675        if value is not None:
 676            self.SetVisibility(value)
 677        return self.GetVisibility()
 678
 679    def pickable(self, value=True) -> Self:
 680        """Set object pickability."""
 681        self.SetPickable(value)
 682        return self
 683
 684    def color(self, value=None) -> Union[np.ndarray, Self]:
 685        """Set/Get the object color."""
 686        if value is None:
 687            return self.properties.GetColor()
 688        self.properties.SetColor(colors.get_color(value))
 689        return self
 690
 691    def c(self, value=None) -> Union[np.ndarray, Self]:
 692        """Set/Get the object color."""
 693        return self.color(value)
 694
 695    def alpha(self, value=None) -> Union[float, Self]:
 696        """Set/Get the object opacity."""
 697        if value is None:
 698            return self.properties.GetOpacity()
 699        self.properties.SetOpacity(value)
 700        return self
 701
 702    def ps(self, point_size=None) -> Union[int, Self]:
 703        """Set/Get the point size of the object. Same as `point_size()`."""
 704        if point_size is None:
 705            return self.properties.GetPointSize()
 706        self.properties.SetPointSize(point_size)
 707        return self
 708
 709    def lw(self, line_width=None) -> Union[int, Self]:
 710        """Set/Get the line width of the object. Same as `line_width()`."""
 711        if line_width is None:
 712            return self.properties.GetLineWidth()
 713        self.properties.SetLineWidth(line_width)
 714        return self
 715
 716    def ontop(self, value=True) -> Self:
 717        """Keep the object always on top of everything else."""
 718        if value:
 719            self.properties.SetDisplayLocationToForeground()
 720        else:
 721            self.properties.SetDisplayLocationToBackground()
 722        return self
 723
 724    def add_observer(self, event_name, func, priority=0) -> int:
 725        """Add a callback function that will be called when an event occurs."""
 726        event_name = utils.get_vtk_name_event(event_name)
 727        idd = self.AddObserver(event_name, func, priority)
 728        return idd
 729
 730########################################################################################
 731class Actor3DHelper:
 732    """Helper class for 3D actors."""
 733
 734    def apply_transform(self, LT) -> Self:
 735        """Apply a linear transformation to the actor."""
 736        self.transform.concatenate(LT)
 737        self.actor.SetPosition(self.transform.T.GetPosition())
 738        self.actor.SetOrientation(self.transform.T.GetOrientation())
 739        self.actor.SetScale(self.transform.T.GetScale())
 740        return self
 741
 742    def pos(self, x=None, y=None, z=None) -> Union[np.ndarray, Self]:
 743        """Set/Get object position."""
 744        if x is None:  # get functionality
 745            return self.transform.position
 746
 747        if z is None and y is None:  # assume x is of the form (x,y,z)
 748            if len(x) == 3:
 749                x, y, z = x
 750            else:
 751                x, y = x
 752                z = 0
 753        elif z is None:  # assume x,y is of the form x, y
 754            z = 0
 755
 756        q = self.transform.position
 757        LT = vedo.LinearTransform().translate([x,y,z]-q)
 758        return self.apply_transform(LT)
 759
 760    def shift(self, dx, dy=0, dz=0) -> Self:
 761        """Add a vector to the current object position."""
 762        if vedo.utils.is_sequence(dx):
 763            vedo.utils.make3d(dx)
 764            dx, dy, dz = dx
 765        LT = vedo.LinearTransform().translate([dx, dy, dz])
 766        return self.apply_transform(LT)
 767
 768    def origin(self, point=None) -> Union[np.ndarray, Self]:
 769        """
 770        Set/get origin of object.
 771        Useful for defining pivoting point when rotating and/or scaling.
 772        """
 773        if point is None:
 774            return np.array(self.actor.GetOrigin())
 775        self.actor.SetOrigin(point)
 776        return self
 777
 778    def scale(self, s, origin=True) -> Self:
 779        """Multiply object size by `s` factor."""
 780        LT = vedo.LinearTransform().scale(s, origin=origin)
 781        return self.apply_transform(LT)
 782
 783    def x(self, val=None) -> Union[float, Self]:
 784        """Set/Get object position along x axis."""
 785        p = self.transform.position
 786        if val is None:
 787            return p[0]
 788        self.pos(val, p[1], p[2])
 789        return self
 790
 791    def y(self, val=None) -> Union[float, Self]:
 792        """Set/Get object position along y axis."""
 793        p = self.transform.position
 794        if val is None:
 795            return p[1]
 796        self.pos(p[0], val, p[2])
 797        return self
 798
 799    def z(self, val=None) -> Union[float, Self]:
 800        """Set/Get object position along z axis."""
 801        p = self.transform.position
 802        if val is None:
 803            return p[2]
 804        self.pos(p[0], p[1], val)
 805        return self
 806
 807    def rotate_x(self, angle) -> Self:
 808        """Rotate object around x axis."""
 809        LT = vedo.LinearTransform().rotate_x(angle)
 810        return self.apply_transform(LT)
 811
 812    def rotate_y(self, angle) -> Self:
 813        """Rotate object around y axis."""
 814        LT = vedo.LinearTransform().rotate_y(angle)
 815        return self.apply_transform(LT)
 816
 817    def rotate_z(self, angle) -> Self:
 818        """Rotate object around z axis."""
 819        LT = vedo.LinearTransform().rotate_z(angle)
 820        return self.apply_transform(LT)
 821
 822    def reorient(self, old_axis, new_axis, rotation=0, rad=False) -> Self:
 823        """Rotate object to a new orientation."""
 824        if rad:
 825            rotation *= 180 / np.pi
 826        axis = old_axis / np.linalg.norm(old_axis)
 827        direction = new_axis / np.linalg.norm(new_axis)
 828        angle = np.arccos(np.dot(axis, direction)) * 180 / np.pi
 829        self.actor.RotateZ(rotation)
 830        a,b,c = np.cross(axis, direction)
 831        self.actor.RotateWXYZ(angle, c,b,a)
 832        return self
 833
 834    def bounds(self) -> np.ndarray:
 835        """
 836        Get the object bounds.
 837        Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`.
 838        """
 839        return np.array(self.actor.GetBounds())
 840
 841    def xbounds(self, i=None) -> Union[float, tuple]:
 842        """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1)."""
 843        b = self.bounds()
 844        if i is not None:
 845            return b[i]
 846        return (b[0], b[1])
 847
 848    def ybounds(self, i=None) -> Union[float, tuple]:
 849        """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1)."""
 850        b = self.bounds()
 851        if i == 0:
 852            return b[2]
 853        if i == 1:
 854            return b[3]
 855        return (b[2], b[3])
 856
 857    def zbounds(self, i=None) -> Union[float, tuple]:
 858        """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1)."""
 859        b = self.bounds()
 860        if i == 0:
 861            return b[4]
 862        if i == 1:
 863            return b[5]
 864        return (b[4], b[5])
 865
 866    def diagonal_size(self) -> float:
 867        """Get the diagonal size of the bounding box."""
 868        b = self.bounds()
 869        return np.sqrt((b[1]-b[0])**2 + (b[3]-b[2])**2 + (b[5]-b[4])**2)
 870
 871###################################################
 872class PointsVisual(CommonVisual):
 873    """Class to manage the visual aspects of a ``Points`` object."""
 874
 875    def __init__(self):
 876        # print("init PointsVisual")
 877        super().__init__()
 878        self.properties_backface = None
 879        self._cmap_name = None
 880        self.trail = None
 881        self.trail_offset = 0
 882        self.trail_points = []
 883        self._caption = None
 884
 885
 886    def clone2d(self, size=None, offset=(), scale=None):
 887        """
 888        Turn a 3D `Points` or `Mesh` into a flat 2D actor.
 889        Returns a `Actor2D`.
 890
 891        Arguments:
 892            size : (float)
 893                size as scaling factor for the 2D actor
 894            offset : (list)
 895                2D (x, y) position of the actor in the range [-1, 1]
 896            scale : (float)
 897                Deprecated. Use `size` instead.
 898
 899        Examples:
 900            - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py)
 901
 902                ![](https://vedo.embl.es/images/other/clone2d.png)
 903        """
 904        # assembly.Assembly.clone2d() superseeds this method
 905        if scale is not None:
 906            vedo.logger.warning("clone2d(): use keyword size not scale")
 907            size = scale
 908
 909        if size is None:
 910            # work out a reasonable scale
 911            msiz = self.diagonal_size()
 912            if vedo.plotter_instance and vedo.plotter_instance.window:
 913                sz = vedo.plotter_instance.window.GetSize()
 914                dsiz = utils.mag(sz)
 915                size = dsiz / msiz / 10
 916            else:
 917                size = 350 / msiz
 918
 919        tp = vtki.new("TransformPolyDataFilter")
 920        transform = vtki.vtkTransform()
 921        transform.Scale(size, size, size)
 922        if len(offset) == 0:
 923            offset = self.pos()
 924        transform.Translate(-utils.make3d(offset))
 925        tp.SetTransform(transform)
 926        tp.SetInputData(self.dataset)
 927        tp.Update()
 928        poly = tp.GetOutput()
 929
 930        cm = self.mapper.GetColorMode()
 931        lut = self.mapper.GetLookupTable()
 932        sv = self.mapper.GetScalarVisibility()
 933        use_lut = self.mapper.GetUseLookupTableScalarRange()
 934        vrange = self.mapper.GetScalarRange()
 935        sm = self.mapper.GetScalarMode()
 936
 937        act2d = Actor2D(poly)
 938        act2d.mapper.SetColorMode(cm)
 939        act2d.mapper.SetLookupTable(lut)
 940        act2d.mapper.SetScalarVisibility(sv)
 941        act2d.mapper.SetUseLookupTableScalarRange(use_lut)
 942        act2d.mapper.SetScalarRange(vrange)
 943        act2d.mapper.SetScalarMode(sm)
 944
 945        act2d.GetPositionCoordinate().SetCoordinateSystem(4)
 946        act2d.properties.SetColor(self.color())
 947        act2d.properties.SetOpacity(self.alpha())
 948        act2d.properties.SetLineWidth(self.properties.GetLineWidth())
 949        act2d.properties.SetPointSize(self.properties.GetPointSize())
 950        act2d.properties.SetDisplayLocation(0) # 0 = back, 1 = front
 951        act2d.PickableOff()
 952        return act2d
 953
 954    ##################################################
 955    def copy_properties_from(self, source, deep=True, actor_related=True) -> Self:
 956        """
 957        Copy properties from another ``Points`` object.
 958        """
 959        pr = vtki.vtkProperty()
 960        try:
 961            sp = source.properties
 962            mp = source.mapper
 963            sa = source.actor
 964        except AttributeError:
 965            sp = source.GetProperty()
 966            mp = source.GetMapper()
 967            sa = source
 968
 969        if deep:
 970            pr.DeepCopy(sp)
 971        else:
 972            pr.ShallowCopy(sp)
 973        self.actor.SetProperty(pr)
 974        self.properties = pr
 975
 976        if self.actor.GetBackfaceProperty():
 977            bfpr = vtki.vtkProperty()
 978            bfpr.DeepCopy(sa.GetBackfaceProperty())
 979            self.actor.SetBackfaceProperty(bfpr)
 980            self.properties_backface = bfpr
 981
 982        if not actor_related:
 983            return self
 984
 985        # mapper related:
 986        self.mapper.SetScalarVisibility(mp.GetScalarVisibility())
 987        self.mapper.SetScalarMode(mp.GetScalarMode())
 988        self.mapper.SetScalarRange(mp.GetScalarRange())
 989        self.mapper.SetLookupTable(mp.GetLookupTable())
 990        self.mapper.SetColorMode(mp.GetColorMode())
 991        self.mapper.SetInterpolateScalarsBeforeMapping(
 992            mp.GetInterpolateScalarsBeforeMapping()
 993        )
 994        self.mapper.SetUseLookupTableScalarRange(
 995            mp.GetUseLookupTableScalarRange()
 996        )
 997
 998        self.actor.SetPickable(sa.GetPickable())
 999        self.actor.SetDragable(sa.GetDragable())
1000        self.actor.SetTexture(sa.GetTexture())
1001        self.actor.SetVisibility(sa.GetVisibility())
1002        return self
1003
1004    def color(self, c=False, alpha=None) -> Union[np.ndarray, Self]:
1005        """
1006        Set/get mesh's color.
1007        If None is passed as input, will use colors from active scalars.
1008        Same as `mesh.c()`.
1009        """
1010        if c is False:
1011            return np.array(self.properties.GetColor())
1012        if c is None:
1013            self.mapper.ScalarVisibilityOn()
1014            return self
1015        self.mapper.ScalarVisibilityOff()
1016        cc = colors.get_color(c)
1017        self.properties.SetColor(cc)
1018        if self.trail:
1019            self.trail.properties.SetColor(cc)
1020        if alpha is not None:
1021            self.alpha(alpha)
1022        return self
1023
1024    def c(self, color=False, alpha=None) -> Union[np.ndarray, Self]:
1025        """
1026        Shortcut for `color()`.
1027        If None is passed as input, will use colors from current active scalars.
1028        """
1029        return self.color(color, alpha)
1030
1031    def alpha(self, opacity=None) -> Union[float, Self]:
1032        """Set/get mesh's transparency. Same as `mesh.opacity()`."""
1033        if opacity is None:
1034            return self.properties.GetOpacity()
1035
1036        self.properties.SetOpacity(opacity)
1037        bfp = self.actor.GetBackfaceProperty()
1038        if bfp:
1039            if opacity < 1:
1040                self.properties_backface = bfp
1041                self.actor.SetBackfaceProperty(None)
1042            else:
1043                self.actor.SetBackfaceProperty(self.properties_backface)
1044        return self
1045
1046    def lut_color_at(self, value) -> np.ndarray:
1047        """
1048        Return the color and alpha in the lookup table at given value.
1049        """
1050        lut = self.mapper.GetLookupTable()
1051        if not lut:
1052            return None
1053        rgb = [0,0,0]
1054        lut.GetColor(value, rgb)
1055        alpha = lut.GetOpacity(value)
1056        return np.array(rgb + [alpha])
1057
1058    def opacity(self, alpha=None) -> Union[float, Self]:
1059        """Set/get mesh's transparency. Same as `mesh.alpha()`."""
1060        return self.alpha(alpha)
1061
1062    def force_opaque(self, value=True) -> Self:
1063        """ Force the Mesh, Line or point cloud to be treated as opaque"""
1064        ## force the opaque pass, fixes picking in vtk9
1065        # but causes other bad troubles with lines..
1066        self.actor.SetForceOpaque(value)
1067        return self
1068
1069    def force_translucent(self, value=True) -> Self:
1070        """ Force the Mesh, Line or point cloud to be treated as translucent"""
1071        self.actor.SetForceTranslucent(value)
1072        return self
1073
1074    def point_size(self, value=None) -> Union[int, Self]:
1075        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
1076        if value is None:
1077            return self.properties.GetPointSize()
1078            # self.properties.SetRepresentationToSurface()
1079        else:
1080            self.properties.SetRepresentationToPoints()
1081            self.properties.SetPointSize(value)
1082        return self
1083
1084    def ps(self, pointsize=None) -> Union[int, Self]:
1085        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
1086        return self.point_size(pointsize)
1087
1088    def render_points_as_spheres(self, value=True) -> Self:
1089        """Make points look spheric or else make them look as squares."""
1090        self.properties.SetRenderPointsAsSpheres(value)
1091        return self
1092
1093    def lighting(
1094        self,
1095        style="",
1096        ambient=None,
1097        diffuse=None,
1098        specular=None,
1099        specular_power=None,
1100        specular_color=None,
1101        metallicity=None,
1102        roughness=None,
1103    ) -> Self:
1104        """
1105        Set the ambient, diffuse, specular and specular_power lighting constants.
1106
1107        Arguments:
1108            style : (str)
1109                preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]`
1110            ambient : (float)
1111                ambient fraction of emission [0-1]
1112            diffuse : (float)
1113                emission of diffused light in fraction [0-1]
1114            specular : (float)
1115                fraction of reflected light [0-1]
1116            specular_power : (float)
1117                precision of reflection [1-100]
1118            specular_color : (color)
1119                color that is being reflected by the surface
1120
1121        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px>
1122
1123        Examples:
1124            - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py)
1125        """
1126        pr = self.properties
1127
1128        if style:
1129
1130            if style != "off":
1131                pr.LightingOn()
1132
1133            if style == "off":
1134                pr.SetInterpolationToFlat()
1135                pr.LightingOff()
1136                return self  ##############
1137
1138            if hasattr(pr, "GetColor"):  # could be Volume
1139                c = pr.GetColor()
1140            else:
1141                c = (1, 1, 0.99)
1142            mpr = self.mapper
1143            if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility():
1144                c = (1,1,0.99)
1145            if   style=='metallic': pars = [0.1, 0.3, 1.0, 10, c]
1146            elif style=='plastic' : pars = [0.3, 0.4, 0.3,  5, c]
1147            elif style=='shiny'   : pars = [0.2, 0.6, 0.8, 50, c]
1148            elif style=='glossy'  : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)]
1149            elif style=='ambient' : pars = [0.8, 0.1, 0.0,  1, (1,1,1)]
1150            elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c]
1151            else:
1152                vedo.logger.error("in lighting(): Available styles are")
1153                vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]")
1154                raise RuntimeError()
1155            pr.SetAmbient(pars[0])
1156            pr.SetDiffuse(pars[1])
1157            pr.SetSpecular(pars[2])
1158            pr.SetSpecularPower(pars[3])
1159            if hasattr(pr, "GetColor"):
1160                pr.SetSpecularColor(pars[4])
1161
1162        if ambient is not None: pr.SetAmbient(ambient)
1163        if diffuse is not None: pr.SetDiffuse(diffuse)
1164        if specular is not None: pr.SetSpecular(specular)
1165        if specular_power is not None: pr.SetSpecularPower(specular_power)
1166        if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color))
1167        if metallicity is not None:
1168            pr.SetInterpolationToPBR()
1169            pr.SetMetallic(metallicity)
1170        if roughness is not None:
1171            pr.SetInterpolationToPBR()
1172            pr.SetRoughness(roughness)
1173
1174        return self
1175
1176    def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self:
1177        """Set point blurring.
1178        Apply a gaussian convolution filter to the points.
1179        In this case the radius `r` is in absolute units of the mesh coordinates.
1180        With emissive set, the halo of point becomes light-emissive.
1181        """
1182        self.properties.SetRepresentationToPoints()
1183        if emissive:
1184            self.mapper.SetEmissive(bool(emissive))
1185        self.mapper.SetScaleFactor(r * 1.4142)
1186
1187        # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
1188        if alpha < 1:
1189            self.mapper.SetSplatShaderCode(
1190                "//VTK::Color::Impl\n"
1191                "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
1192                "if (dist > 1.0) {\n"
1193                "   discard;\n"
1194                "} else {\n"
1195                f"  float scale = ({alpha} - dist);\n"
1196                "   ambientColor *= scale;\n"
1197                "   diffuseColor *= scale;\n"
1198                "}\n"
1199            )
1200            alpha = 1
1201
1202        self.mapper.Modified()
1203        self.actor.Modified()
1204        self.properties.SetOpacity(alpha)
1205        self.actor.SetMapper(self.mapper)
1206        return self
1207
1208    @property
1209    def cellcolors(self):
1210        """
1211        Colorize each cell (face) of a mesh by passing
1212        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1213        Colors levels and opacities must be in the range [0,255].
1214
1215        A single constant color can also be passed as string or RGBA.
1216
1217        A cell array named "CellsRGBA" is automatically created.
1218
1219        Examples:
1220            - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py)
1221            - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py)
1222
1223            ![](https://vedo.embl.es/images/basic/colorMeshCells.png)
1224        """
1225        if "CellsRGBA" not in self.celldata.keys():
1226            lut = self.mapper.GetLookupTable()
1227            vscalars = self.dataset.GetCellData().GetScalars()
1228            if vscalars is None or lut is None:
1229                arr = np.zeros([self.ncells, 4], dtype=np.uint8)
1230                col = np.array(self.properties.GetColor())
1231                col = np.round(col * 255).astype(np.uint8)
1232                alf = self.properties.GetOpacity()
1233                alf = np.round(alf * 255).astype(np.uint8)
1234                arr[:, (0, 1, 2)] = col
1235                arr[:, 3] = alf
1236            else:
1237                cols = lut.MapScalars(vscalars, 0, 0)
1238                arr = utils.vtk2numpy(cols)
1239            self.celldata["CellsRGBA"] = arr
1240        self.celldata.select("CellsRGBA")
1241        return self.celldata["CellsRGBA"]
1242
1243    @cellcolors.setter
1244    def cellcolors(self, value):
1245        if isinstance(value, str):
1246            c = colors.get_color(value)
1247            value = np.array([*c, 1]) * 255
1248            value = np.round(value)
1249
1250        value = np.asarray(value)
1251        n = self.ncells
1252
1253        if value.ndim == 1:
1254            value = np.repeat([value], n, axis=0)
1255
1256        if value.shape[1] == 3:
1257            z = np.zeros((n, 1), dtype=np.uint8)
1258            value = np.append(value, z + 255, axis=1)
1259
1260        assert n == value.shape[0]
1261
1262        self.celldata["CellsRGBA"] = value.astype(np.uint8)
1263        # self.mapper.SetColorModeToDirectScalars() # done in select()
1264        self.celldata.select("CellsRGBA")
1265
1266    @property
1267    def pointcolors(self):
1268        """
1269        Colorize each point (or vertex of a mesh) by passing
1270        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1271        Colors levels and opacities must be in the range [0,255].
1272
1273        A single constant color can also be passed as string or RGBA.
1274
1275        A point array named "PointsRGBA" is automatically created.
1276        """
1277        if "PointsRGBA" not in self.pointdata.keys():
1278            lut = self.mapper.GetLookupTable()
1279            vscalars = self.dataset.GetPointData().GetScalars()
1280            if vscalars is None or lut is None:
1281                # create a constant array
1282                arr = np.zeros([self.npoints, 4], dtype=np.uint8)
1283                col = np.array(self.properties.GetColor())
1284                col = np.round(col * 255).astype(np.uint8)
1285                alf = self.properties.GetOpacity()
1286                alf = np.round(alf * 255).astype(np.uint8)
1287                arr[:, (0, 1, 2)] = col
1288                arr[:, 3] = alf
1289            else:
1290                cols = lut.MapScalars(vscalars, 0, 0)
1291                arr = utils.vtk2numpy(cols)
1292            self.pointdata["PointsRGBA"] = arr
1293        self.pointdata.select("PointsRGBA")
1294        return self.pointdata["PointsRGBA"]
1295
1296    @pointcolors.setter
1297    def pointcolors(self, value):
1298        if isinstance(value, str):
1299            c = colors.get_color(value)
1300            value = np.array([*c, 1]) * 255
1301            value = np.round(value)
1302
1303        value = np.asarray(value)
1304        n = self.npoints
1305
1306        if value.ndim == 1:
1307            value = np.repeat([value], n, axis=0)
1308
1309        if value.shape[1] == 3:
1310            z = np.zeros((n, 1), dtype=np.uint8)
1311            value = np.append(value, z + 255, axis=1)
1312
1313        assert n == value.shape[0]
1314
1315        self.pointdata["PointsRGBA"] = value.astype(np.uint8)
1316        self.mapper.SetColorModeToDirectScalars() # also done in select()
1317        self.pointdata.select("PointsRGBA")
1318
1319    #####################################################################################
1320    def cmap(
1321        self,
1322        input_cmap,
1323        input_array=None,
1324        on="",
1325        name="Scalars",
1326        vmin=None,
1327        vmax=None,
1328        n_colors=256,
1329        alpha=1.0,
1330        logscale=False,
1331    ) -> Self:
1332        """
1333        Set individual point/cell colors by providing a list of scalar values and a color map.
1334
1335        Arguments:
1336            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
1337                color map scheme to transform a real number into a color.
1338            input_array : (str, list, vtkArray)
1339                can be the string name of an existing array, a new array or a `vtkArray`.
1340            on : (str)
1341                either 'points' or 'cells' or blank (automatic).
1342                Apply the color map to data which is defined on either points or cells.
1343            name : (str)
1344                give a name to the provided array (if input_array is an array)
1345            vmin : (float)
1346                clip scalars to this minimum value
1347            vmax : (float)
1348                clip scalars to this maximum value
1349            n_colors : (int)
1350                number of distinct colors to be used in colormap table.
1351            alpha : (float, list)
1352                Mesh transparency. Can be a `list` of values one for each vertex.
1353            logscale : (bool)
1354                Use logscale
1355
1356        Examples:
1357            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
1358            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
1359            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
1360            - (and many others)
1361
1362                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
1363        """
1364        self._cmap_name = input_cmap
1365
1366        if on == "":
1367            try:
1368                on = self.mapper.GetScalarModeAsString().replace("Use", "")
1369                if on not in ["PointData", "CellData"]: # can be "Default"
1370                    on = "points"
1371                    self.mapper.SetScalarModeToUsePointData()
1372            except AttributeError:
1373                on = "points"
1374        elif on == "Default":
1375            on = "points"
1376            self.mapper.SetScalarModeToUsePointData()
1377
1378        if input_array is None:
1379            if not self.pointdata.keys() and self.celldata.keys():
1380                on = "cells"
1381                if not self.dataset.GetCellData().GetScalars():
1382                    input_array = 0  # pick the first at hand
1383
1384        if "point" in on.lower():
1385            data = self.dataset.GetPointData()
1386            n = self.dataset.GetNumberOfPoints()
1387        elif "cell" in on.lower():
1388            data = self.dataset.GetCellData()
1389            n = self.dataset.GetNumberOfCells()
1390        else:
1391            vedo.logger.error(
1392                f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}")
1393            raise RuntimeError()
1394
1395        if input_array is None:  # if None try to fetch the active scalars
1396            arr = data.GetScalars()
1397            if not arr:
1398                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
1399                return self
1400
1401            if not arr.GetName():  # sometimes arrays dont have a name..
1402                arr.SetName(name)
1403
1404        elif isinstance(input_array, str):  # if a string is passed
1405            arr = data.GetArray(input_array)
1406            if not arr:
1407                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
1408                return self
1409
1410        elif isinstance(input_array, int):  # if an int is passed
1411            if input_array < data.GetNumberOfArrays():
1412                arr = data.GetArray(input_array)
1413            else:
1414                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
1415                return self
1416
1417        elif utils.is_sequence(input_array):  # if a numpy array is passed
1418            npts = len(input_array)
1419            if npts != n:
1420                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
1421                return self
1422            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
1423            data.AddArray(arr)
1424            data.Modified()
1425
1426        elif isinstance(input_array, vtki.vtkArray):  # if a vtkArray is passed
1427            arr = input_array
1428            data.AddArray(arr)
1429            data.Modified()
1430
1431        else:
1432            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
1433            raise RuntimeError()
1434
1435        # Now we have array "arr"
1436        array_name = arr.GetName()
1437
1438        if arr.GetNumberOfComponents() == 1:
1439            if vmin is None:
1440                vmin = arr.GetRange()[0]
1441            if vmax is None:
1442                vmax = arr.GetRange()[1]
1443        else:
1444            if vmin is None or vmax is None:
1445                vn = utils.mag(utils.vtk2numpy(arr))
1446            if vmin is None:
1447                vmin = vn.min()
1448            if vmax is None:
1449                vmax = vn.max()
1450
1451        # interpolate alphas if they are not constant
1452        if not utils.is_sequence(alpha):
1453            alpha = [alpha] * n_colors
1454        else:
1455            v = np.linspace(0, 1, n_colors, endpoint=True)
1456            xp = np.linspace(0, 1, len(alpha), endpoint=True)
1457            alpha = np.interp(v, xp, alpha)
1458
1459        ########################### build the look-up table
1460        if isinstance(input_cmap, vtki.vtkLookupTable):  # vtkLookupTable
1461            lut = input_cmap
1462
1463        elif utils.is_sequence(input_cmap):  # manual sequence of colors
1464            lut = vtki.vtkLookupTable()
1465            if logscale:
1466                lut.SetScaleToLog10()
1467            lut.SetRange(vmin, vmax)
1468            ncols = len(input_cmap)
1469            lut.SetNumberOfTableValues(ncols)
1470
1471            for i, c in enumerate(input_cmap):
1472                r, g, b = colors.get_color(c)
1473                lut.SetTableValue(i, r, g, b, alpha[i])
1474            lut.Build()
1475
1476        else:
1477            # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
1478            lut = vtki.vtkLookupTable()
1479            if logscale:
1480                lut.SetScaleToLog10()
1481            lut.SetVectorModeToMagnitude()
1482            lut.SetRange(vmin, vmax)
1483            lut.SetNumberOfTableValues(n_colors)
1484            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
1485            for i, c in enumerate(mycols):
1486                r, g, b = c
1487                lut.SetTableValue(i, r, g, b, alpha[i])
1488            lut.Build()
1489
1490        # TEST NEW WAY
1491        self.mapper.SetLookupTable(lut)
1492        self.mapper.ScalarVisibilityOn()
1493        self.mapper.SetColorModeToMapScalars()
1494        self.mapper.SetScalarRange(lut.GetRange())
1495        if "point" in on.lower():
1496            self.pointdata.select(array_name)
1497        else:
1498            self.celldata.select(array_name)
1499        return self
1500
1501        # # TEST this is the old way:
1502        # # arr.SetLookupTable(lut) # wrong! causes weird instabilities with LUT
1503        # # if data.GetScalars():
1504        # #     data.GetScalars().SetLookupTable(lut)
1505        # #     data.GetScalars().Modified()
1506
1507        # data.SetActiveScalars(array_name)
1508        # # data.SetScalars(arr)  # wrong! it deletes array in position 0, never use SetScalars
1509        # # data.SetActiveAttribute(array_name, 0) # boh!
1510
1511        # self.mapper.SetLookupTable(lut)
1512        # self.mapper.SetColorModeToMapScalars()  # so we dont need to convert uint8 scalars
1513
1514        # self.mapper.ScalarVisibilityOn()
1515        # self.mapper.SetScalarRange(lut.GetRange())
1516
1517        # if on.startswith("point"):
1518        #     self.mapper.SetScalarModeToUsePointData()
1519        # else:
1520        #     self.mapper.SetScalarModeToUseCellData()
1521        # if hasattr(self.mapper, "SetArrayName"):
1522        #     self.mapper.SetArrayName(array_name)
1523        # return self
1524
1525    def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self:
1526        """
1527        Add a trailing line to mesh.
1528        This new mesh is accessible through `mesh.trail`.
1529
1530        Arguments:
1531            offset : (float)
1532                set an offset vector from the object center.
1533            n : (int)
1534                number of segments
1535            lw : (float)
1536                line width of the trail
1537
1538        Examples:
1539            - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py)
1540
1541                ![](https://vedo.embl.es/images/simulations/trail.gif)
1542
1543            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1544            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1545        """
1546        if self.trail is None:
1547            pos = self.pos()
1548            self.trail_offset = np.asarray(offset)
1549            self.trail_points = [pos] * n
1550
1551            if c is None:
1552                col = self.properties.GetColor()
1553            else:
1554                col = colors.get_color(c)
1555
1556            tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw)
1557            self.trail = tline  # holds the Line
1558            self.trail.initilized = False # so the first update will be a reset
1559        return self
1560
1561    def update_trail(self) -> Self:
1562        """
1563        Update the trailing line of a moving object.
1564        """
1565        currentpos = self.pos()
1566        if not self.trail.initilized:
1567            self.trail_points = [currentpos] * self.trail.npoints
1568            self.trail.initilized = True
1569            return self
1570        self.trail_points.append(currentpos)  # cycle
1571        self.trail_points.pop(0)
1572
1573        data = np.array(self.trail_points) + self.trail_offset
1574        tpoly = self.trail.dataset
1575        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1576        return self
1577
1578    def _compute_shadow(self, plane, point, direction):
1579        shad = self.clone()
1580        shad.name = "Shadow"
1581
1582        tarr = shad.dataset.GetPointData().GetTCoords()
1583        if tarr:  # remove any texture coords
1584            tname = tarr.GetName()
1585            shad.pointdata.remove(tname)
1586            shad.dataset.GetPointData().SetTCoords(None)
1587            shad.actor.SetTexture(None)
1588
1589        pts = shad.vertices
1590        if plane == "x":
1591            # shad = shad.project_on_plane('x')
1592            # instead do it manually so in case of alpha<1
1593            # we dont see glitches due to coplanar points
1594            # we leave a small tolerance of 0.1% in thickness
1595            x0, x1 = self.xbounds()
1596            pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[0]
1597            shad.vertices = pts
1598            shad.x(point)
1599        elif plane == "y":
1600            x0, x1 = self.ybounds()
1601            pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[1]
1602            shad.vertices = pts
1603            shad.y(point)
1604        elif plane == "z":
1605            x0, x1 = self.zbounds()
1606            pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[2]
1607            shad.vertices = pts
1608            shad.z(point)
1609        else:
1610            shad = shad.project_on_plane(plane, point, direction)
1611        return shad
1612
1613    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1614        """
1615        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1616        The output is a new `Mesh` representing the shadow.
1617        This new mesh is accessible through `mesh.shadow`.
1618        By default the shadow mesh is placed on the bottom wall of the bounding box.
1619
1620        See also `pointcloud.project_on_plane()`.
1621
1622        Arguments:
1623            plane : (str, Plane)
1624                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1625                represents x-plane, y-plane and z-plane, respectively.
1626                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1627            point : (float, array)
1628                if plane is `str`, point should be a float represents the intercept.
1629                Otherwise, point is the camera point of perspective projection
1630            direction : (list)
1631                direction of oblique projection
1632            culling : (int)
1633                choose between front [1] or backface [-1] culling or None.
1634
1635        Examples:
1636            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1637            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1638            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1639
1640            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1641        """
1642        shad = self._compute_shadow(plane, point, direction)
1643        shad.c(c).alpha(alpha)
1644
1645        try:
1646            # Points dont have these methods
1647            shad.flat()
1648            if culling in (1, True):
1649                shad.frontface_culling()
1650            elif culling == -1:
1651                shad.backface_culling()
1652        except AttributeError:
1653            pass
1654
1655        shad.properties.LightingOff()
1656        shad.actor.SetPickable(False)
1657        shad.actor.SetUseBounds(True)
1658
1659        if shad not in self.shadows:
1660            self.shadows.append(shad)
1661            shad.info = dict(plane=plane, point=point, direction=direction)
1662            # shad.metadata["plane"] = plane
1663            # shad.metadata["point"] = point
1664            # print("AAAA", direction, plane, point)
1665            # if direction is None:
1666            #     direction = [0,0,0]
1667            # shad.metadata["direction"] = direction
1668        return self
1669
1670    def update_shadows(self) -> Self:
1671        """Update the shadows of a moving object."""
1672        for sha in self.shadows:
1673            plane = sha.info["plane"]
1674            point = sha.info["point"]
1675            direction = sha.info["direction"]
1676            # print("update_shadows direction", direction,plane,point )
1677            # plane = sha.metadata["plane"]
1678            # point = sha.metadata["point"]
1679            # direction = sha.metadata["direction"]
1680            # if direction[0]==0 and direction[1]==0 and direction[2]==0:
1681            #     direction = None
1682            # print("BBBB", sha.metadata["direction"],
1683            #       sha.metadata["plane"], sha.metadata["point"])
1684            new_sha = self._compute_shadow(plane, point, direction)
1685            sha._update(new_sha.dataset)
1686        if self.trail:
1687            self.trail.update_shadows()
1688        return self
1689
1690    def labels(
1691        self,
1692        content=None,
1693        on="points",
1694        scale=None,
1695        xrot=0.0,
1696        yrot=0.0,
1697        zrot=0.0,
1698        ratio=1,
1699        precision=None,
1700        italic=False,
1701        font="",
1702        justify="",
1703        c="black",
1704        alpha=1.0,
1705    ) -> Union["vedo.Mesh", None]:
1706        """
1707        Generate value or ID labels for mesh cells or points.
1708        For large nr. of labels use `font="VTK"` which is much faster.
1709
1710        See also:
1711            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1712
1713        Arguments:
1714            content : (list,int,str)
1715                either 'id', 'cellid', array name or array number.
1716                A array can also be passed (must match the nr. of points or cells).
1717            on : (str)
1718                generate labels for "cells" instead of "points"
1719            scale : (float)
1720                absolute size of labels, if left as None it is automatic
1721            zrot : (float)
1722                local rotation angle of label in degrees
1723            ratio : (int)
1724                skipping ratio, to reduce nr of labels for large meshes
1725            precision : (int)
1726                numeric precision of labels
1727
1728        ```python
1729        from vedo import *
1730        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1731        point_ids = s.labels('id', on="points").c('green')
1732        cell_ids  = s.labels('id', on="cells" ).c('black')
1733        show(s, point_ids, cell_ids)
1734        ```
1735        ![](https://vedo.embl.es/images/feats/labels.png)
1736
1737        Examples:
1738            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1739
1740                ![](https://vedo.embl.es/images/basic/boundaries.png)
1741        """
1742
1743        cells = False
1744        if "cell" in on or "face" in on:
1745            cells = True
1746            justify = "centered" if justify == "" else justify
1747
1748        if isinstance(content, str):
1749            if content in ("pointid", "pointsid"):
1750                cells = False
1751                content = "id"
1752                justify = "bottom-left" if justify == "" else justify
1753            if content in ("cellid", "cellsid"):
1754                cells = True
1755                content = "id"
1756                justify = "centered" if justify == "" else justify
1757
1758        try:
1759            if cells:
1760                ns = np.sqrt(self.ncells)
1761                elems = self.cell_centers().points
1762                norms = self.cell_normals
1763                justify = "centered" if justify == "" else justify
1764            else:
1765                ns = np.sqrt(self.npoints)
1766                elems = self.vertices
1767                norms = self.vertex_normals
1768        except AttributeError:
1769            norms = []
1770
1771        if not justify:
1772            justify = "bottom-left"
1773
1774        hasnorms = False
1775        if len(norms) > 0:
1776            hasnorms = True
1777
1778        if scale is None:
1779            if not ns:
1780                ns = 100
1781            scale = self.diagonal_size() / ns / 10
1782
1783        arr = None
1784        mode = 0
1785        if content is None:
1786            mode = 0
1787            if cells:
1788                if self.dataset.GetCellData().GetScalars():
1789                    name = self.dataset.GetCellData().GetScalars().GetName()
1790                    arr = self.celldata[name]
1791            else:
1792                if self.dataset.GetPointData().GetScalars():
1793                    name = self.dataset.GetPointData().GetScalars().GetName()
1794                    arr = self.pointdata[name]
1795        elif isinstance(content, (str, int)):
1796            if content == "id":
1797                mode = 1
1798            elif cells:
1799                mode = 0
1800                arr = self.celldata[content]
1801            else:
1802                mode = 0
1803                arr = self.pointdata[content]
1804        elif utils.is_sequence(content):
1805            mode = 0
1806            arr = content
1807
1808        if arr is None and mode == 0:
1809            vedo.logger.error("in labels(), array not found in point or cell data")
1810            return None
1811
1812        ratio = int(ratio+0.5)
1813        tapp = vtki.new("AppendPolyData")
1814        has_inputs = False
1815
1816        for i, e in enumerate(elems):
1817            if i % ratio:
1818                continue
1819
1820            if mode == 1:
1821                txt_lab = str(i)
1822            else:
1823                if precision:
1824                    txt_lab = utils.precision(arr[i], precision)
1825                else:
1826                    txt_lab = str(arr[i])
1827
1828            if not txt_lab:
1829                continue
1830
1831            if font == "VTK":
1832                tx = vtki.new("VectorText")
1833                tx.SetText(txt_lab)
1834                tx.Update()
1835                tx_poly = tx.GetOutput()
1836            else:
1837                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset
1838
1839            if tx_poly.GetNumberOfPoints() == 0:
1840                continue  ######################
1841
1842            T = vtki.vtkTransform()
1843            T.PostMultiply()
1844            if italic:
1845                T.Concatenate([1, 0.2, 0, 0,
1846                               0, 1  , 0, 0,
1847                               0, 0  , 1, 0,
1848                               0, 0  , 0, 1])
1849            if hasnorms:
1850                ni = norms[i]
1851                if cells and font=="VTK":  # center-justify
1852                    bb = tx_poly.GetBounds()
1853                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1854                    T.Translate(-dx, -dy, 0)
1855                if xrot: T.RotateX(xrot)
1856                if yrot: T.RotateY(yrot)
1857                if zrot: T.RotateZ(zrot)
1858                crossvec = np.cross([0, 0, 1], ni)
1859                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1860                T.RotateWXYZ(float(angle), crossvec.tolist())
1861                T.Translate(ni / 100)
1862            else:
1863                if xrot: T.RotateX(xrot)
1864                if yrot: T.RotateY(yrot)
1865                if zrot: T.RotateZ(zrot)
1866            T.Scale(scale, scale, scale)
1867            T.Translate(e)
1868            tf = vtki.new("TransformPolyDataFilter")
1869            tf.SetInputData(tx_poly)
1870            tf.SetTransform(T)
1871            tf.Update()
1872            tapp.AddInputData(tf.GetOutput())
1873            has_inputs = True
1874
1875        if has_inputs:
1876            tapp.Update()
1877            lpoly = tapp.GetOutput()
1878        else:
1879            lpoly = vtki.vtkPolyData()
1880        ids = vedo.Mesh(lpoly, c=c, alpha=alpha)
1881        ids.properties.LightingOff()
1882        ids.actor.PickableOff()
1883        ids.actor.SetUseBounds(False)
1884        ids.name = "Labels"
1885        return ids
1886
1887    def labels2d(
1888        self,
1889        content="id",
1890        on="points",
1891        scale=1.0,
1892        precision=4,
1893        font="Calco",
1894        justify="bottom-left",
1895        angle=0.0,
1896        frame=False,
1897        c="black",
1898        bc=None,
1899        alpha=1.0,
1900    ) -> Union["Actor2D", None]:
1901        """
1902        Generate value or ID bi-dimensional labels for mesh cells or points.
1903
1904        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
1905
1906        Arguments:
1907            content : (str)
1908                either 'id', 'cellid', or array name
1909            on : (str)
1910                generate labels for "cells" instead of "points" (the default)
1911            scale : (float)
1912                size scaling of labels
1913            precision : (int)
1914                precision of numeric labels
1915            angle : (float)
1916                local rotation angle of label in degrees
1917            frame : (bool)
1918                draw a frame around the label
1919            bc : (str)
1920                background color of the label
1921
1922        ```python
1923        from vedo import Sphere, show
1924        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
1925        sph.celldata["zvals"] = sph.cell_centers().coordinates[:,2]
1926        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
1927        show(sph, l2d, axes=1).close()
1928        ```
1929        ![](https://vedo.embl.es/images/feats/labels2d.png)
1930        """
1931        cells = False
1932        if "cell" in on:
1933            cells = True
1934
1935        if isinstance(content, str):
1936            if content in ("id", "pointid", "pointsid"):
1937                cells = False
1938                content = "id"
1939            if content in ("cellid", "cellsid"):
1940                cells = True
1941                content = "id"
1942
1943        if cells:
1944            if content != "id" and content not in self.celldata.keys():
1945                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
1946                return None
1947            arr = self.dataset.GetCellData().GetScalars()
1948            poly = self.cell_centers().dataset
1949            poly.GetPointData().SetScalars(arr)
1950        else:
1951            arr = self.dataset.GetPointData().GetScalars()
1952            poly = self.dataset
1953            if content != "id" and content not in self.pointdata.keys():
1954                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
1955                return None
1956
1957        mp = vtki.new("LabeledDataMapper")
1958
1959        if content == "id":
1960            mp.SetLabelModeToLabelIds()
1961        else:
1962            mp.SetLabelModeToLabelScalars()
1963            if precision is not None:
1964                dtype = arr.GetDataType()
1965                if dtype in (vtki.VTK_FLOAT, vtki.VTK_DOUBLE):
1966                    mp.SetLabelFormat(f"%-#.{precision}g")
1967
1968        pr = mp.GetLabelTextProperty()
1969        c = colors.get_color(c)
1970        pr.SetColor(c)
1971        pr.SetOpacity(alpha)
1972        pr.SetFrame(frame)
1973        pr.SetFrameColor(c)
1974        pr.SetItalic(False)
1975        pr.BoldOff()
1976        pr.ShadowOff()
1977        pr.UseTightBoundingBoxOn()
1978        pr.SetOrientation(angle)
1979        pr.SetFontFamily(vtki.VTK_FONT_FILE)
1980        fl = utils.get_font_path(font)
1981        pr.SetFontFile(fl)
1982        pr.SetFontSize(int(20 * scale))
1983
1984        if "cent" in justify or "mid" in justify:
1985            pr.SetJustificationToCentered()
1986        elif "rig" in justify:
1987            pr.SetJustificationToRight()
1988        elif "left" in justify:
1989            pr.SetJustificationToLeft()
1990        # ------
1991        if "top" in justify:
1992            pr.SetVerticalJustificationToTop()
1993        else:
1994            pr.SetVerticalJustificationToBottom()
1995
1996        if bc is not None:
1997            bc = colors.get_color(bc)
1998            pr.SetBackgroundColor(bc)
1999            pr.SetBackgroundOpacity(alpha)
2000
2001        mp.SetInputData(poly)
2002        a2d = Actor2D()
2003        a2d.PickableOff()
2004        a2d.SetMapper(mp)
2005        return a2d
2006
2007    def legend(self, txt) -> Self:
2008        """Book a legend text."""
2009        self.info["legend"] = txt
2010        # self.metadata["legend"] = txt
2011        return self
2012
2013    def flagpole(
2014        self,
2015        txt=None,
2016        point=None,
2017        offset=None,
2018        s=None,
2019        font="Calco",
2020        rounded=True,
2021        c=None,
2022        alpha=1.0,
2023        lw=2,
2024        italic=0.0,
2025        padding=0.1,
2026    ) -> Union["vedo.Mesh", None]:
2027        """
2028        Generate a flag pole style element to describe an object.
2029        Returns a `Mesh` object.
2030
2031        Use flagpole.follow_camera() to make it face the camera in the scene.
2032
2033        Consider using `settings.use_parallel_projection = True`
2034        to avoid perspective distortions.
2035
2036        See also `flagpost()`.
2037
2038        Arguments:
2039            txt : (str)
2040                Text to display. The default is the filename or the object name.
2041            point : (list)
2042                position of the flagpole pointer.
2043            offset : (list)
2044                text offset wrt the application point.
2045            s : (float)
2046                size of the flagpole.
2047            font : (str)
2048                font face. Check [available fonts here](https://vedo.embl.es/fonts).
2049            rounded : (bool)
2050                draw a rounded or squared box around the text.
2051            c : (list)
2052                text and box color.
2053            alpha : (float)
2054                opacity of text and box.
2055            lw : (float)
2056                line with of box frame.
2057            italic : (float)
2058                italicness of text.
2059
2060        Examples:
2061            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2062
2063                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2064
2065            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2066            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2067            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2068        """
2069        objs = []
2070
2071        if txt is None:
2072            if self.filename:
2073                txt = self.filename.split("/")[-1]
2074            elif self.name:
2075                txt = self.name
2076            else:
2077                return None
2078
2079        x0, x1, y0, y1, z0, z1 = self.bounds()
2080        d = self.diagonal_size()
2081        if point is None:
2082            if d:
2083                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2084                # point = self.closest_point([x1, y0, z1])
2085            else:  # it's a Point
2086                point = self.transform.position
2087
2088        pt = utils.make3d(point)
2089
2090        if offset is None:
2091            offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0]
2092        offset = utils.make3d(offset)
2093
2094        if s is None:
2095            s = d / 20
2096
2097        sph = None
2098        if d and (z1 - z0) / d > 0.1:
2099            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2100
2101        if c is None:
2102            c = np.array(self.color()) / 1.4
2103
2104        lab = vedo.shapes.Text3D(
2105            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center"
2106        )
2107        objs.append(lab)
2108
2109        if d and not sph:
2110            sph = vedo.shapes.Circle(pt, r=s / 3, res=16)
2111        objs.append(sph)
2112
2113        x0, x1, y0, y1, z0, z1 = lab.bounds()
2114        aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)]
2115        if rounded:
2116            box = vedo.shapes.KSpline(aline, closed=True)
2117        else:
2118            box = vedo.shapes.Line(aline, closed=True)
2119
2120        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2121
2122        # box.actor.SetOrigin(cnt)
2123        box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt)
2124        objs.append(box)
2125
2126        x0, x1, y0, y1, z0, z1 = box.bounds()
2127        if x0 < pt[0] < x1:
2128            c0 = box.closest_point(pt)
2129            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2130        elif (pt[0] - x0) < (x1 - pt[0]):
2131            c0 = [x0, (y0 + y1) / 2, pt[2]]
2132            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2133        else:
2134            c0 = [x1, (y0 + y1) / 2, pt[2]]
2135            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2136
2137        con = vedo.shapes.Line([c0, c1, pt])
2138        objs.append(con)
2139
2140        mobjs = vedo.merge(objs).c(c).alpha(alpha)
2141        mobjs.name = "FlagPole"
2142        mobjs.bc("tomato").pickable(False)
2143        mobjs.properties.LightingOff()
2144        mobjs.properties.SetLineWidth(lw)
2145        mobjs.actor.UseBoundsOff()
2146        mobjs.actor.SetPosition([0,0,0])
2147        mobjs.actor.SetOrigin(pt)
2148        return mobjs
2149
2150        # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha)
2151        # mobjs.name = "FlagPole"
2152        # # mobjs.bc("tomato").pickable(False)
2153        # # mobjs.properties.LightingOff()
2154        # # mobjs.properties.SetLineWidth(lw)
2155        # # mobjs.actor.UseBoundsOff()
2156        # # mobjs.actor.SetPosition([0,0,0])
2157        # # mobjs.actor.SetOrigin(pt)
2158        # # print(pt)
2159        # return mobjs
2160
2161    def flagpost(
2162        self,
2163        txt=None,
2164        point=None,
2165        offset=None,
2166        s=1.0,
2167        c="k9",
2168        bc="k1",
2169        alpha=1,
2170        lw=0,
2171        font="Calco",
2172        justify="center-left",
2173        vspacing=1.0,
2174    ) -> Union["vedo.addons.Flagpost", None]:
2175        """
2176        Generate a flag post style element to describe an object.
2177
2178        Arguments:
2179            txt : (str)
2180                Text to display. The default is the filename or the object name.
2181            point : (list)
2182                position of the flag anchor point. The default is None.
2183            offset : (list)
2184                a 3D displacement or offset. The default is None.
2185            s : (float)
2186                size of the text to be shown
2187            c : (list)
2188                color of text and line
2189            bc : (list)
2190                color of the flag background
2191            alpha : (float)
2192                opacity of text and box.
2193            lw : (int)
2194                line with of box frame. The default is 0.
2195            font : (str)
2196                font name. Use a monospace font for better rendering. The default is "Calco".
2197                Type `vedo -r fonts` for a font demo.
2198                Check [available fonts here](https://vedo.embl.es/fonts).
2199            justify : (str)
2200                internal text justification. The default is "center-left".
2201            vspacing : (float)
2202                vertical spacing between lines.
2203
2204        Examples:
2205            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2206
2207            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2208        """
2209        if txt is None:
2210            if self.filename:
2211                txt = self.filename.split("/")[-1]
2212            elif self.name:
2213                txt = self.name
2214            else:
2215                return None
2216
2217        x0, x1, y0, y1, z0, z1 = self.bounds()
2218        d = self.diagonal_size()
2219        if point is None:
2220            if d:
2221                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2222            else:  # it's a Point
2223                point = self.transform.position
2224
2225        point = utils.make3d(point)
2226
2227        if offset is None:
2228            offset = [0, 0, (z1 - z0) / 2]
2229        offset = utils.make3d(offset)
2230
2231        fpost = vedo.addons.Flagpost(
2232            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2233        )
2234        self._caption = fpost
2235        return fpost
2236
2237    def caption(
2238        self,
2239        txt=None,
2240        point=None,
2241        size=(0.30, 0.15),
2242        padding=5,
2243        font="Calco",
2244        justify="center-right",
2245        vspacing=1.0,
2246        c=None,
2247        alpha=1.0,
2248        lw=1,
2249        ontop=True,
2250    ) -> Union["vtki.vtkCaptionActor2D", None]:
2251        """
2252        Create a 2D caption to an object which follows the camera movements.
2253        Latex is not supported. Returns the same input object for concatenation.
2254
2255        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2256        with similar functionality.
2257
2258        Arguments:
2259            txt : (str)
2260                text to be rendered. The default is the file name.
2261            point : (list)
2262                anchoring point. The default is None.
2263            size : (list)
2264                (width, height) of the caption box. The default is (0.30, 0.15).
2265            padding : (float)
2266                padding space of the caption box in pixels. The default is 5.
2267            font : (str)
2268                font name. Use a monospace font for better rendering. The default is "VictorMono".
2269                Type `vedo -r fonts` for a font demo.
2270                Check [available fonts here](https://vedo.embl.es/fonts).
2271            justify : (str)
2272                internal text justification. The default is "center-right".
2273            vspacing : (float)
2274                vertical spacing between lines. The default is 1.
2275            c : (str)
2276                text and box color. The default is 'lb'.
2277            alpha : (float)
2278                text and box transparency. The default is 1.
2279            lw : (int)
2280                line width in pixels. The default is 1.
2281            ontop : (bool)
2282                keep the 2d caption always on top. The default is True.
2283
2284        Examples:
2285            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2286
2287                ![](https://vedo.embl.es/images/pyplot/caption.png)
2288
2289            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2290            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2291        """
2292        if txt is None:
2293            if self.filename:
2294                txt = self.filename.split("/")[-1]
2295            elif self.name:
2296                txt = self.name
2297
2298        if not txt:  # disable it
2299            self._caption = None
2300            return None
2301
2302        for r in vedo.shapes._reps:
2303            txt = txt.replace(r[0], r[1])
2304
2305        if c is None:
2306            c = np.array(self.properties.GetColor()) / 2
2307        else:
2308            c = colors.get_color(c)
2309
2310        if point is None:
2311            x0, x1, y0, y1, _, z1 = self.dataset.GetBounds()
2312            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2313            point = self.closest_point(pt)
2314
2315        capt = vtki.vtkCaptionActor2D()
2316        capt.SetAttachmentPoint(point)
2317        capt.SetBorder(True)
2318        capt.SetLeader(True)
2319        sph = vtki.new("SphereSource")
2320        sph.Update()
2321        capt.SetLeaderGlyphData(sph.GetOutput())
2322        capt.SetMaximumLeaderGlyphSize(5)
2323        capt.SetPadding(int(padding))
2324        capt.SetCaption(txt)
2325        capt.SetWidth(size[0])
2326        capt.SetHeight(size[1])
2327        capt.SetThreeDimensionalLeader(not ontop)
2328
2329        pra = capt.GetProperty()
2330        pra.SetColor(c)
2331        pra.SetOpacity(alpha)
2332        pra.SetLineWidth(lw)
2333
2334        pr = capt.GetCaptionTextProperty()
2335        pr.SetFontFamily(vtki.VTK_FONT_FILE)
2336        fl = utils.get_font_path(font)
2337        pr.SetFontFile(fl)
2338        pr.ShadowOff()
2339        pr.BoldOff()
2340        pr.FrameOff()
2341        pr.SetColor(c)
2342        pr.SetOpacity(alpha)
2343        pr.SetJustificationToLeft()
2344        if "top" in justify:
2345            pr.SetVerticalJustificationToTop()
2346        if "bottom" in justify:
2347            pr.SetVerticalJustificationToBottom()
2348        if "cent" in justify:
2349            pr.SetVerticalJustificationToCentered()
2350            pr.SetJustificationToCentered()
2351        if "left" in justify:
2352            pr.SetJustificationToLeft()
2353        if "right" in justify:
2354            pr.SetJustificationToRight()
2355        pr.SetLineSpacing(vspacing)
2356        return capt
2357
2358
2359#####################################################################
2360class MeshVisual(PointsVisual):
2361    """Class to manage the visual aspects of a `Mesh` object."""
2362
2363    def __init__(self) -> None:
2364        # print("INIT MeshVisual", super())
2365        super().__init__()
2366
2367    def follow_camera(self, camera=None, origin=None) -> Self:
2368        """
2369        Return an object that will follow camera movements and stay locked to it.
2370        Use `mesh.follow_camera(False)` to disable it.
2371
2372        A `vtkCamera` object can also be passed.
2373        """
2374        if camera is False:
2375            try:
2376                self.SetCamera(None)
2377                return self
2378            except AttributeError:
2379                return self
2380
2381        factor = vtki.vtkFollower()
2382        factor.SetMapper(self.mapper)
2383        factor.SetProperty(self.properties)
2384        factor.SetBackfaceProperty(self.actor.GetBackfaceProperty())
2385        factor.SetTexture(self.actor.GetTexture())
2386        factor.SetScale(self.actor.GetScale())
2387        # factor.SetOrientation(self.actor.GetOrientation())
2388        factor.SetPosition(self.actor.GetPosition())
2389        factor.SetUseBounds(self.actor.GetUseBounds())
2390
2391        if origin is None:
2392            factor.SetOrigin(self.actor.GetOrigin())
2393        else:
2394            factor.SetOrigin(origin)
2395
2396        factor.PickableOff()
2397
2398        if isinstance(camera, vtki.vtkCamera):
2399            factor.SetCamera(camera)
2400        else:
2401            plt = vedo.plotter_instance
2402            if plt and plt.renderer and plt.renderer.GetActiveCamera():
2403                factor.SetCamera(plt.renderer.GetActiveCamera())
2404
2405        self.actor = None
2406        factor.retrieve_object = weak_ref_to(self)
2407        self.actor = factor
2408        return self
2409
2410    def wireframe(self, value=True) -> Self:
2411        """Set mesh's representation as wireframe or solid surface."""
2412        if value:
2413            self.properties.SetRepresentationToWireframe()
2414        else:
2415            self.properties.SetRepresentationToSurface()
2416        return self
2417
2418    def flat(self)  -> Self:
2419        """Set surface interpolation to flat.
2420
2421        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700">
2422        """
2423        self.properties.SetInterpolationToFlat()
2424        return self
2425
2426    def phong(self) -> Self:
2427        """Set surface interpolation to "phong"."""
2428        self.properties.SetInterpolationToPhong()
2429        return self
2430
2431    def backface_culling(self, value=True) -> Self:
2432        """Set culling of polygons based on orientation of normal with respect to camera."""
2433        self.properties.SetBackfaceCulling(value)
2434        return self
2435
2436    def render_lines_as_tubes(self, value=True) -> Self:
2437        """Wrap a fake tube around a simple line for visualization"""
2438        self.properties.SetRenderLinesAsTubes(value)
2439        return self
2440
2441    def frontface_culling(self, value=True) -> Self:
2442        """Set culling of polygons based on orientation of normal with respect to camera."""
2443        self.properties.SetFrontfaceCulling(value)
2444        return self
2445
2446    def backcolor(self, bc=None) -> Union[Self, np.ndarray]:
2447        """
2448        Set/get mesh's backface color.
2449        """
2450        back_prop = self.actor.GetBackfaceProperty()
2451
2452        if bc is None:
2453            if back_prop:
2454                return back_prop.GetDiffuseColor()
2455            return self
2456
2457        if self.properties.GetOpacity() < 1:
2458            return self
2459
2460        if not back_prop:
2461            back_prop = vtki.vtkProperty()
2462
2463        back_prop.SetDiffuseColor(colors.get_color(bc))
2464        back_prop.SetOpacity(self.properties.GetOpacity())
2465        self.actor.SetBackfaceProperty(back_prop)
2466        self.mapper.ScalarVisibilityOff()
2467        return self
2468
2469    def bc(self, backcolor=False) -> Union[Self, np.ndarray]:
2470        """Shortcut for `mesh.backcolor()`."""
2471        return self.backcolor(backcolor)
2472
2473    def linewidth(self, lw=None) -> Union[Self, int]:
2474        """Set/get width of mesh edges. Same as `lw()`."""
2475        if lw is not None:
2476            if lw == 0:
2477                self.properties.EdgeVisibilityOff()
2478                self.properties.SetRepresentationToSurface()
2479                return self
2480            self.properties.EdgeVisibilityOn()
2481            self.properties.SetLineWidth(lw)
2482        else:
2483            return self.properties.GetLineWidth()
2484        return self
2485
2486    def lw(self, linewidth=None) -> Union[Self, int]:
2487        """Set/get width of mesh edges. Same as `linewidth()`."""
2488        return self.linewidth(linewidth)
2489
2490    def linecolor(self, lc=None) -> Union[Self, np.ndarray]:
2491        """Set/get color of mesh edges. Same as `lc()`."""
2492        if lc is None:
2493            return np.array(self.properties.GetEdgeColor())
2494        self.properties.EdgeVisibilityOn()
2495        self.properties.SetEdgeColor(colors.get_color(lc))
2496        return self
2497
2498    def lc(self, linecolor=None) -> Union[Self, np.ndarray]:
2499        """Set/get color of mesh edges. Same as `linecolor()`."""
2500        return self.linecolor(linecolor)
2501
2502    def texture(
2503        self,
2504        tname,
2505        tcoords=None,
2506        interpolate=True,
2507        repeat=True,
2508        edge_clamp=False,
2509        scale=None,
2510        ushift=None,
2511        vshift=None,
2512    ) -> Self:
2513        """
2514        Assign a texture to mesh from image file or predefined texture `tname`.
2515        If tname is set to `None` texture is disabled.
2516        Input tname can also be an array or a `vtkTexture`.
2517
2518        Arguments:
2519            tname : (numpy.array, str, Image, vtkTexture, None)
2520                the input texture to be applied. Can be a numpy array, a path to an image file,
2521                a vedo Image. The None value disables texture.
2522            tcoords : (numpy.array, str)
2523                this is the (u,v) texture coordinate array. Can also be a string of an existing array
2524                in the mesh.
2525            interpolate : (bool)
2526                turn on/off linear interpolation of the texture map when rendering.
2527            repeat : (bool)
2528                repeat of the texture when tcoords extend beyond the [0,1] range.
2529            edge_clamp : (bool)
2530                turn on/off the clamping of the texture map when
2531                the texture coords extend beyond the [0,1] range.
2532                Only used when repeat is False, and edge clamping is supported by the graphics card.
2533            scale : (bool)
2534                scale the texture image by this factor
2535            ushift : (bool)
2536                shift u-coordinates of texture by this amount
2537            vshift : (bool)
2538                shift v-coordinates of texture by this amount
2539
2540        Examples:
2541            - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py)
2542
2543            ![](https://vedo.embl.es/images/basic/texturecubes.png)
2544        """
2545        pd = self.dataset
2546        out_img = None
2547
2548        if tname is None:  # disable texture
2549            pd.GetPointData().SetTCoords(None)
2550            pd.GetPointData().Modified()
2551            return self  ######################################
2552
2553        if isinstance(tname, vtki.vtkTexture):
2554            tu = tname
2555
2556        elif isinstance(tname, vedo.Image):
2557            tu = vtki.vtkTexture()
2558            out_img = tname.dataset
2559
2560        elif utils.is_sequence(tname):
2561            tu = vtki.vtkTexture()
2562            out_img = vedo.image._get_img(tname)
2563
2564        elif isinstance(tname, str):
2565            tu = vtki.vtkTexture()
2566
2567            if "https://" in tname:
2568                try:
2569                    tname = vedo.file_io.download(tname, verbose=False)
2570                except:
2571                    vedo.logger.error(f"texture {tname} could not be downloaded")
2572                    return self
2573
2574            fn = tname + ".jpg"
2575            if os.path.exists(tname):
2576                fn = tname
2577            else:
2578                vedo.logger.error(f"texture file {tname} does not exist")
2579                return self
2580
2581            fnl = fn.lower()
2582            if ".jpg" in fnl or ".jpeg" in fnl:
2583                reader = vtki.new("JPEGReader")
2584            elif ".png" in fnl:
2585                reader = vtki.new("PNGReader")
2586            elif ".bmp" in fnl:
2587                reader = vtki.new("BMPReader")
2588            else:
2589                vedo.logger.error("in texture() supported files are only PNG, BMP or JPG")
2590                return self
2591            reader.SetFileName(fn)
2592            reader.Update()
2593            out_img = reader.GetOutput()
2594
2595        else:
2596            vedo.logger.error(f"in texture() cannot understand input {type(tname)}")
2597            return self
2598
2599        if tcoords is not None:
2600
2601            if isinstance(tcoords, str):
2602                vtarr = pd.GetPointData().GetArray(tcoords)
2603
2604            else:
2605                tcoords = np.asarray(tcoords)
2606                if tcoords.ndim != 2:
2607                    vedo.logger.error("tcoords must be a 2-dimensional array")
2608                    return self
2609                if tcoords.shape[0] != pd.GetNumberOfPoints():
2610                    vedo.logger.error("nr of texture coords must match nr of points")
2611                    return self
2612                if tcoords.shape[1] != 2:
2613                    vedo.logger.error("tcoords texture vector must have 2 components")
2614                vtarr = utils.numpy2vtk(tcoords)
2615                vtarr.SetName("TCoordinates")
2616
2617            pd.GetPointData().SetTCoords(vtarr)
2618            pd.GetPointData().Modified()
2619
2620        elif not pd.GetPointData().GetTCoords():
2621
2622            # TCoords still void..
2623            # check that there are no texture-like arrays:
2624            names = self.pointdata.keys()
2625            candidate_arr = ""
2626            for name in names:
2627                vtarr = pd.GetPointData().GetArray(name)
2628                if vtarr.GetNumberOfComponents() != 2:
2629                    continue
2630                t0, t1 = vtarr.GetRange()
2631                if t0 >= 0 and t1 <= 1:
2632                    candidate_arr = name
2633
2634            if candidate_arr:
2635
2636                vtarr = pd.GetPointData().GetArray(candidate_arr)
2637                pd.GetPointData().SetTCoords(vtarr)
2638                pd.GetPointData().Modified()
2639
2640            else:
2641                # last resource is automatic mapping
2642                tmapper = vtki.new("TextureMapToPlane")
2643                tmapper.AutomaticPlaneGenerationOn()
2644                tmapper.SetInputData(pd)
2645                tmapper.Update()
2646                tc = tmapper.GetOutput().GetPointData().GetTCoords()
2647                if scale or ushift or vshift:
2648                    ntc = utils.vtk2numpy(tc)
2649                    if scale:
2650                        ntc *= scale
2651                    if ushift:
2652                        ntc[:, 0] += ushift
2653                    if vshift:
2654                        ntc[:, 1] += vshift
2655                    tc = utils.numpy2vtk(tc)
2656                pd.GetPointData().SetTCoords(tc)
2657                pd.GetPointData().Modified()
2658
2659        if out_img:
2660            tu.SetInputData(out_img)
2661        tu.SetInterpolate(interpolate)
2662        tu.SetRepeat(repeat)
2663        tu.SetEdgeClamp(edge_clamp)
2664
2665        self.properties.SetColor(1, 1, 1)
2666        self.mapper.ScalarVisibilityOff()
2667        self.actor.SetTexture(tu)
2668
2669        # if seam_threshold is not None:
2670        #     tname = self.dataset.GetPointData().GetTCoords().GetName()
2671        #     grad = self.gradient(tname)
2672        #     ugrad, vgrad = np.split(grad, 2, axis=1)
2673        #     ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad)
2674        #     gradm = np.log(ugradm + vgradm)
2675        #     largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4]
2676        #     uvmap = self.pointdata[tname]
2677        #     # collapse triangles that have large gradient
2678        #     new_points = self.points.copy()
2679        #     for f in self.cells:
2680        #         if np.isin(f, largegrad_ids).all():
2681        #             id1, id2, id3 = f
2682        #             uv1, uv2, uv3 = uvmap[f]
2683        #             d12 = utils.mag2(uv1 - uv2)
2684        #             d23 = utils.mag2(uv2 - uv3)
2685        #             d31 = utils.mag2(uv3 - uv1)
2686        #             idm = np.argmin([d12, d23, d31])
2687        #             if idm == 0:
2688        #                 new_points[id1] = new_points[id3]
2689        #                 new_points[id2] = new_points[id3]
2690        #             elif idm == 1:
2691        #                 new_points[id2] = new_points[id1]
2692        #                 new_points[id3] = new_points[id1]
2693        #     self.points = new_points
2694
2695        self.dataset.Modified()
2696        return self
2697
2698########################################################################################
2699class VolumeVisual(CommonVisual):
2700    """Class to manage the visual aspects of a ``Volume`` object."""
2701
2702    # def __init__(self) -> None:
2703    #     # print("INIT VolumeVisual")
2704    #     super().__init__()
2705
2706    def alpha_unit(self, u=None) -> Union[Self, float]:
2707        """
2708        Defines light attenuation per unit length. Default is 1.
2709        The larger the unit length, the further light has to travel to attenuate the same amount.
2710
2711        E.g., if you set the unit distance to 0, you will get full opacity.
2712        It means that when light travels 0 distance it's already attenuated a finite amount.
2713        Thus, any finite distance should attenuate all light.
2714        The larger you make the unit distance, the more transparent the rendering becomes.
2715        """
2716        if u is None:
2717            return self.properties.GetScalarOpacityUnitDistance()
2718        self.properties.SetScalarOpacityUnitDistance(u)
2719        return self
2720
2721    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2722        """
2723        Assign a set of tranparencies to a volume's gradient
2724        along the range of the scalar value.
2725        A single constant value can also be assigned.
2726        The gradient function is used to decrease the opacity
2727        in the "flat" regions of the volume while maintaining the opacity
2728        at the boundaries between material types.  The gradient is measured
2729        as the amount by which the intensity changes over unit distance.
2730
2731        The format for alpha_grad is the same as for method `volume.alpha()`.
2732        """
2733        if vmin is None:
2734            vmin, _ = self.dataset.GetScalarRange()
2735        if vmax is None:
2736            _, vmax = self.dataset.GetScalarRange()
2737
2738        if alpha_grad is None:
2739            self.properties.DisableGradientOpacityOn()
2740            return self
2741
2742        self.properties.DisableGradientOpacityOff()
2743
2744        gotf = self.properties.GetGradientOpacity()
2745        if utils.is_sequence(alpha_grad):
2746            alpha_grad = np.array(alpha_grad)
2747            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2748                for i, al in enumerate(alpha_grad):
2749                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2750                    # Create transfer mapping scalar value to gradient opacity
2751                    gotf.AddPoint(xalpha, al)
2752            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2753                gotf.AddPoint(vmin, alpha_grad[0][1])
2754                for xalpha, al in alpha_grad:
2755                    # Create transfer mapping scalar value to opacity
2756                    gotf.AddPoint(xalpha, al)
2757                gotf.AddPoint(vmax, alpha_grad[-1][1])
2758            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2759        else:
2760            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2761            gotf.AddPoint(vmax, alpha_grad)
2762        return self
2763
2764    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2765        """Same as `color()`.
2766
2767        Arguments:
2768            alpha : (list)
2769                use a list to specify transparencies along the scalar range
2770            vmin : (float)
2771                force the min of the scalar range to be this value
2772            vmax : (float)
2773                force the max of the scalar range to be this value
2774        """
2775        return self.color(c, alpha, vmin, vmax)
2776
2777    def jittering(self, status=None) -> Union[Self, bool]:
2778        """
2779        If `True`, each ray traversal direction will be perturbed slightly
2780        using a noise-texture to get rid of wood-grain effects.
2781        """
2782        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2783            if status is None:
2784                return self.mapper.GetUseJittering()
2785            self.mapper.SetUseJittering(status)
2786        return self
2787
2788    def hide_voxels(self, ids) -> Self:
2789        """
2790        Hide voxels (cells) from visualization.
2791
2792        Example:
2793            ```python
2794            from vedo import *
2795            embryo = Volume(dataurl+'embryo.tif')
2796            embryo.hide_voxels(list(range(400000)))
2797            show(embryo, axes=1).close()
2798            ```
2799
2800        See also:
2801            `volume.mask()`
2802        """
2803        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2804        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2805        name = vtki.vtkDataSetAttributes.GhostArrayName()
2806        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2807        self.dataset.GetCellData().AddArray(garr)
2808        self.dataset.GetCellData().Modified()
2809        return self
2810
2811    def mask(self, data) -> Self:
2812        """
2813        Mask a volume visualization with a binary value.
2814        Needs to specify `volume.mapper = "gpu"`.
2815
2816        Example:
2817        ```python
2818        from vedo import np, Volume, show
2819        data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2820        # all voxels have value zero except:
2821        data_matrix[ 0:35,  0:35,  0:35] = 1
2822        data_matrix[35:55, 35:55, 35:55] = 2
2823        data_matrix[55:74, 55:74, 55:74] = 3
2824        vol = Volume(data_matrix).cmap('Blues')
2825        vol.mapper = "gpu"
2826        data_mask = np.zeros_like(data_matrix)
2827        data_mask[10:65, 10:60, 20:70] = 1
2828        vol.mask(data_mask)
2829        show(vol, axes=1).close()
2830        ```
2831        See also:
2832            `volume.hide_voxels()`
2833        """
2834        rdata = data.astype(np.uint8).ravel(order="F")
2835        varr = utils.numpy2vtk(rdata, name="input_mask")
2836
2837        img = vtki.vtkImageData()
2838        img.SetDimensions(self.dimensions())
2839        img.GetPointData().AddArray(varr)
2840        img.GetPointData().SetActiveScalars(varr.GetName())
2841
2842        try:
2843            self.mapper.SetMaskTypeToBinary()
2844            self.mapper.SetMaskInput(img)
2845        except AttributeError:
2846            vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'")
2847        return self
2848
2849
2850    def mode(self, mode=None) -> Union[Self, int]:
2851        """
2852        Define the volumetric rendering mode following this:
2853            - 0, composite rendering
2854            - 1, maximum projection rendering
2855            - 2, minimum projection rendering
2856            - 3, average projection rendering
2857            - 4, additive mode
2858
2859        The default mode is "composite" where the scalar values are sampled through
2860        the volume and composited in a front-to-back scheme through alpha blending.
2861        The final color and opacity is determined using the color and opacity transfer
2862        functions specified in alpha keyword.
2863
2864        Maximum and minimum intensity blend modes use the maximum and minimum
2865        scalar values, respectively, along the sampling ray.
2866        The final color and opacity is determined by passing the resultant value
2867        through the color and opacity transfer functions.
2868
2869        Additive blend mode accumulates scalar values by passing each value
2870        through the opacity transfer function and then adding up the product
2871        of the value and its opacity. In other words, the scalar values are scaled
2872        using the opacity transfer function and summed to derive the final color.
2873        Note that the resulting image is always grayscale i.e. aggregated values
2874        are not passed through the color transfer function.
2875        This is because the final value is a derived value and not a real data value
2876        along the sampling ray.
2877
2878        Average intensity blend mode works similar to the additive blend mode where
2879        the scalar values are multiplied by opacity calculated from the opacity
2880        transfer function and then added.
2881        The additional step here is to divide the sum by the number of samples
2882        taken through the volume.
2883        As is the case with the additive intensity projection, the final image will
2884        always be grayscale i.e. the aggregated values are not passed through the
2885        color transfer function.
2886        """
2887        if mode is None:
2888            return self.mapper.GetBlendMode()
2889
2890        if isinstance(mode, str):
2891            if "comp" in mode:
2892                mode = 0
2893            elif "proj" in mode:
2894                if "max" in mode:
2895                    mode = 1
2896                elif "min" in mode:
2897                    mode = 2
2898                elif "ave" in mode:
2899                    mode = 3
2900                else:
2901                    vedo.logger.warning(f"unknown mode {mode}")
2902                    mode = 0
2903            elif "add" in mode:
2904                mode = 4
2905            else:
2906                vedo.logger.warning(f"unknown mode {mode}")
2907                mode = 0
2908
2909        self.mapper.SetBlendMode(mode)
2910        return self
2911
2912    def shade(self, status=None) -> Union[Self, bool]:
2913        """
2914        Set/Get the shading of a Volume.
2915        Shading can be further controlled with `volume.lighting()` method.
2916
2917        If shading is turned on, the mapper may perform shading calculations.
2918        In some cases shading does not apply
2919        (for example, in maximum intensity projection mode).
2920        """
2921        if status is None:
2922            return self.properties.GetShade()
2923        self.properties.SetShade(status)
2924        return self
2925
2926    def interpolation(self, itype) -> Self:
2927        """
2928        Set interpolation type.
2929
2930        0=nearest neighbour, 1=linear
2931        """
2932        self.properties.SetInterpolationType(itype)
2933        return self
2934
2935
2936########################################################################################
2937class ImageVisual(CommonVisual, Actor3DHelper):
2938    """Class to manage the visual aspects of a ``Image`` object."""
2939
2940    # def __init__(self) -> None:
2941    #     # print("init ImageVisual")
2942    #     super().__init__()
2943
2944    def memory_size(self) -> int:
2945        """Return the size in bytes of the object in memory."""
2946        return self.dataset.GetActualMemorySize()
2947
2948    def scalar_range(self) -> np.ndarray:
2949        """Return the scalar range of the image."""
2950        return np.array(self.dataset.GetScalarRange())
2951
2952    def alpha(self, a=None) -> Union[Self, float]:
2953        """Set/get image's transparency in the rendering scene."""
2954        if a is not None:
2955            self.properties.SetOpacity(a)
2956            return self
2957        return self.properties.GetOpacity()
2958
2959    def level(self, value=None) -> Union[Self, float]:
2960        """Get/Set the image color level (brightness) in the rendering scene."""
2961        if value is None:
2962            return self.properties.GetColorLevel()
2963        self.properties.SetColorLevel(value)
2964        return self
2965
2966    def window(self, value=None) -> Union[Self, float]:
2967        """Get/Set the image color window (contrast) in the rendering scene."""
2968        if value is None:
2969            return self.properties.GetColorWindow()
2970        self.properties.SetColorWindow(value)
2971        return self
2972
2973
2974class LightKit:
2975    """
2976    A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'.
2977
2978    The main light is the key light. It is usually positioned so that it appears like
2979    an overhead light (like the sun, or a ceiling light).
2980    It is generally positioned to shine down on the scene from about a 45 degree angle vertically
2981    and at least a little offset side to side. The key light usually at least about twice as bright
2982    as the total of all other lights in the scene to provide good modeling of object features.
2983
2984    The other lights in the kit (the fill light, headlight, and a pair of back lights)
2985    are weaker sources that provide extra illumination to fill in the spots that the key light misses.
2986    The fill light is usually positioned across from or opposite from the key light
2987    (though still on the same side of the object as the camera) in order to simulate diffuse reflections
2988    from other objects in the scene.
2989
2990    The headlight, always located at the position of the camera, reduces the contrast between areas lit
2991    by the key and fill light. The two back lights, one on the left of the object as seen from the observer
2992    and one on the right, fill on the high-contrast areas behind the object.
2993    To enforce the relationship between the different lights, the intensity of the fill, back and headlights
2994    are set as a ratio to the key light brightness.
2995    Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity.
2996
2997    All lights are directional lights, infinitely far away with no falloff. Lights move with the camera.
2998
2999    For simplicity, the position of lights in the LightKit can only be specified using angles:
3000    the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees.
3001    For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight).
3002    A light at (elevation=90, azimuth=0) is above the lookat point, shining down.
3003    Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise.
3004    So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining
3005    slightly from the left side.
3006
3007    LightKit limits the colors that can be assigned to any light to those of incandescent sources such as
3008    light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors
3009    can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red.
3010    Colors close to 0.5 are "cool whites" and "warm whites," respectively.
3011
3012    Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight
3013    ratios are skewed by key, fill, and headlight colors. If `maintain_luminance` is set, LightKit will
3014    attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors.
3015
3016    To specify the color of a light, positioning etc you can pass a dictionary with the following keys:
3017        - `intensity` : (float) The intensity of the key light. Default is 1.
3018        - `ratio`     : (float) The ratio of the light intensity wrt key light.
3019        - `warmth`    : (float) The warmth of the light. Default is 0.5.
3020        - `elevation` : (float) The elevation of the light in degrees.
3021        - `azimuth`   : (float) The azimuth of the light in degrees.
3022
3023    Example:
3024        ```python
3025        from vedo import *
3026        lightkit = LightKit(head={"warmth":0.6})
3027        mesh = Mesh(dataurl+"bunny.obj")
3028        plt = Plotter()
3029        plt.remove_lights().add(mesh, lightkit)
3030        plt.show().close()
3031        ```
3032    """
3033    def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None:
3034
3035        self.lightkit = vtki.new("LightKit")
3036        self.lightkit.SetMaintainLuminance(maintain_luminance)
3037        self.key  = dict(key)
3038        self.head = dict(head)
3039        self.fill = dict(fill)
3040        self.back = dict(back)
3041        self.update()
3042
3043    def update(self) -> None:
3044        """Update the LightKit properties."""
3045        if "warmth" in self.key:
3046            self.lightkit.SetKeyLightWarmth(self.key["warmth"])
3047        if "warmth" in self.fill:
3048            self.lightkit.SetFillLightWarmth(self.fill["warmth"])
3049        if "warmth" in self.head:
3050            self.lightkit.SetHeadLightWarmth(self.head["warmth"])
3051        if "warmth" in self.back:
3052            self.lightkit.SetBackLightWarmth(self.back["warmth"])
3053
3054        if "intensity" in self.key:
3055            self.lightkit.SetKeyLightIntensity(self.key["intensity"])
3056        if "ratio" in self.fill:
3057            self.lightkit.SetKeyToFillRatio(self.key["ratio"])
3058        if "ratio" in self.head:
3059            self.lightkit.SetKeyToHeadRatio(self.key["ratio"])
3060        if "ratio" in self.back:
3061            self.lightkit.SetKeyToBackRatio(self.key["ratio"])
3062
3063        if "elevation" in self.key:
3064            self.lightkit.SetKeyLightElevation(self.key["elevation"])
3065        if "elevation" in self.fill:
3066            self.lightkit.SetFillLightElevation(self.fill["elevation"])
3067        if "elevation" in self.head:
3068            self.lightkit.SetHeadLightElevation(self.head["elevation"])
3069        if "elevation" in self.back:
3070            self.lightkit.SetBackLightElevation(self.back["elevation"])
3071
3072        if "azimuth" in self.key:
3073            self.lightkit.SetKeyLightAzimuth(self.key["azimuth"])
3074        if "azimuth" in self.fill:
3075            self.lightkit.SetFillLightAzimuth(self.fill["azimuth"])
3076        if "azimuth" in self.head:
3077            self.lightkit.SetHeadLightAzimuth(self.head["azimuth"])
3078        if "azimuth" in self.back:
3079            self.lightkit.SetBackLightAzimuth(self.back["azimuth"])
class CommonVisual:
 36class CommonVisual:
 37    """Class to manage the visual aspects common to all objects."""
 38
 39    def __init__(self):
 40        # print("init CommonVisual", type(self))
 41
 42        self.properties = None
 43
 44        self.scalarbar = None
 45        self.pipeline = None
 46
 47        self.trail = None
 48        self.trail_points = []
 49        self.trail_segment_size = 0
 50        self.trail_offset = None
 51
 52        self.shadows = []
 53
 54        self.axes = None
 55        self.picked3d = None
 56
 57        self.rendered_at = set()
 58
 59        self._ligthingnr = 0  # index of the lighting mode changed from CLI
 60        self._cmap_name = ""  # remember the cmap name for self._keypress
 61        self._caption = None
 62
 63        self.actor = None
 64
 65
 66    def print(self):
 67        """Print object info."""
 68        print(self.__str__())
 69        return self
 70
 71    @property
 72    def LUT(self) -> np.ndarray:
 73        """Return the lookup table of the object as a numpy object."""
 74        try:
 75            _lut = self.mapper.GetLookupTable()
 76            values = []
 77            for i in range(_lut.GetTable().GetNumberOfTuples()):
 78                # print("LUT i =", i, "value =", _lut.GetTableValue(i))
 79                values.append(_lut.GetTableValue(i))
 80            return np.array(values)
 81        except AttributeError:
 82            return np.array([], dtype=float)
 83
 84    @LUT.setter
 85    def LUT(self, arr):
 86        """
 87        Set the lookup table of the object from a numpy or `vtkLookupTable` object.
 88        Consider using `cmap()` or `build_lut()` instead as it allows
 89        to set the range of the LUT and to use a string name for the color map.
 90        """
 91        if isinstance(arr, vtki.vtkLookupTable):
 92            newlut = arr
 93            self.mapper.SetScalarRange(newlut.GetRange())
 94        else:
 95            newlut = vtki.vtkLookupTable()
 96            newlut.SetNumberOfTableValues(len(arr))
 97            if len(arr[0]) == 3:
 98                arr = np.insert(arr, 3, 1, axis=1)
 99            for i, v in enumerate(arr):
100                newlut.SetTableValue(i, v)
101            newlut.SetRange(self.mapper.GetScalarRange())
102            # print("newlut.GetRange() =", newlut.GetRange())
103            # print("self.mapper.GetScalarRange() =", self.mapper.GetScalarRange())
104            newlut.Build()
105        self.mapper.SetLookupTable(newlut)
106        self.mapper.ScalarVisibilityOn()
107
108    def scalar_range(self, vmin=None, vmax=None):
109        """Set the range of the scalar value for visualization."""
110        if vmin is None and vmax is None:
111            return np.array(self.mapper.GetScalarRange())
112        if vmax is None:
113            vmin, vmax = vmin # assume it is a list
114        self.mapper.SetScalarRange(float(vmin), float(vmax))
115        return self
116
117    def add_observer(self, event_name, func, priority=0) -> int:
118        """Add a callback function that will be called when an event occurs."""
119        event_name = utils.get_vtk_name_event(event_name)
120        idd = self.actor.AddObserver(event_name, func, priority)
121        return idd
122
123    def invoke_event(self, event_name) -> Self:
124        """Invoke an event."""
125        event_name = utils.get_vtk_name_event(event_name)
126        self.actor.InvokeEvent(event_name)
127        return self
128
129    # def abort_event(self, obs_id):
130    #     """Abort an event."""
131    #     cmd = self.actor.GetCommand(obs_id) # vtkCommand
132    #     if cmd:
133    #         cmd.AbortFlagOn()
134    #     return self
135
136    def show(self, **options) -> Union["vedo.Plotter", None]:
137        """
138        Create on the fly an instance of class `Plotter` or use the last existing one to
139        show one single object.
140
141        This method is meant as a shortcut. If more than one object needs to be visualised
142        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
143
144        Returns the `Plotter` class instance.
145        """
146        return vedo.plotter.show(self, **options)
147
148    def thumbnail(self, zoom=1.25, size=(200, 200), bg="white", azimuth=0, elevation=0, axes=False) -> np.ndarray:
149        """Build a thumbnail of the object and return it as an array."""
150        # speed is about 20Hz for size=[200,200]
151        ren = vtki.vtkRenderer()
152
153        actor = self.actor
154        if isinstance(self, vedo.UnstructuredGrid):
155            geo = vtki.new("GeometryFilter")
156            geo.SetInputData(self.dataset)
157            geo.Update()
158            actor = vedo.Mesh(geo.GetOutput()).cmap("rainbow").actor
159
160        ren.AddActor(actor)
161        if axes:
162            axes = vedo.addons.Axes(self)
163            ren.AddActor(axes.actor)
164        ren.ResetCamera()
165        cam = ren.GetActiveCamera()
166        cam.Zoom(zoom)
167        cam.Elevation(elevation)
168        cam.Azimuth(azimuth)
169
170        ren_win = vtki.vtkRenderWindow()
171        ren_win.SetOffScreenRendering(True)
172        ren_win.SetSize(size)
173        ren.SetBackground(colors.get_color(bg))
174        ren_win.AddRenderer(ren)
175        ren.ResetCameraClippingRange()
176        ren_win.Render()
177
178        nx, ny = ren_win.GetSize()
179        arr = vtki.vtkUnsignedCharArray()
180        ren_win.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr)
181        narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
182        narr = np.ascontiguousarray(np.flip(narr, axis=0))
183
184        ren.RemoveActor(actor)
185        if axes:
186            ren.RemoveActor(axes.actor)
187        ren_win.Finalize()
188        del ren_win
189        return narr
190
191    def pickable(self, value=None) -> Self:
192        """Set/get the pickability property of an object."""
193        if value is None:
194            return self.actor.GetPickable()
195        self.actor.SetPickable(value)
196        return self
197
198    def use_bounds(self, value=True) -> Self:
199        """
200        Instruct the current camera to either take into account or ignore
201        the object bounds when resetting.
202        """
203        self.actor.SetUseBounds(value)
204        return self
205
206    def draggable(self, value=None) -> Self:  # NOT FUNCTIONAL?
207        """Set/get the draggability property of an object."""
208        if value is None:
209            return self.actor.GetDragable()
210        self.actor.SetDragable(value)
211        return self
212
213    def on(self) -> Self:
214        """Switch on  object visibility. Object is not removed."""
215        self.actor.VisibilityOn()
216        try:
217            self.scalarbar.actor.VisibilityOn()
218        except AttributeError:
219            pass
220        try:
221            self.trail.actor.VisibilityOn()
222        except AttributeError:
223            pass
224        try:
225            for sh in self.shadows:
226                sh.actor.VisibilityOn()
227        except AttributeError:
228            pass
229        return self
230
231    def off(self) -> Self:
232        """Switch off object visibility. Object is not removed."""
233        self.actor.VisibilityOff()
234        try:
235            self.scalarbar.actor.VisibilityOff()
236        except AttributeError:
237            pass
238        try:
239            self.trail.actor.VisibilityOff()
240        except AttributeError:
241            pass
242        try:
243            for sh in self.shadows:
244                sh.actor.VisibilityOff()
245        except AttributeError:
246            pass
247        return self
248
249    def toggle(self) -> Self:
250        """Toggle object visibility on/off."""
251        v = self.actor.GetVisibility()
252        if v:
253            self.off()
254        else:
255            self.on()
256        return self
257
258    def add_scalarbar(
259        self,
260        title="",
261        pos=(),
262        size=(80, 400),
263        font_size=14,
264        title_yoffset=15,
265        nlabels=None,
266        c=None,
267        horizontal=False,
268        use_alpha=True,
269        label_format=":6.3g",
270    ) -> Self:
271        """
272        Add a 2D scalar bar for the specified object.
273
274        Arguments:
275            title : (str)
276                scalar bar title
277            pos : (list)
278                position coordinates of the bottom left corner.
279                Can also be a pair of (x,y) values in the range [0,1]
280                to indicate the position of the bottom left and top right corners.
281            size : (float,float)
282                size of the scalarbar in number of pixels (width, height)
283            font_size : (float)
284                size of font for title and numeric labels
285            title_yoffset : (float)
286                vertical space offset between title and color scalarbar
287            nlabels : (int)
288                number of numeric labels
289            c : (list)
290                color of the scalar bar text
291            horizontal : (bool)
292                lay the scalarbar horizontally
293            use_alpha : (bool)
294                render transparency in the color bar itself
295            label_format : (str)
296                c-style format string for numeric labels
297
298        Examples:
299            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
300            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
301        """
302        plt = vedo.plotter_instance
303
304        if plt and plt.renderer:
305            c = (0.9, 0.9, 0.9)
306            if np.sum(plt.renderer.GetBackground()) > 1.5:
307                c = (0.1, 0.1, 0.1)
308            if isinstance(self.scalarbar, vtki.vtkActor):
309                plt.renderer.RemoveActor(self.scalarbar)
310            elif isinstance(self.scalarbar, vedo.Assembly):
311                for a in self.scalarbar.unpack():
312                    plt.renderer.RemoveActor(a)
313        if c is None:
314            c = "gray"
315
316        sb = vedo.addons.ScalarBar(
317            self,
318            title,
319            pos,
320            size,
321            font_size,
322            title_yoffset,
323            nlabels,
324            c,
325            horizontal,
326            use_alpha,
327            label_format,
328        )
329        self.scalarbar = sb
330        return self
331
332    def add_scalarbar3d(
333        self,
334        title="",
335        pos=None,
336        size=(0, 0),
337        title_font="",
338        title_xoffset=-1.2,
339        title_yoffset=0.0,
340        title_size=1.5,
341        title_rotation=0.0,
342        nlabels=9,
343        label_font="",
344        label_size=1,
345        label_offset=0.375,
346        label_rotation=0,
347        label_format="",
348        italic=0,
349        c=None,
350        draw_box=True,
351        above_text=None,
352        below_text=None,
353        nan_text="NaN",
354        categories=None,
355    ) -> Self:
356        """
357        Associate a 3D scalar bar to the object and add it to the scene.
358        The new scalarbar object (Assembly) will be accessible as obj.scalarbar
359
360        Arguments:
361            size : (list)
362                (thickness, length) of scalarbar
363            title : (str)
364                scalar bar title
365            title_xoffset : (float)
366                horizontal space btw title and color scalarbar
367            title_yoffset : (float)
368                vertical space offset
369            title_size : (float)
370                size of title wrt numeric labels
371            title_rotation : (float)
372                title rotation in degrees
373            nlabels : (int)
374                number of numeric labels
375            label_font : (str)
376                font type for labels
377            label_size : (float)
378                label scale factor
379            label_offset : (float)
380                space btw numeric labels and scale
381            label_rotation : (float)
382                label rotation in degrees
383            label_format : (str)
384                label format for floats and integers (e.g. `':.2f'`)
385            draw_box : (bool)
386                draw a box around the colorbar
387            categories : (list)
388                make a categorical scalarbar,
389                the input list will have the format `[value, color, alpha, textlabel]`
390
391        Examples:
392            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
393        """
394        plt = vedo.plotter_instance
395        if plt and c is None:  # automatic black or white
396            c = (0.9, 0.9, 0.9)
397            if np.sum(vedo.get_color(plt.backgrcol)) > 1.5:
398                c = (0.1, 0.1, 0.1)
399        if c is None:
400            c = (0, 0, 0)
401        c = vedo.get_color(c)
402
403        self.scalarbar = vedo.addons.ScalarBar3D(
404            self,
405            title,
406            pos,
407            size,
408            title_font,
409            title_xoffset,
410            title_yoffset,
411            title_size,
412            title_rotation,
413            nlabels,
414            label_font,
415            label_size,
416            label_offset,
417            label_rotation,
418            label_format,
419            italic,
420            c,
421            draw_box,
422            above_text,
423            below_text,
424            nan_text,
425            categories,
426        )
427        return self
428
429    def color(self, col, alpha=None, vmin=None, vmax=None):
430        """
431        Assign a color or a set of colors along the range of the scalar value.
432        A single constant color can also be assigned.
433        Any matplotlib color map name is also accepted, e.g. `volume.color('jet')`.
434
435        E.g.: say that your cells scalar runs from -3 to 6,
436        and you want -3 to show red and 1.5 violet and 6 green, then just set:
437
438        `volume.color(['red', 'violet', 'green'])`
439
440        You can also assign a specific color to a aspecific value with eg.:
441
442        `volume.color([(0,'red'), (0.5,'violet'), (1,'green')])`
443
444        Arguments:
445            alpha : (list)
446                use a list to specify transparencies along the scalar range
447            vmin : (float)
448                force the min of the scalar range to be this value
449            vmax : (float)
450                force the max of the scalar range to be this value
451        """
452        # supersedes method in Points, Mesh
453
454        if col is None:
455            return self
456        
457        if vmin is None:
458            vmin, _ = self.dataset.GetScalarRange()
459        if vmax is None:
460            _, vmax = self.dataset.GetScalarRange()
461        ctf = self.properties.GetRGBTransferFunction()
462        ctf.RemoveAllPoints()
463
464        if utils.is_sequence(col):
465            if utils.is_sequence(col[0]) and len(col[0]) == 2:
466                # user passing [(value1, color1), ...]
467                for x, ci in col:
468                    r, g, b = colors.get_color(ci)
469                    ctf.AddRGBPoint(x, r, g, b)
470                    # colors.printc('color at', round(x, 1),
471                    #               'set to', colors.get_color_name((r, g, b)), bold=0)
472            else:
473                # user passing [color1, color2, ..]
474                for i, ci in enumerate(col):
475                    r, g, b = colors.get_color(ci)
476                    x = vmin + (vmax - vmin) * i / (len(col) - 1)
477                    ctf.AddRGBPoint(x, r, g, b)
478        elif isinstance(col, str):
479            if col in colors.colors.keys() or col in colors.color_nicks.keys():
480                r, g, b = colors.get_color(col)
481                ctf.AddRGBPoint(vmin, r, g, b)  # constant color
482                ctf.AddRGBPoint(vmax, r, g, b)
483            else:  # assume it's a colormap
484                for x in np.linspace(vmin, vmax, num=64, endpoint=True):
485                    r, g, b = colors.color_map(x, name=col, vmin=vmin, vmax=vmax)
486                    ctf.AddRGBPoint(x, r, g, b)
487        elif isinstance(col, int):
488            r, g, b = colors.get_color(col)
489            ctf.AddRGBPoint(vmin, r, g, b)  # constant color
490            ctf.AddRGBPoint(vmax, r, g, b)
491        elif isinstance(col, vtki.vtkLookupTable):
492            alpha=[]
493            nt = col.GetNumberOfTableValues()
494            for i in range(nt):
495                r, g, b, a = col.GetTableValue(i)
496                x = vmin + (vmax - vmin) * i / (nt - 1)
497                ctf.AddRGBPoint(x, r, g, b)
498                alpha.append(a)
499        elif hasattr(col, "resampled"): # cover the case of LinearSegmentedColormap
500            N = col.N
501            cs = np.array([col(i/N) for i in range(N)])
502            alpha = cs[:,3].copy()
503            for i, v in enumerate(cs):
504                r, g, b, _ = v
505                x = vmin + (vmax - vmin) * i / (N - 1)
506                ctf.AddRGBPoint(x, r, g, b)
507        elif hasattr(col, "to_rgba"):   # col is a matplotlib colormap
508            for i in range(256):
509                r, g, b, a = col(i / 255)
510                x = vmin + (vmax - vmin) * i / 255
511                ctf.AddRGBPoint(x, r, g, b)
512                alpha.append(a)
513        else:
514            vedo.logger.warning(f"in color() unknown input type {type(col)}")
515
516        if alpha is not None:
517            self.alpha(alpha, vmin=vmin, vmax=vmax)
518        return self
519
520    def alpha(self, alpha, vmin=None, vmax=None) -> Self:
521        """
522        Assign a set of tranparencies along the range of the scalar value.
523        A single constant value can also be assigned.
524
525        E.g.: say `alpha=(0.0, 0.3, 0.9, 1)` and the scalar range goes from -10 to 150.
526        Then all cells with a value close to -10 will be completely transparent, cells at 1/4
527        of the range will get an alpha equal to 0.3 and voxels with value close to 150
528        will be completely opaque.
529
530        As a second option one can set explicit (x, alpha_x) pairs to define the transfer function.
531
532        E.g.: say `alpha=[(-5, 0), (35, 0.4) (123,0.9)]` and the scalar range goes from -10 to 150.
533        Then all cells below -5 will be completely transparent, cells with a scalar value of 35
534        will get an opacity of 40% and above 123 alpha is set to 90%.
535        """
536        if vmin is None:
537            vmin, _ = self.dataset.GetScalarRange()
538        if vmax is None:
539            _, vmax = self.dataset.GetScalarRange()
540        otf = self.properties.GetScalarOpacity()
541        otf.RemoveAllPoints()
542
543        if utils.is_sequence(alpha):
544            alpha = np.array(alpha)
545            if len(alpha.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
546                for i, al in enumerate(alpha):
547                    xalpha = vmin + (vmax - vmin) * i / (len(alpha) - 1)
548                    # Create transfer mapping scalar value to opacity
549                    otf.AddPoint(xalpha, al)
550                    # print("alpha at", round(xalpha, 1), "\tset to", al)
551            elif len(alpha.shape) == 2:  # user passing [(x0,alpha0), ...]
552                otf.AddPoint(vmin, alpha[0][1])
553                for xalpha, al in alpha:
554                    # Create transfer mapping scalar value to opacity
555                    otf.AddPoint(xalpha, al)
556                otf.AddPoint(vmax, alpha[-1][1])
557
558        else:
559
560            otf.AddPoint(vmin, alpha)  # constant alpha
561            otf.AddPoint(vmax, alpha)
562
563        return self

Class to manage the visual aspects common to all objects.

CommonVisual()
39    def __init__(self):
40        # print("init CommonVisual", type(self))
41
42        self.properties = None
43
44        self.scalarbar = None
45        self.pipeline = None
46
47        self.trail = None
48        self.trail_points = []
49        self.trail_segment_size = 0
50        self.trail_offset = None
51
52        self.shadows = []
53
54        self.axes = None
55        self.picked3d = None
56
57        self.rendered_at = set()
58
59        self._ligthingnr = 0  # index of the lighting mode changed from CLI
60        self._cmap_name = ""  # remember the cmap name for self._keypress
61        self._caption = None
62
63        self.actor = None
def print(self):
66    def print(self):
67        """Print object info."""
68        print(self.__str__())
69        return self

Print object info.

LUT: numpy.ndarray
71    @property
72    def LUT(self) -> np.ndarray:
73        """Return the lookup table of the object as a numpy object."""
74        try:
75            _lut = self.mapper.GetLookupTable()
76            values = []
77            for i in range(_lut.GetTable().GetNumberOfTuples()):
78                # print("LUT i =", i, "value =", _lut.GetTableValue(i))
79                values.append(_lut.GetTableValue(i))
80            return np.array(values)
81        except AttributeError:
82            return np.array([], dtype=float)

Return the lookup table of the object as a numpy object.

def scalar_range(self, vmin=None, vmax=None):
108    def scalar_range(self, vmin=None, vmax=None):
109        """Set the range of the scalar value for visualization."""
110        if vmin is None and vmax is None:
111            return np.array(self.mapper.GetScalarRange())
112        if vmax is None:
113            vmin, vmax = vmin # assume it is a list
114        self.mapper.SetScalarRange(float(vmin), float(vmax))
115        return self

Set the range of the scalar value for visualization.

def add_observer(self, event_name, func, priority=0) -> int:
117    def add_observer(self, event_name, func, priority=0) -> int:
118        """Add a callback function that will be called when an event occurs."""
119        event_name = utils.get_vtk_name_event(event_name)
120        idd = self.actor.AddObserver(event_name, func, priority)
121        return idd

Add a callback function that will be called when an event occurs.

def invoke_event(self, event_name) -> Self:
123    def invoke_event(self, event_name) -> Self:
124        """Invoke an event."""
125        event_name = utils.get_vtk_name_event(event_name)
126        self.actor.InvokeEvent(event_name)
127        return self

Invoke an event.

def show(self, **options) -> Optional[vedo.plotter.Plotter]:
136    def show(self, **options) -> Union["vedo.Plotter", None]:
137        """
138        Create on the fly an instance of class `Plotter` or use the last existing one to
139        show one single object.
140
141        This method is meant as a shortcut. If more than one object needs to be visualised
142        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
143
144        Returns the `Plotter` class instance.
145        """
146        return vedo.plotter.show(self, **options)

Create on the fly an instance of class Plotter or use the last existing one to show one single object.

This method is meant as a shortcut. If more than one object needs to be visualised please use the syntax show(mesh1, mesh2, volume, ..., options).

Returns the Plotter class instance.

def thumbnail( self, zoom=1.25, size=(200, 200), bg='white', azimuth=0, elevation=0, axes=False) -> numpy.ndarray:
148    def thumbnail(self, zoom=1.25, size=(200, 200), bg="white", azimuth=0, elevation=0, axes=False) -> np.ndarray:
149        """Build a thumbnail of the object and return it as an array."""
150        # speed is about 20Hz for size=[200,200]
151        ren = vtki.vtkRenderer()
152
153        actor = self.actor
154        if isinstance(self, vedo.UnstructuredGrid):
155            geo = vtki.new("GeometryFilter")
156            geo.SetInputData(self.dataset)
157            geo.Update()
158            actor = vedo.Mesh(geo.GetOutput()).cmap("rainbow").actor
159
160        ren.AddActor(actor)
161        if axes:
162            axes = vedo.addons.Axes(self)
163            ren.AddActor(axes.actor)
164        ren.ResetCamera()
165        cam = ren.GetActiveCamera()
166        cam.Zoom(zoom)
167        cam.Elevation(elevation)
168        cam.Azimuth(azimuth)
169
170        ren_win = vtki.vtkRenderWindow()
171        ren_win.SetOffScreenRendering(True)
172        ren_win.SetSize(size)
173        ren.SetBackground(colors.get_color(bg))
174        ren_win.AddRenderer(ren)
175        ren.ResetCameraClippingRange()
176        ren_win.Render()
177
178        nx, ny = ren_win.GetSize()
179        arr = vtki.vtkUnsignedCharArray()
180        ren_win.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr)
181        narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
182        narr = np.ascontiguousarray(np.flip(narr, axis=0))
183
184        ren.RemoveActor(actor)
185        if axes:
186            ren.RemoveActor(axes.actor)
187        ren_win.Finalize()
188        del ren_win
189        return narr

Build a thumbnail of the object and return it as an array.

def pickable(self, value=None) -> Self:
191    def pickable(self, value=None) -> Self:
192        """Set/get the pickability property of an object."""
193        if value is None:
194            return self.actor.GetPickable()
195        self.actor.SetPickable(value)
196        return self

Set/get the pickability property of an object.

def use_bounds(self, value=True) -> Self:
198    def use_bounds(self, value=True) -> Self:
199        """
200        Instruct the current camera to either take into account or ignore
201        the object bounds when resetting.
202        """
203        self.actor.SetUseBounds(value)
204        return self

Instruct the current camera to either take into account or ignore the object bounds when resetting.

def draggable(self, value=None) -> Self:
206    def draggable(self, value=None) -> Self:  # NOT FUNCTIONAL?
207        """Set/get the draggability property of an object."""
208        if value is None:
209            return self.actor.GetDragable()
210        self.actor.SetDragable(value)
211        return self

Set/get the draggability property of an object.

def on(self) -> Self:
213    def on(self) -> Self:
214        """Switch on  object visibility. Object is not removed."""
215        self.actor.VisibilityOn()
216        try:
217            self.scalarbar.actor.VisibilityOn()
218        except AttributeError:
219            pass
220        try:
221            self.trail.actor.VisibilityOn()
222        except AttributeError:
223            pass
224        try:
225            for sh in self.shadows:
226                sh.actor.VisibilityOn()
227        except AttributeError:
228            pass
229        return self

Switch on object visibility. Object is not removed.

def off(self) -> Self:
231    def off(self) -> Self:
232        """Switch off object visibility. Object is not removed."""
233        self.actor.VisibilityOff()
234        try:
235            self.scalarbar.actor.VisibilityOff()
236        except AttributeError:
237            pass
238        try:
239            self.trail.actor.VisibilityOff()
240        except AttributeError:
241            pass
242        try:
243            for sh in self.shadows:
244                sh.actor.VisibilityOff()
245        except AttributeError:
246            pass
247        return self

Switch off object visibility. Object is not removed.

def toggle(self) -> Self:
249    def toggle(self) -> Self:
250        """Toggle object visibility on/off."""
251        v = self.actor.GetVisibility()
252        if v:
253            self.off()
254        else:
255            self.on()
256        return self

Toggle object visibility on/off.

def add_scalarbar( self, title='', pos=(), size=(80, 400), font_size=14, title_yoffset=15, nlabels=None, c=None, horizontal=False, use_alpha=True, label_format=':6.3g') -> Self:
258    def add_scalarbar(
259        self,
260        title="",
261        pos=(),
262        size=(80, 400),
263        font_size=14,
264        title_yoffset=15,
265        nlabels=None,
266        c=None,
267        horizontal=False,
268        use_alpha=True,
269        label_format=":6.3g",
270    ) -> Self:
271        """
272        Add a 2D scalar bar for the specified object.
273
274        Arguments:
275            title : (str)
276                scalar bar title
277            pos : (list)
278                position coordinates of the bottom left corner.
279                Can also be a pair of (x,y) values in the range [0,1]
280                to indicate the position of the bottom left and top right corners.
281            size : (float,float)
282                size of the scalarbar in number of pixels (width, height)
283            font_size : (float)
284                size of font for title and numeric labels
285            title_yoffset : (float)
286                vertical space offset between title and color scalarbar
287            nlabels : (int)
288                number of numeric labels
289            c : (list)
290                color of the scalar bar text
291            horizontal : (bool)
292                lay the scalarbar horizontally
293            use_alpha : (bool)
294                render transparency in the color bar itself
295            label_format : (str)
296                c-style format string for numeric labels
297
298        Examples:
299            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
300            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
301        """
302        plt = vedo.plotter_instance
303
304        if plt and plt.renderer:
305            c = (0.9, 0.9, 0.9)
306            if np.sum(plt.renderer.GetBackground()) > 1.5:
307                c = (0.1, 0.1, 0.1)
308            if isinstance(self.scalarbar, vtki.vtkActor):
309                plt.renderer.RemoveActor(self.scalarbar)
310            elif isinstance(self.scalarbar, vedo.Assembly):
311                for a in self.scalarbar.unpack():
312                    plt.renderer.RemoveActor(a)
313        if c is None:
314            c = "gray"
315
316        sb = vedo.addons.ScalarBar(
317            self,
318            title,
319            pos,
320            size,
321            font_size,
322            title_yoffset,
323            nlabels,
324            c,
325            horizontal,
326            use_alpha,
327            label_format,
328        )
329        self.scalarbar = sb
330        return self

Add a 2D scalar bar for the specified object.

Arguments:
  • title : (str) scalar bar title
  • pos : (list) position coordinates of the bottom left corner. Can also be a pair of (x,y) values in the range [0,1] to indicate the position of the bottom left and top right corners.
  • size : (float,float) size of the scalarbar in number of pixels (width, height)
  • font_size : (float) size of font for title and numeric labels
  • title_yoffset : (float) vertical space offset between title and color scalarbar
  • nlabels : (int) number of numeric labels
  • c : (list) color of the scalar bar text
  • horizontal : (bool) lay the scalarbar horizontally
  • use_alpha : (bool) render transparency in the color bar itself
  • label_format : (str) c-style format string for numeric labels
Examples:
def add_scalarbar3d( self, title='', pos=None, size=(0, 0), title_font='', title_xoffset=-1.2, title_yoffset=0.0, title_size=1.5, title_rotation=0.0, nlabels=9, label_font='', label_size=1, label_offset=0.375, label_rotation=0, label_format='', italic=0, c=None, draw_box=True, above_text=None, below_text=None, nan_text='NaN', categories=None) -> Self:
332    def add_scalarbar3d(
333        self,
334        title="",
335        pos=None,
336        size=(0, 0),
337        title_font="",
338        title_xoffset=-1.2,
339        title_yoffset=0.0,
340        title_size=1.5,
341        title_rotation=0.0,
342        nlabels=9,
343        label_font="",
344        label_size=1,
345        label_offset=0.375,
346        label_rotation=0,
347        label_format="",
348        italic=0,
349        c=None,
350        draw_box=True,
351        above_text=None,
352        below_text=None,
353        nan_text="NaN",
354        categories=None,
355    ) -> Self:
356        """
357        Associate a 3D scalar bar to the object and add it to the scene.
358        The new scalarbar object (Assembly) will be accessible as obj.scalarbar
359
360        Arguments:
361            size : (list)
362                (thickness, length) of scalarbar
363            title : (str)
364                scalar bar title
365            title_xoffset : (float)
366                horizontal space btw title and color scalarbar
367            title_yoffset : (float)
368                vertical space offset
369            title_size : (float)
370                size of title wrt numeric labels
371            title_rotation : (float)
372                title rotation in degrees
373            nlabels : (int)
374                number of numeric labels
375            label_font : (str)
376                font type for labels
377            label_size : (float)
378                label scale factor
379            label_offset : (float)
380                space btw numeric labels and scale
381            label_rotation : (float)
382                label rotation in degrees
383            label_format : (str)
384                label format for floats and integers (e.g. `':.2f'`)
385            draw_box : (bool)
386                draw a box around the colorbar
387            categories : (list)
388                make a categorical scalarbar,
389                the input list will have the format `[value, color, alpha, textlabel]`
390
391        Examples:
392            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
393        """
394        plt = vedo.plotter_instance
395        if plt and c is None:  # automatic black or white
396            c = (0.9, 0.9, 0.9)
397            if np.sum(vedo.get_color(plt.backgrcol)) > 1.5:
398                c = (0.1, 0.1, 0.1)
399        if c is None:
400            c = (0, 0, 0)
401        c = vedo.get_color(c)
402
403        self.scalarbar = vedo.addons.ScalarBar3D(
404            self,
405            title,
406            pos,
407            size,
408            title_font,
409            title_xoffset,
410            title_yoffset,
411            title_size,
412            title_rotation,
413            nlabels,
414            label_font,
415            label_size,
416            label_offset,
417            label_rotation,
418            label_format,
419            italic,
420            c,
421            draw_box,
422            above_text,
423            below_text,
424            nan_text,
425            categories,
426        )
427        return self

Associate a 3D scalar bar to the object and add it to the scene. The new scalarbar object (Assembly) will be accessible as obj.scalarbar

Arguments:
  • size : (list) (thickness, length) of scalarbar
  • title : (str) scalar bar title
  • title_xoffset : (float) horizontal space btw title and color scalarbar
  • title_yoffset : (float) vertical space offset
  • title_size : (float) size of title wrt numeric labels
  • title_rotation : (float) title rotation in degrees
  • nlabels : (int) number of numeric labels
  • label_font : (str) font type for labels
  • label_size : (float) label scale factor
  • label_offset : (float) space btw numeric labels and scale
  • label_rotation : (float) label rotation in degrees
  • label_format : (str) label format for floats and integers (e.g. ':.2f')
  • draw_box : (bool) draw a box around the colorbar
  • categories : (list) make a categorical scalarbar, the input list will have the format [value, color, alpha, textlabel]
Examples:
def color(self, col, alpha=None, vmin=None, vmax=None):
429    def color(self, col, alpha=None, vmin=None, vmax=None):
430        """
431        Assign a color or a set of colors along the range of the scalar value.
432        A single constant color can also be assigned.
433        Any matplotlib color map name is also accepted, e.g. `volume.color('jet')`.
434
435        E.g.: say that your cells scalar runs from -3 to 6,
436        and you want -3 to show red and 1.5 violet and 6 green, then just set:
437
438        `volume.color(['red', 'violet', 'green'])`
439
440        You can also assign a specific color to a aspecific value with eg.:
441
442        `volume.color([(0,'red'), (0.5,'violet'), (1,'green')])`
443
444        Arguments:
445            alpha : (list)
446                use a list to specify transparencies along the scalar range
447            vmin : (float)
448                force the min of the scalar range to be this value
449            vmax : (float)
450                force the max of the scalar range to be this value
451        """
452        # supersedes method in Points, Mesh
453
454        if col is None:
455            return self
456        
457        if vmin is None:
458            vmin, _ = self.dataset.GetScalarRange()
459        if vmax is None:
460            _, vmax = self.dataset.GetScalarRange()
461        ctf = self.properties.GetRGBTransferFunction()
462        ctf.RemoveAllPoints()
463
464        if utils.is_sequence(col):
465            if utils.is_sequence(col[0]) and len(col[0]) == 2:
466                # user passing [(value1, color1), ...]
467                for x, ci in col:
468                    r, g, b = colors.get_color(ci)
469                    ctf.AddRGBPoint(x, r, g, b)
470                    # colors.printc('color at', round(x, 1),
471                    #               'set to', colors.get_color_name((r, g, b)), bold=0)
472            else:
473                # user passing [color1, color2, ..]
474                for i, ci in enumerate(col):
475                    r, g, b = colors.get_color(ci)
476                    x = vmin + (vmax - vmin) * i / (len(col) - 1)
477                    ctf.AddRGBPoint(x, r, g, b)
478        elif isinstance(col, str):
479            if col in colors.colors.keys() or col in colors.color_nicks.keys():
480                r, g, b = colors.get_color(col)
481                ctf.AddRGBPoint(vmin, r, g, b)  # constant color
482                ctf.AddRGBPoint(vmax, r, g, b)
483            else:  # assume it's a colormap
484                for x in np.linspace(vmin, vmax, num=64, endpoint=True):
485                    r, g, b = colors.color_map(x, name=col, vmin=vmin, vmax=vmax)
486                    ctf.AddRGBPoint(x, r, g, b)
487        elif isinstance(col, int):
488            r, g, b = colors.get_color(col)
489            ctf.AddRGBPoint(vmin, r, g, b)  # constant color
490            ctf.AddRGBPoint(vmax, r, g, b)
491        elif isinstance(col, vtki.vtkLookupTable):
492            alpha=[]
493            nt = col.GetNumberOfTableValues()
494            for i in range(nt):
495                r, g, b, a = col.GetTableValue(i)
496                x = vmin + (vmax - vmin) * i / (nt - 1)
497                ctf.AddRGBPoint(x, r, g, b)
498                alpha.append(a)
499        elif hasattr(col, "resampled"): # cover the case of LinearSegmentedColormap
500            N = col.N
501            cs = np.array([col(i/N) for i in range(N)])
502            alpha = cs[:,3].copy()
503            for i, v in enumerate(cs):
504                r, g, b, _ = v
505                x = vmin + (vmax - vmin) * i / (N - 1)
506                ctf.AddRGBPoint(x, r, g, b)
507        elif hasattr(col, "to_rgba"):   # col is a matplotlib colormap
508            for i in range(256):
509                r, g, b, a = col(i / 255)
510                x = vmin + (vmax - vmin) * i / 255
511                ctf.AddRGBPoint(x, r, g, b)
512                alpha.append(a)
513        else:
514            vedo.logger.warning(f"in color() unknown input type {type(col)}")
515
516        if alpha is not None:
517            self.alpha(alpha, vmin=vmin, vmax=vmax)
518        return self

Assign a color or a set of colors along the range of the scalar value. A single constant color can also be assigned. Any matplotlib color map name is also accepted, e.g. volume.color('jet').

E.g.: say that your cells scalar runs from -3 to 6, and you want -3 to show red and 1.5 violet and 6 green, then just set:

volume.color(['red', 'violet', 'green'])

You can also assign a specific color to a aspecific value with eg.:

volume.color([(0,'red'), (0.5,'violet'), (1,'green')])

Arguments:
  • alpha : (list) use a list to specify transparencies along the scalar range
  • vmin : (float) force the min of the scalar range to be this value
  • vmax : (float) force the max of the scalar range to be this value
def alpha(self, alpha, vmin=None, vmax=None) -> Self:
520    def alpha(self, alpha, vmin=None, vmax=None) -> Self:
521        """
522        Assign a set of tranparencies along the range of the scalar value.
523        A single constant value can also be assigned.
524
525        E.g.: say `alpha=(0.0, 0.3, 0.9, 1)` and the scalar range goes from -10 to 150.
526        Then all cells with a value close to -10 will be completely transparent, cells at 1/4
527        of the range will get an alpha equal to 0.3 and voxels with value close to 150
528        will be completely opaque.
529
530        As a second option one can set explicit (x, alpha_x) pairs to define the transfer function.
531
532        E.g.: say `alpha=[(-5, 0), (35, 0.4) (123,0.9)]` and the scalar range goes from -10 to 150.
533        Then all cells below -5 will be completely transparent, cells with a scalar value of 35
534        will get an opacity of 40% and above 123 alpha is set to 90%.
535        """
536        if vmin is None:
537            vmin, _ = self.dataset.GetScalarRange()
538        if vmax is None:
539            _, vmax = self.dataset.GetScalarRange()
540        otf = self.properties.GetScalarOpacity()
541        otf.RemoveAllPoints()
542
543        if utils.is_sequence(alpha):
544            alpha = np.array(alpha)
545            if len(alpha.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
546                for i, al in enumerate(alpha):
547                    xalpha = vmin + (vmax - vmin) * i / (len(alpha) - 1)
548                    # Create transfer mapping scalar value to opacity
549                    otf.AddPoint(xalpha, al)
550                    # print("alpha at", round(xalpha, 1), "\tset to", al)
551            elif len(alpha.shape) == 2:  # user passing [(x0,alpha0), ...]
552                otf.AddPoint(vmin, alpha[0][1])
553                for xalpha, al in alpha:
554                    # Create transfer mapping scalar value to opacity
555                    otf.AddPoint(xalpha, al)
556                otf.AddPoint(vmax, alpha[-1][1])
557
558        else:
559
560            otf.AddPoint(vmin, alpha)  # constant alpha
561            otf.AddPoint(vmax, alpha)
562
563        return self

Assign a set of tranparencies along the range of the scalar value. A single constant value can also be assigned.

E.g.: say alpha=(0.0, 0.3, 0.9, 1) and the scalar range goes from -10 to 150. Then all cells with a value close to -10 will be completely transparent, cells at 1/4 of the range will get an alpha equal to 0.3 and voxels with value close to 150 will be completely opaque.

As a second option one can set explicit (x, alpha_x) pairs to define the transfer function.

E.g.: say alpha=[(-5, 0), (35, 0.4) (123,0.9)] and the scalar range goes from -10 to 150. Then all cells below -5 will be completely transparent, cells with a scalar value of 35 will get an opacity of 40% and above 123 alpha is set to 90%.

class PointsVisual(CommonVisual):
 873class PointsVisual(CommonVisual):
 874    """Class to manage the visual aspects of a ``Points`` object."""
 875
 876    def __init__(self):
 877        # print("init PointsVisual")
 878        super().__init__()
 879        self.properties_backface = None
 880        self._cmap_name = None
 881        self.trail = None
 882        self.trail_offset = 0
 883        self.trail_points = []
 884        self._caption = None
 885
 886
 887    def clone2d(self, size=None, offset=(), scale=None):
 888        """
 889        Turn a 3D `Points` or `Mesh` into a flat 2D actor.
 890        Returns a `Actor2D`.
 891
 892        Arguments:
 893            size : (float)
 894                size as scaling factor for the 2D actor
 895            offset : (list)
 896                2D (x, y) position of the actor in the range [-1, 1]
 897            scale : (float)
 898                Deprecated. Use `size` instead.
 899
 900        Examples:
 901            - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py)
 902
 903                ![](https://vedo.embl.es/images/other/clone2d.png)
 904        """
 905        # assembly.Assembly.clone2d() superseeds this method
 906        if scale is not None:
 907            vedo.logger.warning("clone2d(): use keyword size not scale")
 908            size = scale
 909
 910        if size is None:
 911            # work out a reasonable scale
 912            msiz = self.diagonal_size()
 913            if vedo.plotter_instance and vedo.plotter_instance.window:
 914                sz = vedo.plotter_instance.window.GetSize()
 915                dsiz = utils.mag(sz)
 916                size = dsiz / msiz / 10
 917            else:
 918                size = 350 / msiz
 919
 920        tp = vtki.new("TransformPolyDataFilter")
 921        transform = vtki.vtkTransform()
 922        transform.Scale(size, size, size)
 923        if len(offset) == 0:
 924            offset = self.pos()
 925        transform.Translate(-utils.make3d(offset))
 926        tp.SetTransform(transform)
 927        tp.SetInputData(self.dataset)
 928        tp.Update()
 929        poly = tp.GetOutput()
 930
 931        cm = self.mapper.GetColorMode()
 932        lut = self.mapper.GetLookupTable()
 933        sv = self.mapper.GetScalarVisibility()
 934        use_lut = self.mapper.GetUseLookupTableScalarRange()
 935        vrange = self.mapper.GetScalarRange()
 936        sm = self.mapper.GetScalarMode()
 937
 938        act2d = Actor2D(poly)
 939        act2d.mapper.SetColorMode(cm)
 940        act2d.mapper.SetLookupTable(lut)
 941        act2d.mapper.SetScalarVisibility(sv)
 942        act2d.mapper.SetUseLookupTableScalarRange(use_lut)
 943        act2d.mapper.SetScalarRange(vrange)
 944        act2d.mapper.SetScalarMode(sm)
 945
 946        act2d.GetPositionCoordinate().SetCoordinateSystem(4)
 947        act2d.properties.SetColor(self.color())
 948        act2d.properties.SetOpacity(self.alpha())
 949        act2d.properties.SetLineWidth(self.properties.GetLineWidth())
 950        act2d.properties.SetPointSize(self.properties.GetPointSize())
 951        act2d.properties.SetDisplayLocation(0) # 0 = back, 1 = front
 952        act2d.PickableOff()
 953        return act2d
 954
 955    ##################################################
 956    def copy_properties_from(self, source, deep=True, actor_related=True) -> Self:
 957        """
 958        Copy properties from another ``Points`` object.
 959        """
 960        pr = vtki.vtkProperty()
 961        try:
 962            sp = source.properties
 963            mp = source.mapper
 964            sa = source.actor
 965        except AttributeError:
 966            sp = source.GetProperty()
 967            mp = source.GetMapper()
 968            sa = source
 969
 970        if deep:
 971            pr.DeepCopy(sp)
 972        else:
 973            pr.ShallowCopy(sp)
 974        self.actor.SetProperty(pr)
 975        self.properties = pr
 976
 977        if self.actor.GetBackfaceProperty():
 978            bfpr = vtki.vtkProperty()
 979            bfpr.DeepCopy(sa.GetBackfaceProperty())
 980            self.actor.SetBackfaceProperty(bfpr)
 981            self.properties_backface = bfpr
 982
 983        if not actor_related:
 984            return self
 985
 986        # mapper related:
 987        self.mapper.SetScalarVisibility(mp.GetScalarVisibility())
 988        self.mapper.SetScalarMode(mp.GetScalarMode())
 989        self.mapper.SetScalarRange(mp.GetScalarRange())
 990        self.mapper.SetLookupTable(mp.GetLookupTable())
 991        self.mapper.SetColorMode(mp.GetColorMode())
 992        self.mapper.SetInterpolateScalarsBeforeMapping(
 993            mp.GetInterpolateScalarsBeforeMapping()
 994        )
 995        self.mapper.SetUseLookupTableScalarRange(
 996            mp.GetUseLookupTableScalarRange()
 997        )
 998
 999        self.actor.SetPickable(sa.GetPickable())
1000        self.actor.SetDragable(sa.GetDragable())
1001        self.actor.SetTexture(sa.GetTexture())
1002        self.actor.SetVisibility(sa.GetVisibility())
1003        return self
1004
1005    def color(self, c=False, alpha=None) -> Union[np.ndarray, Self]:
1006        """
1007        Set/get mesh's color.
1008        If None is passed as input, will use colors from active scalars.
1009        Same as `mesh.c()`.
1010        """
1011        if c is False:
1012            return np.array(self.properties.GetColor())
1013        if c is None:
1014            self.mapper.ScalarVisibilityOn()
1015            return self
1016        self.mapper.ScalarVisibilityOff()
1017        cc = colors.get_color(c)
1018        self.properties.SetColor(cc)
1019        if self.trail:
1020            self.trail.properties.SetColor(cc)
1021        if alpha is not None:
1022            self.alpha(alpha)
1023        return self
1024
1025    def c(self, color=False, alpha=None) -> Union[np.ndarray, Self]:
1026        """
1027        Shortcut for `color()`.
1028        If None is passed as input, will use colors from current active scalars.
1029        """
1030        return self.color(color, alpha)
1031
1032    def alpha(self, opacity=None) -> Union[float, Self]:
1033        """Set/get mesh's transparency. Same as `mesh.opacity()`."""
1034        if opacity is None:
1035            return self.properties.GetOpacity()
1036
1037        self.properties.SetOpacity(opacity)
1038        bfp = self.actor.GetBackfaceProperty()
1039        if bfp:
1040            if opacity < 1:
1041                self.properties_backface = bfp
1042                self.actor.SetBackfaceProperty(None)
1043            else:
1044                self.actor.SetBackfaceProperty(self.properties_backface)
1045        return self
1046
1047    def lut_color_at(self, value) -> np.ndarray:
1048        """
1049        Return the color and alpha in the lookup table at given value.
1050        """
1051        lut = self.mapper.GetLookupTable()
1052        if not lut:
1053            return None
1054        rgb = [0,0,0]
1055        lut.GetColor(value, rgb)
1056        alpha = lut.GetOpacity(value)
1057        return np.array(rgb + [alpha])
1058
1059    def opacity(self, alpha=None) -> Union[float, Self]:
1060        """Set/get mesh's transparency. Same as `mesh.alpha()`."""
1061        return self.alpha(alpha)
1062
1063    def force_opaque(self, value=True) -> Self:
1064        """ Force the Mesh, Line or point cloud to be treated as opaque"""
1065        ## force the opaque pass, fixes picking in vtk9
1066        # but causes other bad troubles with lines..
1067        self.actor.SetForceOpaque(value)
1068        return self
1069
1070    def force_translucent(self, value=True) -> Self:
1071        """ Force the Mesh, Line or point cloud to be treated as translucent"""
1072        self.actor.SetForceTranslucent(value)
1073        return self
1074
1075    def point_size(self, value=None) -> Union[int, Self]:
1076        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
1077        if value is None:
1078            return self.properties.GetPointSize()
1079            # self.properties.SetRepresentationToSurface()
1080        else:
1081            self.properties.SetRepresentationToPoints()
1082            self.properties.SetPointSize(value)
1083        return self
1084
1085    def ps(self, pointsize=None) -> Union[int, Self]:
1086        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
1087        return self.point_size(pointsize)
1088
1089    def render_points_as_spheres(self, value=True) -> Self:
1090        """Make points look spheric or else make them look as squares."""
1091        self.properties.SetRenderPointsAsSpheres(value)
1092        return self
1093
1094    def lighting(
1095        self,
1096        style="",
1097        ambient=None,
1098        diffuse=None,
1099        specular=None,
1100        specular_power=None,
1101        specular_color=None,
1102        metallicity=None,
1103        roughness=None,
1104    ) -> Self:
1105        """
1106        Set the ambient, diffuse, specular and specular_power lighting constants.
1107
1108        Arguments:
1109            style : (str)
1110                preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]`
1111            ambient : (float)
1112                ambient fraction of emission [0-1]
1113            diffuse : (float)
1114                emission of diffused light in fraction [0-1]
1115            specular : (float)
1116                fraction of reflected light [0-1]
1117            specular_power : (float)
1118                precision of reflection [1-100]
1119            specular_color : (color)
1120                color that is being reflected by the surface
1121
1122        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px>
1123
1124        Examples:
1125            - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py)
1126        """
1127        pr = self.properties
1128
1129        if style:
1130
1131            if style != "off":
1132                pr.LightingOn()
1133
1134            if style == "off":
1135                pr.SetInterpolationToFlat()
1136                pr.LightingOff()
1137                return self  ##############
1138
1139            if hasattr(pr, "GetColor"):  # could be Volume
1140                c = pr.GetColor()
1141            else:
1142                c = (1, 1, 0.99)
1143            mpr = self.mapper
1144            if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility():
1145                c = (1,1,0.99)
1146            if   style=='metallic': pars = [0.1, 0.3, 1.0, 10, c]
1147            elif style=='plastic' : pars = [0.3, 0.4, 0.3,  5, c]
1148            elif style=='shiny'   : pars = [0.2, 0.6, 0.8, 50, c]
1149            elif style=='glossy'  : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)]
1150            elif style=='ambient' : pars = [0.8, 0.1, 0.0,  1, (1,1,1)]
1151            elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c]
1152            else:
1153                vedo.logger.error("in lighting(): Available styles are")
1154                vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]")
1155                raise RuntimeError()
1156            pr.SetAmbient(pars[0])
1157            pr.SetDiffuse(pars[1])
1158            pr.SetSpecular(pars[2])
1159            pr.SetSpecularPower(pars[3])
1160            if hasattr(pr, "GetColor"):
1161                pr.SetSpecularColor(pars[4])
1162
1163        if ambient is not None: pr.SetAmbient(ambient)
1164        if diffuse is not None: pr.SetDiffuse(diffuse)
1165        if specular is not None: pr.SetSpecular(specular)
1166        if specular_power is not None: pr.SetSpecularPower(specular_power)
1167        if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color))
1168        if metallicity is not None:
1169            pr.SetInterpolationToPBR()
1170            pr.SetMetallic(metallicity)
1171        if roughness is not None:
1172            pr.SetInterpolationToPBR()
1173            pr.SetRoughness(roughness)
1174
1175        return self
1176
1177    def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self:
1178        """Set point blurring.
1179        Apply a gaussian convolution filter to the points.
1180        In this case the radius `r` is in absolute units of the mesh coordinates.
1181        With emissive set, the halo of point becomes light-emissive.
1182        """
1183        self.properties.SetRepresentationToPoints()
1184        if emissive:
1185            self.mapper.SetEmissive(bool(emissive))
1186        self.mapper.SetScaleFactor(r * 1.4142)
1187
1188        # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
1189        if alpha < 1:
1190            self.mapper.SetSplatShaderCode(
1191                "//VTK::Color::Impl\n"
1192                "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
1193                "if (dist > 1.0) {\n"
1194                "   discard;\n"
1195                "} else {\n"
1196                f"  float scale = ({alpha} - dist);\n"
1197                "   ambientColor *= scale;\n"
1198                "   diffuseColor *= scale;\n"
1199                "}\n"
1200            )
1201            alpha = 1
1202
1203        self.mapper.Modified()
1204        self.actor.Modified()
1205        self.properties.SetOpacity(alpha)
1206        self.actor.SetMapper(self.mapper)
1207        return self
1208
1209    @property
1210    def cellcolors(self):
1211        """
1212        Colorize each cell (face) of a mesh by passing
1213        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1214        Colors levels and opacities must be in the range [0,255].
1215
1216        A single constant color can also be passed as string or RGBA.
1217
1218        A cell array named "CellsRGBA" is automatically created.
1219
1220        Examples:
1221            - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py)
1222            - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py)
1223
1224            ![](https://vedo.embl.es/images/basic/colorMeshCells.png)
1225        """
1226        if "CellsRGBA" not in self.celldata.keys():
1227            lut = self.mapper.GetLookupTable()
1228            vscalars = self.dataset.GetCellData().GetScalars()
1229            if vscalars is None or lut is None:
1230                arr = np.zeros([self.ncells, 4], dtype=np.uint8)
1231                col = np.array(self.properties.GetColor())
1232                col = np.round(col * 255).astype(np.uint8)
1233                alf = self.properties.GetOpacity()
1234                alf = np.round(alf * 255).astype(np.uint8)
1235                arr[:, (0, 1, 2)] = col
1236                arr[:, 3] = alf
1237            else:
1238                cols = lut.MapScalars(vscalars, 0, 0)
1239                arr = utils.vtk2numpy(cols)
1240            self.celldata["CellsRGBA"] = arr
1241        self.celldata.select("CellsRGBA")
1242        return self.celldata["CellsRGBA"]
1243
1244    @cellcolors.setter
1245    def cellcolors(self, value):
1246        if isinstance(value, str):
1247            c = colors.get_color(value)
1248            value = np.array([*c, 1]) * 255
1249            value = np.round(value)
1250
1251        value = np.asarray(value)
1252        n = self.ncells
1253
1254        if value.ndim == 1:
1255            value = np.repeat([value], n, axis=0)
1256
1257        if value.shape[1] == 3:
1258            z = np.zeros((n, 1), dtype=np.uint8)
1259            value = np.append(value, z + 255, axis=1)
1260
1261        assert n == value.shape[0]
1262
1263        self.celldata["CellsRGBA"] = value.astype(np.uint8)
1264        # self.mapper.SetColorModeToDirectScalars() # done in select()
1265        self.celldata.select("CellsRGBA")
1266
1267    @property
1268    def pointcolors(self):
1269        """
1270        Colorize each point (or vertex of a mesh) by passing
1271        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1272        Colors levels and opacities must be in the range [0,255].
1273
1274        A single constant color can also be passed as string or RGBA.
1275
1276        A point array named "PointsRGBA" is automatically created.
1277        """
1278        if "PointsRGBA" not in self.pointdata.keys():
1279            lut = self.mapper.GetLookupTable()
1280            vscalars = self.dataset.GetPointData().GetScalars()
1281            if vscalars is None or lut is None:
1282                # create a constant array
1283                arr = np.zeros([self.npoints, 4], dtype=np.uint8)
1284                col = np.array(self.properties.GetColor())
1285                col = np.round(col * 255).astype(np.uint8)
1286                alf = self.properties.GetOpacity()
1287                alf = np.round(alf * 255).astype(np.uint8)
1288                arr[:, (0, 1, 2)] = col
1289                arr[:, 3] = alf
1290            else:
1291                cols = lut.MapScalars(vscalars, 0, 0)
1292                arr = utils.vtk2numpy(cols)
1293            self.pointdata["PointsRGBA"] = arr
1294        self.pointdata.select("PointsRGBA")
1295        return self.pointdata["PointsRGBA"]
1296
1297    @pointcolors.setter
1298    def pointcolors(self, value):
1299        if isinstance(value, str):
1300            c = colors.get_color(value)
1301            value = np.array([*c, 1]) * 255
1302            value = np.round(value)
1303
1304        value = np.asarray(value)
1305        n = self.npoints
1306
1307        if value.ndim == 1:
1308            value = np.repeat([value], n, axis=0)
1309
1310        if value.shape[1] == 3:
1311            z = np.zeros((n, 1), dtype=np.uint8)
1312            value = np.append(value, z + 255, axis=1)
1313
1314        assert n == value.shape[0]
1315
1316        self.pointdata["PointsRGBA"] = value.astype(np.uint8)
1317        self.mapper.SetColorModeToDirectScalars() # also done in select()
1318        self.pointdata.select("PointsRGBA")
1319
1320    #####################################################################################
1321    def cmap(
1322        self,
1323        input_cmap,
1324        input_array=None,
1325        on="",
1326        name="Scalars",
1327        vmin=None,
1328        vmax=None,
1329        n_colors=256,
1330        alpha=1.0,
1331        logscale=False,
1332    ) -> Self:
1333        """
1334        Set individual point/cell colors by providing a list of scalar values and a color map.
1335
1336        Arguments:
1337            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
1338                color map scheme to transform a real number into a color.
1339            input_array : (str, list, vtkArray)
1340                can be the string name of an existing array, a new array or a `vtkArray`.
1341            on : (str)
1342                either 'points' or 'cells' or blank (automatic).
1343                Apply the color map to data which is defined on either points or cells.
1344            name : (str)
1345                give a name to the provided array (if input_array is an array)
1346            vmin : (float)
1347                clip scalars to this minimum value
1348            vmax : (float)
1349                clip scalars to this maximum value
1350            n_colors : (int)
1351                number of distinct colors to be used in colormap table.
1352            alpha : (float, list)
1353                Mesh transparency. Can be a `list` of values one for each vertex.
1354            logscale : (bool)
1355                Use logscale
1356
1357        Examples:
1358            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
1359            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
1360            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
1361            - (and many others)
1362
1363                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
1364        """
1365        self._cmap_name = input_cmap
1366
1367        if on == "":
1368            try:
1369                on = self.mapper.GetScalarModeAsString().replace("Use", "")
1370                if on not in ["PointData", "CellData"]: # can be "Default"
1371                    on = "points"
1372                    self.mapper.SetScalarModeToUsePointData()
1373            except AttributeError:
1374                on = "points"
1375        elif on == "Default":
1376            on = "points"
1377            self.mapper.SetScalarModeToUsePointData()
1378
1379        if input_array is None:
1380            if not self.pointdata.keys() and self.celldata.keys():
1381                on = "cells"
1382                if not self.dataset.GetCellData().GetScalars():
1383                    input_array = 0  # pick the first at hand
1384
1385        if "point" in on.lower():
1386            data = self.dataset.GetPointData()
1387            n = self.dataset.GetNumberOfPoints()
1388        elif "cell" in on.lower():
1389            data = self.dataset.GetCellData()
1390            n = self.dataset.GetNumberOfCells()
1391        else:
1392            vedo.logger.error(
1393                f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}")
1394            raise RuntimeError()
1395
1396        if input_array is None:  # if None try to fetch the active scalars
1397            arr = data.GetScalars()
1398            if not arr:
1399                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
1400                return self
1401
1402            if not arr.GetName():  # sometimes arrays dont have a name..
1403                arr.SetName(name)
1404
1405        elif isinstance(input_array, str):  # if a string is passed
1406            arr = data.GetArray(input_array)
1407            if not arr:
1408                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
1409                return self
1410
1411        elif isinstance(input_array, int):  # if an int is passed
1412            if input_array < data.GetNumberOfArrays():
1413                arr = data.GetArray(input_array)
1414            else:
1415                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
1416                return self
1417
1418        elif utils.is_sequence(input_array):  # if a numpy array is passed
1419            npts = len(input_array)
1420            if npts != n:
1421                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
1422                return self
1423            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
1424            data.AddArray(arr)
1425            data.Modified()
1426
1427        elif isinstance(input_array, vtki.vtkArray):  # if a vtkArray is passed
1428            arr = input_array
1429            data.AddArray(arr)
1430            data.Modified()
1431
1432        else:
1433            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
1434            raise RuntimeError()
1435
1436        # Now we have array "arr"
1437        array_name = arr.GetName()
1438
1439        if arr.GetNumberOfComponents() == 1:
1440            if vmin is None:
1441                vmin = arr.GetRange()[0]
1442            if vmax is None:
1443                vmax = arr.GetRange()[1]
1444        else:
1445            if vmin is None or vmax is None:
1446                vn = utils.mag(utils.vtk2numpy(arr))
1447            if vmin is None:
1448                vmin = vn.min()
1449            if vmax is None:
1450                vmax = vn.max()
1451
1452        # interpolate alphas if they are not constant
1453        if not utils.is_sequence(alpha):
1454            alpha = [alpha] * n_colors
1455        else:
1456            v = np.linspace(0, 1, n_colors, endpoint=True)
1457            xp = np.linspace(0, 1, len(alpha), endpoint=True)
1458            alpha = np.interp(v, xp, alpha)
1459
1460        ########################### build the look-up table
1461        if isinstance(input_cmap, vtki.vtkLookupTable):  # vtkLookupTable
1462            lut = input_cmap
1463
1464        elif utils.is_sequence(input_cmap):  # manual sequence of colors
1465            lut = vtki.vtkLookupTable()
1466            if logscale:
1467                lut.SetScaleToLog10()
1468            lut.SetRange(vmin, vmax)
1469            ncols = len(input_cmap)
1470            lut.SetNumberOfTableValues(ncols)
1471
1472            for i, c in enumerate(input_cmap):
1473                r, g, b = colors.get_color(c)
1474                lut.SetTableValue(i, r, g, b, alpha[i])
1475            lut.Build()
1476
1477        else:
1478            # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
1479            lut = vtki.vtkLookupTable()
1480            if logscale:
1481                lut.SetScaleToLog10()
1482            lut.SetVectorModeToMagnitude()
1483            lut.SetRange(vmin, vmax)
1484            lut.SetNumberOfTableValues(n_colors)
1485            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
1486            for i, c in enumerate(mycols):
1487                r, g, b = c
1488                lut.SetTableValue(i, r, g, b, alpha[i])
1489            lut.Build()
1490
1491        # TEST NEW WAY
1492        self.mapper.SetLookupTable(lut)
1493        self.mapper.ScalarVisibilityOn()
1494        self.mapper.SetColorModeToMapScalars()
1495        self.mapper.SetScalarRange(lut.GetRange())
1496        if "point" in on.lower():
1497            self.pointdata.select(array_name)
1498        else:
1499            self.celldata.select(array_name)
1500        return self
1501
1502        # # TEST this is the old way:
1503        # # arr.SetLookupTable(lut) # wrong! causes weird instabilities with LUT
1504        # # if data.GetScalars():
1505        # #     data.GetScalars().SetLookupTable(lut)
1506        # #     data.GetScalars().Modified()
1507
1508        # data.SetActiveScalars(array_name)
1509        # # data.SetScalars(arr)  # wrong! it deletes array in position 0, never use SetScalars
1510        # # data.SetActiveAttribute(array_name, 0) # boh!
1511
1512        # self.mapper.SetLookupTable(lut)
1513        # self.mapper.SetColorModeToMapScalars()  # so we dont need to convert uint8 scalars
1514
1515        # self.mapper.ScalarVisibilityOn()
1516        # self.mapper.SetScalarRange(lut.GetRange())
1517
1518        # if on.startswith("point"):
1519        #     self.mapper.SetScalarModeToUsePointData()
1520        # else:
1521        #     self.mapper.SetScalarModeToUseCellData()
1522        # if hasattr(self.mapper, "SetArrayName"):
1523        #     self.mapper.SetArrayName(array_name)
1524        # return self
1525
1526    def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self:
1527        """
1528        Add a trailing line to mesh.
1529        This new mesh is accessible through `mesh.trail`.
1530
1531        Arguments:
1532            offset : (float)
1533                set an offset vector from the object center.
1534            n : (int)
1535                number of segments
1536            lw : (float)
1537                line width of the trail
1538
1539        Examples:
1540            - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py)
1541
1542                ![](https://vedo.embl.es/images/simulations/trail.gif)
1543
1544            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1545            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1546        """
1547        if self.trail is None:
1548            pos = self.pos()
1549            self.trail_offset = np.asarray(offset)
1550            self.trail_points = [pos] * n
1551
1552            if c is None:
1553                col = self.properties.GetColor()
1554            else:
1555                col = colors.get_color(c)
1556
1557            tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw)
1558            self.trail = tline  # holds the Line
1559            self.trail.initilized = False # so the first update will be a reset
1560        return self
1561
1562    def update_trail(self) -> Self:
1563        """
1564        Update the trailing line of a moving object.
1565        """
1566        currentpos = self.pos()
1567        if not self.trail.initilized:
1568            self.trail_points = [currentpos] * self.trail.npoints
1569            self.trail.initilized = True
1570            return self
1571        self.trail_points.append(currentpos)  # cycle
1572        self.trail_points.pop(0)
1573
1574        data = np.array(self.trail_points) + self.trail_offset
1575        tpoly = self.trail.dataset
1576        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1577        return self
1578
1579    def _compute_shadow(self, plane, point, direction):
1580        shad = self.clone()
1581        shad.name = "Shadow"
1582
1583        tarr = shad.dataset.GetPointData().GetTCoords()
1584        if tarr:  # remove any texture coords
1585            tname = tarr.GetName()
1586            shad.pointdata.remove(tname)
1587            shad.dataset.GetPointData().SetTCoords(None)
1588            shad.actor.SetTexture(None)
1589
1590        pts = shad.vertices
1591        if plane == "x":
1592            # shad = shad.project_on_plane('x')
1593            # instead do it manually so in case of alpha<1
1594            # we dont see glitches due to coplanar points
1595            # we leave a small tolerance of 0.1% in thickness
1596            x0, x1 = self.xbounds()
1597            pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[0]
1598            shad.vertices = pts
1599            shad.x(point)
1600        elif plane == "y":
1601            x0, x1 = self.ybounds()
1602            pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[1]
1603            shad.vertices = pts
1604            shad.y(point)
1605        elif plane == "z":
1606            x0, x1 = self.zbounds()
1607            pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[2]
1608            shad.vertices = pts
1609            shad.z(point)
1610        else:
1611            shad = shad.project_on_plane(plane, point, direction)
1612        return shad
1613
1614    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1615        """
1616        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1617        The output is a new `Mesh` representing the shadow.
1618        This new mesh is accessible through `mesh.shadow`.
1619        By default the shadow mesh is placed on the bottom wall of the bounding box.
1620
1621        See also `pointcloud.project_on_plane()`.
1622
1623        Arguments:
1624            plane : (str, Plane)
1625                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1626                represents x-plane, y-plane and z-plane, respectively.
1627                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1628            point : (float, array)
1629                if plane is `str`, point should be a float represents the intercept.
1630                Otherwise, point is the camera point of perspective projection
1631            direction : (list)
1632                direction of oblique projection
1633            culling : (int)
1634                choose between front [1] or backface [-1] culling or None.
1635
1636        Examples:
1637            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1638            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1639            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1640
1641            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1642        """
1643        shad = self._compute_shadow(plane, point, direction)
1644        shad.c(c).alpha(alpha)
1645
1646        try:
1647            # Points dont have these methods
1648            shad.flat()
1649            if culling in (1, True):
1650                shad.frontface_culling()
1651            elif culling == -1:
1652                shad.backface_culling()
1653        except AttributeError:
1654            pass
1655
1656        shad.properties.LightingOff()
1657        shad.actor.SetPickable(False)
1658        shad.actor.SetUseBounds(True)
1659
1660        if shad not in self.shadows:
1661            self.shadows.append(shad)
1662            shad.info = dict(plane=plane, point=point, direction=direction)
1663            # shad.metadata["plane"] = plane
1664            # shad.metadata["point"] = point
1665            # print("AAAA", direction, plane, point)
1666            # if direction is None:
1667            #     direction = [0,0,0]
1668            # shad.metadata["direction"] = direction
1669        return self
1670
1671    def update_shadows(self) -> Self:
1672        """Update the shadows of a moving object."""
1673        for sha in self.shadows:
1674            plane = sha.info["plane"]
1675            point = sha.info["point"]
1676            direction = sha.info["direction"]
1677            # print("update_shadows direction", direction,plane,point )
1678            # plane = sha.metadata["plane"]
1679            # point = sha.metadata["point"]
1680            # direction = sha.metadata["direction"]
1681            # if direction[0]==0 and direction[1]==0 and direction[2]==0:
1682            #     direction = None
1683            # print("BBBB", sha.metadata["direction"],
1684            #       sha.metadata["plane"], sha.metadata["point"])
1685            new_sha = self._compute_shadow(plane, point, direction)
1686            sha._update(new_sha.dataset)
1687        if self.trail:
1688            self.trail.update_shadows()
1689        return self
1690
1691    def labels(
1692        self,
1693        content=None,
1694        on="points",
1695        scale=None,
1696        xrot=0.0,
1697        yrot=0.0,
1698        zrot=0.0,
1699        ratio=1,
1700        precision=None,
1701        italic=False,
1702        font="",
1703        justify="",
1704        c="black",
1705        alpha=1.0,
1706    ) -> Union["vedo.Mesh", None]:
1707        """
1708        Generate value or ID labels for mesh cells or points.
1709        For large nr. of labels use `font="VTK"` which is much faster.
1710
1711        See also:
1712            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1713
1714        Arguments:
1715            content : (list,int,str)
1716                either 'id', 'cellid', array name or array number.
1717                A array can also be passed (must match the nr. of points or cells).
1718            on : (str)
1719                generate labels for "cells" instead of "points"
1720            scale : (float)
1721                absolute size of labels, if left as None it is automatic
1722            zrot : (float)
1723                local rotation angle of label in degrees
1724            ratio : (int)
1725                skipping ratio, to reduce nr of labels for large meshes
1726            precision : (int)
1727                numeric precision of labels
1728
1729        ```python
1730        from vedo import *
1731        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1732        point_ids = s.labels('id', on="points").c('green')
1733        cell_ids  = s.labels('id', on="cells" ).c('black')
1734        show(s, point_ids, cell_ids)
1735        ```
1736        ![](https://vedo.embl.es/images/feats/labels.png)
1737
1738        Examples:
1739            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1740
1741                ![](https://vedo.embl.es/images/basic/boundaries.png)
1742        """
1743
1744        cells = False
1745        if "cell" in on or "face" in on:
1746            cells = True
1747            justify = "centered" if justify == "" else justify
1748
1749        if isinstance(content, str):
1750            if content in ("pointid", "pointsid"):
1751                cells = False
1752                content = "id"
1753                justify = "bottom-left" if justify == "" else justify
1754            if content in ("cellid", "cellsid"):
1755                cells = True
1756                content = "id"
1757                justify = "centered" if justify == "" else justify
1758
1759        try:
1760            if cells:
1761                ns = np.sqrt(self.ncells)
1762                elems = self.cell_centers().points
1763                norms = self.cell_normals
1764                justify = "centered" if justify == "" else justify
1765            else:
1766                ns = np.sqrt(self.npoints)
1767                elems = self.vertices
1768                norms = self.vertex_normals
1769        except AttributeError:
1770            norms = []
1771
1772        if not justify:
1773            justify = "bottom-left"
1774
1775        hasnorms = False
1776        if len(norms) > 0:
1777            hasnorms = True
1778
1779        if scale is None:
1780            if not ns:
1781                ns = 100
1782            scale = self.diagonal_size() / ns / 10
1783
1784        arr = None
1785        mode = 0
1786        if content is None:
1787            mode = 0
1788            if cells:
1789                if self.dataset.GetCellData().GetScalars():
1790                    name = self.dataset.GetCellData().GetScalars().GetName()
1791                    arr = self.celldata[name]
1792            else:
1793                if self.dataset.GetPointData().GetScalars():
1794                    name = self.dataset.GetPointData().GetScalars().GetName()
1795                    arr = self.pointdata[name]
1796        elif isinstance(content, (str, int)):
1797            if content == "id":
1798                mode = 1
1799            elif cells:
1800                mode = 0
1801                arr = self.celldata[content]
1802            else:
1803                mode = 0
1804                arr = self.pointdata[content]
1805        elif utils.is_sequence(content):
1806            mode = 0
1807            arr = content
1808
1809        if arr is None and mode == 0:
1810            vedo.logger.error("in labels(), array not found in point or cell data")
1811            return None
1812
1813        ratio = int(ratio+0.5)
1814        tapp = vtki.new("AppendPolyData")
1815        has_inputs = False
1816
1817        for i, e in enumerate(elems):
1818            if i % ratio:
1819                continue
1820
1821            if mode == 1:
1822                txt_lab = str(i)
1823            else:
1824                if precision:
1825                    txt_lab = utils.precision(arr[i], precision)
1826                else:
1827                    txt_lab = str(arr[i])
1828
1829            if not txt_lab:
1830                continue
1831
1832            if font == "VTK":
1833                tx = vtki.new("VectorText")
1834                tx.SetText(txt_lab)
1835                tx.Update()
1836                tx_poly = tx.GetOutput()
1837            else:
1838                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset
1839
1840            if tx_poly.GetNumberOfPoints() == 0:
1841                continue  ######################
1842
1843            T = vtki.vtkTransform()
1844            T.PostMultiply()
1845            if italic:
1846                T.Concatenate([1, 0.2, 0, 0,
1847                               0, 1  , 0, 0,
1848                               0, 0  , 1, 0,
1849                               0, 0  , 0, 1])
1850            if hasnorms:
1851                ni = norms[i]
1852                if cells and font=="VTK":  # center-justify
1853                    bb = tx_poly.GetBounds()
1854                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1855                    T.Translate(-dx, -dy, 0)
1856                if xrot: T.RotateX(xrot)
1857                if yrot: T.RotateY(yrot)
1858                if zrot: T.RotateZ(zrot)
1859                crossvec = np.cross([0, 0, 1], ni)
1860                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1861                T.RotateWXYZ(float(angle), crossvec.tolist())
1862                T.Translate(ni / 100)
1863            else:
1864                if xrot: T.RotateX(xrot)
1865                if yrot: T.RotateY(yrot)
1866                if zrot: T.RotateZ(zrot)
1867            T.Scale(scale, scale, scale)
1868            T.Translate(e)
1869            tf = vtki.new("TransformPolyDataFilter")
1870            tf.SetInputData(tx_poly)
1871            tf.SetTransform(T)
1872            tf.Update()
1873            tapp.AddInputData(tf.GetOutput())
1874            has_inputs = True
1875
1876        if has_inputs:
1877            tapp.Update()
1878            lpoly = tapp.GetOutput()
1879        else:
1880            lpoly = vtki.vtkPolyData()
1881        ids = vedo.Mesh(lpoly, c=c, alpha=alpha)
1882        ids.properties.LightingOff()
1883        ids.actor.PickableOff()
1884        ids.actor.SetUseBounds(False)
1885        ids.name = "Labels"
1886        return ids
1887
1888    def labels2d(
1889        self,
1890        content="id",
1891        on="points",
1892        scale=1.0,
1893        precision=4,
1894        font="Calco",
1895        justify="bottom-left",
1896        angle=0.0,
1897        frame=False,
1898        c="black",
1899        bc=None,
1900        alpha=1.0,
1901    ) -> Union["Actor2D", None]:
1902        """
1903        Generate value or ID bi-dimensional labels for mesh cells or points.
1904
1905        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
1906
1907        Arguments:
1908            content : (str)
1909                either 'id', 'cellid', or array name
1910            on : (str)
1911                generate labels for "cells" instead of "points" (the default)
1912            scale : (float)
1913                size scaling of labels
1914            precision : (int)
1915                precision of numeric labels
1916            angle : (float)
1917                local rotation angle of label in degrees
1918            frame : (bool)
1919                draw a frame around the label
1920            bc : (str)
1921                background color of the label
1922
1923        ```python
1924        from vedo import Sphere, show
1925        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
1926        sph.celldata["zvals"] = sph.cell_centers().coordinates[:,2]
1927        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
1928        show(sph, l2d, axes=1).close()
1929        ```
1930        ![](https://vedo.embl.es/images/feats/labels2d.png)
1931        """
1932        cells = False
1933        if "cell" in on:
1934            cells = True
1935
1936        if isinstance(content, str):
1937            if content in ("id", "pointid", "pointsid"):
1938                cells = False
1939                content = "id"
1940            if content in ("cellid", "cellsid"):
1941                cells = True
1942                content = "id"
1943
1944        if cells:
1945            if content != "id" and content not in self.celldata.keys():
1946                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
1947                return None
1948            arr = self.dataset.GetCellData().GetScalars()
1949            poly = self.cell_centers().dataset
1950            poly.GetPointData().SetScalars(arr)
1951        else:
1952            arr = self.dataset.GetPointData().GetScalars()
1953            poly = self.dataset
1954            if content != "id" and content not in self.pointdata.keys():
1955                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
1956                return None
1957
1958        mp = vtki.new("LabeledDataMapper")
1959
1960        if content == "id":
1961            mp.SetLabelModeToLabelIds()
1962        else:
1963            mp.SetLabelModeToLabelScalars()
1964            if precision is not None:
1965                dtype = arr.GetDataType()
1966                if dtype in (vtki.VTK_FLOAT, vtki.VTK_DOUBLE):
1967                    mp.SetLabelFormat(f"%-#.{precision}g")
1968
1969        pr = mp.GetLabelTextProperty()
1970        c = colors.get_color(c)
1971        pr.SetColor(c)
1972        pr.SetOpacity(alpha)
1973        pr.SetFrame(frame)
1974        pr.SetFrameColor(c)
1975        pr.SetItalic(False)
1976        pr.BoldOff()
1977        pr.ShadowOff()
1978        pr.UseTightBoundingBoxOn()
1979        pr.SetOrientation(angle)
1980        pr.SetFontFamily(vtki.VTK_FONT_FILE)
1981        fl = utils.get_font_path(font)
1982        pr.SetFontFile(fl)
1983        pr.SetFontSize(int(20 * scale))
1984
1985        if "cent" in justify or "mid" in justify:
1986            pr.SetJustificationToCentered()
1987        elif "rig" in justify:
1988            pr.SetJustificationToRight()
1989        elif "left" in justify:
1990            pr.SetJustificationToLeft()
1991        # ------
1992        if "top" in justify:
1993            pr.SetVerticalJustificationToTop()
1994        else:
1995            pr.SetVerticalJustificationToBottom()
1996
1997        if bc is not None:
1998            bc = colors.get_color(bc)
1999            pr.SetBackgroundColor(bc)
2000            pr.SetBackgroundOpacity(alpha)
2001
2002        mp.SetInputData(poly)
2003        a2d = Actor2D()
2004        a2d.PickableOff()
2005        a2d.SetMapper(mp)
2006        return a2d
2007
2008    def legend(self, txt) -> Self:
2009        """Book a legend text."""
2010        self.info["legend"] = txt
2011        # self.metadata["legend"] = txt
2012        return self
2013
2014    def flagpole(
2015        self,
2016        txt=None,
2017        point=None,
2018        offset=None,
2019        s=None,
2020        font="Calco",
2021        rounded=True,
2022        c=None,
2023        alpha=1.0,
2024        lw=2,
2025        italic=0.0,
2026        padding=0.1,
2027    ) -> Union["vedo.Mesh", None]:
2028        """
2029        Generate a flag pole style element to describe an object.
2030        Returns a `Mesh` object.
2031
2032        Use flagpole.follow_camera() to make it face the camera in the scene.
2033
2034        Consider using `settings.use_parallel_projection = True`
2035        to avoid perspective distortions.
2036
2037        See also `flagpost()`.
2038
2039        Arguments:
2040            txt : (str)
2041                Text to display. The default is the filename or the object name.
2042            point : (list)
2043                position of the flagpole pointer.
2044            offset : (list)
2045                text offset wrt the application point.
2046            s : (float)
2047                size of the flagpole.
2048            font : (str)
2049                font face. Check [available fonts here](https://vedo.embl.es/fonts).
2050            rounded : (bool)
2051                draw a rounded or squared box around the text.
2052            c : (list)
2053                text and box color.
2054            alpha : (float)
2055                opacity of text and box.
2056            lw : (float)
2057                line with of box frame.
2058            italic : (float)
2059                italicness of text.
2060
2061        Examples:
2062            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2063
2064                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2065
2066            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2067            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2068            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2069        """
2070        objs = []
2071
2072        if txt is None:
2073            if self.filename:
2074                txt = self.filename.split("/")[-1]
2075            elif self.name:
2076                txt = self.name
2077            else:
2078                return None
2079
2080        x0, x1, y0, y1, z0, z1 = self.bounds()
2081        d = self.diagonal_size()
2082        if point is None:
2083            if d:
2084                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2085                # point = self.closest_point([x1, y0, z1])
2086            else:  # it's a Point
2087                point = self.transform.position
2088
2089        pt = utils.make3d(point)
2090
2091        if offset is None:
2092            offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0]
2093        offset = utils.make3d(offset)
2094
2095        if s is None:
2096            s = d / 20
2097
2098        sph = None
2099        if d and (z1 - z0) / d > 0.1:
2100            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2101
2102        if c is None:
2103            c = np.array(self.color()) / 1.4
2104
2105        lab = vedo.shapes.Text3D(
2106            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center"
2107        )
2108        objs.append(lab)
2109
2110        if d and not sph:
2111            sph = vedo.shapes.Circle(pt, r=s / 3, res=16)
2112        objs.append(sph)
2113
2114        x0, x1, y0, y1, z0, z1 = lab.bounds()
2115        aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)]
2116        if rounded:
2117            box = vedo.shapes.KSpline(aline, closed=True)
2118        else:
2119            box = vedo.shapes.Line(aline, closed=True)
2120
2121        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2122
2123        # box.actor.SetOrigin(cnt)
2124        box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt)
2125        objs.append(box)
2126
2127        x0, x1, y0, y1, z0, z1 = box.bounds()
2128        if x0 < pt[0] < x1:
2129            c0 = box.closest_point(pt)
2130            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2131        elif (pt[0] - x0) < (x1 - pt[0]):
2132            c0 = [x0, (y0 + y1) / 2, pt[2]]
2133            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2134        else:
2135            c0 = [x1, (y0 + y1) / 2, pt[2]]
2136            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2137
2138        con = vedo.shapes.Line([c0, c1, pt])
2139        objs.append(con)
2140
2141        mobjs = vedo.merge(objs).c(c).alpha(alpha)
2142        mobjs.name = "FlagPole"
2143        mobjs.bc("tomato").pickable(False)
2144        mobjs.properties.LightingOff()
2145        mobjs.properties.SetLineWidth(lw)
2146        mobjs.actor.UseBoundsOff()
2147        mobjs.actor.SetPosition([0,0,0])
2148        mobjs.actor.SetOrigin(pt)
2149        return mobjs
2150
2151        # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha)
2152        # mobjs.name = "FlagPole"
2153        # # mobjs.bc("tomato").pickable(False)
2154        # # mobjs.properties.LightingOff()
2155        # # mobjs.properties.SetLineWidth(lw)
2156        # # mobjs.actor.UseBoundsOff()
2157        # # mobjs.actor.SetPosition([0,0,0])
2158        # # mobjs.actor.SetOrigin(pt)
2159        # # print(pt)
2160        # return mobjs
2161
2162    def flagpost(
2163        self,
2164        txt=None,
2165        point=None,
2166        offset=None,
2167        s=1.0,
2168        c="k9",
2169        bc="k1",
2170        alpha=1,
2171        lw=0,
2172        font="Calco",
2173        justify="center-left",
2174        vspacing=1.0,
2175    ) -> Union["vedo.addons.Flagpost", None]:
2176        """
2177        Generate a flag post style element to describe an object.
2178
2179        Arguments:
2180            txt : (str)
2181                Text to display. The default is the filename or the object name.
2182            point : (list)
2183                position of the flag anchor point. The default is None.
2184            offset : (list)
2185                a 3D displacement or offset. The default is None.
2186            s : (float)
2187                size of the text to be shown
2188            c : (list)
2189                color of text and line
2190            bc : (list)
2191                color of the flag background
2192            alpha : (float)
2193                opacity of text and box.
2194            lw : (int)
2195                line with of box frame. The default is 0.
2196            font : (str)
2197                font name. Use a monospace font for better rendering. The default is "Calco".
2198                Type `vedo -r fonts` for a font demo.
2199                Check [available fonts here](https://vedo.embl.es/fonts).
2200            justify : (str)
2201                internal text justification. The default is "center-left".
2202            vspacing : (float)
2203                vertical spacing between lines.
2204
2205        Examples:
2206            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2207
2208            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2209        """
2210        if txt is None:
2211            if self.filename:
2212                txt = self.filename.split("/")[-1]
2213            elif self.name:
2214                txt = self.name
2215            else:
2216                return None
2217
2218        x0, x1, y0, y1, z0, z1 = self.bounds()
2219        d = self.diagonal_size()
2220        if point is None:
2221            if d:
2222                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2223            else:  # it's a Point
2224                point = self.transform.position
2225
2226        point = utils.make3d(point)
2227
2228        if offset is None:
2229            offset = [0, 0, (z1 - z0) / 2]
2230        offset = utils.make3d(offset)
2231
2232        fpost = vedo.addons.Flagpost(
2233            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2234        )
2235        self._caption = fpost
2236        return fpost
2237
2238    def caption(
2239        self,
2240        txt=None,
2241        point=None,
2242        size=(0.30, 0.15),
2243        padding=5,
2244        font="Calco",
2245        justify="center-right",
2246        vspacing=1.0,
2247        c=None,
2248        alpha=1.0,
2249        lw=1,
2250        ontop=True,
2251    ) -> Union["vtki.vtkCaptionActor2D", None]:
2252        """
2253        Create a 2D caption to an object which follows the camera movements.
2254        Latex is not supported. Returns the same input object for concatenation.
2255
2256        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2257        with similar functionality.
2258
2259        Arguments:
2260            txt : (str)
2261                text to be rendered. The default is the file name.
2262            point : (list)
2263                anchoring point. The default is None.
2264            size : (list)
2265                (width, height) of the caption box. The default is (0.30, 0.15).
2266            padding : (float)
2267                padding space of the caption box in pixels. The default is 5.
2268            font : (str)
2269                font name. Use a monospace font for better rendering. The default is "VictorMono".
2270                Type `vedo -r fonts` for a font demo.
2271                Check [available fonts here](https://vedo.embl.es/fonts).
2272            justify : (str)
2273                internal text justification. The default is "center-right".
2274            vspacing : (float)
2275                vertical spacing between lines. The default is 1.
2276            c : (str)
2277                text and box color. The default is 'lb'.
2278            alpha : (float)
2279                text and box transparency. The default is 1.
2280            lw : (int)
2281                line width in pixels. The default is 1.
2282            ontop : (bool)
2283                keep the 2d caption always on top. The default is True.
2284
2285        Examples:
2286            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2287
2288                ![](https://vedo.embl.es/images/pyplot/caption.png)
2289
2290            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2291            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2292        """
2293        if txt is None:
2294            if self.filename:
2295                txt = self.filename.split("/")[-1]
2296            elif self.name:
2297                txt = self.name
2298
2299        if not txt:  # disable it
2300            self._caption = None
2301            return None
2302
2303        for r in vedo.shapes._reps:
2304            txt = txt.replace(r[0], r[1])
2305
2306        if c is None:
2307            c = np.array(self.properties.GetColor()) / 2
2308        else:
2309            c = colors.get_color(c)
2310
2311        if point is None:
2312            x0, x1, y0, y1, _, z1 = self.dataset.GetBounds()
2313            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2314            point = self.closest_point(pt)
2315
2316        capt = vtki.vtkCaptionActor2D()
2317        capt.SetAttachmentPoint(point)
2318        capt.SetBorder(True)
2319        capt.SetLeader(True)
2320        sph = vtki.new("SphereSource")
2321        sph.Update()
2322        capt.SetLeaderGlyphData(sph.GetOutput())
2323        capt.SetMaximumLeaderGlyphSize(5)
2324        capt.SetPadding(int(padding))
2325        capt.SetCaption(txt)
2326        capt.SetWidth(size[0])
2327        capt.SetHeight(size[1])
2328        capt.SetThreeDimensionalLeader(not ontop)
2329
2330        pra = capt.GetProperty()
2331        pra.SetColor(c)
2332        pra.SetOpacity(alpha)
2333        pra.SetLineWidth(lw)
2334
2335        pr = capt.GetCaptionTextProperty()
2336        pr.SetFontFamily(vtki.VTK_FONT_FILE)
2337        fl = utils.get_font_path(font)
2338        pr.SetFontFile(fl)
2339        pr.ShadowOff()
2340        pr.BoldOff()
2341        pr.FrameOff()
2342        pr.SetColor(c)
2343        pr.SetOpacity(alpha)
2344        pr.SetJustificationToLeft()
2345        if "top" in justify:
2346            pr.SetVerticalJustificationToTop()
2347        if "bottom" in justify:
2348            pr.SetVerticalJustificationToBottom()
2349        if "cent" in justify:
2350            pr.SetVerticalJustificationToCentered()
2351            pr.SetJustificationToCentered()
2352        if "left" in justify:
2353            pr.SetJustificationToLeft()
2354        if "right" in justify:
2355            pr.SetJustificationToRight()
2356        pr.SetLineSpacing(vspacing)
2357        return capt

Class to manage the visual aspects of a Points object.

PointsVisual()
876    def __init__(self):
877        # print("init PointsVisual")
878        super().__init__()
879        self.properties_backface = None
880        self._cmap_name = None
881        self.trail = None
882        self.trail_offset = 0
883        self.trail_points = []
884        self._caption = None
def clone2d(self, size=None, offset=(), scale=None):
887    def clone2d(self, size=None, offset=(), scale=None):
888        """
889        Turn a 3D `Points` or `Mesh` into a flat 2D actor.
890        Returns a `Actor2D`.
891
892        Arguments:
893            size : (float)
894                size as scaling factor for the 2D actor
895            offset : (list)
896                2D (x, y) position of the actor in the range [-1, 1]
897            scale : (float)
898                Deprecated. Use `size` instead.
899
900        Examples:
901            - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py)
902
903                ![](https://vedo.embl.es/images/other/clone2d.png)
904        """
905        # assembly.Assembly.clone2d() superseeds this method
906        if scale is not None:
907            vedo.logger.warning("clone2d(): use keyword size not scale")
908            size = scale
909
910        if size is None:
911            # work out a reasonable scale
912            msiz = self.diagonal_size()
913            if vedo.plotter_instance and vedo.plotter_instance.window:
914                sz = vedo.plotter_instance.window.GetSize()
915                dsiz = utils.mag(sz)
916                size = dsiz / msiz / 10
917            else:
918                size = 350 / msiz
919
920        tp = vtki.new("TransformPolyDataFilter")
921        transform = vtki.vtkTransform()
922        transform.Scale(size, size, size)
923        if len(offset) == 0:
924            offset = self.pos()
925        transform.Translate(-utils.make3d(offset))
926        tp.SetTransform(transform)
927        tp.SetInputData(self.dataset)
928        tp.Update()
929        poly = tp.GetOutput()
930
931        cm = self.mapper.GetColorMode()
932        lut = self.mapper.GetLookupTable()
933        sv = self.mapper.GetScalarVisibility()
934        use_lut = self.mapper.GetUseLookupTableScalarRange()
935        vrange = self.mapper.GetScalarRange()
936        sm = self.mapper.GetScalarMode()
937
938        act2d = Actor2D(poly)
939        act2d.mapper.SetColorMode(cm)
940        act2d.mapper.SetLookupTable(lut)
941        act2d.mapper.SetScalarVisibility(sv)
942        act2d.mapper.SetUseLookupTableScalarRange(use_lut)
943        act2d.mapper.SetScalarRange(vrange)
944        act2d.mapper.SetScalarMode(sm)
945
946        act2d.GetPositionCoordinate().SetCoordinateSystem(4)
947        act2d.properties.SetColor(self.color())
948        act2d.properties.SetOpacity(self.alpha())
949        act2d.properties.SetLineWidth(self.properties.GetLineWidth())
950        act2d.properties.SetPointSize(self.properties.GetPointSize())
951        act2d.properties.SetDisplayLocation(0) # 0 = back, 1 = front
952        act2d.PickableOff()
953        return act2d

Turn a 3D Points or Mesh into a flat 2D actor. Returns a Actor2D.

Arguments:
  • size : (float) size as scaling factor for the 2D actor
  • offset : (list) 2D (x, y) position of the actor in the range [-1, 1]
  • scale : (float) Deprecated. Use size instead.
Examples:
def copy_properties_from(self, source, deep=True, actor_related=True) -> Self:
 956    def copy_properties_from(self, source, deep=True, actor_related=True) -> Self:
 957        """
 958        Copy properties from another ``Points`` object.
 959        """
 960        pr = vtki.vtkProperty()
 961        try:
 962            sp = source.properties
 963            mp = source.mapper
 964            sa = source.actor
 965        except AttributeError:
 966            sp = source.GetProperty()
 967            mp = source.GetMapper()
 968            sa = source
 969
 970        if deep:
 971            pr.DeepCopy(sp)
 972        else:
 973            pr.ShallowCopy(sp)
 974        self.actor.SetProperty(pr)
 975        self.properties = pr
 976
 977        if self.actor.GetBackfaceProperty():
 978            bfpr = vtki.vtkProperty()
 979            bfpr.DeepCopy(sa.GetBackfaceProperty())
 980            self.actor.SetBackfaceProperty(bfpr)
 981            self.properties_backface = bfpr
 982
 983        if not actor_related:
 984            return self
 985
 986        # mapper related:
 987        self.mapper.SetScalarVisibility(mp.GetScalarVisibility())
 988        self.mapper.SetScalarMode(mp.GetScalarMode())
 989        self.mapper.SetScalarRange(mp.GetScalarRange())
 990        self.mapper.SetLookupTable(mp.GetLookupTable())
 991        self.mapper.SetColorMode(mp.GetColorMode())
 992        self.mapper.SetInterpolateScalarsBeforeMapping(
 993            mp.GetInterpolateScalarsBeforeMapping()
 994        )
 995        self.mapper.SetUseLookupTableScalarRange(
 996            mp.GetUseLookupTableScalarRange()
 997        )
 998
 999        self.actor.SetPickable(sa.GetPickable())
1000        self.actor.SetDragable(sa.GetDragable())
1001        self.actor.SetTexture(sa.GetTexture())
1002        self.actor.SetVisibility(sa.GetVisibility())
1003        return self

Copy properties from another Points object.

def color(self, c=False, alpha=None) -> Union[numpy.ndarray, Self]:
1005    def color(self, c=False, alpha=None) -> Union[np.ndarray, Self]:
1006        """
1007        Set/get mesh's color.
1008        If None is passed as input, will use colors from active scalars.
1009        Same as `mesh.c()`.
1010        """
1011        if c is False:
1012            return np.array(self.properties.GetColor())
1013        if c is None:
1014            self.mapper.ScalarVisibilityOn()
1015            return self
1016        self.mapper.ScalarVisibilityOff()
1017        cc = colors.get_color(c)
1018        self.properties.SetColor(cc)
1019        if self.trail:
1020            self.trail.properties.SetColor(cc)
1021        if alpha is not None:
1022            self.alpha(alpha)
1023        return self

Set/get mesh's color. If None is passed as input, will use colors from active scalars. Same as mesh.c().

def c(self, color=False, alpha=None) -> Union[numpy.ndarray, Self]:
1025    def c(self, color=False, alpha=None) -> Union[np.ndarray, Self]:
1026        """
1027        Shortcut for `color()`.
1028        If None is passed as input, will use colors from current active scalars.
1029        """
1030        return self.color(color, alpha)

Shortcut for color(). If None is passed as input, will use colors from current active scalars.

def alpha(self, opacity=None) -> Union[float, Self]:
1032    def alpha(self, opacity=None) -> Union[float, Self]:
1033        """Set/get mesh's transparency. Same as `mesh.opacity()`."""
1034        if opacity is None:
1035            return self.properties.GetOpacity()
1036
1037        self.properties.SetOpacity(opacity)
1038        bfp = self.actor.GetBackfaceProperty()
1039        if bfp:
1040            if opacity < 1:
1041                self.properties_backface = bfp
1042                self.actor.SetBackfaceProperty(None)
1043            else:
1044                self.actor.SetBackfaceProperty(self.properties_backface)
1045        return self

Set/get mesh's transparency. Same as mesh.opacity().

def lut_color_at(self, value) -> numpy.ndarray:
1047    def lut_color_at(self, value) -> np.ndarray:
1048        """
1049        Return the color and alpha in the lookup table at given value.
1050        """
1051        lut = self.mapper.GetLookupTable()
1052        if not lut:
1053            return None
1054        rgb = [0,0,0]
1055        lut.GetColor(value, rgb)
1056        alpha = lut.GetOpacity(value)
1057        return np.array(rgb + [alpha])

Return the color and alpha in the lookup table at given value.

def opacity(self, alpha=None) -> Union[float, Self]:
1059    def opacity(self, alpha=None) -> Union[float, Self]:
1060        """Set/get mesh's transparency. Same as `mesh.alpha()`."""
1061        return self.alpha(alpha)

Set/get mesh's transparency. Same as mesh.alpha().

def force_opaque(self, value=True) -> Self:
1063    def force_opaque(self, value=True) -> Self:
1064        """ Force the Mesh, Line or point cloud to be treated as opaque"""
1065        ## force the opaque pass, fixes picking in vtk9
1066        # but causes other bad troubles with lines..
1067        self.actor.SetForceOpaque(value)
1068        return self

Force the Mesh, Line or point cloud to be treated as opaque

def force_translucent(self, value=True) -> Self:
1070    def force_translucent(self, value=True) -> Self:
1071        """ Force the Mesh, Line or point cloud to be treated as translucent"""
1072        self.actor.SetForceTranslucent(value)
1073        return self

Force the Mesh, Line or point cloud to be treated as translucent

def point_size(self, value=None) -> Union[int, Self]:
1075    def point_size(self, value=None) -> Union[int, Self]:
1076        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
1077        if value is None:
1078            return self.properties.GetPointSize()
1079            # self.properties.SetRepresentationToSurface()
1080        else:
1081            self.properties.SetRepresentationToPoints()
1082            self.properties.SetPointSize(value)
1083        return self

Set/get mesh's point size of vertices. Same as mesh.ps()

def ps(self, pointsize=None) -> Union[int, Self]:
1085    def ps(self, pointsize=None) -> Union[int, Self]:
1086        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
1087        return self.point_size(pointsize)

Set/get mesh's point size of vertices. Same as mesh.point_size()

def render_points_as_spheres(self, value=True) -> Self:
1089    def render_points_as_spheres(self, value=True) -> Self:
1090        """Make points look spheric or else make them look as squares."""
1091        self.properties.SetRenderPointsAsSpheres(value)
1092        return self

Make points look spheric or else make them look as squares.

def lighting( self, style='', ambient=None, diffuse=None, specular=None, specular_power=None, specular_color=None, metallicity=None, roughness=None) -> Self:
1094    def lighting(
1095        self,
1096        style="",
1097        ambient=None,
1098        diffuse=None,
1099        specular=None,
1100        specular_power=None,
1101        specular_color=None,
1102        metallicity=None,
1103        roughness=None,
1104    ) -> Self:
1105        """
1106        Set the ambient, diffuse, specular and specular_power lighting constants.
1107
1108        Arguments:
1109            style : (str)
1110                preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]`
1111            ambient : (float)
1112                ambient fraction of emission [0-1]
1113            diffuse : (float)
1114                emission of diffused light in fraction [0-1]
1115            specular : (float)
1116                fraction of reflected light [0-1]
1117            specular_power : (float)
1118                precision of reflection [1-100]
1119            specular_color : (color)
1120                color that is being reflected by the surface
1121
1122        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px>
1123
1124        Examples:
1125            - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py)
1126        """
1127        pr = self.properties
1128
1129        if style:
1130
1131            if style != "off":
1132                pr.LightingOn()
1133
1134            if style == "off":
1135                pr.SetInterpolationToFlat()
1136                pr.LightingOff()
1137                return self  ##############
1138
1139            if hasattr(pr, "GetColor"):  # could be Volume
1140                c = pr.GetColor()
1141            else:
1142                c = (1, 1, 0.99)
1143            mpr = self.mapper
1144            if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility():
1145                c = (1,1,0.99)
1146            if   style=='metallic': pars = [0.1, 0.3, 1.0, 10, c]
1147            elif style=='plastic' : pars = [0.3, 0.4, 0.3,  5, c]
1148            elif style=='shiny'   : pars = [0.2, 0.6, 0.8, 50, c]
1149            elif style=='glossy'  : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)]
1150            elif style=='ambient' : pars = [0.8, 0.1, 0.0,  1, (1,1,1)]
1151            elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c]
1152            else:
1153                vedo.logger.error("in lighting(): Available styles are")
1154                vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]")
1155                raise RuntimeError()
1156            pr.SetAmbient(pars[0])
1157            pr.SetDiffuse(pars[1])
1158            pr.SetSpecular(pars[2])
1159            pr.SetSpecularPower(pars[3])
1160            if hasattr(pr, "GetColor"):
1161                pr.SetSpecularColor(pars[4])
1162
1163        if ambient is not None: pr.SetAmbient(ambient)
1164        if diffuse is not None: pr.SetDiffuse(diffuse)
1165        if specular is not None: pr.SetSpecular(specular)
1166        if specular_power is not None: pr.SetSpecularPower(specular_power)
1167        if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color))
1168        if metallicity is not None:
1169            pr.SetInterpolationToPBR()
1170            pr.SetMetallic(metallicity)
1171        if roughness is not None:
1172            pr.SetInterpolationToPBR()
1173            pr.SetRoughness(roughness)
1174
1175        return self

Set the ambient, diffuse, specular and specular_power lighting constants.

Arguments:
  • style : (str) preset style, options are [metallic, plastic, shiny, glossy, ambient, off]
  • ambient : (float) ambient fraction of emission [0-1]
  • diffuse : (float) emission of diffused light in fraction [0-1]
  • specular : (float) fraction of reflected light [0-1]
  • specular_power : (float) precision of reflection [1-100]
  • specular_color : (color) color that is being reflected by the surface

Examples:
def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self:
1177    def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self:
1178        """Set point blurring.
1179        Apply a gaussian convolution filter to the points.
1180        In this case the radius `r` is in absolute units of the mesh coordinates.
1181        With emissive set, the halo of point becomes light-emissive.
1182        """
1183        self.properties.SetRepresentationToPoints()
1184        if emissive:
1185            self.mapper.SetEmissive(bool(emissive))
1186        self.mapper.SetScaleFactor(r * 1.4142)
1187
1188        # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
1189        if alpha < 1:
1190            self.mapper.SetSplatShaderCode(
1191                "//VTK::Color::Impl\n"
1192                "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
1193                "if (dist > 1.0) {\n"
1194                "   discard;\n"
1195                "} else {\n"
1196                f"  float scale = ({alpha} - dist);\n"
1197                "   ambientColor *= scale;\n"
1198                "   diffuseColor *= scale;\n"
1199                "}\n"
1200            )
1201            alpha = 1
1202
1203        self.mapper.Modified()
1204        self.actor.Modified()
1205        self.properties.SetOpacity(alpha)
1206        self.actor.SetMapper(self.mapper)
1207        return self

Set point blurring. Apply a gaussian convolution filter to the points. In this case the radius r is in absolute units of the mesh coordinates. With emissive set, the halo of point becomes light-emissive.

cellcolors
1209    @property
1210    def cellcolors(self):
1211        """
1212        Colorize each cell (face) of a mesh by passing
1213        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1214        Colors levels and opacities must be in the range [0,255].
1215
1216        A single constant color can also be passed as string or RGBA.
1217
1218        A cell array named "CellsRGBA" is automatically created.
1219
1220        Examples:
1221            - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py)
1222            - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py)
1223
1224            ![](https://vedo.embl.es/images/basic/colorMeshCells.png)
1225        """
1226        if "CellsRGBA" not in self.celldata.keys():
1227            lut = self.mapper.GetLookupTable()
1228            vscalars = self.dataset.GetCellData().GetScalars()
1229            if vscalars is None or lut is None:
1230                arr = np.zeros([self.ncells, 4], dtype=np.uint8)
1231                col = np.array(self.properties.GetColor())
1232                col = np.round(col * 255).astype(np.uint8)
1233                alf = self.properties.GetOpacity()
1234                alf = np.round(alf * 255).astype(np.uint8)
1235                arr[:, (0, 1, 2)] = col
1236                arr[:, 3] = alf
1237            else:
1238                cols = lut.MapScalars(vscalars, 0, 0)
1239                arr = utils.vtk2numpy(cols)
1240            self.celldata["CellsRGBA"] = arr
1241        self.celldata.select("CellsRGBA")
1242        return self.celldata["CellsRGBA"]

Colorize each cell (face) of a mesh by passing a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. Colors levels and opacities must be in the range [0,255].

A single constant color can also be passed as string or RGBA.

A cell array named "CellsRGBA" is automatically created.

Examples:

pointcolors
1267    @property
1268    def pointcolors(self):
1269        """
1270        Colorize each point (or vertex of a mesh) by passing
1271        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1272        Colors levels and opacities must be in the range [0,255].
1273
1274        A single constant color can also be passed as string or RGBA.
1275
1276        A point array named "PointsRGBA" is automatically created.
1277        """
1278        if "PointsRGBA" not in self.pointdata.keys():
1279            lut = self.mapper.GetLookupTable()
1280            vscalars = self.dataset.GetPointData().GetScalars()
1281            if vscalars is None or lut is None:
1282                # create a constant array
1283                arr = np.zeros([self.npoints, 4], dtype=np.uint8)
1284                col = np.array(self.properties.GetColor())
1285                col = np.round(col * 255).astype(np.uint8)
1286                alf = self.properties.GetOpacity()
1287                alf = np.round(alf * 255).astype(np.uint8)
1288                arr[:, (0, 1, 2)] = col
1289                arr[:, 3] = alf
1290            else:
1291                cols = lut.MapScalars(vscalars, 0, 0)
1292                arr = utils.vtk2numpy(cols)
1293            self.pointdata["PointsRGBA"] = arr
1294        self.pointdata.select("PointsRGBA")
1295        return self.pointdata["PointsRGBA"]

Colorize each point (or vertex of a mesh) by passing a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. Colors levels and opacities must be in the range [0,255].

A single constant color can also be passed as string or RGBA.

A point array named "PointsRGBA" is automatically created.

def cmap( self, input_cmap, input_array=None, on='', name='Scalars', vmin=None, vmax=None, n_colors=256, alpha=1.0, logscale=False) -> Self:
1321    def cmap(
1322        self,
1323        input_cmap,
1324        input_array=None,
1325        on="",
1326        name="Scalars",
1327        vmin=None,
1328        vmax=None,
1329        n_colors=256,
1330        alpha=1.0,
1331        logscale=False,
1332    ) -> Self:
1333        """
1334        Set individual point/cell colors by providing a list of scalar values and a color map.
1335
1336        Arguments:
1337            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
1338                color map scheme to transform a real number into a color.
1339            input_array : (str, list, vtkArray)
1340                can be the string name of an existing array, a new array or a `vtkArray`.
1341            on : (str)
1342                either 'points' or 'cells' or blank (automatic).
1343                Apply the color map to data which is defined on either points or cells.
1344            name : (str)
1345                give a name to the provided array (if input_array is an array)
1346            vmin : (float)
1347                clip scalars to this minimum value
1348            vmax : (float)
1349                clip scalars to this maximum value
1350            n_colors : (int)
1351                number of distinct colors to be used in colormap table.
1352            alpha : (float, list)
1353                Mesh transparency. Can be a `list` of values one for each vertex.
1354            logscale : (bool)
1355                Use logscale
1356
1357        Examples:
1358            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
1359            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
1360            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
1361            - (and many others)
1362
1363                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
1364        """
1365        self._cmap_name = input_cmap
1366
1367        if on == "":
1368            try:
1369                on = self.mapper.GetScalarModeAsString().replace("Use", "")
1370                if on not in ["PointData", "CellData"]: # can be "Default"
1371                    on = "points"
1372                    self.mapper.SetScalarModeToUsePointData()
1373            except AttributeError:
1374                on = "points"
1375        elif on == "Default":
1376            on = "points"
1377            self.mapper.SetScalarModeToUsePointData()
1378
1379        if input_array is None:
1380            if not self.pointdata.keys() and self.celldata.keys():
1381                on = "cells"
1382                if not self.dataset.GetCellData().GetScalars():
1383                    input_array = 0  # pick the first at hand
1384
1385        if "point" in on.lower():
1386            data = self.dataset.GetPointData()
1387            n = self.dataset.GetNumberOfPoints()
1388        elif "cell" in on.lower():
1389            data = self.dataset.GetCellData()
1390            n = self.dataset.GetNumberOfCells()
1391        else:
1392            vedo.logger.error(
1393                f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}")
1394            raise RuntimeError()
1395
1396        if input_array is None:  # if None try to fetch the active scalars
1397            arr = data.GetScalars()
1398            if not arr:
1399                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
1400                return self
1401
1402            if not arr.GetName():  # sometimes arrays dont have a name..
1403                arr.SetName(name)
1404
1405        elif isinstance(input_array, str):  # if a string is passed
1406            arr = data.GetArray(input_array)
1407            if not arr:
1408                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
1409                return self
1410
1411        elif isinstance(input_array, int):  # if an int is passed
1412            if input_array < data.GetNumberOfArrays():
1413                arr = data.GetArray(input_array)
1414            else:
1415                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
1416                return self
1417
1418        elif utils.is_sequence(input_array):  # if a numpy array is passed
1419            npts = len(input_array)
1420            if npts != n:
1421                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
1422                return self
1423            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
1424            data.AddArray(arr)
1425            data.Modified()
1426
1427        elif isinstance(input_array, vtki.vtkArray):  # if a vtkArray is passed
1428            arr = input_array
1429            data.AddArray(arr)
1430            data.Modified()
1431
1432        else:
1433            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
1434            raise RuntimeError()
1435
1436        # Now we have array "arr"
1437        array_name = arr.GetName()
1438
1439        if arr.GetNumberOfComponents() == 1:
1440            if vmin is None:
1441                vmin = arr.GetRange()[0]
1442            if vmax is None:
1443                vmax = arr.GetRange()[1]
1444        else:
1445            if vmin is None or vmax is None:
1446                vn = utils.mag(utils.vtk2numpy(arr))
1447            if vmin is None:
1448                vmin = vn.min()
1449            if vmax is None:
1450                vmax = vn.max()
1451
1452        # interpolate alphas if they are not constant
1453        if not utils.is_sequence(alpha):
1454            alpha = [alpha] * n_colors
1455        else:
1456            v = np.linspace(0, 1, n_colors, endpoint=True)
1457            xp = np.linspace(0, 1, len(alpha), endpoint=True)
1458            alpha = np.interp(v, xp, alpha)
1459
1460        ########################### build the look-up table
1461        if isinstance(input_cmap, vtki.vtkLookupTable):  # vtkLookupTable
1462            lut = input_cmap
1463
1464        elif utils.is_sequence(input_cmap):  # manual sequence of colors
1465            lut = vtki.vtkLookupTable()
1466            if logscale:
1467                lut.SetScaleToLog10()
1468            lut.SetRange(vmin, vmax)
1469            ncols = len(input_cmap)
1470            lut.SetNumberOfTableValues(ncols)
1471
1472            for i, c in enumerate(input_cmap):
1473                r, g, b = colors.get_color(c)
1474                lut.SetTableValue(i, r, g, b, alpha[i])
1475            lut.Build()
1476
1477        else:
1478            # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
1479            lut = vtki.vtkLookupTable()
1480            if logscale:
1481                lut.SetScaleToLog10()
1482            lut.SetVectorModeToMagnitude()
1483            lut.SetRange(vmin, vmax)
1484            lut.SetNumberOfTableValues(n_colors)
1485            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
1486            for i, c in enumerate(mycols):
1487                r, g, b = c
1488                lut.SetTableValue(i, r, g, b, alpha[i])
1489            lut.Build()
1490
1491        # TEST NEW WAY
1492        self.mapper.SetLookupTable(lut)
1493        self.mapper.ScalarVisibilityOn()
1494        self.mapper.SetColorModeToMapScalars()
1495        self.mapper.SetScalarRange(lut.GetRange())
1496        if "point" in on.lower():
1497            self.pointdata.select(array_name)
1498        else:
1499            self.celldata.select(array_name)
1500        return self
1501
1502        # # TEST this is the old way:
1503        # # arr.SetLookupTable(lut) # wrong! causes weird instabilities with LUT
1504        # # if data.GetScalars():
1505        # #     data.GetScalars().SetLookupTable(lut)
1506        # #     data.GetScalars().Modified()
1507
1508        # data.SetActiveScalars(array_name)
1509        # # data.SetScalars(arr)  # wrong! it deletes array in position 0, never use SetScalars
1510        # # data.SetActiveAttribute(array_name, 0) # boh!
1511
1512        # self.mapper.SetLookupTable(lut)
1513        # self.mapper.SetColorModeToMapScalars()  # so we dont need to convert uint8 scalars
1514
1515        # self.mapper.ScalarVisibilityOn()
1516        # self.mapper.SetScalarRange(lut.GetRange())
1517
1518        # if on.startswith("point"):
1519        #     self.mapper.SetScalarModeToUsePointData()
1520        # else:
1521        #     self.mapper.SetScalarModeToUseCellData()
1522        # if hasattr(self.mapper, "SetArrayName"):
1523        #     self.mapper.SetArrayName(array_name)
1524        # return self

Set individual point/cell colors by providing a list of scalar values and a color map.

Arguments:
  • input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) color map scheme to transform a real number into a color.
  • input_array : (str, list, vtkArray) can be the string name of an existing array, a new array or a vtkArray.
  • on : (str) either 'points' or 'cells' or blank (automatic). Apply the color map to data which is defined on either points or cells.
  • name : (str) give a name to the provided array (if input_array is an array)
  • vmin : (float) clip scalars to this minimum value
  • vmax : (float) clip scalars to this maximum value
  • n_colors : (int) number of distinct colors to be used in colormap table.
  • alpha : (float, list) Mesh transparency. Can be a list of values one for each vertex.
  • logscale : (bool) Use logscale
Examples:
def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self:
1526    def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self:
1527        """
1528        Add a trailing line to mesh.
1529        This new mesh is accessible through `mesh.trail`.
1530
1531        Arguments:
1532            offset : (float)
1533                set an offset vector from the object center.
1534            n : (int)
1535                number of segments
1536            lw : (float)
1537                line width of the trail
1538
1539        Examples:
1540            - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py)
1541
1542                ![](https://vedo.embl.es/images/simulations/trail.gif)
1543
1544            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1545            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1546        """
1547        if self.trail is None:
1548            pos = self.pos()
1549            self.trail_offset = np.asarray(offset)
1550            self.trail_points = [pos] * n
1551
1552            if c is None:
1553                col = self.properties.GetColor()
1554            else:
1555                col = colors.get_color(c)
1556
1557            tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw)
1558            self.trail = tline  # holds the Line
1559            self.trail.initilized = False # so the first update will be a reset
1560        return self

Add a trailing line to mesh. This new mesh is accessible through mesh.trail.

Arguments:
  • offset : (float) set an offset vector from the object center.
  • n : (int) number of segments
  • lw : (float) line width of the trail
Examples:
def update_trail(self) -> Self:
1562    def update_trail(self) -> Self:
1563        """
1564        Update the trailing line of a moving object.
1565        """
1566        currentpos = self.pos()
1567        if not self.trail.initilized:
1568            self.trail_points = [currentpos] * self.trail.npoints
1569            self.trail.initilized = True
1570            return self
1571        self.trail_points.append(currentpos)  # cycle
1572        self.trail_points.pop(0)
1573
1574        data = np.array(self.trail_points) + self.trail_offset
1575        tpoly = self.trail.dataset
1576        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1577        return self

Update the trailing line of a moving object.

def add_shadow( self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1614    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1615        """
1616        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1617        The output is a new `Mesh` representing the shadow.
1618        This new mesh is accessible through `mesh.shadow`.
1619        By default the shadow mesh is placed on the bottom wall of the bounding box.
1620
1621        See also `pointcloud.project_on_plane()`.
1622
1623        Arguments:
1624            plane : (str, Plane)
1625                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1626                represents x-plane, y-plane and z-plane, respectively.
1627                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1628            point : (float, array)
1629                if plane is `str`, point should be a float represents the intercept.
1630                Otherwise, point is the camera point of perspective projection
1631            direction : (list)
1632                direction of oblique projection
1633            culling : (int)
1634                choose between front [1] or backface [-1] culling or None.
1635
1636        Examples:
1637            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1638            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1639            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1640
1641            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1642        """
1643        shad = self._compute_shadow(plane, point, direction)
1644        shad.c(c).alpha(alpha)
1645
1646        try:
1647            # Points dont have these methods
1648            shad.flat()
1649            if culling in (1, True):
1650                shad.frontface_culling()
1651            elif culling == -1:
1652                shad.backface_culling()
1653        except AttributeError:
1654            pass
1655
1656        shad.properties.LightingOff()
1657        shad.actor.SetPickable(False)
1658        shad.actor.SetUseBounds(True)
1659
1660        if shad not in self.shadows:
1661            self.shadows.append(shad)
1662            shad.info = dict(plane=plane, point=point, direction=direction)
1663            # shad.metadata["plane"] = plane
1664            # shad.metadata["point"] = point
1665            # print("AAAA", direction, plane, point)
1666            # if direction is None:
1667            #     direction = [0,0,0]
1668            # shad.metadata["direction"] = direction
1669        return self

Generate a shadow out of an Mesh on one of the three Cartesian planes. The output is a new Mesh representing the shadow. This new mesh is accessible through mesh.shadow. By default the shadow mesh is placed on the bottom wall of the bounding box.

See also pointcloud.project_on_plane().

Arguments:
  • plane : (str, Plane) if plane is str, plane can be one of ['x', 'y', 'z'], represents x-plane, y-plane and z-plane, respectively. Otherwise, plane should be an instance of vedo.shapes.Plane
  • point : (float, array) if plane is str, point should be a float represents the intercept. Otherwise, point is the camera point of perspective projection
  • direction : (list) direction of oblique projection
  • culling : (int) choose between front [1] or backface [-1] culling or None.
Examples:

def update_shadows(self) -> Self:
1671    def update_shadows(self) -> Self:
1672        """Update the shadows of a moving object."""
1673        for sha in self.shadows:
1674            plane = sha.info["plane"]
1675            point = sha.info["point"]
1676            direction = sha.info["direction"]
1677            # print("update_shadows direction", direction,plane,point )
1678            # plane = sha.metadata["plane"]
1679            # point = sha.metadata["point"]
1680            # direction = sha.metadata["direction"]
1681            # if direction[0]==0 and direction[1]==0 and direction[2]==0:
1682            #     direction = None
1683            # print("BBBB", sha.metadata["direction"],
1684            #       sha.metadata["plane"], sha.metadata["point"])
1685            new_sha = self._compute_shadow(plane, point, direction)
1686            sha._update(new_sha.dataset)
1687        if self.trail:
1688            self.trail.update_shadows()
1689        return self

Update the shadows of a moving object.

def labels( self, content=None, on='points', scale=None, xrot=0.0, yrot=0.0, zrot=0.0, ratio=1, precision=None, italic=False, font='', justify='', c='black', alpha=1.0) -> Optional[vedo.mesh.Mesh]:
1691    def labels(
1692        self,
1693        content=None,
1694        on="points",
1695        scale=None,
1696        xrot=0.0,
1697        yrot=0.0,
1698        zrot=0.0,
1699        ratio=1,
1700        precision=None,
1701        italic=False,
1702        font="",
1703        justify="",
1704        c="black",
1705        alpha=1.0,
1706    ) -> Union["vedo.Mesh", None]:
1707        """
1708        Generate value or ID labels for mesh cells or points.
1709        For large nr. of labels use `font="VTK"` which is much faster.
1710
1711        See also:
1712            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1713
1714        Arguments:
1715            content : (list,int,str)
1716                either 'id', 'cellid', array name or array number.
1717                A array can also be passed (must match the nr. of points or cells).
1718            on : (str)
1719                generate labels for "cells" instead of "points"
1720            scale : (float)
1721                absolute size of labels, if left as None it is automatic
1722            zrot : (float)
1723                local rotation angle of label in degrees
1724            ratio : (int)
1725                skipping ratio, to reduce nr of labels for large meshes
1726            precision : (int)
1727                numeric precision of labels
1728
1729        ```python
1730        from vedo import *
1731        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1732        point_ids = s.labels('id', on="points").c('green')
1733        cell_ids  = s.labels('id', on="cells" ).c('black')
1734        show(s, point_ids, cell_ids)
1735        ```
1736        ![](https://vedo.embl.es/images/feats/labels.png)
1737
1738        Examples:
1739            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1740
1741                ![](https://vedo.embl.es/images/basic/boundaries.png)
1742        """
1743
1744        cells = False
1745        if "cell" in on or "face" in on:
1746            cells = True
1747            justify = "centered" if justify == "" else justify
1748
1749        if isinstance(content, str):
1750            if content in ("pointid", "pointsid"):
1751                cells = False
1752                content = "id"
1753                justify = "bottom-left" if justify == "" else justify
1754            if content in ("cellid", "cellsid"):
1755                cells = True
1756                content = "id"
1757                justify = "centered" if justify == "" else justify
1758
1759        try:
1760            if cells:
1761                ns = np.sqrt(self.ncells)
1762                elems = self.cell_centers().points
1763                norms = self.cell_normals
1764                justify = "centered" if justify == "" else justify
1765            else:
1766                ns = np.sqrt(self.npoints)
1767                elems = self.vertices
1768                norms = self.vertex_normals
1769        except AttributeError:
1770            norms = []
1771
1772        if not justify:
1773            justify = "bottom-left"
1774
1775        hasnorms = False
1776        if len(norms) > 0:
1777            hasnorms = True
1778
1779        if scale is None:
1780            if not ns:
1781                ns = 100
1782            scale = self.diagonal_size() / ns / 10
1783
1784        arr = None
1785        mode = 0
1786        if content is None:
1787            mode = 0
1788            if cells:
1789                if self.dataset.GetCellData().GetScalars():
1790                    name = self.dataset.GetCellData().GetScalars().GetName()
1791                    arr = self.celldata[name]
1792            else:
1793                if self.dataset.GetPointData().GetScalars():
1794                    name = self.dataset.GetPointData().GetScalars().GetName()
1795                    arr = self.pointdata[name]
1796        elif isinstance(content, (str, int)):
1797            if content == "id":
1798                mode = 1
1799            elif cells:
1800                mode = 0
1801                arr = self.celldata[content]
1802            else:
1803                mode = 0
1804                arr = self.pointdata[content]
1805        elif utils.is_sequence(content):
1806            mode = 0
1807            arr = content
1808
1809        if arr is None and mode == 0:
1810            vedo.logger.error("in labels(), array not found in point or cell data")
1811            return None
1812
1813        ratio = int(ratio+0.5)
1814        tapp = vtki.new("AppendPolyData")
1815        has_inputs = False
1816
1817        for i, e in enumerate(elems):
1818            if i % ratio:
1819                continue
1820
1821            if mode == 1:
1822                txt_lab = str(i)
1823            else:
1824                if precision:
1825                    txt_lab = utils.precision(arr[i], precision)
1826                else:
1827                    txt_lab = str(arr[i])
1828
1829            if not txt_lab:
1830                continue
1831
1832            if font == "VTK":
1833                tx = vtki.new("VectorText")
1834                tx.SetText(txt_lab)
1835                tx.Update()
1836                tx_poly = tx.GetOutput()
1837            else:
1838                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset
1839
1840            if tx_poly.GetNumberOfPoints() == 0:
1841                continue  ######################
1842
1843            T = vtki.vtkTransform()
1844            T.PostMultiply()
1845            if italic:
1846                T.Concatenate([1, 0.2, 0, 0,
1847                               0, 1  , 0, 0,
1848                               0, 0  , 1, 0,
1849                               0, 0  , 0, 1])
1850            if hasnorms:
1851                ni = norms[i]
1852                if cells and font=="VTK":  # center-justify
1853                    bb = tx_poly.GetBounds()
1854                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1855                    T.Translate(-dx, -dy, 0)
1856                if xrot: T.RotateX(xrot)
1857                if yrot: T.RotateY(yrot)
1858                if zrot: T.RotateZ(zrot)
1859                crossvec = np.cross([0, 0, 1], ni)
1860                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1861                T.RotateWXYZ(float(angle), crossvec.tolist())
1862                T.Translate(ni / 100)
1863            else:
1864                if xrot: T.RotateX(xrot)
1865                if yrot: T.RotateY(yrot)
1866                if zrot: T.RotateZ(zrot)
1867            T.Scale(scale, scale, scale)
1868            T.Translate(e)
1869            tf = vtki.new("TransformPolyDataFilter")
1870            tf.SetInputData(tx_poly)
1871            tf.SetTransform(T)
1872            tf.Update()
1873            tapp.AddInputData(tf.GetOutput())
1874            has_inputs = True
1875
1876        if has_inputs:
1877            tapp.Update()
1878            lpoly = tapp.GetOutput()
1879        else:
1880            lpoly = vtki.vtkPolyData()
1881        ids = vedo.Mesh(lpoly, c=c, alpha=alpha)
1882        ids.properties.LightingOff()
1883        ids.actor.PickableOff()
1884        ids.actor.SetUseBounds(False)
1885        ids.name = "Labels"
1886        return ids

Generate value or ID labels for mesh cells or points. For large nr. of labels use font="VTK" which is much faster.

See also:

labels2d(), flagpole(), caption() and legend().

Arguments:
  • content : (list,int,str) either 'id', 'cellid', array name or array number. A array can also be passed (must match the nr. of points or cells).
  • on : (str) generate labels for "cells" instead of "points"
  • scale : (float) absolute size of labels, if left as None it is automatic
  • zrot : (float) local rotation angle of label in degrees
  • ratio : (int) skipping ratio, to reduce nr of labels for large meshes
  • precision : (int) numeric precision of labels
from vedo import *
s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
point_ids = s.labels('id', on="points").c('green')
cell_ids  = s.labels('id', on="cells" ).c('black')
show(s, point_ids, cell_ids)

Examples:
def labels2d( self, content='id', on='points', scale=1.0, precision=4, font='Calco', justify='bottom-left', angle=0.0, frame=False, c='black', bc=None, alpha=1.0) -> Optional[Actor2D]:
1888    def labels2d(
1889        self,
1890        content="id",
1891        on="points",
1892        scale=1.0,
1893        precision=4,
1894        font="Calco",
1895        justify="bottom-left",
1896        angle=0.0,
1897        frame=False,
1898        c="black",
1899        bc=None,
1900        alpha=1.0,
1901    ) -> Union["Actor2D", None]:
1902        """
1903        Generate value or ID bi-dimensional labels for mesh cells or points.
1904
1905        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
1906
1907        Arguments:
1908            content : (str)
1909                either 'id', 'cellid', or array name
1910            on : (str)
1911                generate labels for "cells" instead of "points" (the default)
1912            scale : (float)
1913                size scaling of labels
1914            precision : (int)
1915                precision of numeric labels
1916            angle : (float)
1917                local rotation angle of label in degrees
1918            frame : (bool)
1919                draw a frame around the label
1920            bc : (str)
1921                background color of the label
1922
1923        ```python
1924        from vedo import Sphere, show
1925        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
1926        sph.celldata["zvals"] = sph.cell_centers().coordinates[:,2]
1927        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
1928        show(sph, l2d, axes=1).close()
1929        ```
1930        ![](https://vedo.embl.es/images/feats/labels2d.png)
1931        """
1932        cells = False
1933        if "cell" in on:
1934            cells = True
1935
1936        if isinstance(content, str):
1937            if content in ("id", "pointid", "pointsid"):
1938                cells = False
1939                content = "id"
1940            if content in ("cellid", "cellsid"):
1941                cells = True
1942                content = "id"
1943
1944        if cells:
1945            if content != "id" and content not in self.celldata.keys():
1946                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
1947                return None
1948            arr = self.dataset.GetCellData().GetScalars()
1949            poly = self.cell_centers().dataset
1950            poly.GetPointData().SetScalars(arr)
1951        else:
1952            arr = self.dataset.GetPointData().GetScalars()
1953            poly = self.dataset
1954            if content != "id" and content not in self.pointdata.keys():
1955                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
1956                return None
1957
1958        mp = vtki.new("LabeledDataMapper")
1959
1960        if content == "id":
1961            mp.SetLabelModeToLabelIds()
1962        else:
1963            mp.SetLabelModeToLabelScalars()
1964            if precision is not None:
1965                dtype = arr.GetDataType()
1966                if dtype in (vtki.VTK_FLOAT, vtki.VTK_DOUBLE):
1967                    mp.SetLabelFormat(f"%-#.{precision}g")
1968
1969        pr = mp.GetLabelTextProperty()
1970        c = colors.get_color(c)
1971        pr.SetColor(c)
1972        pr.SetOpacity(alpha)
1973        pr.SetFrame(frame)
1974        pr.SetFrameColor(c)
1975        pr.SetItalic(False)
1976        pr.BoldOff()
1977        pr.ShadowOff()
1978        pr.UseTightBoundingBoxOn()
1979        pr.SetOrientation(angle)
1980        pr.SetFontFamily(vtki.VTK_FONT_FILE)
1981        fl = utils.get_font_path(font)
1982        pr.SetFontFile(fl)
1983        pr.SetFontSize(int(20 * scale))
1984
1985        if "cent" in justify or "mid" in justify:
1986            pr.SetJustificationToCentered()
1987        elif "rig" in justify:
1988            pr.SetJustificationToRight()
1989        elif "left" in justify:
1990            pr.SetJustificationToLeft()
1991        # ------
1992        if "top" in justify:
1993            pr.SetVerticalJustificationToTop()
1994        else:
1995            pr.SetVerticalJustificationToBottom()
1996
1997        if bc is not None:
1998            bc = colors.get_color(bc)
1999            pr.SetBackgroundColor(bc)
2000            pr.SetBackgroundOpacity(alpha)
2001
2002        mp.SetInputData(poly)
2003        a2d = Actor2D()
2004        a2d.PickableOff()
2005        a2d.SetMapper(mp)
2006        return a2d

Generate value or ID bi-dimensional labels for mesh cells or points.

See also: labels(), flagpole(), caption() and legend().

Arguments:
  • content : (str) either 'id', 'cellid', or array name
  • on : (str) generate labels for "cells" instead of "points" (the default)
  • scale : (float) size scaling of labels
  • precision : (int) precision of numeric labels
  • angle : (float) local rotation angle of label in degrees
  • frame : (bool) draw a frame around the label
  • bc : (str) background color of the label
from vedo import Sphere, show
sph = Sphere(quads=True, res=4).compute_normals().wireframe()
sph.celldata["zvals"] = sph.cell_centers().coordinates[:,2]
l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
show(sph, l2d, axes=1).close()

def legend(self, txt) -> Self:
2008    def legend(self, txt) -> Self:
2009        """Book a legend text."""
2010        self.info["legend"] = txt
2011        # self.metadata["legend"] = txt
2012        return self

Book a legend text.

def flagpole( self, txt=None, point=None, offset=None, s=None, font='Calco', rounded=True, c=None, alpha=1.0, lw=2, italic=0.0, padding=0.1) -> Optional[vedo.mesh.Mesh]:
2014    def flagpole(
2015        self,
2016        txt=None,
2017        point=None,
2018        offset=None,
2019        s=None,
2020        font="Calco",
2021        rounded=True,
2022        c=None,
2023        alpha=1.0,
2024        lw=2,
2025        italic=0.0,
2026        padding=0.1,
2027    ) -> Union["vedo.Mesh", None]:
2028        """
2029        Generate a flag pole style element to describe an object.
2030        Returns a `Mesh` object.
2031
2032        Use flagpole.follow_camera() to make it face the camera in the scene.
2033
2034        Consider using `settings.use_parallel_projection = True`
2035        to avoid perspective distortions.
2036
2037        See also `flagpost()`.
2038
2039        Arguments:
2040            txt : (str)
2041                Text to display. The default is the filename or the object name.
2042            point : (list)
2043                position of the flagpole pointer.
2044            offset : (list)
2045                text offset wrt the application point.
2046            s : (float)
2047                size of the flagpole.
2048            font : (str)
2049                font face. Check [available fonts here](https://vedo.embl.es/fonts).
2050            rounded : (bool)
2051                draw a rounded or squared box around the text.
2052            c : (list)
2053                text and box color.
2054            alpha : (float)
2055                opacity of text and box.
2056            lw : (float)
2057                line with of box frame.
2058            italic : (float)
2059                italicness of text.
2060
2061        Examples:
2062            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2063
2064                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2065
2066            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2067            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2068            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2069        """
2070        objs = []
2071
2072        if txt is None:
2073            if self.filename:
2074                txt = self.filename.split("/")[-1]
2075            elif self.name:
2076                txt = self.name
2077            else:
2078                return None
2079
2080        x0, x1, y0, y1, z0, z1 = self.bounds()
2081        d = self.diagonal_size()
2082        if point is None:
2083            if d:
2084                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2085                # point = self.closest_point([x1, y0, z1])
2086            else:  # it's a Point
2087                point = self.transform.position
2088
2089        pt = utils.make3d(point)
2090
2091        if offset is None:
2092            offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0]
2093        offset = utils.make3d(offset)
2094
2095        if s is None:
2096            s = d / 20
2097
2098        sph = None
2099        if d and (z1 - z0) / d > 0.1:
2100            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2101
2102        if c is None:
2103            c = np.array(self.color()) / 1.4
2104
2105        lab = vedo.shapes.Text3D(
2106            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center"
2107        )
2108        objs.append(lab)
2109
2110        if d and not sph:
2111            sph = vedo.shapes.Circle(pt, r=s / 3, res=16)
2112        objs.append(sph)
2113
2114        x0, x1, y0, y1, z0, z1 = lab.bounds()
2115        aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)]
2116        if rounded:
2117            box = vedo.shapes.KSpline(aline, closed=True)
2118        else:
2119            box = vedo.shapes.Line(aline, closed=True)
2120
2121        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2122
2123        # box.actor.SetOrigin(cnt)
2124        box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt)
2125        objs.append(box)
2126
2127        x0, x1, y0, y1, z0, z1 = box.bounds()
2128        if x0 < pt[0] < x1:
2129            c0 = box.closest_point(pt)
2130            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2131        elif (pt[0] - x0) < (x1 - pt[0]):
2132            c0 = [x0, (y0 + y1) / 2, pt[2]]
2133            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2134        else:
2135            c0 = [x1, (y0 + y1) / 2, pt[2]]
2136            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2137
2138        con = vedo.shapes.Line([c0, c1, pt])
2139        objs.append(con)
2140
2141        mobjs = vedo.merge(objs).c(c).alpha(alpha)
2142        mobjs.name = "FlagPole"
2143        mobjs.bc("tomato").pickable(False)
2144        mobjs.properties.LightingOff()
2145        mobjs.properties.SetLineWidth(lw)
2146        mobjs.actor.UseBoundsOff()
2147        mobjs.actor.SetPosition([0,0,0])
2148        mobjs.actor.SetOrigin(pt)
2149        return mobjs
2150
2151        # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha)
2152        # mobjs.name = "FlagPole"
2153        # # mobjs.bc("tomato").pickable(False)
2154        # # mobjs.properties.LightingOff()
2155        # # mobjs.properties.SetLineWidth(lw)
2156        # # mobjs.actor.UseBoundsOff()
2157        # # mobjs.actor.SetPosition([0,0,0])
2158        # # mobjs.actor.SetOrigin(pt)
2159        # # print(pt)
2160        # return mobjs

Generate a flag pole style element to describe an object. Returns a Mesh object.

Use flagpole.follow_camera() to make it face the camera in the scene.

Consider using settings.use_parallel_projection = True to avoid perspective distortions.

See also flagpost().

Arguments:
  • txt : (str) Text to display. The default is the filename or the object name.
  • point : (list) position of the flagpole pointer.
  • offset : (list) text offset wrt the application point.
  • s : (float) size of the flagpole.
  • font : (str) font face. Check available fonts here.
  • rounded : (bool) draw a rounded or squared box around the text.
  • c : (list) text and box color.
  • alpha : (float) opacity of text and box.
  • lw : (float) line with of box frame.
  • italic : (float) italicness of text.
Examples:
def flagpost( self, txt=None, point=None, offset=None, s=1.0, c='k9', bc='k1', alpha=1, lw=0, font='Calco', justify='center-left', vspacing=1.0) -> Optional[vedo.addons.Flagpost]:
2162    def flagpost(
2163        self,
2164        txt=None,
2165        point=None,
2166        offset=None,
2167        s=1.0,
2168        c="k9",
2169        bc="k1",
2170        alpha=1,
2171        lw=0,
2172        font="Calco",
2173        justify="center-left",
2174        vspacing=1.0,
2175    ) -> Union["vedo.addons.Flagpost", None]:
2176        """
2177        Generate a flag post style element to describe an object.
2178
2179        Arguments:
2180            txt : (str)
2181                Text to display. The default is the filename or the object name.
2182            point : (list)
2183                position of the flag anchor point. The default is None.
2184            offset : (list)
2185                a 3D displacement or offset. The default is None.
2186            s : (float)
2187                size of the text to be shown
2188            c : (list)
2189                color of text and line
2190            bc : (list)
2191                color of the flag background
2192            alpha : (float)
2193                opacity of text and box.
2194            lw : (int)
2195                line with of box frame. The default is 0.
2196            font : (str)
2197                font name. Use a monospace font for better rendering. The default is "Calco".
2198                Type `vedo -r fonts` for a font demo.
2199                Check [available fonts here](https://vedo.embl.es/fonts).
2200            justify : (str)
2201                internal text justification. The default is "center-left".
2202            vspacing : (float)
2203                vertical spacing between lines.
2204
2205        Examples:
2206            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2207
2208            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2209        """
2210        if txt is None:
2211            if self.filename:
2212                txt = self.filename.split("/")[-1]
2213            elif self.name:
2214                txt = self.name
2215            else:
2216                return None
2217
2218        x0, x1, y0, y1, z0, z1 = self.bounds()
2219        d = self.diagonal_size()
2220        if point is None:
2221            if d:
2222                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2223            else:  # it's a Point
2224                point = self.transform.position
2225
2226        point = utils.make3d(point)
2227
2228        if offset is None:
2229            offset = [0, 0, (z1 - z0) / 2]
2230        offset = utils.make3d(offset)
2231
2232        fpost = vedo.addons.Flagpost(
2233            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2234        )
2235        self._caption = fpost
2236        return fpost

Generate a flag post style element to describe an object.

Arguments:
  • txt : (str) Text to display. The default is the filename or the object name.
  • point : (list) position of the flag anchor point. The default is None.
  • offset : (list) a 3D displacement or offset. The default is None.
  • s : (float) size of the text to be shown
  • c : (list) color of text and line
  • bc : (list) color of the flag background
  • alpha : (float) opacity of text and box.
  • lw : (int) line with of box frame. The default is 0.
  • font : (str) font name. Use a monospace font for better rendering. The default is "Calco". Type vedo -r fonts for a font demo. Check available fonts here.
  • justify : (str) internal text justification. The default is "center-left".
  • vspacing : (float) vertical spacing between lines.
Examples:

def caption( self, txt=None, point=None, size=(0.3, 0.15), padding=5, font='Calco', justify='center-right', vspacing=1.0, c=None, alpha=1.0, lw=1, ontop=True) -> Optional[vtkmodules.vtkRenderingAnnotation.vtkCaptionActor2D]:
2238    def caption(
2239        self,
2240        txt=None,
2241        point=None,
2242        size=(0.30, 0.15),
2243        padding=5,
2244        font="Calco",
2245        justify="center-right",
2246        vspacing=1.0,
2247        c=None,
2248        alpha=1.0,
2249        lw=1,
2250        ontop=True,
2251    ) -> Union["vtki.vtkCaptionActor2D", None]:
2252        """
2253        Create a 2D caption to an object which follows the camera movements.
2254        Latex is not supported. Returns the same input object for concatenation.
2255
2256        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2257        with similar functionality.
2258
2259        Arguments:
2260            txt : (str)
2261                text to be rendered. The default is the file name.
2262            point : (list)
2263                anchoring point. The default is None.
2264            size : (list)
2265                (width, height) of the caption box. The default is (0.30, 0.15).
2266            padding : (float)
2267                padding space of the caption box in pixels. The default is 5.
2268            font : (str)
2269                font name. Use a monospace font for better rendering. The default is "VictorMono".
2270                Type `vedo -r fonts` for a font demo.
2271                Check [available fonts here](https://vedo.embl.es/fonts).
2272            justify : (str)
2273                internal text justification. The default is "center-right".
2274            vspacing : (float)
2275                vertical spacing between lines. The default is 1.
2276            c : (str)
2277                text and box color. The default is 'lb'.
2278            alpha : (float)
2279                text and box transparency. The default is 1.
2280            lw : (int)
2281                line width in pixels. The default is 1.
2282            ontop : (bool)
2283                keep the 2d caption always on top. The default is True.
2284
2285        Examples:
2286            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2287
2288                ![](https://vedo.embl.es/images/pyplot/caption.png)
2289
2290            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2291            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2292        """
2293        if txt is None:
2294            if self.filename:
2295                txt = self.filename.split("/")[-1]
2296            elif self.name:
2297                txt = self.name
2298
2299        if not txt:  # disable it
2300            self._caption = None
2301            return None
2302
2303        for r in vedo.shapes._reps:
2304            txt = txt.replace(r[0], r[1])
2305
2306        if c is None:
2307            c = np.array(self.properties.GetColor()) / 2
2308        else:
2309            c = colors.get_color(c)
2310
2311        if point is None:
2312            x0, x1, y0, y1, _, z1 = self.dataset.GetBounds()
2313            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2314            point = self.closest_point(pt)
2315
2316        capt = vtki.vtkCaptionActor2D()
2317        capt.SetAttachmentPoint(point)
2318        capt.SetBorder(True)
2319        capt.SetLeader(True)
2320        sph = vtki.new("SphereSource")
2321        sph.Update()
2322        capt.SetLeaderGlyphData(sph.GetOutput())
2323        capt.SetMaximumLeaderGlyphSize(5)
2324        capt.SetPadding(int(padding))
2325        capt.SetCaption(txt)
2326        capt.SetWidth(size[0])
2327        capt.SetHeight(size[1])
2328        capt.SetThreeDimensionalLeader(not ontop)
2329
2330        pra = capt.GetProperty()
2331        pra.SetColor(c)
2332        pra.SetOpacity(alpha)
2333        pra.SetLineWidth(lw)
2334
2335        pr = capt.GetCaptionTextProperty()
2336        pr.SetFontFamily(vtki.VTK_FONT_FILE)
2337        fl = utils.get_font_path(font)
2338        pr.SetFontFile(fl)
2339        pr.ShadowOff()
2340        pr.BoldOff()
2341        pr.FrameOff()
2342        pr.SetColor(c)
2343        pr.SetOpacity(alpha)
2344        pr.SetJustificationToLeft()
2345        if "top" in justify:
2346            pr.SetVerticalJustificationToTop()
2347        if "bottom" in justify:
2348            pr.SetVerticalJustificationToBottom()
2349        if "cent" in justify:
2350            pr.SetVerticalJustificationToCentered()
2351            pr.SetJustificationToCentered()
2352        if "left" in justify:
2353            pr.SetJustificationToLeft()
2354        if "right" in justify:
2355            pr.SetJustificationToRight()
2356        pr.SetLineSpacing(vspacing)
2357        return capt

Create a 2D caption to an object which follows the camera movements. Latex is not supported. Returns the same input object for concatenation.

See also flagpole(), flagpost(), labels() and legend() with similar functionality.

Arguments:
  • txt : (str) text to be rendered. The default is the file name.
  • point : (list) anchoring point. The default is None.
  • size : (list) (width, height) of the caption box. The default is (0.30, 0.15).
  • padding : (float) padding space of the caption box in pixels. The default is 5.
  • font : (str) font name. Use a monospace font for better rendering. The default is "VictorMono". Type vedo -r fonts for a font demo. Check available fonts here.
  • justify : (str) internal text justification. The default is "center-right".
  • vspacing : (float) vertical spacing between lines. The default is 1.
  • c : (str) text and box color. The default is 'lb'.
  • alpha : (float) text and box transparency. The default is 1.
  • lw : (int) line width in pixels. The default is 1.
  • ontop : (bool) keep the 2d caption always on top. The default is True.
Examples:
class VolumeVisual(CommonVisual):
2700class VolumeVisual(CommonVisual):
2701    """Class to manage the visual aspects of a ``Volume`` object."""
2702
2703    # def __init__(self) -> None:
2704    #     # print("INIT VolumeVisual")
2705    #     super().__init__()
2706
2707    def alpha_unit(self, u=None) -> Union[Self, float]:
2708        """
2709        Defines light attenuation per unit length. Default is 1.
2710        The larger the unit length, the further light has to travel to attenuate the same amount.
2711
2712        E.g., if you set the unit distance to 0, you will get full opacity.
2713        It means that when light travels 0 distance it's already attenuated a finite amount.
2714        Thus, any finite distance should attenuate all light.
2715        The larger you make the unit distance, the more transparent the rendering becomes.
2716        """
2717        if u is None:
2718            return self.properties.GetScalarOpacityUnitDistance()
2719        self.properties.SetScalarOpacityUnitDistance(u)
2720        return self
2721
2722    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2723        """
2724        Assign a set of tranparencies to a volume's gradient
2725        along the range of the scalar value.
2726        A single constant value can also be assigned.
2727        The gradient function is used to decrease the opacity
2728        in the "flat" regions of the volume while maintaining the opacity
2729        at the boundaries between material types.  The gradient is measured
2730        as the amount by which the intensity changes over unit distance.
2731
2732        The format for alpha_grad is the same as for method `volume.alpha()`.
2733        """
2734        if vmin is None:
2735            vmin, _ = self.dataset.GetScalarRange()
2736        if vmax is None:
2737            _, vmax = self.dataset.GetScalarRange()
2738
2739        if alpha_grad is None:
2740            self.properties.DisableGradientOpacityOn()
2741            return self
2742
2743        self.properties.DisableGradientOpacityOff()
2744
2745        gotf = self.properties.GetGradientOpacity()
2746        if utils.is_sequence(alpha_grad):
2747            alpha_grad = np.array(alpha_grad)
2748            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2749                for i, al in enumerate(alpha_grad):
2750                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2751                    # Create transfer mapping scalar value to gradient opacity
2752                    gotf.AddPoint(xalpha, al)
2753            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2754                gotf.AddPoint(vmin, alpha_grad[0][1])
2755                for xalpha, al in alpha_grad:
2756                    # Create transfer mapping scalar value to opacity
2757                    gotf.AddPoint(xalpha, al)
2758                gotf.AddPoint(vmax, alpha_grad[-1][1])
2759            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2760        else:
2761            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2762            gotf.AddPoint(vmax, alpha_grad)
2763        return self
2764
2765    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2766        """Same as `color()`.
2767
2768        Arguments:
2769            alpha : (list)
2770                use a list to specify transparencies along the scalar range
2771            vmin : (float)
2772                force the min of the scalar range to be this value
2773            vmax : (float)
2774                force the max of the scalar range to be this value
2775        """
2776        return self.color(c, alpha, vmin, vmax)
2777
2778    def jittering(self, status=None) -> Union[Self, bool]:
2779        """
2780        If `True`, each ray traversal direction will be perturbed slightly
2781        using a noise-texture to get rid of wood-grain effects.
2782        """
2783        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2784            if status is None:
2785                return self.mapper.GetUseJittering()
2786            self.mapper.SetUseJittering(status)
2787        return self
2788
2789    def hide_voxels(self, ids) -> Self:
2790        """
2791        Hide voxels (cells) from visualization.
2792
2793        Example:
2794            ```python
2795            from vedo import *
2796            embryo = Volume(dataurl+'embryo.tif')
2797            embryo.hide_voxels(list(range(400000)))
2798            show(embryo, axes=1).close()
2799            ```
2800
2801        See also:
2802            `volume.mask()`
2803        """
2804        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2805        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2806        name = vtki.vtkDataSetAttributes.GhostArrayName()
2807        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2808        self.dataset.GetCellData().AddArray(garr)
2809        self.dataset.GetCellData().Modified()
2810        return self
2811
2812    def mask(self, data) -> Self:
2813        """
2814        Mask a volume visualization with a binary value.
2815        Needs to specify `volume.mapper = "gpu"`.
2816
2817        Example:
2818        ```python
2819        from vedo import np, Volume, show
2820        data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2821        # all voxels have value zero except:
2822        data_matrix[ 0:35,  0:35,  0:35] = 1
2823        data_matrix[35:55, 35:55, 35:55] = 2
2824        data_matrix[55:74, 55:74, 55:74] = 3
2825        vol = Volume(data_matrix).cmap('Blues')
2826        vol.mapper = "gpu"
2827        data_mask = np.zeros_like(data_matrix)
2828        data_mask[10:65, 10:60, 20:70] = 1
2829        vol.mask(data_mask)
2830        show(vol, axes=1).close()
2831        ```
2832        See also:
2833            `volume.hide_voxels()`
2834        """
2835        rdata = data.astype(np.uint8).ravel(order="F")
2836        varr = utils.numpy2vtk(rdata, name="input_mask")
2837
2838        img = vtki.vtkImageData()
2839        img.SetDimensions(self.dimensions())
2840        img.GetPointData().AddArray(varr)
2841        img.GetPointData().SetActiveScalars(varr.GetName())
2842
2843        try:
2844            self.mapper.SetMaskTypeToBinary()
2845            self.mapper.SetMaskInput(img)
2846        except AttributeError:
2847            vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'")
2848        return self
2849
2850
2851    def mode(self, mode=None) -> Union[Self, int]:
2852        """
2853        Define the volumetric rendering mode following this:
2854            - 0, composite rendering
2855            - 1, maximum projection rendering
2856            - 2, minimum projection rendering
2857            - 3, average projection rendering
2858            - 4, additive mode
2859
2860        The default mode is "composite" where the scalar values are sampled through
2861        the volume and composited in a front-to-back scheme through alpha blending.
2862        The final color and opacity is determined using the color and opacity transfer
2863        functions specified in alpha keyword.
2864
2865        Maximum and minimum intensity blend modes use the maximum and minimum
2866        scalar values, respectively, along the sampling ray.
2867        The final color and opacity is determined by passing the resultant value
2868        through the color and opacity transfer functions.
2869
2870        Additive blend mode accumulates scalar values by passing each value
2871        through the opacity transfer function and then adding up the product
2872        of the value and its opacity. In other words, the scalar values are scaled
2873        using the opacity transfer function and summed to derive the final color.
2874        Note that the resulting image is always grayscale i.e. aggregated values
2875        are not passed through the color transfer function.
2876        This is because the final value is a derived value and not a real data value
2877        along the sampling ray.
2878
2879        Average intensity blend mode works similar to the additive blend mode where
2880        the scalar values are multiplied by opacity calculated from the opacity
2881        transfer function and then added.
2882        The additional step here is to divide the sum by the number of samples
2883        taken through the volume.
2884        As is the case with the additive intensity projection, the final image will
2885        always be grayscale i.e. the aggregated values are not passed through the
2886        color transfer function.
2887        """
2888        if mode is None:
2889            return self.mapper.GetBlendMode()
2890
2891        if isinstance(mode, str):
2892            if "comp" in mode:
2893                mode = 0
2894            elif "proj" in mode:
2895                if "max" in mode:
2896                    mode = 1
2897                elif "min" in mode:
2898                    mode = 2
2899                elif "ave" in mode:
2900                    mode = 3
2901                else:
2902                    vedo.logger.warning(f"unknown mode {mode}")
2903                    mode = 0
2904            elif "add" in mode:
2905                mode = 4
2906            else:
2907                vedo.logger.warning(f"unknown mode {mode}")
2908                mode = 0
2909
2910        self.mapper.SetBlendMode(mode)
2911        return self
2912
2913    def shade(self, status=None) -> Union[Self, bool]:
2914        """
2915        Set/Get the shading of a Volume.
2916        Shading can be further controlled with `volume.lighting()` method.
2917
2918        If shading is turned on, the mapper may perform shading calculations.
2919        In some cases shading does not apply
2920        (for example, in maximum intensity projection mode).
2921        """
2922        if status is None:
2923            return self.properties.GetShade()
2924        self.properties.SetShade(status)
2925        return self
2926
2927    def interpolation(self, itype) -> Self:
2928        """
2929        Set interpolation type.
2930
2931        0=nearest neighbour, 1=linear
2932        """
2933        self.properties.SetInterpolationType(itype)
2934        return self

Class to manage the visual aspects of a Volume object.

def alpha_unit(self, u=None) -> Union[Self, float]:
2707    def alpha_unit(self, u=None) -> Union[Self, float]:
2708        """
2709        Defines light attenuation per unit length. Default is 1.
2710        The larger the unit length, the further light has to travel to attenuate the same amount.
2711
2712        E.g., if you set the unit distance to 0, you will get full opacity.
2713        It means that when light travels 0 distance it's already attenuated a finite amount.
2714        Thus, any finite distance should attenuate all light.
2715        The larger you make the unit distance, the more transparent the rendering becomes.
2716        """
2717        if u is None:
2718            return self.properties.GetScalarOpacityUnitDistance()
2719        self.properties.SetScalarOpacityUnitDistance(u)
2720        return self

Defines light attenuation per unit length. Default is 1. The larger the unit length, the further light has to travel to attenuate the same amount.

E.g., if you set the unit distance to 0, you will get full opacity. It means that when light travels 0 distance it's already attenuated a finite amount. Thus, any finite distance should attenuate all light. The larger you make the unit distance, the more transparent the rendering becomes.

def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2722    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2723        """
2724        Assign a set of tranparencies to a volume's gradient
2725        along the range of the scalar value.
2726        A single constant value can also be assigned.
2727        The gradient function is used to decrease the opacity
2728        in the "flat" regions of the volume while maintaining the opacity
2729        at the boundaries between material types.  The gradient is measured
2730        as the amount by which the intensity changes over unit distance.
2731
2732        The format for alpha_grad is the same as for method `volume.alpha()`.
2733        """
2734        if vmin is None:
2735            vmin, _ = self.dataset.GetScalarRange()
2736        if vmax is None:
2737            _, vmax = self.dataset.GetScalarRange()
2738
2739        if alpha_grad is None:
2740            self.properties.DisableGradientOpacityOn()
2741            return self
2742
2743        self.properties.DisableGradientOpacityOff()
2744
2745        gotf = self.properties.GetGradientOpacity()
2746        if utils.is_sequence(alpha_grad):
2747            alpha_grad = np.array(alpha_grad)
2748            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2749                for i, al in enumerate(alpha_grad):
2750                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2751                    # Create transfer mapping scalar value to gradient opacity
2752                    gotf.AddPoint(xalpha, al)
2753            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2754                gotf.AddPoint(vmin, alpha_grad[0][1])
2755                for xalpha, al in alpha_grad:
2756                    # Create transfer mapping scalar value to opacity
2757                    gotf.AddPoint(xalpha, al)
2758                gotf.AddPoint(vmax, alpha_grad[-1][1])
2759            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2760        else:
2761            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2762            gotf.AddPoint(vmax, alpha_grad)
2763        return self

Assign a set of tranparencies to a volume's gradient along the range of the scalar value. A single constant value can also be assigned. The gradient function is used to decrease the opacity in the "flat" regions of the volume while maintaining the opacity at the boundaries between material types. The gradient is measured as the amount by which the intensity changes over unit distance.

The format for alpha_grad is the same as for method volume.alpha().

def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2765    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2766        """Same as `color()`.
2767
2768        Arguments:
2769            alpha : (list)
2770                use a list to specify transparencies along the scalar range
2771            vmin : (float)
2772                force the min of the scalar range to be this value
2773            vmax : (float)
2774                force the max of the scalar range to be this value
2775        """
2776        return self.color(c, alpha, vmin, vmax)

Same as color().

Arguments:
  • alpha : (list) use a list to specify transparencies along the scalar range
  • vmin : (float) force the min of the scalar range to be this value
  • vmax : (float) force the max of the scalar range to be this value
def jittering(self, status=None) -> Union[Self, bool]:
2778    def jittering(self, status=None) -> Union[Self, bool]:
2779        """
2780        If `True`, each ray traversal direction will be perturbed slightly
2781        using a noise-texture to get rid of wood-grain effects.
2782        """
2783        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2784            if status is None:
2785                return self.mapper.GetUseJittering()
2786            self.mapper.SetUseJittering(status)
2787        return self

If True, each ray traversal direction will be perturbed slightly using a noise-texture to get rid of wood-grain effects.

def hide_voxels(self, ids) -> Self:
2789    def hide_voxels(self, ids) -> Self:
2790        """
2791        Hide voxels (cells) from visualization.
2792
2793        Example:
2794            ```python
2795            from vedo import *
2796            embryo = Volume(dataurl+'embryo.tif')
2797            embryo.hide_voxels(list(range(400000)))
2798            show(embryo, axes=1).close()
2799            ```
2800
2801        See also:
2802            `volume.mask()`
2803        """
2804        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2805        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2806        name = vtki.vtkDataSetAttributes.GhostArrayName()
2807        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2808        self.dataset.GetCellData().AddArray(garr)
2809        self.dataset.GetCellData().Modified()
2810        return self

Hide voxels (cells) from visualization.

Example:
from vedo import *
embryo = Volume(dataurl+'embryo.tif')
embryo.hide_voxels(list(range(400000)))
show(embryo, axes=1).close()
See also:

volume.mask()

def mask(self, data) -> Self:
2812    def mask(self, data) -> Self:
2813        """
2814        Mask a volume visualization with a binary value.
2815        Needs to specify `volume.mapper = "gpu"`.
2816
2817        Example:
2818        ```python
2819        from vedo import np, Volume, show
2820        data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2821        # all voxels have value zero except:
2822        data_matrix[ 0:35,  0:35,  0:35] = 1
2823        data_matrix[35:55, 35:55, 35:55] = 2
2824        data_matrix[55:74, 55:74, 55:74] = 3
2825        vol = Volume(data_matrix).cmap('Blues')
2826        vol.mapper = "gpu"
2827        data_mask = np.zeros_like(data_matrix)
2828        data_mask[10:65, 10:60, 20:70] = 1
2829        vol.mask(data_mask)
2830        show(vol, axes=1).close()
2831        ```
2832        See also:
2833            `volume.hide_voxels()`
2834        """
2835        rdata = data.astype(np.uint8).ravel(order="F")
2836        varr = utils.numpy2vtk(rdata, name="input_mask")
2837
2838        img = vtki.vtkImageData()
2839        img.SetDimensions(self.dimensions())
2840        img.GetPointData().AddArray(varr)
2841        img.GetPointData().SetActiveScalars(varr.GetName())
2842
2843        try:
2844            self.mapper.SetMaskTypeToBinary()
2845            self.mapper.SetMaskInput(img)
2846        except AttributeError:
2847            vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'")
2848        return self

Mask a volume visualization with a binary value. Needs to specify volume.mapper = "gpu".

Example:

from vedo import np, Volume, show
data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
# all voxels have value zero except:
data_matrix[ 0:35,  0:35,  0:35] = 1
data_matrix[35:55, 35:55, 35:55] = 2
data_matrix[55:74, 55:74, 55:74] = 3
vol = Volume(data_matrix).cmap('Blues')
vol.mapper = "gpu"
data_mask = np.zeros_like(data_matrix)
data_mask[10:65, 10:60, 20:70] = 1
vol.mask(data_mask)
show(vol, axes=1).close()
See also:

volume.hide_voxels()

def mode(self, mode=None) -> Union[Self, int]:
2851    def mode(self, mode=None) -> Union[Self, int]:
2852        """
2853        Define the volumetric rendering mode following this:
2854            - 0, composite rendering
2855            - 1, maximum projection rendering
2856            - 2, minimum projection rendering
2857            - 3, average projection rendering
2858            - 4, additive mode
2859
2860        The default mode is "composite" where the scalar values are sampled through
2861        the volume and composited in a front-to-back scheme through alpha blending.
2862        The final color and opacity is determined using the color and opacity transfer
2863        functions specified in alpha keyword.
2864
2865        Maximum and minimum intensity blend modes use the maximum and minimum
2866        scalar values, respectively, along the sampling ray.
2867        The final color and opacity is determined by passing the resultant value
2868        through the color and opacity transfer functions.
2869
2870        Additive blend mode accumulates scalar values by passing each value
2871        through the opacity transfer function and then adding up the product
2872        of the value and its opacity. In other words, the scalar values are scaled
2873        using the opacity transfer function and summed to derive the final color.
2874        Note that the resulting image is always grayscale i.e. aggregated values
2875        are not passed through the color transfer function.
2876        This is because the final value is a derived value and not a real data value
2877        along the sampling ray.
2878
2879        Average intensity blend mode works similar to the additive blend mode where
2880        the scalar values are multiplied by opacity calculated from the opacity
2881        transfer function and then added.
2882        The additional step here is to divide the sum by the number of samples
2883        taken through the volume.
2884        As is the case with the additive intensity projection, the final image will
2885        always be grayscale i.e. the aggregated values are not passed through the
2886        color transfer function.
2887        """
2888        if mode is None:
2889            return self.mapper.GetBlendMode()
2890
2891        if isinstance(mode, str):
2892            if "comp" in mode:
2893                mode = 0
2894            elif "proj" in mode:
2895                if "max" in mode:
2896                    mode = 1
2897                elif "min" in mode:
2898                    mode = 2
2899                elif "ave" in mode:
2900                    mode = 3
2901                else:
2902                    vedo.logger.warning(f"unknown mode {mode}")
2903                    mode = 0
2904            elif "add" in mode:
2905                mode = 4
2906            else:
2907                vedo.logger.warning(f"unknown mode {mode}")
2908                mode = 0
2909
2910        self.mapper.SetBlendMode(mode)
2911        return self
Define the volumetric rendering mode following this:
  • 0, composite rendering
  • 1, maximum projection rendering
  • 2, minimum projection rendering
  • 3, average projection rendering
  • 4, additive mode

The default mode is "composite" where the scalar values are sampled through the volume and composited in a front-to-back scheme through alpha blending. The final color and opacity is determined using the color and opacity transfer functions specified in alpha keyword.

Maximum and minimum intensity blend modes use the maximum and minimum scalar values, respectively, along the sampling ray. The final color and opacity is determined by passing the resultant value through the color and opacity transfer functions.

Additive blend mode accumulates scalar values by passing each value through the opacity transfer function and then adding up the product of the value and its opacity. In other words, the scalar values are scaled using the opacity transfer function and summed to derive the final color. Note that the resulting image is always grayscale i.e. aggregated values are not passed through the color transfer function. This is because the final value is a derived value and not a real data value along the sampling ray.

Average intensity blend mode works similar to the additive blend mode where the scalar values are multiplied by opacity calculated from the opacity transfer function and then added. The additional step here is to divide the sum by the number of samples taken through the volume. As is the case with the additive intensity projection, the final image will always be grayscale i.e. the aggregated values are not passed through the color transfer function.

def shade(self, status=None) -> Union[Self, bool]:
2913    def shade(self, status=None) -> Union[Self, bool]:
2914        """
2915        Set/Get the shading of a Volume.
2916        Shading can be further controlled with `volume.lighting()` method.
2917
2918        If shading is turned on, the mapper may perform shading calculations.
2919        In some cases shading does not apply
2920        (for example, in maximum intensity projection mode).
2921        """
2922        if status is None:
2923            return self.properties.GetShade()
2924        self.properties.SetShade(status)
2925        return self

Set/Get the shading of a Volume. Shading can be further controlled with volume.lighting() method.

If shading is turned on, the mapper may perform shading calculations. In some cases shading does not apply (for example, in maximum intensity projection mode).

def interpolation(self, itype) -> Self:
2927    def interpolation(self, itype) -> Self:
2928        """
2929        Set interpolation type.
2930
2931        0=nearest neighbour, 1=linear
2932        """
2933        self.properties.SetInterpolationType(itype)
2934        return self

Set interpolation type.

0=nearest neighbour, 1=linear

class MeshVisual(PointsVisual):
2361class MeshVisual(PointsVisual):
2362    """Class to manage the visual aspects of a `Mesh` object."""
2363
2364    def __init__(self) -> None:
2365        # print("INIT MeshVisual", super())
2366        super().__init__()
2367
2368    def follow_camera(self, camera=None, origin=None) -> Self:
2369        """
2370        Return an object that will follow camera movements and stay locked to it.
2371        Use `mesh.follow_camera(False)` to disable it.
2372
2373        A `vtkCamera` object can also be passed.
2374        """
2375        if camera is False:
2376            try:
2377                self.SetCamera(None)
2378                return self
2379            except AttributeError:
2380                return self
2381
2382        factor = vtki.vtkFollower()
2383        factor.SetMapper(self.mapper)
2384        factor.SetProperty(self.properties)
2385        factor.SetBackfaceProperty(self.actor.GetBackfaceProperty())
2386        factor.SetTexture(self.actor.GetTexture())
2387        factor.SetScale(self.actor.GetScale())
2388        # factor.SetOrientation(self.actor.GetOrientation())
2389        factor.SetPosition(self.actor.GetPosition())
2390        factor.SetUseBounds(self.actor.GetUseBounds())
2391
2392        if origin is None:
2393            factor.SetOrigin(self.actor.GetOrigin())
2394        else:
2395            factor.SetOrigin(origin)
2396
2397        factor.PickableOff()
2398
2399        if isinstance(camera, vtki.vtkCamera):
2400            factor.SetCamera(camera)
2401        else:
2402            plt = vedo.plotter_instance
2403            if plt and plt.renderer and plt.renderer.GetActiveCamera():
2404                factor.SetCamera(plt.renderer.GetActiveCamera())
2405
2406        self.actor = None
2407        factor.retrieve_object = weak_ref_to(self)
2408        self.actor = factor
2409        return self
2410
2411    def wireframe(self, value=True) -> Self:
2412        """Set mesh's representation as wireframe or solid surface."""
2413        if value:
2414            self.properties.SetRepresentationToWireframe()
2415        else:
2416            self.properties.SetRepresentationToSurface()
2417        return self
2418
2419    def flat(self)  -> Self:
2420        """Set surface interpolation to flat.
2421
2422        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700">
2423        """
2424        self.properties.SetInterpolationToFlat()
2425        return self
2426
2427    def phong(self) -> Self:
2428        """Set surface interpolation to "phong"."""
2429        self.properties.SetInterpolationToPhong()
2430        return self
2431
2432    def backface_culling(self, value=True) -> Self:
2433        """Set culling of polygons based on orientation of normal with respect to camera."""
2434        self.properties.SetBackfaceCulling(value)
2435        return self
2436
2437    def render_lines_as_tubes(self, value=True) -> Self:
2438        """Wrap a fake tube around a simple line for visualization"""
2439        self.properties.SetRenderLinesAsTubes(value)
2440        return self
2441
2442    def frontface_culling(self, value=True) -> Self:
2443        """Set culling of polygons based on orientation of normal with respect to camera."""
2444        self.properties.SetFrontfaceCulling(value)
2445        return self
2446
2447    def backcolor(self, bc=None) -> Union[Self, np.ndarray]:
2448        """
2449        Set/get mesh's backface color.
2450        """
2451        back_prop = self.actor.GetBackfaceProperty()
2452
2453        if bc is None:
2454            if back_prop:
2455                return back_prop.GetDiffuseColor()
2456            return self
2457
2458        if self.properties.GetOpacity() < 1:
2459            return self
2460
2461        if not back_prop:
2462            back_prop = vtki.vtkProperty()
2463
2464        back_prop.SetDiffuseColor(colors.get_color(bc))
2465        back_prop.SetOpacity(self.properties.GetOpacity())
2466        self.actor.SetBackfaceProperty(back_prop)
2467        self.mapper.ScalarVisibilityOff()
2468        return self
2469
2470    def bc(self, backcolor=False) -> Union[Self, np.ndarray]:
2471        """Shortcut for `mesh.backcolor()`."""
2472        return self.backcolor(backcolor)
2473
2474    def linewidth(self, lw=None) -> Union[Self, int]:
2475        """Set/get width of mesh edges. Same as `lw()`."""
2476        if lw is not None:
2477            if lw == 0:
2478                self.properties.EdgeVisibilityOff()
2479                self.properties.SetRepresentationToSurface()
2480                return self
2481            self.properties.EdgeVisibilityOn()
2482            self.properties.SetLineWidth(lw)
2483        else:
2484            return self.properties.GetLineWidth()
2485        return self
2486
2487    def lw(self, linewidth=None) -> Union[Self, int]:
2488        """Set/get width of mesh edges. Same as `linewidth()`."""
2489        return self.linewidth(linewidth)
2490
2491    def linecolor(self, lc=None) -> Union[Self, np.ndarray]:
2492        """Set/get color of mesh edges. Same as `lc()`."""
2493        if lc is None:
2494            return np.array(self.properties.GetEdgeColor())
2495        self.properties.EdgeVisibilityOn()
2496        self.properties.SetEdgeColor(colors.get_color(lc))
2497        return self
2498
2499    def lc(self, linecolor=None) -> Union[Self, np.ndarray]:
2500        """Set/get color of mesh edges. Same as `linecolor()`."""
2501        return self.linecolor(linecolor)
2502
2503    def texture(
2504        self,
2505        tname,
2506        tcoords=None,
2507        interpolate=True,
2508        repeat=True,
2509        edge_clamp=False,
2510        scale=None,
2511        ushift=None,
2512        vshift=None,
2513    ) -> Self:
2514        """
2515        Assign a texture to mesh from image file or predefined texture `tname`.
2516        If tname is set to `None` texture is disabled.
2517        Input tname can also be an array or a `vtkTexture`.
2518
2519        Arguments:
2520            tname : (numpy.array, str, Image, vtkTexture, None)
2521                the input texture to be applied. Can be a numpy array, a path to an image file,
2522                a vedo Image. The None value disables texture.
2523            tcoords : (numpy.array, str)
2524                this is the (u,v) texture coordinate array. Can also be a string of an existing array
2525                in the mesh.
2526            interpolate : (bool)
2527                turn on/off linear interpolation of the texture map when rendering.
2528            repeat : (bool)
2529                repeat of the texture when tcoords extend beyond the [0,1] range.
2530            edge_clamp : (bool)
2531                turn on/off the clamping of the texture map when
2532                the texture coords extend beyond the [0,1] range.
2533                Only used when repeat is False, and edge clamping is supported by the graphics card.
2534            scale : (bool)
2535                scale the texture image by this factor
2536            ushift : (bool)
2537                shift u-coordinates of texture by this amount
2538            vshift : (bool)
2539                shift v-coordinates of texture by this amount
2540
2541        Examples:
2542            - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py)
2543
2544            ![](https://vedo.embl.es/images/basic/texturecubes.png)
2545        """
2546        pd = self.dataset
2547        out_img = None
2548
2549        if tname is None:  # disable texture
2550            pd.GetPointData().SetTCoords(None)
2551            pd.GetPointData().Modified()
2552            return self  ######################################
2553
2554        if isinstance(tname, vtki.vtkTexture):
2555            tu = tname
2556
2557        elif isinstance(tname, vedo.Image):
2558            tu = vtki.vtkTexture()
2559            out_img = tname.dataset
2560
2561        elif utils.is_sequence(tname):
2562            tu = vtki.vtkTexture()
2563            out_img = vedo.image._get_img(tname)
2564
2565        elif isinstance(tname, str):
2566            tu = vtki.vtkTexture()
2567
2568            if "https://" in tname:
2569                try:
2570                    tname = vedo.file_io.download(tname, verbose=False)
2571                except:
2572                    vedo.logger.error(f"texture {tname} could not be downloaded")
2573                    return self
2574
2575            fn = tname + ".jpg"
2576            if os.path.exists(tname):
2577                fn = tname
2578            else:
2579                vedo.logger.error(f"texture file {tname} does not exist")
2580                return self
2581
2582            fnl = fn.lower()
2583            if ".jpg" in fnl or ".jpeg" in fnl:
2584                reader = vtki.new("JPEGReader")
2585            elif ".png" in fnl:
2586                reader = vtki.new("PNGReader")
2587            elif ".bmp" in fnl:
2588                reader = vtki.new("BMPReader")
2589            else:
2590                vedo.logger.error("in texture() supported files are only PNG, BMP or JPG")
2591                return self
2592            reader.SetFileName(fn)
2593            reader.Update()
2594            out_img = reader.GetOutput()
2595
2596        else:
2597            vedo.logger.error(f"in texture() cannot understand input {type(tname)}")
2598            return self
2599
2600        if tcoords is not None:
2601
2602            if isinstance(tcoords, str):
2603                vtarr = pd.GetPointData().GetArray(tcoords)
2604
2605            else:
2606                tcoords = np.asarray(tcoords)
2607                if tcoords.ndim != 2:
2608                    vedo.logger.error("tcoords must be a 2-dimensional array")
2609                    return self
2610                if tcoords.shape[0] != pd.GetNumberOfPoints():
2611                    vedo.logger.error("nr of texture coords must match nr of points")
2612                    return self
2613                if tcoords.shape[1] != 2:
2614                    vedo.logger.error("tcoords texture vector must have 2 components")
2615                vtarr = utils.numpy2vtk(tcoords)
2616                vtarr.SetName("TCoordinates")
2617
2618            pd.GetPointData().SetTCoords(vtarr)
2619            pd.GetPointData().Modified()
2620
2621        elif not pd.GetPointData().GetTCoords():
2622
2623            # TCoords still void..
2624            # check that there are no texture-like arrays:
2625            names = self.pointdata.keys()
2626            candidate_arr = ""
2627            for name in names:
2628                vtarr = pd.GetPointData().GetArray(name)
2629                if vtarr.GetNumberOfComponents() != 2:
2630                    continue
2631                t0, t1 = vtarr.GetRange()
2632                if t0 >= 0 and t1 <= 1:
2633                    candidate_arr = name
2634
2635            if candidate_arr:
2636
2637                vtarr = pd.GetPointData().GetArray(candidate_arr)
2638                pd.GetPointData().SetTCoords(vtarr)
2639                pd.GetPointData().Modified()
2640
2641            else:
2642                # last resource is automatic mapping
2643                tmapper = vtki.new("TextureMapToPlane")
2644                tmapper.AutomaticPlaneGenerationOn()
2645                tmapper.SetInputData(pd)
2646                tmapper.Update()
2647                tc = tmapper.GetOutput().GetPointData().GetTCoords()
2648                if scale or ushift or vshift:
2649                    ntc = utils.vtk2numpy(tc)
2650                    if scale:
2651                        ntc *= scale
2652                    if ushift:
2653                        ntc[:, 0] += ushift
2654                    if vshift:
2655                        ntc[:, 1] += vshift
2656                    tc = utils.numpy2vtk(tc)
2657                pd.GetPointData().SetTCoords(tc)
2658                pd.GetPointData().Modified()
2659
2660        if out_img:
2661            tu.SetInputData(out_img)
2662        tu.SetInterpolate(interpolate)
2663        tu.SetRepeat(repeat)
2664        tu.SetEdgeClamp(edge_clamp)
2665
2666        self.properties.SetColor(1, 1, 1)
2667        self.mapper.ScalarVisibilityOff()
2668        self.actor.SetTexture(tu)
2669
2670        # if seam_threshold is not None:
2671        #     tname = self.dataset.GetPointData().GetTCoords().GetName()
2672        #     grad = self.gradient(tname)
2673        #     ugrad, vgrad = np.split(grad, 2, axis=1)
2674        #     ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad)
2675        #     gradm = np.log(ugradm + vgradm)
2676        #     largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4]
2677        #     uvmap = self.pointdata[tname]
2678        #     # collapse triangles that have large gradient
2679        #     new_points = self.points.copy()
2680        #     for f in self.cells:
2681        #         if np.isin(f, largegrad_ids).all():
2682        #             id1, id2, id3 = f
2683        #             uv1, uv2, uv3 = uvmap[f]
2684        #             d12 = utils.mag2(uv1 - uv2)
2685        #             d23 = utils.mag2(uv2 - uv3)
2686        #             d31 = utils.mag2(uv3 - uv1)
2687        #             idm = np.argmin([d12, d23, d31])
2688        #             if idm == 0:
2689        #                 new_points[id1] = new_points[id3]
2690        #                 new_points[id2] = new_points[id3]
2691        #             elif idm == 1:
2692        #                 new_points[id2] = new_points[id1]
2693        #                 new_points[id3] = new_points[id1]
2694        #     self.points = new_points
2695
2696        self.dataset.Modified()
2697        return self

Class to manage the visual aspects of a Mesh object.

MeshVisual()
2364    def __init__(self) -> None:
2365        # print("INIT MeshVisual", super())
2366        super().__init__()
def follow_camera(self, camera=None, origin=None) -> Self:
2368    def follow_camera(self, camera=None, origin=None) -> Self:
2369        """
2370        Return an object that will follow camera movements and stay locked to it.
2371        Use `mesh.follow_camera(False)` to disable it.
2372
2373        A `vtkCamera` object can also be passed.
2374        """
2375        if camera is False:
2376            try:
2377                self.SetCamera(None)
2378                return self
2379            except AttributeError:
2380                return self
2381
2382        factor = vtki.vtkFollower()
2383        factor.SetMapper(self.mapper)
2384        factor.SetProperty(self.properties)
2385        factor.SetBackfaceProperty(self.actor.GetBackfaceProperty())
2386        factor.SetTexture(self.actor.GetTexture())
2387        factor.SetScale(self.actor.GetScale())
2388        # factor.SetOrientation(self.actor.GetOrientation())
2389        factor.SetPosition(self.actor.GetPosition())
2390        factor.SetUseBounds(self.actor.GetUseBounds())
2391
2392        if origin is None:
2393            factor.SetOrigin(self.actor.GetOrigin())
2394        else:
2395            factor.SetOrigin(origin)
2396
2397        factor.PickableOff()
2398
2399        if isinstance(camera, vtki.vtkCamera):
2400            factor.SetCamera(camera)
2401        else:
2402            plt = vedo.plotter_instance
2403            if plt and plt.renderer and plt.renderer.GetActiveCamera():
2404                factor.SetCamera(plt.renderer.GetActiveCamera())
2405
2406        self.actor = None
2407        factor.retrieve_object = weak_ref_to(self)
2408        self.actor = factor
2409        return self

Return an object that will follow camera movements and stay locked to it. Use mesh.follow_camera(False) to disable it.

A vtkCamera object can also be passed.

def wireframe(self, value=True) -> Self:
2411    def wireframe(self, value=True) -> Self:
2412        """Set mesh's representation as wireframe or solid surface."""
2413        if value:
2414            self.properties.SetRepresentationToWireframe()
2415        else:
2416            self.properties.SetRepresentationToSurface()
2417        return self

Set mesh's representation as wireframe or solid surface.

def flat(self) -> Self:
2419    def flat(self)  -> Self:
2420        """Set surface interpolation to flat.
2421
2422        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700">
2423        """
2424        self.properties.SetInterpolationToFlat()
2425        return self

Set surface interpolation to flat.

def phong(self) -> Self:
2427    def phong(self) -> Self:
2428        """Set surface interpolation to "phong"."""
2429        self.properties.SetInterpolationToPhong()
2430        return self

Set surface interpolation to "phong".

def backface_culling(self, value=True) -> Self:
2432    def backface_culling(self, value=True) -> Self:
2433        """Set culling of polygons based on orientation of normal with respect to camera."""
2434        self.properties.SetBackfaceCulling(value)
2435        return self

Set culling of polygons based on orientation of normal with respect to camera.

def render_lines_as_tubes(self, value=True) -> Self:
2437    def render_lines_as_tubes(self, value=True) -> Self:
2438        """Wrap a fake tube around a simple line for visualization"""
2439        self.properties.SetRenderLinesAsTubes(value)
2440        return self

Wrap a fake tube around a simple line for visualization

def frontface_culling(self, value=True) -> Self:
2442    def frontface_culling(self, value=True) -> Self:
2443        """Set culling of polygons based on orientation of normal with respect to camera."""
2444        self.properties.SetFrontfaceCulling(value)
2445        return self

Set culling of polygons based on orientation of normal with respect to camera.

def backcolor(self, bc=None) -> Union[Self, numpy.ndarray]:
2447    def backcolor(self, bc=None) -> Union[Self, np.ndarray]:
2448        """
2449        Set/get mesh's backface color.
2450        """
2451        back_prop = self.actor.GetBackfaceProperty()
2452
2453        if bc is None:
2454            if back_prop:
2455                return back_prop.GetDiffuseColor()
2456            return self
2457
2458        if self.properties.GetOpacity() < 1:
2459            return self
2460
2461        if not back_prop:
2462            back_prop = vtki.vtkProperty()
2463
2464        back_prop.SetDiffuseColor(colors.get_color(bc))
2465        back_prop.SetOpacity(self.properties.GetOpacity())
2466        self.actor.SetBackfaceProperty(back_prop)
2467        self.mapper.ScalarVisibilityOff()
2468        return self

Set/get mesh's backface color.

def bc(self, backcolor=False) -> Union[Self, numpy.ndarray]:
2470    def bc(self, backcolor=False) -> Union[Self, np.ndarray]:
2471        """Shortcut for `mesh.backcolor()`."""
2472        return self.backcolor(backcolor)

Shortcut for mesh.backcolor().

def linewidth(self, lw=None) -> Union[Self, int]:
2474    def linewidth(self, lw=None) -> Union[Self, int]:
2475        """Set/get width of mesh edges. Same as `lw()`."""
2476        if lw is not None:
2477            if lw == 0:
2478                self.properties.EdgeVisibilityOff()
2479                self.properties.SetRepresentationToSurface()
2480                return self
2481            self.properties.EdgeVisibilityOn()
2482            self.properties.SetLineWidth(lw)
2483        else:
2484            return self.properties.GetLineWidth()
2485        return self

Set/get width of mesh edges. Same as lw().

def lw(self, linewidth=None) -> Union[Self, int]:
2487    def lw(self, linewidth=None) -> Union[Self, int]:
2488        """Set/get width of mesh edges. Same as `linewidth()`."""
2489        return self.linewidth(linewidth)

Set/get width of mesh edges. Same as linewidth().

def linecolor(self, lc=None) -> Union[Self, numpy.ndarray]:
2491    def linecolor(self, lc=None) -> Union[Self, np.ndarray]:
2492        """Set/get color of mesh edges. Same as `lc()`."""
2493        if lc is None:
2494            return np.array(self.properties.GetEdgeColor())
2495        self.properties.EdgeVisibilityOn()
2496        self.properties.SetEdgeColor(colors.get_color(lc))
2497        return self

Set/get color of mesh edges. Same as lc().

def lc(self, linecolor=None) -> Union[Self, numpy.ndarray]:
2499    def lc(self, linecolor=None) -> Union[Self, np.ndarray]:
2500        """Set/get color of mesh edges. Same as `linecolor()`."""
2501        return self.linecolor(linecolor)

Set/get color of mesh edges. Same as linecolor().

def texture( self, tname, tcoords=None, interpolate=True, repeat=True, edge_clamp=False, scale=None, ushift=None, vshift=None) -> Self:
2503    def texture(
2504        self,
2505        tname,
2506        tcoords=None,
2507        interpolate=True,
2508        repeat=True,
2509        edge_clamp=False,
2510        scale=None,
2511        ushift=None,
2512        vshift=None,
2513    ) -> Self:
2514        """
2515        Assign a texture to mesh from image file or predefined texture `tname`.
2516        If tname is set to `None` texture is disabled.
2517        Input tname can also be an array or a `vtkTexture`.
2518
2519        Arguments:
2520            tname : (numpy.array, str, Image, vtkTexture, None)
2521                the input texture to be applied. Can be a numpy array, a path to an image file,
2522                a vedo Image. The None value disables texture.
2523            tcoords : (numpy.array, str)
2524                this is the (u,v) texture coordinate array. Can also be a string of an existing array
2525                in the mesh.
2526            interpolate : (bool)
2527                turn on/off linear interpolation of the texture map when rendering.
2528            repeat : (bool)
2529                repeat of the texture when tcoords extend beyond the [0,1] range.
2530            edge_clamp : (bool)
2531                turn on/off the clamping of the texture map when
2532                the texture coords extend beyond the [0,1] range.
2533                Only used when repeat is False, and edge clamping is supported by the graphics card.
2534            scale : (bool)
2535                scale the texture image by this factor
2536            ushift : (bool)
2537                shift u-coordinates of texture by this amount
2538            vshift : (bool)
2539                shift v-coordinates of texture by this amount
2540
2541        Examples:
2542            - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py)
2543
2544            ![](https://vedo.embl.es/images/basic/texturecubes.png)
2545        """
2546        pd = self.dataset
2547        out_img = None
2548
2549        if tname is None:  # disable texture
2550            pd.GetPointData().SetTCoords(None)
2551            pd.GetPointData().Modified()
2552            return self  ######################################
2553
2554        if isinstance(tname, vtki.vtkTexture):
2555            tu = tname
2556
2557        elif isinstance(tname, vedo.Image):
2558            tu = vtki.vtkTexture()
2559            out_img = tname.dataset
2560
2561        elif utils.is_sequence(tname):
2562            tu = vtki.vtkTexture()
2563            out_img = vedo.image._get_img(tname)
2564
2565        elif isinstance(tname, str):
2566            tu = vtki.vtkTexture()
2567
2568            if "https://" in tname:
2569                try:
2570                    tname = vedo.file_io.download(tname, verbose=False)
2571                except:
2572                    vedo.logger.error(f"texture {tname} could not be downloaded")
2573                    return self
2574
2575            fn = tname + ".jpg"
2576            if os.path.exists(tname):
2577                fn = tname
2578            else:
2579                vedo.logger.error(f"texture file {tname} does not exist")
2580                return self
2581
2582            fnl = fn.lower()
2583            if ".jpg" in fnl or ".jpeg" in fnl:
2584                reader = vtki.new("JPEGReader")
2585            elif ".png" in fnl:
2586                reader = vtki.new("PNGReader")
2587            elif ".bmp" in fnl:
2588                reader = vtki.new("BMPReader")
2589            else:
2590                vedo.logger.error("in texture() supported files are only PNG, BMP or JPG")
2591                return self
2592            reader.SetFileName(fn)
2593            reader.Update()
2594            out_img = reader.GetOutput()
2595
2596        else:
2597            vedo.logger.error(f"in texture() cannot understand input {type(tname)}")
2598            return self
2599
2600        if tcoords is not None:
2601
2602            if isinstance(tcoords, str):
2603                vtarr = pd.GetPointData().GetArray(tcoords)
2604
2605            else:
2606                tcoords = np.asarray(tcoords)
2607                if tcoords.ndim != 2:
2608                    vedo.logger.error("tcoords must be a 2-dimensional array")
2609                    return self
2610                if tcoords.shape[0] != pd.GetNumberOfPoints():
2611                    vedo.logger.error("nr of texture coords must match nr of points")
2612                    return self
2613                if tcoords.shape[1] != 2:
2614                    vedo.logger.error("tcoords texture vector must have 2 components")
2615                vtarr = utils.numpy2vtk(tcoords)
2616                vtarr.SetName("TCoordinates")
2617
2618            pd.GetPointData().SetTCoords(vtarr)
2619            pd.GetPointData().Modified()
2620
2621        elif not pd.GetPointData().GetTCoords():
2622
2623            # TCoords still void..
2624            # check that there are no texture-like arrays:
2625            names = self.pointdata.keys()
2626            candidate_arr = ""
2627            for name in names:
2628                vtarr = pd.GetPointData().GetArray(name)
2629                if vtarr.GetNumberOfComponents() != 2:
2630                    continue
2631                t0, t1 = vtarr.GetRange()
2632                if t0 >= 0 and t1 <= 1:
2633                    candidate_arr = name
2634
2635            if candidate_arr:
2636
2637                vtarr = pd.GetPointData().GetArray(candidate_arr)
2638                pd.GetPointData().SetTCoords(vtarr)
2639                pd.GetPointData().Modified()
2640
2641            else:
2642                # last resource is automatic mapping
2643                tmapper = vtki.new("TextureMapToPlane")
2644                tmapper.AutomaticPlaneGenerationOn()
2645                tmapper.SetInputData(pd)
2646                tmapper.Update()
2647                tc = tmapper.GetOutput().GetPointData().GetTCoords()
2648                if scale or ushift or vshift:
2649                    ntc = utils.vtk2numpy(tc)
2650                    if scale:
2651                        ntc *= scale
2652                    if ushift:
2653                        ntc[:, 0] += ushift
2654                    if vshift:
2655                        ntc[:, 1] += vshift
2656                    tc = utils.numpy2vtk(tc)
2657                pd.GetPointData().SetTCoords(tc)
2658                pd.GetPointData().Modified()
2659
2660        if out_img:
2661            tu.SetInputData(out_img)
2662        tu.SetInterpolate(interpolate)
2663        tu.SetRepeat(repeat)
2664        tu.SetEdgeClamp(edge_clamp)
2665
2666        self.properties.SetColor(1, 1, 1)
2667        self.mapper.ScalarVisibilityOff()
2668        self.actor.SetTexture(tu)
2669
2670        # if seam_threshold is not None:
2671        #     tname = self.dataset.GetPointData().GetTCoords().GetName()
2672        #     grad = self.gradient(tname)
2673        #     ugrad, vgrad = np.split(grad, 2, axis=1)
2674        #     ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad)
2675        #     gradm = np.log(ugradm + vgradm)
2676        #     largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4]
2677        #     uvmap = self.pointdata[tname]
2678        #     # collapse triangles that have large gradient
2679        #     new_points = self.points.copy()
2680        #     for f in self.cells:
2681        #         if np.isin(f, largegrad_ids).all():
2682        #             id1, id2, id3 = f
2683        #             uv1, uv2, uv3 = uvmap[f]
2684        #             d12 = utils.mag2(uv1 - uv2)
2685        #             d23 = utils.mag2(uv2 - uv3)
2686        #             d31 = utils.mag2(uv3 - uv1)
2687        #             idm = np.argmin([d12, d23, d31])
2688        #             if idm == 0:
2689        #                 new_points[id1] = new_points[id3]
2690        #                 new_points[id2] = new_points[id3]
2691        #             elif idm == 1:
2692        #                 new_points[id2] = new_points[id1]
2693        #                 new_points[id3] = new_points[id1]
2694        #     self.points = new_points
2695
2696        self.dataset.Modified()
2697        return self

Assign a texture to mesh from image file or predefined texture tname. If tname is set to None texture is disabled. Input tname can also be an array or a vtkTexture.

Arguments:
  • tname : (numpy.array, str, Image, vtkTexture, None) the input texture to be applied. Can be a numpy array, a path to an image file, a vedo Image. The None value disables texture.
  • tcoords : (numpy.array, str) this is the (u,v) texture coordinate array. Can also be a string of an existing array in the mesh.
  • interpolate : (bool) turn on/off linear interpolation of the texture map when rendering.
  • repeat : (bool) repeat of the texture when tcoords extend beyond the [0,1] range.
  • edge_clamp : (bool) turn on/off the clamping of the texture map when the texture coords extend beyond the [0,1] range. Only used when repeat is False, and edge clamping is supported by the graphics card.
  • scale : (bool) scale the texture image by this factor
  • ushift : (bool) shift u-coordinates of texture by this amount
  • vshift : (bool) shift v-coordinates of texture by this amount
Examples:

class ImageVisual(CommonVisual, Actor3DHelper):
2938class ImageVisual(CommonVisual, Actor3DHelper):
2939    """Class to manage the visual aspects of a ``Image`` object."""
2940
2941    # def __init__(self) -> None:
2942    #     # print("init ImageVisual")
2943    #     super().__init__()
2944
2945    def memory_size(self) -> int:
2946        """Return the size in bytes of the object in memory."""
2947        return self.dataset.GetActualMemorySize()
2948
2949    def scalar_range(self) -> np.ndarray:
2950        """Return the scalar range of the image."""
2951        return np.array(self.dataset.GetScalarRange())
2952
2953    def alpha(self, a=None) -> Union[Self, float]:
2954        """Set/get image's transparency in the rendering scene."""
2955        if a is not None:
2956            self.properties.SetOpacity(a)
2957            return self
2958        return self.properties.GetOpacity()
2959
2960    def level(self, value=None) -> Union[Self, float]:
2961        """Get/Set the image color level (brightness) in the rendering scene."""
2962        if value is None:
2963            return self.properties.GetColorLevel()
2964        self.properties.SetColorLevel(value)
2965        return self
2966
2967    def window(self, value=None) -> Union[Self, float]:
2968        """Get/Set the image color window (contrast) in the rendering scene."""
2969        if value is None:
2970            return self.properties.GetColorWindow()
2971        self.properties.SetColorWindow(value)
2972        return self

Class to manage the visual aspects of a Image object.

def memory_size(self) -> int:
2945    def memory_size(self) -> int:
2946        """Return the size in bytes of the object in memory."""
2947        return self.dataset.GetActualMemorySize()

Return the size in bytes of the object in memory.

def scalar_range(self) -> numpy.ndarray:
2949    def scalar_range(self) -> np.ndarray:
2950        """Return the scalar range of the image."""
2951        return np.array(self.dataset.GetScalarRange())

Return the scalar range of the image.

def alpha(self, a=None) -> Union[Self, float]:
2953    def alpha(self, a=None) -> Union[Self, float]:
2954        """Set/get image's transparency in the rendering scene."""
2955        if a is not None:
2956            self.properties.SetOpacity(a)
2957            return self
2958        return self.properties.GetOpacity()

Set/get image's transparency in the rendering scene.

def level(self, value=None) -> Union[Self, float]:
2960    def level(self, value=None) -> Union[Self, float]:
2961        """Get/Set the image color level (brightness) in the rendering scene."""
2962        if value is None:
2963            return self.properties.GetColorLevel()
2964        self.properties.SetColorLevel(value)
2965        return self

Get/Set the image color level (brightness) in the rendering scene.

def window(self, value=None) -> Union[Self, float]:
2967    def window(self, value=None) -> Union[Self, float]:
2968        """Get/Set the image color window (contrast) in the rendering scene."""
2969        if value is None:
2970            return self.properties.GetColorWindow()
2971        self.properties.SetColorWindow(value)
2972        return self

Get/Set the image color window (contrast) in the rendering scene.

class Actor2D(vtkmodules.vtkRenderingCore.vtkActor2D):
567class Actor2D(vtki.vtkActor2D):
568    """Wrapping of `vtkActor2D` class."""
569
570    def __init__(self, dataset=None):
571        """Manage 2D objects."""
572        super().__init__()
573
574        self.dataset = None
575        self.name = "Actor2D"
576        self.filename = ""
577        self.file_size = 0
578        self.pipeline = None
579        self.shape = [] # for images
580        self.coordinate = None
581
582        if dataset is not None:
583            mapper = vtki.new("PolyDataMapper2D")
584            mapper.SetInputData(dataset)
585            self.SetMapper(mapper)
586
587        self.dataset = dataset
588        self.properties = self.GetProperty()
589
590
591    @property
592    def mapper(self):
593        """Get the internal vtkMapper."""
594        return self.GetMapper()
595    
596    # not usable
597    # @property
598    # def properties(self):
599    #     """Get the internal vtkProperty."""
600    #     return self.GetProperty()
601    
602    # @properties.setter
603    # def properties(self, prop):
604    #     """Set the internal vtkProperty."""
605    #     self.SetProperty(prop)
606
607    @mapper.setter
608    def mapper(self, amapper):
609        """Set the internal vtkMapper."""
610        self.SetMapper(amapper)
611
612    def layer(self, value=None):
613        """Set/Get the layer number in the overlay planes into which to render."""
614        if value is None:
615            return self.GetLayerNumber()
616        self.SetLayerNumber(value)
617        return self
618
619    def pos(self, px=None, py=None) -> Union[np.ndarray, Self]:
620        """Set/Get the screen-coordinate position."""
621        if isinstance(px, str):
622            vedo.logger.error("Use string descriptors only inside the constructor")
623            return self
624        if px is None:
625            return np.array(self.GetPosition(), dtype=int)
626        if py is not None:
627            p = [px, py]
628        else:
629            p = px
630        assert len(p) == 2, "Error: len(pos) must be 2 for Actor2D"
631        self.SetPosition(p)
632        return self
633
634    def coordinate_system(self, value=None) -> Self:
635        """
636        Set/get the coordinate system which this coordinate is defined in.
637
638        The options are:
639            0. Display
640            1. Normalized Display
641            2. Viewport
642            3. Normalized Viewport
643            4. View
644            5. Pose
645            6. World
646        """
647        coor = self.GetPositionCoordinate()
648        if value is None:
649            return coor.GetCoordinateSystem()
650        coor.SetCoordinateSystem(value)
651        return self
652    
653    def set_position_coordinates(self, p1, p2):
654        """Set the position coordinates."""
655        self.GetPositionCoordinate().SetValue(*p1)
656        self.GetPosition2Coordinate().SetValue(*p2)
657        return self
658
659    def on(self) -> Self:
660        """Set object visibility."""
661        self.VisibilityOn()
662        return self
663
664    def off(self) -> Self:
665        """Set object visibility."""
666        self.VisibilityOn()
667        return self
668
669    def toggle(self) -> Self:
670        """Toggle object visibility."""
671        self.SetVisibility(not self.GetVisibility())
672        return self
673
674    def visibility(self, value=None) -> bool:
675        """Get/Set object visibility."""
676        if value is not None:
677            self.SetVisibility(value)
678        return self.GetVisibility()
679
680    def pickable(self, value=True) -> Self:
681        """Set object pickability."""
682        self.SetPickable(value)
683        return self
684
685    def color(self, value=None) -> Union[np.ndarray, Self]:
686        """Set/Get the object color."""
687        if value is None:
688            return self.properties.GetColor()
689        self.properties.SetColor(colors.get_color(value))
690        return self
691
692    def c(self, value=None) -> Union[np.ndarray, Self]:
693        """Set/Get the object color."""
694        return self.color(value)
695
696    def alpha(self, value=None) -> Union[float, Self]:
697        """Set/Get the object opacity."""
698        if value is None:
699            return self.properties.GetOpacity()
700        self.properties.SetOpacity(value)
701        return self
702
703    def ps(self, point_size=None) -> Union[int, Self]:
704        """Set/Get the point size of the object. Same as `point_size()`."""
705        if point_size is None:
706            return self.properties.GetPointSize()
707        self.properties.SetPointSize(point_size)
708        return self
709
710    def lw(self, line_width=None) -> Union[int, Self]:
711        """Set/Get the line width of the object. Same as `line_width()`."""
712        if line_width is None:
713            return self.properties.GetLineWidth()
714        self.properties.SetLineWidth(line_width)
715        return self
716
717    def ontop(self, value=True) -> Self:
718        """Keep the object always on top of everything else."""
719        if value:
720            self.properties.SetDisplayLocationToForeground()
721        else:
722            self.properties.SetDisplayLocationToBackground()
723        return self
724
725    def add_observer(self, event_name, func, priority=0) -> int:
726        """Add a callback function that will be called when an event occurs."""
727        event_name = utils.get_vtk_name_event(event_name)
728        idd = self.AddObserver(event_name, func, priority)
729        return idd

Wrapping of vtkActor2D class.

Actor2D(dataset=None)
570    def __init__(self, dataset=None):
571        """Manage 2D objects."""
572        super().__init__()
573
574        self.dataset = None
575        self.name = "Actor2D"
576        self.filename = ""
577        self.file_size = 0
578        self.pipeline = None
579        self.shape = [] # for images
580        self.coordinate = None
581
582        if dataset is not None:
583            mapper = vtki.new("PolyDataMapper2D")
584            mapper.SetInputData(dataset)
585            self.SetMapper(mapper)
586
587        self.dataset = dataset
588        self.properties = self.GetProperty()

Manage 2D objects.

mapper
591    @property
592    def mapper(self):
593        """Get the internal vtkMapper."""
594        return self.GetMapper()

Get the internal vtkMapper.

def layer(self, value=None):
612    def layer(self, value=None):
613        """Set/Get the layer number in the overlay planes into which to render."""
614        if value is None:
615            return self.GetLayerNumber()
616        self.SetLayerNumber(value)
617        return self

Set/Get the layer number in the overlay planes into which to render.

def pos(self, px=None, py=None) -> Union[numpy.ndarray, Self]:
619    def pos(self, px=None, py=None) -> Union[np.ndarray, Self]:
620        """Set/Get the screen-coordinate position."""
621        if isinstance(px, str):
622            vedo.logger.error("Use string descriptors only inside the constructor")
623            return self
624        if px is None:
625            return np.array(self.GetPosition(), dtype=int)
626        if py is not None:
627            p = [px, py]
628        else:
629            p = px
630        assert len(p) == 2, "Error: len(pos) must be 2 for Actor2D"
631        self.SetPosition(p)
632        return self

Set/Get the screen-coordinate position.

def coordinate_system(self, value=None) -> Self:
634    def coordinate_system(self, value=None) -> Self:
635        """
636        Set/get the coordinate system which this coordinate is defined in.
637
638        The options are:
639            0. Display
640            1. Normalized Display
641            2. Viewport
642            3. Normalized Viewport
643            4. View
644            5. Pose
645            6. World
646        """
647        coor = self.GetPositionCoordinate()
648        if value is None:
649            return coor.GetCoordinateSystem()
650        coor.SetCoordinateSystem(value)
651        return self

Set/get the coordinate system which this coordinate is defined in.

The options are:
  1. Display
  2. Normalized Display
  3. Viewport
  4. Normalized Viewport
  5. View
  6. Pose
  7. World
def set_position_coordinates(self, p1, p2):
653    def set_position_coordinates(self, p1, p2):
654        """Set the position coordinates."""
655        self.GetPositionCoordinate().SetValue(*p1)
656        self.GetPosition2Coordinate().SetValue(*p2)
657        return self

Set the position coordinates.

def on(self) -> Self:
659    def on(self) -> Self:
660        """Set object visibility."""
661        self.VisibilityOn()
662        return self

Set object visibility.

def off(self) -> Self:
664    def off(self) -> Self:
665        """Set object visibility."""
666        self.VisibilityOn()
667        return self

Set object visibility.

def toggle(self) -> Self:
669    def toggle(self) -> Self:
670        """Toggle object visibility."""
671        self.SetVisibility(not self.GetVisibility())
672        return self

Toggle object visibility.

def visibility(self, value=None) -> bool:
674    def visibility(self, value=None) -> bool:
675        """Get/Set object visibility."""
676        if value is not None:
677            self.SetVisibility(value)
678        return self.GetVisibility()

Get/Set object visibility.

def pickable(self, value=True) -> Self:
680    def pickable(self, value=True) -> Self:
681        """Set object pickability."""
682        self.SetPickable(value)
683        return self

Set object pickability.

def color(self, value=None) -> Union[numpy.ndarray, Self]:
685    def color(self, value=None) -> Union[np.ndarray, Self]:
686        """Set/Get the object color."""
687        if value is None:
688            return self.properties.GetColor()
689        self.properties.SetColor(colors.get_color(value))
690        return self

Set/Get the object color.

def c(self, value=None) -> Union[numpy.ndarray, Self]:
692    def c(self, value=None) -> Union[np.ndarray, Self]:
693        """Set/Get the object color."""
694        return self.color(value)

Set/Get the object color.

def alpha(self, value=None) -> Union[float, Self]:
696    def alpha(self, value=None) -> Union[float, Self]:
697        """Set/Get the object opacity."""
698        if value is None:
699            return self.properties.GetOpacity()
700        self.properties.SetOpacity(value)
701        return self

Set/Get the object opacity.

def ps(self, point_size=None) -> Union[int, Self]:
703    def ps(self, point_size=None) -> Union[int, Self]:
704        """Set/Get the point size of the object. Same as `point_size()`."""
705        if point_size is None:
706            return self.properties.GetPointSize()
707        self.properties.SetPointSize(point_size)
708        return self

Set/Get the point size of the object. Same as point_size().

def lw(self, line_width=None) -> Union[int, Self]:
710    def lw(self, line_width=None) -> Union[int, Self]:
711        """Set/Get the line width of the object. Same as `line_width()`."""
712        if line_width is None:
713            return self.properties.GetLineWidth()
714        self.properties.SetLineWidth(line_width)
715        return self

Set/Get the line width of the object. Same as line_width().

def ontop(self, value=True) -> Self:
717    def ontop(self, value=True) -> Self:
718        """Keep the object always on top of everything else."""
719        if value:
720            self.properties.SetDisplayLocationToForeground()
721        else:
722            self.properties.SetDisplayLocationToBackground()
723        return self

Keep the object always on top of everything else.

def add_observer(self, event_name, func, priority=0) -> int:
725    def add_observer(self, event_name, func, priority=0) -> int:
726        """Add a callback function that will be called when an event occurs."""
727        event_name = utils.get_vtk_name_event(event_name)
728        idd = self.AddObserver(event_name, func, priority)
729        return idd

Add a callback function that will be called when an event occurs.

class LightKit:
2975class LightKit:
2976    """
2977    A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'.
2978
2979    The main light is the key light. It is usually positioned so that it appears like
2980    an overhead light (like the sun, or a ceiling light).
2981    It is generally positioned to shine down on the scene from about a 45 degree angle vertically
2982    and at least a little offset side to side. The key light usually at least about twice as bright
2983    as the total of all other lights in the scene to provide good modeling of object features.
2984
2985    The other lights in the kit (the fill light, headlight, and a pair of back lights)
2986    are weaker sources that provide extra illumination to fill in the spots that the key light misses.
2987    The fill light is usually positioned across from or opposite from the key light
2988    (though still on the same side of the object as the camera) in order to simulate diffuse reflections
2989    from other objects in the scene.
2990
2991    The headlight, always located at the position of the camera, reduces the contrast between areas lit
2992    by the key and fill light. The two back lights, one on the left of the object as seen from the observer
2993    and one on the right, fill on the high-contrast areas behind the object.
2994    To enforce the relationship between the different lights, the intensity of the fill, back and headlights
2995    are set as a ratio to the key light brightness.
2996    Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity.
2997
2998    All lights are directional lights, infinitely far away with no falloff. Lights move with the camera.
2999
3000    For simplicity, the position of lights in the LightKit can only be specified using angles:
3001    the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees.
3002    For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight).
3003    A light at (elevation=90, azimuth=0) is above the lookat point, shining down.
3004    Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise.
3005    So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining
3006    slightly from the left side.
3007
3008    LightKit limits the colors that can be assigned to any light to those of incandescent sources such as
3009    light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors
3010    can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red.
3011    Colors close to 0.5 are "cool whites" and "warm whites," respectively.
3012
3013    Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight
3014    ratios are skewed by key, fill, and headlight colors. If `maintain_luminance` is set, LightKit will
3015    attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors.
3016
3017    To specify the color of a light, positioning etc you can pass a dictionary with the following keys:
3018        - `intensity` : (float) The intensity of the key light. Default is 1.
3019        - `ratio`     : (float) The ratio of the light intensity wrt key light.
3020        - `warmth`    : (float) The warmth of the light. Default is 0.5.
3021        - `elevation` : (float) The elevation of the light in degrees.
3022        - `azimuth`   : (float) The azimuth of the light in degrees.
3023
3024    Example:
3025        ```python
3026        from vedo import *
3027        lightkit = LightKit(head={"warmth":0.6})
3028        mesh = Mesh(dataurl+"bunny.obj")
3029        plt = Plotter()
3030        plt.remove_lights().add(mesh, lightkit)
3031        plt.show().close()
3032        ```
3033    """
3034    def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None:
3035
3036        self.lightkit = vtki.new("LightKit")
3037        self.lightkit.SetMaintainLuminance(maintain_luminance)
3038        self.key  = dict(key)
3039        self.head = dict(head)
3040        self.fill = dict(fill)
3041        self.back = dict(back)
3042        self.update()
3043
3044    def update(self) -> None:
3045        """Update the LightKit properties."""
3046        if "warmth" in self.key:
3047            self.lightkit.SetKeyLightWarmth(self.key["warmth"])
3048        if "warmth" in self.fill:
3049            self.lightkit.SetFillLightWarmth(self.fill["warmth"])
3050        if "warmth" in self.head:
3051            self.lightkit.SetHeadLightWarmth(self.head["warmth"])
3052        if "warmth" in self.back:
3053            self.lightkit.SetBackLightWarmth(self.back["warmth"])
3054
3055        if "intensity" in self.key:
3056            self.lightkit.SetKeyLightIntensity(self.key["intensity"])
3057        if "ratio" in self.fill:
3058            self.lightkit.SetKeyToFillRatio(self.key["ratio"])
3059        if "ratio" in self.head:
3060            self.lightkit.SetKeyToHeadRatio(self.key["ratio"])
3061        if "ratio" in self.back:
3062            self.lightkit.SetKeyToBackRatio(self.key["ratio"])
3063
3064        if "elevation" in self.key:
3065            self.lightkit.SetKeyLightElevation(self.key["elevation"])
3066        if "elevation" in self.fill:
3067            self.lightkit.SetFillLightElevation(self.fill["elevation"])
3068        if "elevation" in self.head:
3069            self.lightkit.SetHeadLightElevation(self.head["elevation"])
3070        if "elevation" in self.back:
3071            self.lightkit.SetBackLightElevation(self.back["elevation"])
3072
3073        if "azimuth" in self.key:
3074            self.lightkit.SetKeyLightAzimuth(self.key["azimuth"])
3075        if "azimuth" in self.fill:
3076            self.lightkit.SetFillLightAzimuth(self.fill["azimuth"])
3077        if "azimuth" in self.head:
3078            self.lightkit.SetHeadLightAzimuth(self.head["azimuth"])
3079        if "azimuth" in self.back:
3080            self.lightkit.SetBackLightAzimuth(self.back["azimuth"])

A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'.

The main light is the key light. It is usually positioned so that it appears like an overhead light (like the sun, or a ceiling light). It is generally positioned to shine down on the scene from about a 45 degree angle vertically and at least a little offset side to side. The key light usually at least about twice as bright as the total of all other lights in the scene to provide good modeling of object features.

The other lights in the kit (the fill light, headlight, and a pair of back lights) are weaker sources that provide extra illumination to fill in the spots that the key light misses. The fill light is usually positioned across from or opposite from the key light (though still on the same side of the object as the camera) in order to simulate diffuse reflections from other objects in the scene.

The headlight, always located at the position of the camera, reduces the contrast between areas lit by the key and fill light. The two back lights, one on the left of the object as seen from the observer and one on the right, fill on the high-contrast areas behind the object. To enforce the relationship between the different lights, the intensity of the fill, back and headlights are set as a ratio to the key light brightness. Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity.

All lights are directional lights, infinitely far away with no falloff. Lights move with the camera.

For simplicity, the position of lights in the LightKit can only be specified using angles: the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees. For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight). A light at (elevation=90, azimuth=0) is above the lookat point, shining down. Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise. So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining slightly from the left side.

LightKit limits the colors that can be assigned to any light to those of incandescent sources such as light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red. Colors close to 0.5 are "cool whites" and "warm whites," respectively.

Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight ratios are skewed by key, fill, and headlight colors. If maintain_luminance is set, LightKit will attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors.

To specify the color of a light, positioning etc you can pass a dictionary with the following keys: - intensity : (float) The intensity of the key light. Default is 1. - ratio : (float) The ratio of the light intensity wrt key light. - warmth : (float) The warmth of the light. Default is 0.5. - elevation : (float) The elevation of the light in degrees. - azimuth : (float) The azimuth of the light in degrees.

Example:
from vedo import *
lightkit = LightKit(head={"warmth":0.6})
mesh = Mesh(dataurl+"bunny.obj")
plt = Plotter()
plt.remove_lights().add(mesh, lightkit)
plt.show().close()
LightKit(key=(), fill=(), back=(), head=(), maintain_luminance=False)
3034    def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None:
3035
3036        self.lightkit = vtki.new("LightKit")
3037        self.lightkit.SetMaintainLuminance(maintain_luminance)
3038        self.key  = dict(key)
3039        self.head = dict(head)
3040        self.fill = dict(fill)
3041        self.back = dict(back)
3042        self.update()
def update(self) -> None:
3044    def update(self) -> None:
3045        """Update the LightKit properties."""
3046        if "warmth" in self.key:
3047            self.lightkit.SetKeyLightWarmth(self.key["warmth"])
3048        if "warmth" in self.fill:
3049            self.lightkit.SetFillLightWarmth(self.fill["warmth"])
3050        if "warmth" in self.head:
3051            self.lightkit.SetHeadLightWarmth(self.head["warmth"])
3052        if "warmth" in self.back:
3053            self.lightkit.SetBackLightWarmth(self.back["warmth"])
3054
3055        if "intensity" in self.key:
3056            self.lightkit.SetKeyLightIntensity(self.key["intensity"])
3057        if "ratio" in self.fill:
3058            self.lightkit.SetKeyToFillRatio(self.key["ratio"])
3059        if "ratio" in self.head:
3060            self.lightkit.SetKeyToHeadRatio(self.key["ratio"])
3061        if "ratio" in self.back:
3062            self.lightkit.SetKeyToBackRatio(self.key["ratio"])
3063
3064        if "elevation" in self.key:
3065            self.lightkit.SetKeyLightElevation(self.key["elevation"])
3066        if "elevation" in self.fill:
3067            self.lightkit.SetFillLightElevation(self.fill["elevation"])
3068        if "elevation" in self.head:
3069            self.lightkit.SetHeadLightElevation(self.head["elevation"])
3070        if "elevation" in self.back:
3071            self.lightkit.SetBackLightElevation(self.back["elevation"])
3072
3073        if "azimuth" in self.key:
3074            self.lightkit.SetKeyLightAzimuth(self.key["azimuth"])
3075        if "azimuth" in self.fill:
3076            self.lightkit.SetFillLightAzimuth(self.fill["azimuth"])
3077        if "azimuth" in self.head:
3078            self.lightkit.SetHeadLightAzimuth(self.head["azimuth"])
3079        if "azimuth" in self.back:
3080            self.lightkit.SetBackLightAzimuth(self.back["azimuth"])

Update the LightKit properties.