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

Class to manage the visual aspects common to all objects.

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

Print object info.

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

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

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

Set the range of the scalar value for visualization.

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

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

def invoke_event(self, event_name) -> Self:
119    def invoke_event(self, event_name) -> Self:
120        """Invoke an event."""
121        event_name = utils.get_vtk_name_event(event_name)
122        self.actor.InvokeEvent(event_name)
123        return self

Invoke an event.

def show(self, **options) -> Optional[vedo.plotter.Plotter]:
132    def show(self, **options) -> Union["vedo.Plotter", None]:
133        """
134        Create on the fly an instance of class `Plotter` or use the last existing one to
135        show one single object.
136
137        This method is meant as a shortcut. If more than one object needs to be visualised
138        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
139
140        Returns the `Plotter` class instance.
141        """
142        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:
144    def thumbnail(self, zoom=1.25, size=(200, 200), bg="white", azimuth=0, elevation=0, axes=False) -> np.ndarray:
145        """Build a thumbnail of the object and return it as an array."""
146        # speed is about 20Hz for size=[200,200]
147        ren = vtki.vtkRenderer()
148
149        actor = self.actor
150        if isinstance(self, vedo.UnstructuredGrid):
151            geo = vtki.new("GeometryFilter")
152            geo.SetInputData(self.dataset)
153            geo.Update()
154            actor = vedo.Mesh(geo.GetOutput()).cmap("rainbow").actor
155
156        ren.AddActor(actor)
157        if axes:
158            axes = vedo.addons.Axes(self)
159            ren.AddActor(axes.actor)
160        ren.ResetCamera()
161        cam = ren.GetActiveCamera()
162        cam.Zoom(zoom)
163        cam.Elevation(elevation)
164        cam.Azimuth(azimuth)
165
166        ren_win = vtki.vtkRenderWindow()
167        ren_win.SetOffScreenRendering(True)
168        ren_win.SetSize(size)
169        ren.SetBackground(colors.get_color(bg))
170        ren_win.AddRenderer(ren)
171        ren.ResetCameraClippingRange()
172        ren_win.Render()
173
174        nx, ny = ren_win.GetSize()
175        arr = vtki.vtkUnsignedCharArray()
176        ren_win.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr)
177        narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
178        narr = np.ascontiguousarray(np.flip(narr, axis=0))
179
180        ren.RemoveActor(actor)
181        if axes:
182            ren.RemoveActor(axes.actor)
183        ren_win.Finalize()
184        del ren_win
185        return narr

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

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

Set/get the pickability property of an object.

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

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

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

Set/get the draggability property of an object.

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

Switch on object visibility. Object is not removed.

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

Switch off object visibility. Object is not removed.

def toggle(self) -> Self:
245    def toggle(self) -> Self:
246        """Toggle object visibility on/off."""
247        v = self.actor.GetVisibility()
248        if v:
249            self.off()
250        else:
251            self.on()
252        return self

Toggle object visibility on/off.

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

Add a 2D scalar bar for the specified obj.

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

Class to manage the visual aspects of a Points object.

PointsVisual()
814    def __init__(self):
815        # print("init PointsVisual")
816        super().__init__()
817        self.properties_backface = None
818        self._cmap_name = None
819        self.trail = None
820        self.trail_offset = 0
821        self.trail_points = []
822        self._caption = None
def clone2d(self, size=None, offset=(), scale=None):
825    def clone2d(self, size=None, offset=(), scale=None):
826        """
827        Turn a 3D `Points` or `Mesh` into a flat 2D actor.
828        Returns a `Actor2D`.
829
830        Arguments:
831            size : (float)
832                size as scaling factor for the 2D actor
833            offset : (list)
834                2D (x, y) position of the actor in the range [-1, 1]
835            scale : (float)
836                Deprecated. Use `size` instead.
837
838        Examples:
839            - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py)
840
841                ![](https://vedo.embl.es/images/other/clone2d.png)
842        """
843        # assembly.Assembly.clone2d() superseeds this method
844        if scale is not None:
845            vedo.logger.warning("clone2d(): use keyword size not scale")
846            size = scale
847
848        if size is None:
849            # work out a reasonable scale
850            msiz = self.diagonal_size()
851            if vedo.plotter_instance and vedo.plotter_instance.window:
852                sz = vedo.plotter_instance.window.GetSize()
853                dsiz = utils.mag(sz)
854                size = dsiz / msiz / 10
855            else:
856                size = 350 / msiz
857
858        tp = vtki.new("TransformPolyDataFilter")
859        transform = vtki.vtkTransform()
860        transform.Scale(size, size, size)
861        if len(offset) == 0:
862            offset = self.pos()
863        transform.Translate(-utils.make3d(offset))
864        tp.SetTransform(transform)
865        tp.SetInputData(self.dataset)
866        tp.Update()
867        poly = tp.GetOutput()
868
869        cm = self.mapper.GetColorMode()
870        lut = self.mapper.GetLookupTable()
871        sv = self.mapper.GetScalarVisibility()
872        use_lut = self.mapper.GetUseLookupTableScalarRange()
873        vrange = self.mapper.GetScalarRange()
874        sm = self.mapper.GetScalarMode()
875
876        act2d = Actor2D()
877        act2d.dataset = poly
878        mapper2d = vtki.new("PolyDataMapper2D")
879        mapper2d.SetInputData(poly)
880        mapper2d.SetColorMode(cm)
881        mapper2d.SetLookupTable(lut)
882        mapper2d.SetScalarVisibility(sv)
883        mapper2d.SetUseLookupTableScalarRange(use_lut)
884        mapper2d.SetScalarRange(vrange)
885        mapper2d.SetScalarMode(sm)
886        act2d.mapper = mapper2d
887
888        act2d.GetPositionCoordinate().SetCoordinateSystem(4)
889        act2d.properties.SetColor(self.color())
890        act2d.properties.SetOpacity(self.alpha())
891        act2d.properties.SetLineWidth(self.properties.GetLineWidth())
892        act2d.properties.SetPointSize(self.properties.GetPointSize())
893        act2d.properties.SetDisplayLocation(0) # 0 = back, 1 = front
894        act2d.PickableOff()
895        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:
898    def copy_properties_from(self, source, deep=True, actor_related=True) -> Self:
899        """
900        Copy properties from another ``Points`` object.
901        """
902        pr = vtki.vtkProperty()
903        try:
904            sp = source.properties
905            mp = source.mapper
906            sa = source.actor
907        except AttributeError:
908            sp = source.GetProperty()
909            mp = source.GetMapper()
910            sa = source
911            
912        if deep:
913            pr.DeepCopy(sp)
914        else:
915            pr.ShallowCopy(sp)
916        self.actor.SetProperty(pr)
917        self.properties = pr
918
919        if self.actor.GetBackfaceProperty():
920            bfpr = vtki.vtkProperty()
921            bfpr.DeepCopy(sa.GetBackfaceProperty())
922            self.actor.SetBackfaceProperty(bfpr)
923            self.properties_backface = bfpr
924
925        if not actor_related:
926            return self
927
928        # mapper related:
929        self.mapper.SetScalarVisibility(mp.GetScalarVisibility())
930        self.mapper.SetScalarMode(mp.GetScalarMode())
931        self.mapper.SetScalarRange(mp.GetScalarRange())
932        self.mapper.SetLookupTable(mp.GetLookupTable())
933        self.mapper.SetColorMode(mp.GetColorMode())
934        self.mapper.SetInterpolateScalarsBeforeMapping(
935            mp.GetInterpolateScalarsBeforeMapping()
936        )
937        self.mapper.SetUseLookupTableScalarRange(
938            mp.GetUseLookupTableScalarRange()
939        )
940
941        self.actor.SetPickable(sa.GetPickable())
942        self.actor.SetDragable(sa.GetDragable())
943        self.actor.SetTexture(sa.GetTexture())
944        self.actor.SetVisibility(sa.GetVisibility())
945        return self

Copy properties from another Points object.

def color(self, c=False, alpha=None) -> Union[numpy.ndarray, Self]:
947    def color(self, c=False, alpha=None) -> Union[np.ndarray, Self]:
948        """
949        Set/get mesh's color.
950        If None is passed as input, will use colors from active scalars.
951        Same as `mesh.c()`.
952        """
953        if c is False:
954            return np.array(self.properties.GetColor())
955        if c is None:
956            self.mapper.ScalarVisibilityOn()
957            return self
958        self.mapper.ScalarVisibilityOff()
959        cc = colors.get_color(c)
960        self.properties.SetColor(cc)
961        if self.trail:
962            self.trail.properties.SetColor(cc)
963        if alpha is not None:
964            self.alpha(alpha)
965        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]:
967    def c(self, color=False, alpha=None) -> Union[np.ndarray, Self]:
968        """
969        Shortcut for `color()`.
970        If None is passed as input, will use colors from current active scalars.
971        """
972        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]:
974    def alpha(self, opacity=None) -> Union[float, Self]:
975        """Set/get mesh's transparency. Same as `mesh.opacity()`."""
976        if opacity is None:
977            return self.properties.GetOpacity()
978
979        self.properties.SetOpacity(opacity)
980        bfp = self.actor.GetBackfaceProperty()
981        if bfp:
982            if opacity < 1:
983                self.properties_backface = bfp
984                self.actor.SetBackfaceProperty(None)
985            else:
986                self.actor.SetBackfaceProperty(self.properties_backface)
987        return self

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

def lut_color_at(self, value) -> numpy.ndarray:
989    def lut_color_at(self, value) -> np.ndarray:
990        """
991        Return the color and alpha in the lookup table at given value.
992        """
993        lut = self.mapper.GetLookupTable()
994        if not lut:
995            return None
996        rgb = [0,0,0]
997        lut.GetColor(value, rgb)
998        alpha = lut.GetOpacity(value)
999        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]:
1001    def opacity(self, alpha=None) -> Union[float, Self]:
1002        """Set/get mesh's transparency. Same as `mesh.alpha()`."""
1003        return self.alpha(alpha)

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

def force_opaque(self, value=True) -> Self:
1005    def force_opaque(self, value=True) -> Self:
1006        """ Force the Mesh, Line or point cloud to be treated as opaque"""
1007        ## force the opaque pass, fixes picking in vtk9
1008        # but causes other bad troubles with lines..
1009        self.actor.SetForceOpaque(value)
1010        return self

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

def force_translucent(self, value=True) -> Self:
1012    def force_translucent(self, value=True) -> Self:
1013        """ Force the Mesh, Line or point cloud to be treated as translucent"""
1014        self.actor.SetForceTranslucent(value)
1015        return self

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

def point_size(self, value=None) -> Union[int, Self]:
1017    def point_size(self, value=None) -> Union[int, Self]:
1018        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
1019        if value is None:
1020            return self.properties.GetPointSize()
1021            # self.properties.SetRepresentationToSurface()
1022        else:
1023            self.properties.SetRepresentationToPoints()
1024            self.properties.SetPointSize(value)
1025        return self

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

def ps(self, pointsize=None) -> Union[int, Self]:
1027    def ps(self, pointsize=None) -> Union[int, Self]:
1028        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
1029        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:
1031    def render_points_as_spheres(self, value=True) -> Self:
1032        """Make points look spheric or else make them look as squares."""
1033        self.properties.SetRenderPointsAsSpheres(value)
1034        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:
1036    def lighting(
1037        self,
1038        style="",
1039        ambient=None,
1040        diffuse=None,
1041        specular=None,
1042        specular_power=None,
1043        specular_color=None,
1044        metallicity=None,
1045        roughness=None,
1046    ) -> Self:
1047        """
1048        Set the ambient, diffuse, specular and specular_power lighting constants.
1049
1050        Arguments:
1051            style : (str)
1052                preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]`
1053            ambient : (float)
1054                ambient fraction of emission [0-1]
1055            diffuse : (float)
1056                emission of diffused light in fraction [0-1]
1057            specular : (float)
1058                fraction of reflected light [0-1]
1059            specular_power : (float)
1060                precision of reflection [1-100]
1061            specular_color : (color)
1062                color that is being reflected by the surface
1063
1064        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px>
1065
1066        Examples:
1067            - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py)
1068        """
1069        pr = self.properties
1070
1071        if style:
1072
1073            if style != "off":
1074                pr.LightingOn()
1075
1076            if style == "off":
1077                pr.SetInterpolationToFlat()
1078                pr.LightingOff()
1079                return self  ##############
1080
1081            if hasattr(pr, "GetColor"):  # could be Volume
1082                c = pr.GetColor()
1083            else:
1084                c = (1, 1, 0.99)
1085            mpr = self.mapper
1086            if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility():
1087                c = (1,1,0.99)
1088            if   style=='metallic': pars = [0.1, 0.3, 1.0, 10, c]
1089            elif style=='plastic' : pars = [0.3, 0.4, 0.3,  5, c]
1090            elif style=='shiny'   : pars = [0.2, 0.6, 0.8, 50, c]
1091            elif style=='glossy'  : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)]
1092            elif style=='ambient' : pars = [0.8, 0.1, 0.0,  1, (1,1,1)]
1093            elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c]
1094            else:
1095                vedo.logger.error("in lighting(): Available styles are")
1096                vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]")
1097                raise RuntimeError()
1098            pr.SetAmbient(pars[0])
1099            pr.SetDiffuse(pars[1])
1100            pr.SetSpecular(pars[2])
1101            pr.SetSpecularPower(pars[3])
1102            if hasattr(pr, "GetColor"):
1103                pr.SetSpecularColor(pars[4])
1104
1105        if ambient is not None: pr.SetAmbient(ambient)
1106        if diffuse is not None: pr.SetDiffuse(diffuse)
1107        if specular is not None: pr.SetSpecular(specular)
1108        if specular_power is not None: pr.SetSpecularPower(specular_power)
1109        if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color))
1110        if metallicity is not None:
1111            pr.SetInterpolationToPBR()
1112            pr.SetMetallic(metallicity)
1113        if roughness is not None:
1114            pr.SetInterpolationToPBR()
1115            pr.SetRoughness(roughness)
1116
1117        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:
1119    def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self:
1120        """Set point blurring.
1121        Apply a gaussian convolution filter to the points.
1122        In this case the radius `r` is in absolute units of the mesh coordinates.
1123        With emissive set, the halo of point becomes light-emissive.
1124        """
1125        self.properties.SetRepresentationToPoints()
1126        if emissive:
1127            self.mapper.SetEmissive(bool(emissive))
1128        self.mapper.SetScaleFactor(r * 1.4142)
1129
1130        # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
1131        if alpha < 1:
1132            self.mapper.SetSplatShaderCode(
1133                "//VTK::Color::Impl\n"
1134                "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
1135                "if (dist > 1.0) {\n"
1136                "   discard;\n"
1137                "} else {\n"
1138                f"  float scale = ({alpha} - dist);\n"
1139                "   ambientColor *= scale;\n"
1140                "   diffuseColor *= scale;\n"
1141                "}\n"
1142            )
1143            alpha = 1
1144
1145        self.mapper.Modified()
1146        self.actor.Modified()
1147        self.properties.SetOpacity(alpha)
1148        self.actor.SetMapper(self.mapper)
1149        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
1151    @property
1152    def cellcolors(self):
1153        """
1154        Colorize each cell (face) of a mesh by passing
1155        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1156        Colors levels and opacities must be in the range [0,255].
1157
1158        A single constant color can also be passed as string or RGBA.
1159
1160        A cell array named "CellsRGBA" is automatically created.
1161
1162        Examples:
1163            - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py)
1164            - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py)
1165
1166            ![](https://vedo.embl.es/images/basic/colorMeshCells.png)
1167        """
1168        if "CellsRGBA" not in self.celldata.keys():
1169            lut = self.mapper.GetLookupTable()
1170            vscalars = self.dataset.GetCellData().GetScalars()
1171            if vscalars is None or lut is None:
1172                arr = np.zeros([self.ncells, 4], dtype=np.uint8)
1173                col = np.array(self.properties.GetColor())
1174                col = np.round(col * 255).astype(np.uint8)
1175                alf = self.properties.GetOpacity()
1176                alf = np.round(alf * 255).astype(np.uint8)
1177                arr[:, (0, 1, 2)] = col
1178                arr[:, 3] = alf
1179            else:
1180                cols = lut.MapScalars(vscalars, 0, 0)
1181                arr = utils.vtk2numpy(cols)
1182            self.celldata["CellsRGBA"] = arr
1183        self.celldata.select("CellsRGBA")
1184        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
1209    @property
1210    def pointcolors(self):
1211        """
1212        Colorize each point (or vertex 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 point array named "PointsRGBA" is automatically created.
1219        """
1220        if "PointsRGBA" not in self.pointdata.keys():
1221            lut = self.mapper.GetLookupTable()
1222            vscalars = self.dataset.GetPointData().GetScalars()
1223            if vscalars is None or lut is None:
1224                # create a constant array
1225                arr = np.zeros([self.npoints, 4], dtype=np.uint8)
1226                col = np.array(self.properties.GetColor())
1227                col = np.round(col * 255).astype(np.uint8)
1228                alf = self.properties.GetOpacity()
1229                alf = np.round(alf * 255).astype(np.uint8)
1230                arr[:, (0, 1, 2)] = col
1231                arr[:, 3] = alf
1232            else:
1233                cols = lut.MapScalars(vscalars, 0, 0)
1234                arr = utils.vtk2numpy(cols)
1235            self.pointdata["PointsRGBA"] = arr
1236        self.pointdata.select("PointsRGBA")
1237        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:
1263    def cmap(
1264        self,
1265        input_cmap,
1266        input_array=None,
1267        on="",
1268        name="Scalars",
1269        vmin=None,
1270        vmax=None,
1271        n_colors=256,
1272        alpha=1.0,
1273        logscale=False,
1274    ) -> Self:
1275        """
1276        Set individual point/cell colors by providing a list of scalar values and a color map.
1277
1278        Arguments:
1279            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
1280                color map scheme to transform a real number into a color.
1281            input_array : (str, list, vtkArray)
1282                can be the string name of an existing array, a new array or a `vtkArray`.
1283            on : (str)
1284                either 'points' or 'cells' or blank (automatic).
1285                Apply the color map to data which is defined on either points or cells.
1286            name : (str)
1287                give a name to the provided array (if input_array is an array)
1288            vmin : (float)
1289                clip scalars to this minimum value
1290            vmax : (float)
1291                clip scalars to this maximum value
1292            n_colors : (int)
1293                number of distinct colors to be used in colormap table.
1294            alpha : (float, list)
1295                Mesh transparency. Can be a `list` of values one for each vertex.
1296            logscale : (bool)
1297                Use logscale
1298
1299        Examples:
1300            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
1301            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
1302            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
1303            - (and many others)
1304
1305                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
1306        """
1307        self._cmap_name = input_cmap
1308
1309        if on == "":
1310            try:
1311                on = self.mapper.GetScalarModeAsString().replace("Use", "")
1312                if on not in ["PointData", "CellData"]: # can be "Default"
1313                    on = "points"
1314                    self.mapper.SetScalarModeToUsePointData()
1315            except AttributeError:
1316                on = "points"
1317        elif on == "Default":
1318            on = "points"
1319            self.mapper.SetScalarModeToUsePointData()
1320
1321        if input_array is None:
1322            if not self.pointdata.keys() and self.celldata.keys():
1323                on = "cells"
1324                if not self.dataset.GetCellData().GetScalars():
1325                    input_array = 0  # pick the first at hand
1326
1327        if "point" in on.lower():
1328            data = self.dataset.GetPointData()
1329            n = self.dataset.GetNumberOfPoints()
1330        elif "cell" in on.lower():
1331            data = self.dataset.GetCellData()
1332            n = self.dataset.GetNumberOfCells()
1333        else:
1334            vedo.logger.error(
1335                f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}")
1336            raise RuntimeError()
1337
1338        if input_array is None:  # if None try to fetch the active scalars
1339            arr = data.GetScalars()
1340            if not arr:
1341                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
1342                return self
1343
1344            if not arr.GetName():  # sometimes arrays dont have a name..
1345                arr.SetName(name)
1346
1347        elif isinstance(input_array, str):  # if a string is passed
1348            arr = data.GetArray(input_array)
1349            if not arr:
1350                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
1351                return self
1352
1353        elif isinstance(input_array, int):  # if an int is passed
1354            if input_array < data.GetNumberOfArrays():
1355                arr = data.GetArray(input_array)
1356            else:
1357                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
1358                return self
1359
1360        elif utils.is_sequence(input_array):  # if a numpy array is passed
1361            npts = len(input_array)
1362            if npts != n:
1363                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
1364                return self
1365            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
1366            data.AddArray(arr)
1367            data.Modified()
1368
1369        elif isinstance(input_array, vtki.vtkArray):  # if a vtkArray is passed
1370            arr = input_array
1371            data.AddArray(arr)
1372            data.Modified()
1373
1374        else:
1375            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
1376            raise RuntimeError()
1377
1378        # Now we have array "arr"
1379        array_name = arr.GetName()
1380
1381        if arr.GetNumberOfComponents() == 1:
1382            if vmin is None:
1383                vmin = arr.GetRange()[0]
1384            if vmax is None:
1385                vmax = arr.GetRange()[1]
1386        else:
1387            if vmin is None or vmax is None:
1388                vn = utils.mag(utils.vtk2numpy(arr))
1389            if vmin is None:
1390                vmin = vn.min()
1391            if vmax is None:
1392                vmax = vn.max()
1393
1394        # interpolate alphas if they are not constant
1395        if not utils.is_sequence(alpha):
1396            alpha = [alpha] * n_colors
1397        else:
1398            v = np.linspace(0, 1, n_colors, endpoint=True)
1399            xp = np.linspace(0, 1, len(alpha), endpoint=True)
1400            alpha = np.interp(v, xp, alpha)
1401
1402        ########################### build the look-up table
1403        if isinstance(input_cmap, vtki.vtkLookupTable):  # vtkLookupTable
1404            lut = input_cmap
1405
1406        elif utils.is_sequence(input_cmap):  # manual sequence of colors
1407            lut = vtki.vtkLookupTable()
1408            if logscale:
1409                lut.SetScaleToLog10()
1410            lut.SetRange(vmin, vmax)
1411            ncols = len(input_cmap)
1412            lut.SetNumberOfTableValues(ncols)
1413
1414            for i, c in enumerate(input_cmap):
1415                r, g, b = colors.get_color(c)
1416                lut.SetTableValue(i, r, g, b, alpha[i])
1417            lut.Build()
1418
1419        else:  
1420            # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
1421            lut = vtki.vtkLookupTable()
1422            if logscale:
1423                lut.SetScaleToLog10()
1424            lut.SetVectorModeToMagnitude()
1425            lut.SetRange(vmin, vmax)
1426            lut.SetNumberOfTableValues(n_colors)
1427            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
1428            for i, c in enumerate(mycols):
1429                r, g, b = c
1430                lut.SetTableValue(i, r, g, b, alpha[i])
1431            lut.Build()
1432
1433        # TEST NEW WAY
1434        self.mapper.SetLookupTable(lut)
1435        self.mapper.ScalarVisibilityOn()
1436        self.mapper.SetColorModeToMapScalars()
1437        self.mapper.SetScalarRange(lut.GetRange())
1438        if "point" in on.lower():
1439            self.pointdata.select(array_name)
1440        else:
1441            self.celldata.select(array_name)
1442        return self
1443
1444        # # TEST this is the old way:
1445        # # arr.SetLookupTable(lut) # wrong! causes weird instabilities with LUT
1446        # # if data.GetScalars():
1447        # #     data.GetScalars().SetLookupTable(lut)
1448        # #     data.GetScalars().Modified()
1449
1450        # data.SetActiveScalars(array_name)
1451        # # data.SetScalars(arr)  # wrong! it deletes array in position 0, never use SetScalars
1452        # # data.SetActiveAttribute(array_name, 0) # boh!
1453
1454        # self.mapper.SetLookupTable(lut)
1455        # self.mapper.SetColorModeToMapScalars()  # so we dont need to convert uint8 scalars
1456
1457        # self.mapper.ScalarVisibilityOn()
1458        # self.mapper.SetScalarRange(lut.GetRange())
1459
1460        # if on.startswith("point"):
1461        #     self.mapper.SetScalarModeToUsePointData()
1462        # else:
1463        #     self.mapper.SetScalarModeToUseCellData()
1464        # if hasattr(self.mapper, "SetArrayName"):
1465        #     self.mapper.SetArrayName(array_name)
1466        # 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:
1468    def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self:
1469        """
1470        Add a trailing line to mesh.
1471        This new mesh is accessible through `mesh.trail`.
1472
1473        Arguments:
1474            offset : (float)
1475                set an offset vector from the object center.
1476            n : (int)
1477                number of segments
1478            lw : (float)
1479                line width of the trail
1480
1481        Examples:
1482            - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py)
1483
1484                ![](https://vedo.embl.es/images/simulations/trail.gif)
1485
1486            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1487            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1488        """
1489        if self.trail is None:
1490            pos = self.pos()
1491            self.trail_offset = np.asarray(offset)
1492            self.trail_points = [pos] * n
1493
1494            if c is None:
1495                col = self.properties.GetColor()
1496            else:
1497                col = colors.get_color(c)
1498
1499            tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw)
1500            self.trail = tline  # holds the Line
1501        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:
1503    def update_trail(self) -> Self:
1504        """
1505        Update the trailing line of a moving object.
1506        """
1507        currentpos = self.pos()
1508        self.trail_points.append(currentpos)  # cycle
1509        self.trail_points.pop(0)
1510        data = np.array(self.trail_points) + self.trail_offset
1511        tpoly = self.trail.dataset
1512        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1513        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:
1550    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1551        """
1552        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1553        The output is a new `Mesh` representing the shadow.
1554        This new mesh is accessible through `mesh.shadow`.
1555        By default the shadow mesh is placed on the bottom wall of the bounding box.
1556
1557        See also `pointcloud.project_on_plane()`.
1558
1559        Arguments:
1560            plane : (str, Plane)
1561                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1562                represents x-plane, y-plane and z-plane, respectively.
1563                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1564            point : (float, array)
1565                if plane is `str`, point should be a float represents the intercept.
1566                Otherwise, point is the camera point of perspective projection
1567            direction : (list)
1568                direction of oblique projection
1569            culling : (int)
1570                choose between front [1] or backface [-1] culling or None.
1571
1572        Examples:
1573            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1574            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1575            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1576
1577            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1578        """
1579        shad = self._compute_shadow(plane, point, direction)
1580        shad.c(c).alpha(alpha)
1581
1582        try:
1583            # Points dont have these methods
1584            shad.flat()
1585            if culling in (1, True):
1586                shad.frontface_culling()
1587            elif culling == -1:
1588                shad.backface_culling()
1589        except AttributeError:
1590            pass
1591
1592        shad.properties.LightingOff()
1593        shad.actor.SetPickable(False)
1594        shad.actor.SetUseBounds(True)
1595
1596        if shad not in self.shadows:
1597            self.shadows.append(shad)
1598            shad.info = dict(plane=plane, point=point, direction=direction)
1599            # shad.metadata["plane"] = plane
1600            # shad.metadata["point"] = point
1601            # print("AAAA", direction, plane, point)
1602            # if direction is None:
1603            #     direction = [0,0,0]
1604            # shad.metadata["direction"] = direction
1605        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:
1607    def update_shadows(self) -> Self:
1608        """Update the shadows of a moving object."""
1609        for sha in self.shadows:
1610            plane = sha.info["plane"]
1611            point = sha.info["point"]
1612            direction = sha.info["direction"]
1613            # print("update_shadows direction", direction,plane,point )
1614            # plane = sha.metadata["plane"]
1615            # point = sha.metadata["point"]
1616            # direction = sha.metadata["direction"]
1617            # if direction[0]==0 and direction[1]==0 and direction[2]==0:
1618            #     direction = None
1619            # print("BBBB", sha.metadata["direction"], 
1620            #       sha.metadata["plane"], sha.metadata["point"])
1621            new_sha = self._compute_shadow(plane, point, direction)
1622            sha._update(new_sha.dataset)
1623        if self.trail:
1624            self.trail.update_shadows()
1625        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]:
1627    def labels(
1628        self,
1629        content=None,
1630        on="points",
1631        scale=None,
1632        xrot=0.0,
1633        yrot=0.0,
1634        zrot=0.0,
1635        ratio=1,
1636        precision=None,
1637        italic=False,
1638        font="",
1639        justify="",
1640        c="black",
1641        alpha=1.0,
1642    ) -> Union["vedo.Mesh", None]:
1643        """
1644        Generate value or ID labels for mesh cells or points.
1645        For large nr. of labels use `font="VTK"` which is much faster.
1646
1647        See also:
1648            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1649
1650        Arguments:
1651            content : (list,int,str)
1652                either 'id', 'cellid', array name or array number.
1653                A array can also be passed (must match the nr. of points or cells).
1654            on : (str)
1655                generate labels for "cells" instead of "points"
1656            scale : (float)
1657                absolute size of labels, if left as None it is automatic
1658            zrot : (float)
1659                local rotation angle of label in degrees
1660            ratio : (int)
1661                skipping ratio, to reduce nr of labels for large meshes
1662            precision : (int)
1663                numeric precision of labels
1664
1665        ```python
1666        from vedo import *
1667        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1668        point_ids = s.labels('id', on="points").c('green')
1669        cell_ids  = s.labels('id', on="cells" ).c('black')
1670        show(s, point_ids, cell_ids)
1671        ```
1672        ![](https://vedo.embl.es/images/feats/labels.png)
1673
1674        Examples:
1675            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1676
1677                ![](https://vedo.embl.es/images/basic/boundaries.png)
1678        """
1679        
1680        cells = False
1681        if "cell" in on or "face" in on:
1682            cells = True
1683            justify = "centered" if justify == "" else justify
1684
1685        if isinstance(content, str):
1686            if content in ("pointid", "pointsid"):
1687                cells = False
1688                content = "id"
1689                justify = "bottom-left" if justify == "" else justify
1690            if content in ("cellid", "cellsid"):
1691                cells = True
1692                content = "id"
1693                justify = "centered" if justify == "" else justify
1694
1695        try:
1696            if cells:
1697                elems = self.cell_centers
1698                norms = self.cell_normals
1699                ns = np.sqrt(self.ncells)
1700                justify = "centered" if justify == "" else justify
1701            else:
1702                elems = self.vertices
1703                norms = self.vertex_normals
1704                ns = np.sqrt(self.npoints)
1705        except AttributeError:
1706            norms = []
1707        
1708        if not justify:
1709            justify = "bottom-left"
1710
1711        hasnorms = False
1712        if len(norms) > 0:
1713            hasnorms = True
1714
1715        if scale is None:
1716            if not ns:
1717                ns = 100
1718            scale = self.diagonal_size() / ns / 10
1719
1720        arr = None
1721        mode = 0
1722        if content is None:
1723            mode = 0
1724            if cells:
1725                if self.dataset.GetCellData().GetScalars():
1726                    name = self.dataset.GetCellData().GetScalars().GetName()
1727                    arr = self.celldata[name]
1728            else:
1729                if self.dataset.GetPointData().GetScalars():
1730                    name = self.dataset.GetPointData().GetScalars().GetName()
1731                    arr = self.pointdata[name]
1732        elif isinstance(content, (str, int)):
1733            if content == "id":
1734                mode = 1
1735            elif cells:
1736                mode = 0
1737                arr = self.celldata[content]
1738            else:
1739                mode = 0
1740                arr = self.pointdata[content]
1741        elif utils.is_sequence(content):
1742            mode = 0
1743            arr = content
1744
1745        if arr is None and mode == 0:
1746            vedo.logger.error("in labels(), array not found in point or cell data")
1747            return None
1748
1749        ratio = int(ratio+0.5)
1750        tapp = vtki.new("AppendPolyData")
1751        has_inputs = False
1752
1753        for i, e in enumerate(elems):
1754            if i % ratio:
1755                continue
1756
1757            if mode == 1:
1758                txt_lab = str(i)
1759            else:
1760                if precision:
1761                    txt_lab = utils.precision(arr[i], precision)
1762                else:
1763                    txt_lab = str(arr[i])
1764
1765            if not txt_lab:
1766                continue
1767
1768            if font == "VTK":
1769                tx = vtki.new("VectorText")
1770                tx.SetText(txt_lab)
1771                tx.Update()
1772                tx_poly = tx.GetOutput()
1773            else:
1774                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset
1775
1776            if tx_poly.GetNumberOfPoints() == 0:
1777                continue  ######################
1778
1779            T = vtki.vtkTransform()
1780            T.PostMultiply()
1781            if italic:
1782                T.Concatenate([1, 0.2, 0, 0,
1783                               0, 1  , 0, 0,
1784                               0, 0  , 1, 0,
1785                               0, 0  , 0, 1])
1786            if hasnorms:
1787                ni = norms[i]
1788                if cells and font=="VTK":  # center-justify
1789                    bb = tx_poly.GetBounds()
1790                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1791                    T.Translate(-dx, -dy, 0)
1792                if xrot: T.RotateX(xrot)
1793                if yrot: T.RotateY(yrot)
1794                if zrot: T.RotateZ(zrot)
1795                crossvec = np.cross([0, 0, 1], ni)
1796                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1797                T.RotateWXYZ(float(angle), crossvec.tolist())
1798                T.Translate(ni / 100)
1799            else:
1800                if xrot: T.RotateX(xrot)
1801                if yrot: T.RotateY(yrot)
1802                if zrot: T.RotateZ(zrot)
1803            T.Scale(scale, scale, scale)
1804            T.Translate(e)
1805            tf = vtki.new("TransformPolyDataFilter")
1806            tf.SetInputData(tx_poly)
1807            tf.SetTransform(T)
1808            tf.Update()
1809            tapp.AddInputData(tf.GetOutput())
1810            has_inputs = True
1811
1812        if has_inputs:
1813            tapp.Update()
1814            lpoly = tapp.GetOutput()
1815        else:
1816            lpoly = vtki.vtkPolyData()
1817        ids = vedo.Mesh(lpoly, c=c, alpha=alpha)
1818        ids.properties.LightingOff()
1819        ids.actor.PickableOff()
1820        ids.actor.SetUseBounds(False)
1821        ids.name = "Labels"
1822        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]:
1824    def labels2d(
1825        self,
1826        content="id",
1827        on="points",
1828        scale=1.0,
1829        precision=4,
1830        font="Calco",
1831        justify="bottom-left",
1832        angle=0.0,
1833        frame=False,
1834        c="black",
1835        bc=None,
1836        alpha=1.0,
1837    ) -> Union["Actor2D", None]:
1838        """
1839        Generate value or ID bi-dimensional labels for mesh cells or points.
1840
1841        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
1842
1843        Arguments:
1844            content : (str)
1845                either 'id', 'cellid', or array name
1846            on : (str)
1847                generate labels for "cells" instead of "points" (the default)
1848            scale : (float)
1849                size scaling of labels
1850            precision : (int)
1851                precision of numeric labels
1852            angle : (float)
1853                local rotation angle of label in degrees
1854            frame : (bool)
1855                draw a frame around the label
1856            bc : (str)
1857                background color of the label
1858
1859        ```python
1860        from vedo import Sphere, show
1861        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
1862        sph.celldata["zvals"] = sph.cell_centers[:,2]
1863        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
1864        show(sph, l2d, axes=1).close()
1865        ```
1866        ![](https://vedo.embl.es/images/feats/labels2d.png)
1867        """
1868        cells = False
1869        if "cell" in on:
1870            cells = True
1871
1872        if isinstance(content, str):
1873            if content in ("id", "pointid", "pointsid"):
1874                cells = False
1875                content = "id"
1876            if content in ("cellid", "cellsid"):
1877                cells = True
1878                content = "id"
1879
1880        if cells:
1881            if content != "id" and content not in self.celldata.keys():
1882                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
1883                return None
1884            cellcloud = vedo.Points(self.cell_centers)
1885            arr = self.dataset.GetCellData().GetScalars()
1886            poly = cellcloud.dataset
1887            poly.GetPointData().SetScalars(arr)
1888        else:
1889            poly = self.dataset
1890            if content != "id" and content not in self.pointdata.keys():
1891                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
1892                return None
1893
1894        mp = vtki.new("LabeledDataMapper")
1895
1896        if content == "id":
1897            mp.SetLabelModeToLabelIds()
1898        else:
1899            mp.SetLabelModeToLabelScalars()
1900            if precision is not None:
1901                mp.SetLabelFormat(f"%-#.{precision}g")
1902
1903        pr = mp.GetLabelTextProperty()
1904        c = colors.get_color(c)
1905        pr.SetColor(c)
1906        pr.SetOpacity(alpha)
1907        pr.SetFrame(frame)
1908        pr.SetFrameColor(c)
1909        pr.SetItalic(False)
1910        pr.BoldOff()
1911        pr.ShadowOff()
1912        pr.UseTightBoundingBoxOn()
1913        pr.SetOrientation(angle)
1914        pr.SetFontFamily(vtki.VTK_FONT_FILE)
1915        fl = utils.get_font_path(font)
1916        pr.SetFontFile(fl)
1917        pr.SetFontSize(int(20 * scale))
1918
1919        if "cent" in justify or "mid" in justify:
1920            pr.SetJustificationToCentered()
1921        elif "rig" in justify:
1922            pr.SetJustificationToRight()
1923        elif "left" in justify:
1924            pr.SetJustificationToLeft()
1925        # ------
1926        if "top" in justify:
1927            pr.SetVerticalJustificationToTop()
1928        else:
1929            pr.SetVerticalJustificationToBottom()
1930
1931        if bc is not None:
1932            bc = colors.get_color(bc)
1933            pr.SetBackgroundColor(bc)
1934            pr.SetBackgroundOpacity(alpha)
1935
1936        mp.SetInputData(poly)
1937        a2d = Actor2D()
1938        a2d.PickableOff()
1939        a2d.SetMapper(mp)
1940        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[:,2]
l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
show(sph, l2d, axes=1).close()

def legend(self, txt) -> Self:
1942    def legend(self, txt) -> Self:
1943        """Book a legend text."""
1944        self.info["legend"] = txt
1945        # self.metadata["legend"] = txt
1946        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]:
1948    def flagpole(
1949        self,
1950        txt=None,
1951        point=None,
1952        offset=None,
1953        s=None,
1954        font="Calco",
1955        rounded=True,
1956        c=None,
1957        alpha=1.0,
1958        lw=2,
1959        italic=0.0,
1960        padding=0.1,
1961    ) -> Union["vedo.Mesh", None]:
1962        """
1963        Generate a flag pole style element to describe an object.
1964        Returns a `Mesh` object.
1965
1966        Use flagpole.follow_camera() to make it face the camera in the scene.
1967
1968        Consider using `settings.use_parallel_projection = True` 
1969        to avoid perspective distortions.
1970
1971        See also `flagpost()`.
1972
1973        Arguments:
1974            txt : (str)
1975                Text to display. The default is the filename or the object name.
1976            point : (list)
1977                position of the flagpole pointer. 
1978            offset : (list)
1979                text offset wrt the application point. 
1980            s : (float)
1981                size of the flagpole.
1982            font : (str)
1983                font face. Check [available fonts here](https://vedo.embl.es/fonts).
1984            rounded : (bool)
1985                draw a rounded or squared box around the text.
1986            c : (list)
1987                text and box color.
1988            alpha : (float)
1989                opacity of text and box.
1990            lw : (float)
1991                line with of box frame.
1992            italic : (float)
1993                italicness of text.
1994
1995        Examples:
1996            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
1997
1998                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
1999
2000            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2001            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2002            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2003        """
2004        objs = []
2005
2006        if txt is None:
2007            if self.filename:
2008                txt = self.filename.split("/")[-1]
2009            elif self.name:
2010                txt = self.name
2011            else:
2012                return None
2013
2014        x0, x1, y0, y1, z0, z1 = self.bounds()
2015        d = self.diagonal_size()
2016        if point is None:
2017            if d:
2018                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2019                # point = self.closest_point([x1, y0, z1])
2020            else:  # it's a Point
2021                point = self.transform.position
2022
2023        pt = utils.make3d(point)
2024
2025        if offset is None:
2026            offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0]
2027        offset = utils.make3d(offset)
2028
2029        if s is None:
2030            s = d / 20
2031
2032        sph = None
2033        if d and (z1 - z0) / d > 0.1:
2034            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2035
2036        if c is None:
2037            c = np.array(self.color()) / 1.4
2038
2039        lab = vedo.shapes.Text3D(
2040            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center"
2041        )
2042        objs.append(lab)
2043
2044        if d and not sph:
2045            sph = vedo.shapes.Circle(pt, r=s / 3, res=16)
2046        objs.append(sph)
2047
2048        x0, x1, y0, y1, z0, z1 = lab.bounds()
2049        aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)]
2050        if rounded:
2051            box = vedo.shapes.KSpline(aline, closed=True)
2052        else:
2053            box = vedo.shapes.Line(aline, closed=True)
2054
2055        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2056
2057        # box.actor.SetOrigin(cnt)
2058        box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt)
2059        objs.append(box)
2060
2061        x0, x1, y0, y1, z0, z1 = box.bounds()
2062        if x0 < pt[0] < x1:
2063            c0 = box.closest_point(pt)
2064            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2065        elif (pt[0] - x0) < (x1 - pt[0]):
2066            c0 = [x0, (y0 + y1) / 2, pt[2]]
2067            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2068        else:
2069            c0 = [x1, (y0 + y1) / 2, pt[2]]
2070            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2071
2072        con = vedo.shapes.Line([c0, c1, pt])
2073        objs.append(con)
2074
2075        mobjs = vedo.merge(objs).c(c).alpha(alpha)
2076        mobjs.name = "FlagPole"
2077        mobjs.bc("tomato").pickable(False)
2078        mobjs.properties.LightingOff()
2079        mobjs.properties.SetLineWidth(lw)
2080        mobjs.actor.UseBoundsOff()
2081        mobjs.actor.SetPosition([0,0,0])
2082        mobjs.actor.SetOrigin(pt)
2083        return mobjs
2084
2085        # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha)
2086        # mobjs.name = "FlagPole"
2087        # # mobjs.bc("tomato").pickable(False)
2088        # # mobjs.properties.LightingOff()
2089        # # mobjs.properties.SetLineWidth(lw)
2090        # # mobjs.actor.UseBoundsOff()
2091        # # mobjs.actor.SetPosition([0,0,0])
2092        # # mobjs.actor.SetOrigin(pt)
2093        # # print(pt)
2094        # 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]:
2096    def flagpost(
2097        self,
2098        txt=None,
2099        point=None,
2100        offset=None,
2101        s=1.0,
2102        c="k9",
2103        bc="k1",
2104        alpha=1,
2105        lw=0,
2106        font="Calco",
2107        justify="center-left",
2108        vspacing=1.0,
2109    ) -> Union["vedo.addons.Flagpost", None]:
2110        """
2111        Generate a flag post style element to describe an object.
2112
2113        Arguments:
2114            txt : (str)
2115                Text to display. The default is the filename or the object name.
2116            point : (list)
2117                position of the flag anchor point. The default is None.
2118            offset : (list)
2119                a 3D displacement or offset. The default is None.
2120            s : (float)
2121                size of the text to be shown
2122            c : (list)
2123                color of text and line
2124            bc : (list)
2125                color of the flag background
2126            alpha : (float)
2127                opacity of text and box.
2128            lw : (int)
2129                line with of box frame. The default is 0.
2130            font : (str)
2131                font name. Use a monospace font for better rendering. The default is "Calco".
2132                Type `vedo -r fonts` for a font demo.
2133                Check [available fonts here](https://vedo.embl.es/fonts).
2134            justify : (str)
2135                internal text justification. The default is "center-left".
2136            vspacing : (float)
2137                vertical spacing between lines.
2138
2139        Examples:
2140            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
2141
2142            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2143        """
2144        if txt is None:
2145            if self.filename:
2146                txt = self.filename.split("/")[-1]
2147            elif self.name:
2148                txt = self.name
2149            else:
2150                return None
2151
2152        x0, x1, y0, y1, z0, z1 = self.bounds()
2153        d = self.diagonal_size()
2154        if point is None:
2155            if d:
2156                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2157            else:  # it's a Point
2158                point = self.transform.position
2159
2160        point = utils.make3d(point)
2161
2162        if offset is None:
2163            offset = [0, 0, (z1 - z0) / 2]
2164        offset = utils.make3d(offset)
2165
2166        fpost = vedo.addons.Flagpost(
2167            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2168        )
2169        self._caption = fpost
2170        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]:
2172    def caption(
2173        self,
2174        txt=None,
2175        point=None,
2176        size=(0.30, 0.15),
2177        padding=5,
2178        font="Calco",
2179        justify="center-right",
2180        vspacing=1.0,
2181        c=None,
2182        alpha=1.0,
2183        lw=1,
2184        ontop=True,
2185    ) -> Union["vtki.vtkCaptionActor2D", None]:
2186        """
2187        Create a 2D caption to an object which follows the camera movements.
2188        Latex is not supported. Returns the same input object for concatenation.
2189
2190        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2191        with similar functionality.
2192
2193        Arguments:
2194            txt : (str)
2195                text to be rendered. The default is the file name.
2196            point : (list)
2197                anchoring point. The default is None.
2198            size : (list)
2199                (width, height) of the caption box. The default is (0.30, 0.15).
2200            padding : (float)
2201                padding space of the caption box in pixels. The default is 5.
2202            font : (str)
2203                font name. Use a monospace font for better rendering. The default is "VictorMono".
2204                Type `vedo -r fonts` for a font demo.
2205                Check [available fonts here](https://vedo.embl.es/fonts).
2206            justify : (str)
2207                internal text justification. The default is "center-right".
2208            vspacing : (float)
2209                vertical spacing between lines. The default is 1.
2210            c : (str)
2211                text and box color. The default is 'lb'.
2212            alpha : (float)
2213                text and box transparency. The default is 1.
2214            lw : (int)
2215                line width in pixels. The default is 1.
2216            ontop : (bool)
2217                keep the 2d caption always on top. The default is True.
2218
2219        Examples:
2220            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2221
2222                ![](https://vedo.embl.es/images/pyplot/caption.png)
2223
2224            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2225            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2226        """
2227        if txt is None:
2228            if self.filename:
2229                txt = self.filename.split("/")[-1]
2230            elif self.name:
2231                txt = self.name
2232
2233        if not txt:  # disable it
2234            self._caption = None
2235            return None
2236
2237        for r in vedo.shapes._reps:
2238            txt = txt.replace(r[0], r[1])
2239
2240        if c is None:
2241            c = np.array(self.properties.GetColor()) / 2
2242        else:
2243            c = colors.get_color(c)
2244
2245        if point is None:
2246            x0, x1, y0, y1, _, z1 = self.dataset.GetBounds()
2247            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2248            point = self.closest_point(pt)
2249
2250        capt = vtki.vtkCaptionActor2D()
2251        capt.SetAttachmentPoint(point)
2252        capt.SetBorder(True)
2253        capt.SetLeader(True)
2254        sph = vtki.new("SphereSource")
2255        sph.Update()
2256        capt.SetLeaderGlyphData(sph.GetOutput())
2257        capt.SetMaximumLeaderGlyphSize(5)
2258        capt.SetPadding(int(padding))
2259        capt.SetCaption(txt)
2260        capt.SetWidth(size[0])
2261        capt.SetHeight(size[1])
2262        capt.SetThreeDimensionalLeader(not ontop)
2263
2264        pra = capt.GetProperty()
2265        pra.SetColor(c)
2266        pra.SetOpacity(alpha)
2267        pra.SetLineWidth(lw)
2268
2269        pr = capt.GetCaptionTextProperty()
2270        pr.SetFontFamily(vtki.VTK_FONT_FILE)
2271        fl = utils.get_font_path(font)
2272        pr.SetFontFile(fl)
2273        pr.ShadowOff()
2274        pr.BoldOff()
2275        pr.FrameOff()
2276        pr.SetColor(c)
2277        pr.SetOpacity(alpha)
2278        pr.SetJustificationToLeft()
2279        if "top" in justify:
2280            pr.SetVerticalJustificationToTop()
2281        if "bottom" in justify:
2282            pr.SetVerticalJustificationToBottom()
2283        if "cent" in justify:
2284            pr.SetVerticalJustificationToCentered()
2285            pr.SetJustificationToCentered()
2286        if "left" in justify:
2287            pr.SetJustificationToLeft()
2288        if "right" in justify:
2289            pr.SetJustificationToRight()
2290        pr.SetLineSpacing(vspacing)
2291        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):
2634class VolumeVisual(CommonVisual):
2635    """Class to manage the visual aspects of a ``Volume`` object."""
2636
2637    def __init__(self) -> None:
2638        # print("INIT VolumeVisual")
2639        super().__init__()
2640
2641    def alpha_unit(self, u=None) -> Union[Self, float]:
2642        """
2643        Defines light attenuation per unit length. Default is 1.
2644        The larger the unit length, the further light has to travel to attenuate the same amount.
2645
2646        E.g., if you set the unit distance to 0, you will get full opacity.
2647        It means that when light travels 0 distance it's already attenuated a finite amount.
2648        Thus, any finite distance should attenuate all light.
2649        The larger you make the unit distance, the more transparent the rendering becomes.
2650        """
2651        if u is None:
2652            return self.properties.GetScalarOpacityUnitDistance()
2653        self.properties.SetScalarOpacityUnitDistance(u)
2654        return self
2655
2656    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2657        """
2658        Assign a set of tranparencies to a volume's gradient
2659        along the range of the scalar value.
2660        A single constant value can also be assigned.
2661        The gradient function is used to decrease the opacity
2662        in the "flat" regions of the volume while maintaining the opacity
2663        at the boundaries between material types.  The gradient is measured
2664        as the amount by which the intensity changes over unit distance.
2665
2666        The format for alpha_grad is the same as for method `volume.alpha()`.
2667        """
2668        if vmin is None:
2669            vmin, _ = self.dataset.GetScalarRange()
2670        if vmax is None:
2671            _, vmax = self.dataset.GetScalarRange()
2672
2673        if alpha_grad is None:
2674            self.properties.DisableGradientOpacityOn()
2675            return self
2676
2677        self.properties.DisableGradientOpacityOff()
2678
2679        gotf = self.properties.GetGradientOpacity()
2680        if utils.is_sequence(alpha_grad):
2681            alpha_grad = np.array(alpha_grad)
2682            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2683                for i, al in enumerate(alpha_grad):
2684                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2685                    # Create transfer mapping scalar value to gradient opacity
2686                    gotf.AddPoint(xalpha, al)
2687            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2688                gotf.AddPoint(vmin, alpha_grad[0][1])
2689                for xalpha, al in alpha_grad:
2690                    # Create transfer mapping scalar value to opacity
2691                    gotf.AddPoint(xalpha, al)
2692                gotf.AddPoint(vmax, alpha_grad[-1][1])
2693            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2694        else:
2695            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2696            gotf.AddPoint(vmax, alpha_grad)
2697        return self
2698
2699    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2700        """Same as `color()`.
2701
2702        Arguments:
2703            alpha : (list)
2704                use a list to specify transparencies along the scalar range
2705            vmin : (float)
2706                force the min of the scalar range to be this value
2707            vmax : (float)
2708                force the max of the scalar range to be this value
2709        """
2710        return self.color(c, alpha, vmin, vmax)
2711
2712    def jittering(self, status=None) -> Union[Self, bool]:
2713        """
2714        If `True`, each ray traversal direction will be perturbed slightly
2715        using a noise-texture to get rid of wood-grain effects.
2716        """
2717        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2718            if status is None:
2719                return self.mapper.GetUseJittering()
2720            self.mapper.SetUseJittering(status)
2721        return self
2722
2723    def hide_voxels(self, ids) -> Self:
2724        """
2725        Hide voxels (cells) from visualization.
2726
2727        Example:
2728            ```python
2729            from vedo import *
2730            embryo = Volume(dataurl+'embryo.tif')
2731            embryo.hide_voxels(list(range(10000)))
2732            show(embryo, axes=1).close()
2733            ```
2734
2735        See also:
2736            `volume.mask()`
2737        """
2738        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2739        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2740        name = vtki.vtkDataSetAttributes.GhostArrayName()
2741        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2742        self.dataset.GetCellData().AddArray(garr)
2743        self.dataset.GetCellData().Modified()
2744        return self
2745
2746
2747    def mode(self, mode=None) -> Union[Self, int]:
2748        """
2749        Define the volumetric rendering mode following this:
2750            - 0, composite rendering
2751            - 1, maximum projection rendering
2752            - 2, minimum projection rendering
2753            - 3, average projection rendering
2754            - 4, additive mode
2755
2756        The default mode is "composite" where the scalar values are sampled through
2757        the volume and composited in a front-to-back scheme through alpha blending.
2758        The final color and opacity is determined using the color and opacity transfer
2759        functions specified in alpha keyword.
2760
2761        Maximum and minimum intensity blend modes use the maximum and minimum
2762        scalar values, respectively, along the sampling ray.
2763        The final color and opacity is determined by passing the resultant value
2764        through the color and opacity transfer functions.
2765
2766        Additive blend mode accumulates scalar values by passing each value
2767        through the opacity transfer function and then adding up the product
2768        of the value and its opacity. In other words, the scalar values are scaled
2769        using the opacity transfer function and summed to derive the final color.
2770        Note that the resulting image is always grayscale i.e. aggregated values
2771        are not passed through the color transfer function.
2772        This is because the final value is a derived value and not a real data value
2773        along the sampling ray.
2774
2775        Average intensity blend mode works similar to the additive blend mode where
2776        the scalar values are multiplied by opacity calculated from the opacity
2777        transfer function and then added.
2778        The additional step here is to divide the sum by the number of samples
2779        taken through the volume.
2780        As is the case with the additive intensity projection, the final image will
2781        always be grayscale i.e. the aggregated values are not passed through the
2782        color transfer function.
2783        """
2784        if mode is None:
2785            return self.mapper.GetBlendMode()
2786
2787        if isinstance(mode, str):
2788            if "comp" in mode:
2789                mode = 0
2790            elif "proj" in mode:
2791                if "max" in mode:
2792                    mode = 1
2793                elif "min" in mode:
2794                    mode = 2
2795                elif "ave" in mode:
2796                    mode = 3
2797                else:
2798                    vedo.logger.warning(f"unknown mode {mode}")
2799                    mode = 0
2800            elif "add" in mode:
2801                mode = 4
2802            else:
2803                vedo.logger.warning(f"unknown mode {mode}")
2804                mode = 0
2805
2806        self.mapper.SetBlendMode(mode)
2807        return self
2808
2809    def shade(self, status=None) -> Union[Self, bool]:
2810        """
2811        Set/Get the shading of a Volume.
2812        Shading can be further controlled with `volume.lighting()` method.
2813
2814        If shading is turned on, the mapper may perform shading calculations.
2815        In some cases shading does not apply
2816        (for example, in maximum intensity projection mode).
2817        """
2818        if status is None:
2819            return self.properties.GetShade()
2820        self.properties.SetShade(status)
2821        return self
2822
2823
2824    def mask(self, data) -> Self:
2825        """
2826        Mask a volume visualization with a binary value.
2827        Needs to specify keyword mapper='gpu'.
2828
2829        Example:
2830        ```python
2831            from vedo import np, Volume, show
2832            data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2833            # all voxels have value zero except:
2834            data_matrix[0:35,   0:35,  0:35] = 1
2835            data_matrix[35:55, 35:55, 35:55] = 2
2836            data_matrix[55:74, 55:74, 55:74] = 3
2837            vol = Volume(data_matrix, c=['white','b','g','r'], mapper='gpu')
2838            data_mask = np.zeros_like(data_matrix)
2839            data_mask[10:65, 10:45, 20:75] = 1
2840            vol.mask(data_mask)
2841            show(vol, axes=1).close()
2842        ```
2843        See also:
2844            `volume.hide_voxels()`
2845        """
2846        mask = vedo.Volume(data.astype(np.uint8))
2847        try:
2848            self.mapper.SetMaskTypeToBinary()
2849            self.mapper.SetMaskInput(mask.dataset)
2850        except AttributeError:
2851            vedo.logger.error("volume.mask() must create the volume with Volume(..., mapper='gpu')")
2852        return self
2853
2854    def interpolation(self, itype) -> Self:
2855        """
2856        Set interpolation type.
2857
2858        0=nearest neighbour, 1=linear
2859        """
2860        self.properties.SetInterpolationType(itype)
2861        return self

Class to manage the visual aspects of a Volume object.

VolumeVisual()
2637    def __init__(self) -> None:
2638        # print("INIT VolumeVisual")
2639        super().__init__()
def alpha_unit(self, u=None) -> Union[Self, float]:
2641    def alpha_unit(self, u=None) -> Union[Self, float]:
2642        """
2643        Defines light attenuation per unit length. Default is 1.
2644        The larger the unit length, the further light has to travel to attenuate the same amount.
2645
2646        E.g., if you set the unit distance to 0, you will get full opacity.
2647        It means that when light travels 0 distance it's already attenuated a finite amount.
2648        Thus, any finite distance should attenuate all light.
2649        The larger you make the unit distance, the more transparent the rendering becomes.
2650        """
2651        if u is None:
2652            return self.properties.GetScalarOpacityUnitDistance()
2653        self.properties.SetScalarOpacityUnitDistance(u)
2654        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:
2656    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2657        """
2658        Assign a set of tranparencies to a volume's gradient
2659        along the range of the scalar value.
2660        A single constant value can also be assigned.
2661        The gradient function is used to decrease the opacity
2662        in the "flat" regions of the volume while maintaining the opacity
2663        at the boundaries between material types.  The gradient is measured
2664        as the amount by which the intensity changes over unit distance.
2665
2666        The format for alpha_grad is the same as for method `volume.alpha()`.
2667        """
2668        if vmin is None:
2669            vmin, _ = self.dataset.GetScalarRange()
2670        if vmax is None:
2671            _, vmax = self.dataset.GetScalarRange()
2672
2673        if alpha_grad is None:
2674            self.properties.DisableGradientOpacityOn()
2675            return self
2676
2677        self.properties.DisableGradientOpacityOff()
2678
2679        gotf = self.properties.GetGradientOpacity()
2680        if utils.is_sequence(alpha_grad):
2681            alpha_grad = np.array(alpha_grad)
2682            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2683                for i, al in enumerate(alpha_grad):
2684                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2685                    # Create transfer mapping scalar value to gradient opacity
2686                    gotf.AddPoint(xalpha, al)
2687            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2688                gotf.AddPoint(vmin, alpha_grad[0][1])
2689                for xalpha, al in alpha_grad:
2690                    # Create transfer mapping scalar value to opacity
2691                    gotf.AddPoint(xalpha, al)
2692                gotf.AddPoint(vmax, alpha_grad[-1][1])
2693            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2694        else:
2695            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2696            gotf.AddPoint(vmax, alpha_grad)
2697        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:
2699    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2700        """Same as `color()`.
2701
2702        Arguments:
2703            alpha : (list)
2704                use a list to specify transparencies along the scalar range
2705            vmin : (float)
2706                force the min of the scalar range to be this value
2707            vmax : (float)
2708                force the max of the scalar range to be this value
2709        """
2710        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]:
2712    def jittering(self, status=None) -> Union[Self, bool]:
2713        """
2714        If `True`, each ray traversal direction will be perturbed slightly
2715        using a noise-texture to get rid of wood-grain effects.
2716        """
2717        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2718            if status is None:
2719                return self.mapper.GetUseJittering()
2720            self.mapper.SetUseJittering(status)
2721        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:
2723    def hide_voxels(self, ids) -> Self:
2724        """
2725        Hide voxels (cells) from visualization.
2726
2727        Example:
2728            ```python
2729            from vedo import *
2730            embryo = Volume(dataurl+'embryo.tif')
2731            embryo.hide_voxels(list(range(10000)))
2732            show(embryo, axes=1).close()
2733            ```
2734
2735        See also:
2736            `volume.mask()`
2737        """
2738        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2739        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2740        name = vtki.vtkDataSetAttributes.GhostArrayName()
2741        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2742        self.dataset.GetCellData().AddArray(garr)
2743        self.dataset.GetCellData().Modified()
2744        return self

Hide voxels (cells) from visualization.

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

volume.mask()

def mode(self, mode=None) -> Union[Self, int]:
2747    def mode(self, mode=None) -> Union[Self, int]:
2748        """
2749        Define the volumetric rendering mode following this:
2750            - 0, composite rendering
2751            - 1, maximum projection rendering
2752            - 2, minimum projection rendering
2753            - 3, average projection rendering
2754            - 4, additive mode
2755
2756        The default mode is "composite" where the scalar values are sampled through
2757        the volume and composited in a front-to-back scheme through alpha blending.
2758        The final color and opacity is determined using the color and opacity transfer
2759        functions specified in alpha keyword.
2760
2761        Maximum and minimum intensity blend modes use the maximum and minimum
2762        scalar values, respectively, along the sampling ray.
2763        The final color and opacity is determined by passing the resultant value
2764        through the color and opacity transfer functions.
2765
2766        Additive blend mode accumulates scalar values by passing each value
2767        through the opacity transfer function and then adding up the product
2768        of the value and its opacity. In other words, the scalar values are scaled
2769        using the opacity transfer function and summed to derive the final color.
2770        Note that the resulting image is always grayscale i.e. aggregated values
2771        are not passed through the color transfer function.
2772        This is because the final value is a derived value and not a real data value
2773        along the sampling ray.
2774
2775        Average intensity blend mode works similar to the additive blend mode where
2776        the scalar values are multiplied by opacity calculated from the opacity
2777        transfer function and then added.
2778        The additional step here is to divide the sum by the number of samples
2779        taken through the volume.
2780        As is the case with the additive intensity projection, the final image will
2781        always be grayscale i.e. the aggregated values are not passed through the
2782        color transfer function.
2783        """
2784        if mode is None:
2785            return self.mapper.GetBlendMode()
2786
2787        if isinstance(mode, str):
2788            if "comp" in mode:
2789                mode = 0
2790            elif "proj" in mode:
2791                if "max" in mode:
2792                    mode = 1
2793                elif "min" in mode:
2794                    mode = 2
2795                elif "ave" in mode:
2796                    mode = 3
2797                else:
2798                    vedo.logger.warning(f"unknown mode {mode}")
2799                    mode = 0
2800            elif "add" in mode:
2801                mode = 4
2802            else:
2803                vedo.logger.warning(f"unknown mode {mode}")
2804                mode = 0
2805
2806        self.mapper.SetBlendMode(mode)
2807        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]:
2809    def shade(self, status=None) -> Union[Self, bool]:
2810        """
2811        Set/Get the shading of a Volume.
2812        Shading can be further controlled with `volume.lighting()` method.
2813
2814        If shading is turned on, the mapper may perform shading calculations.
2815        In some cases shading does not apply
2816        (for example, in maximum intensity projection mode).
2817        """
2818        if status is None:
2819            return self.properties.GetShade()
2820        self.properties.SetShade(status)
2821        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 mask(self, data) -> Self:
2824    def mask(self, data) -> Self:
2825        """
2826        Mask a volume visualization with a binary value.
2827        Needs to specify keyword mapper='gpu'.
2828
2829        Example:
2830        ```python
2831            from vedo import np, Volume, show
2832            data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2833            # all voxels have value zero except:
2834            data_matrix[0:35,   0:35,  0:35] = 1
2835            data_matrix[35:55, 35:55, 35:55] = 2
2836            data_matrix[55:74, 55:74, 55:74] = 3
2837            vol = Volume(data_matrix, c=['white','b','g','r'], mapper='gpu')
2838            data_mask = np.zeros_like(data_matrix)
2839            data_mask[10:65, 10:45, 20:75] = 1
2840            vol.mask(data_mask)
2841            show(vol, axes=1).close()
2842        ```
2843        See also:
2844            `volume.hide_voxels()`
2845        """
2846        mask = vedo.Volume(data.astype(np.uint8))
2847        try:
2848            self.mapper.SetMaskTypeToBinary()
2849            self.mapper.SetMaskInput(mask.dataset)
2850        except AttributeError:
2851            vedo.logger.error("volume.mask() must create the volume with Volume(..., mapper='gpu')")
2852        return self

Mask a volume visualization with a binary value. Needs to specify keyword 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, c=['white','b','g','r'], mapper='gpu')
    data_mask = np.zeros_like(data_matrix)
    data_mask[10:65, 10:45, 20:75] = 1
    vol.mask(data_mask)
    show(vol, axes=1).close()
See also:

volume.hide_voxels()

def interpolation(self, itype) -> Self:
2854    def interpolation(self, itype) -> Self:
2855        """
2856        Set interpolation type.
2857
2858        0=nearest neighbour, 1=linear
2859        """
2860        self.properties.SetInterpolationType(itype)
2861        return self

Set interpolation type.

0=nearest neighbour, 1=linear

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

Class to manage the visual aspects of a Maesh object.

MeshVisual()
2298    def __init__(self) -> None:
2299        # print("INIT MeshVisual", super())
2300        super().__init__()
def follow_camera(self, camera=None, origin=None) -> Self:
2302    def follow_camera(self, camera=None, origin=None) -> Self:
2303        """
2304        Return an object that will follow camera movements and stay locked to it.
2305        Use `mesh.follow_camera(False)` to disable it.
2306
2307        A `vtkCamera` object can also be passed.
2308        """
2309        if camera is False:
2310            try:
2311                self.SetCamera(None)
2312                return self
2313            except AttributeError:
2314                return self
2315
2316        factor = vtki.vtkFollower()
2317        factor.SetMapper(self.mapper)
2318        factor.SetProperty(self.properties)
2319        factor.SetBackfaceProperty(self.actor.GetBackfaceProperty())
2320        factor.SetTexture(self.actor.GetTexture())
2321        factor.SetScale(self.actor.GetScale())
2322        # factor.SetOrientation(self.actor.GetOrientation())
2323        factor.SetPosition(self.actor.GetPosition())
2324        factor.SetUseBounds(self.actor.GetUseBounds())
2325
2326        if origin is None:
2327            factor.SetOrigin(self.actor.GetOrigin())
2328        else:
2329            factor.SetOrigin(origin)
2330
2331        factor.PickableOff()
2332
2333        if isinstance(camera, vtki.vtkCamera):
2334            factor.SetCamera(camera)
2335        else:
2336            plt = vedo.plotter_instance
2337            if plt and plt.renderer and plt.renderer.GetActiveCamera():
2338                factor.SetCamera(plt.renderer.GetActiveCamera())
2339
2340        self.actor = None
2341        factor.retrieve_object = weak_ref_to(self)
2342        self.actor = factor
2343        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:
2345    def wireframe(self, value=True) -> Self:
2346        """Set mesh's representation as wireframe or solid surface."""
2347        if value:
2348            self.properties.SetRepresentationToWireframe()
2349        else:
2350            self.properties.SetRepresentationToSurface()
2351        return self

Set mesh's representation as wireframe or solid surface.

def flat(self) -> Self:
2353    def flat(self)  -> Self:
2354        """Set surface interpolation to flat.
2355
2356        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700">
2357        """
2358        self.properties.SetInterpolationToFlat()
2359        return self

Set surface interpolation to flat.

def phong(self) -> Self:
2361    def phong(self) -> Self:
2362        """Set surface interpolation to "phong"."""
2363        self.properties.SetInterpolationToPhong()
2364        return self

Set surface interpolation to "phong".

def backface_culling(self, value=True) -> Self:
2366    def backface_culling(self, value=True) -> Self:
2367        """Set culling of polygons based on orientation of normal with respect to camera."""
2368        self.properties.SetBackfaceCulling(value)
2369        return self

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

def render_lines_as_tubes(self, value=True) -> Self:
2371    def render_lines_as_tubes(self, value=True) -> Self:
2372        """Wrap a fake tube around a simple line for visualization"""
2373        self.properties.SetRenderLinesAsTubes(value)
2374        return self

Wrap a fake tube around a simple line for visualization

def frontface_culling(self, value=True) -> Self:
2376    def frontface_culling(self, value=True) -> Self:
2377        """Set culling of polygons based on orientation of normal with respect to camera."""
2378        self.properties.SetFrontfaceCulling(value)
2379        return self

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

def backcolor(self, bc=None) -> Union[Self, numpy.ndarray]:
2381    def backcolor(self, bc=None) -> Union[Self, np.ndarray]:
2382        """
2383        Set/get mesh's backface color.
2384        """
2385        back_prop = self.actor.GetBackfaceProperty()
2386
2387        if bc is None:
2388            if back_prop:
2389                return back_prop.GetDiffuseColor()
2390            return self
2391
2392        if self.properties.GetOpacity() < 1:
2393            return self
2394
2395        if not back_prop:
2396            back_prop = vtki.vtkProperty()
2397
2398        back_prop.SetDiffuseColor(colors.get_color(bc))
2399        back_prop.SetOpacity(self.properties.GetOpacity())
2400        self.actor.SetBackfaceProperty(back_prop)
2401        self.mapper.ScalarVisibilityOff()
2402        return self

Set/get mesh's backface color.

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

Shortcut for mesh.backcolor().

def linewidth(self, lw=None) -> Union[Self, int]:
2408    def linewidth(self, lw=None) -> Union[Self, int]:
2409        """Set/get width of mesh edges. Same as `lw()`."""
2410        if lw is not None:
2411            if lw == 0:
2412                self.properties.EdgeVisibilityOff()
2413                self.properties.SetRepresentationToSurface()
2414                return self
2415            self.properties.EdgeVisibilityOn()
2416            self.properties.SetLineWidth(lw)
2417        else:
2418            return self.properties.GetLineWidth()
2419        return self

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

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

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

def linecolor(self, lc=None) -> Union[Self, numpy.ndarray]:
2425    def linecolor(self, lc=None) -> Union[Self, np.ndarray]:
2426        """Set/get color of mesh edges. Same as `lc()`."""
2427        if lc is None:
2428            return np.array(self.properties.GetEdgeColor())
2429        self.properties.EdgeVisibilityOn()
2430        self.properties.SetEdgeColor(colors.get_color(lc))
2431        return self

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

def lc(self, linecolor=None) -> Union[Self, numpy.ndarray]:
2433    def lc(self, linecolor=None) -> Union[Self, np.ndarray]:
2434        """Set/get color of mesh edges. Same as `linecolor()`."""
2435        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:
2437    def texture(
2438        self,
2439        tname,
2440        tcoords=None,
2441        interpolate=True,
2442        repeat=True,
2443        edge_clamp=False,
2444        scale=None,
2445        ushift=None,
2446        vshift=None,
2447    ) -> Self:
2448        """
2449        Assign a texture to mesh from image file or predefined texture `tname`.
2450        If tname is set to `None` texture is disabled.
2451        Input tname can also be an array or a `vtkTexture`.
2452
2453        Arguments:
2454            tname : (numpy.array, str, Image, vtkTexture, None)
2455                the input texture to be applied. Can be a numpy array, a path to an image file,
2456                a vedo Image. The None value disables texture.
2457            tcoords : (numpy.array, str)
2458                this is the (u,v) texture coordinate array. Can also be a string of an existing array
2459                in the mesh.
2460            interpolate : (bool)
2461                turn on/off linear interpolation of the texture map when rendering.
2462            repeat : (bool)
2463                repeat of the texture when tcoords extend beyond the [0,1] range.
2464            edge_clamp : (bool)
2465                turn on/off the clamping of the texture map when
2466                the texture coords extend beyond the [0,1] range.
2467                Only used when repeat is False, and edge clamping is supported by the graphics card.
2468            scale : (bool)
2469                scale the texture image by this factor
2470            ushift : (bool)
2471                shift u-coordinates of texture by this amount
2472            vshift : (bool)
2473                shift v-coordinates of texture by this amount
2474
2475        Examples:
2476            - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py)
2477
2478            ![](https://vedo.embl.es/images/basic/texturecubes.png)
2479        """
2480        pd = self.dataset
2481        out_img = None
2482
2483        if tname is None:  # disable texture
2484            pd.GetPointData().SetTCoords(None)
2485            pd.GetPointData().Modified()
2486            return self  ######################################
2487
2488        if isinstance(tname, vtki.vtkTexture):
2489            tu = tname
2490
2491        elif isinstance(tname, vedo.Image):
2492            tu = vtki.vtkTexture()
2493            out_img = tname.dataset
2494
2495        elif utils.is_sequence(tname):
2496            tu = vtki.vtkTexture()
2497            out_img = vedo.image._get_img(tname)
2498
2499        elif isinstance(tname, str):
2500            tu = vtki.vtkTexture()
2501
2502            if "https://" in tname:
2503                try:
2504                    tname = vedo.file_io.download(tname, verbose=False)
2505                except:
2506                    vedo.logger.error(f"texture {tname} could not be downloaded")
2507                    return self
2508
2509            fn = tname + ".jpg"
2510            if os.path.exists(tname):
2511                fn = tname
2512            else:
2513                vedo.logger.error(f"texture file {tname} does not exist")
2514                return self
2515
2516            fnl = fn.lower()
2517            if ".jpg" in fnl or ".jpeg" in fnl:
2518                reader = vtki.new("JPEGReader")
2519            elif ".png" in fnl:
2520                reader = vtki.new("PNGReader")
2521            elif ".bmp" in fnl:
2522                reader = vtki.new("BMPReader")
2523            else:
2524                vedo.logger.error("in texture() supported files are only PNG, BMP or JPG")
2525                return self
2526            reader.SetFileName(fn)
2527            reader.Update()
2528            out_img = reader.GetOutput()
2529
2530        else:
2531            vedo.logger.error(f"in texture() cannot understand input {type(tname)}")
2532            return self
2533
2534        if tcoords is not None:
2535
2536            if isinstance(tcoords, str):
2537                vtarr = pd.GetPointData().GetArray(tcoords)
2538
2539            else:
2540                tcoords = np.asarray(tcoords)
2541                if tcoords.ndim != 2:
2542                    vedo.logger.error("tcoords must be a 2-dimensional array")
2543                    return self
2544                if tcoords.shape[0] != pd.GetNumberOfPoints():
2545                    vedo.logger.error("nr of texture coords must match nr of points")
2546                    return self
2547                if tcoords.shape[1] != 2:
2548                    vedo.logger.error("tcoords texture vector must have 2 components")
2549                vtarr = utils.numpy2vtk(tcoords)
2550                vtarr.SetName("TCoordinates")
2551
2552            pd.GetPointData().SetTCoords(vtarr)
2553            pd.GetPointData().Modified()
2554
2555        elif not pd.GetPointData().GetTCoords():
2556
2557            # TCoords still void..
2558            # check that there are no texture-like arrays:
2559            names = self.pointdata.keys()
2560            candidate_arr = ""
2561            for name in names:
2562                vtarr = pd.GetPointData().GetArray(name)
2563                if vtarr.GetNumberOfComponents() != 2:
2564                    continue
2565                t0, t1 = vtarr.GetRange()
2566                if t0 >= 0 and t1 <= 1:
2567                    candidate_arr = name
2568
2569            if candidate_arr:
2570
2571                vtarr = pd.GetPointData().GetArray(candidate_arr)
2572                pd.GetPointData().SetTCoords(vtarr)
2573                pd.GetPointData().Modified()
2574
2575            else:
2576                # last resource is automatic mapping
2577                tmapper = vtki.new("TextureMapToPlane")
2578                tmapper.AutomaticPlaneGenerationOn()
2579                tmapper.SetInputData(pd)
2580                tmapper.Update()
2581                tc = tmapper.GetOutput().GetPointData().GetTCoords()
2582                if scale or ushift or vshift:
2583                    ntc = utils.vtk2numpy(tc)
2584                    if scale:
2585                        ntc *= scale
2586                    if ushift:
2587                        ntc[:, 0] += ushift
2588                    if vshift:
2589                        ntc[:, 1] += vshift
2590                    tc = utils.numpy2vtk(tc)
2591                pd.GetPointData().SetTCoords(tc)
2592                pd.GetPointData().Modified()
2593
2594        if out_img:
2595            tu.SetInputData(out_img)
2596        tu.SetInterpolate(interpolate)
2597        tu.SetRepeat(repeat)
2598        tu.SetEdgeClamp(edge_clamp)
2599
2600        self.properties.SetColor(1, 1, 1)
2601        self.mapper.ScalarVisibilityOff()
2602        self.actor.SetTexture(tu)
2603
2604        # if seam_threshold is not None:
2605        #     tname = self.dataset.GetPointData().GetTCoords().GetName()
2606        #     grad = self.gradient(tname)
2607        #     ugrad, vgrad = np.split(grad, 2, axis=1)
2608        #     ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad)
2609        #     gradm = np.log(ugradm + vgradm)
2610        #     largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4]
2611        #     uvmap = self.pointdata[tname]
2612        #     # collapse triangles that have large gradient
2613        #     new_points = self.vertices.copy()
2614        #     for f in self.cells:
2615        #         if np.isin(f, largegrad_ids).all():
2616        #             id1, id2, id3 = f
2617        #             uv1, uv2, uv3 = uvmap[f]
2618        #             d12 = utils.mag2(uv1 - uv2)
2619        #             d23 = utils.mag2(uv2 - uv3)
2620        #             d31 = utils.mag2(uv3 - uv1)
2621        #             idm = np.argmin([d12, d23, d31])
2622        #             if idm == 0:
2623        #                 new_points[id1] = new_points[id3]
2624        #                 new_points[id2] = new_points[id3]
2625        #             elif idm == 1:
2626        #                 new_points[id2] = new_points[id1]
2627        #                 new_points[id3] = new_points[id1]
2628        #     self.vertices = new_points
2629
2630        self.dataset.Modified()
2631        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):
2865class ImageVisual(CommonVisual, Actor3DHelper):
2866
2867    def __init__(self) -> None:
2868        # print("init ImageVisual")
2869        super().__init__()
2870
2871    def memory_size(self) -> int:
2872        """
2873        Return the size in bytes of the object in memory.
2874        """
2875        return self.dataset.GetActualMemorySize()
2876
2877    def scalar_range(self) -> np.ndarray:
2878        """
2879        Return the scalar range of the image.
2880        """
2881        return np.array(self.dataset.GetScalarRange())
2882
2883    def alpha(self, a=None) -> Union[Self, float]:
2884        """Set/get image's transparency in the rendering scene."""
2885        if a is not None:
2886            self.properties.SetOpacity(a)
2887            return self
2888        return self.properties.GetOpacity()
2889
2890    def level(self, value=None) -> Union[Self, float]:
2891        """Get/Set the image color level (brightness) in the rendering scene."""
2892        if value is None:
2893            return self.properties.GetColorLevel()
2894        self.properties.SetColorLevel(value)
2895        return self
2896
2897    def window(self, value=None) -> Union[Self, float]:
2898        """Get/Set the image color window (contrast) in the rendering scene."""
2899        if value is None:
2900            return self.properties.GetColorWindow()
2901        self.properties.SetColorWindow(value)
2902        return self

Class to manage the visual aspects common to all objects.

ImageVisual()
2867    def __init__(self) -> None:
2868        # print("init ImageVisual")
2869        super().__init__()
def memory_size(self) -> int:
2871    def memory_size(self) -> int:
2872        """
2873        Return the size in bytes of the object in memory.
2874        """
2875        return self.dataset.GetActualMemorySize()

Return the size in bytes of the object in memory.

def scalar_range(self) -> numpy.ndarray:
2877    def scalar_range(self) -> np.ndarray:
2878        """
2879        Return the scalar range of the image.
2880        """
2881        return np.array(self.dataset.GetScalarRange())

Return the scalar range of the image.

def alpha(self, a=None) -> Union[Self, float]:
2883    def alpha(self, a=None) -> Union[Self, float]:
2884        """Set/get image's transparency in the rendering scene."""
2885        if a is not None:
2886            self.properties.SetOpacity(a)
2887            return self
2888        return self.properties.GetOpacity()

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

def level(self, value=None) -> Union[Self, float]:
2890    def level(self, value=None) -> Union[Self, float]:
2891        """Get/Set the image color level (brightness) in the rendering scene."""
2892        if value is None:
2893            return self.properties.GetColorLevel()
2894        self.properties.SetColorLevel(value)
2895        return self

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

def window(self, value=None) -> Union[Self, float]:
2897    def window(self, value=None) -> Union[Self, float]:
2898        """Get/Set the image color window (contrast) in the rendering scene."""
2899        if value is None:
2900            return self.properties.GetColorWindow()
2901        self.properties.SetColorWindow(value)
2902        return self

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

class Actor2D(vtkmodules.vtkRenderingCore.vtkActor2D):
539class Actor2D(vtki.vtkActor2D):
540    """Wrapping of `vtkActor2D`."""
541
542    def __init__(self):
543        """Manage 2D objects."""
544        super().__init__()
545
546        self.dataset = None
547        self.properties = self.GetProperty()
548        self.name = ""
549        self.filename = ""
550        self.file_size = 0
551        self.pipeline = None
552        self.shape = [] # for images
553
554    @property
555    def mapper(self):
556        """Get the internal vtkMapper."""
557        return self.GetMapper()
558    
559    @mapper.setter
560    def mapper(self, amapper):
561        """Set the internal vtkMapper."""
562        self.SetMapper(amapper)
563
564    def layer(self, value=None):
565        """Set/Get the layer number in the overlay planes into which to render."""
566        if value is None:
567            return self.GetLayerNumber()
568        self.SetLayerNumber(value)
569        return self
570
571    def pos(self, px=None, py=None) -> Union[np.ndarray, Self]:
572        """Set/Get the screen-coordinate position."""
573        if isinstance(px, str):
574            vedo.logger.error("Use string descriptors only inside the constructor")
575            return self
576        if px is None:
577            return np.array(self.GetPosition(), dtype=int)
578        if py is not None:
579            p = [px, py]
580        else:
581            p = px
582        assert len(p) == 2, "Error: len(pos) must be 2 for Actor2D"
583        self.SetPosition(p)
584        return self
585
586    def coordinate_system(self, value=None) -> Self:
587        """
588        Set/get the coordinate system which this coordinate is defined in.
589
590        The options are:
591            0. Display
592            1. Normalized Display
593            2. Viewport
594            3. Normalized Viewport
595            4. View
596            5. Pose
597            6. World
598        """
599        coor = self.GetPositionCoordinate()
600        if value is None:
601            return coor.GetCoordinateSystem()
602        coor.SetCoordinateSystem(value)
603        return self
604
605    def on(self) -> Self:
606        """Set object visibility."""
607        self.VisibilityOn()
608        return self
609
610    def off(self) -> Self:
611        """Set object visibility."""
612        self.VisibilityOn()
613        return self
614
615    def toggle(self) -> Self:
616        """Toggle object visibility."""
617        self.SetVisibility(not self.GetVisibility())
618        return self
619
620    def pickable(self, value=True) -> Self:
621        """Set object pickability."""
622        self.SetPickable(value)
623        return self
624
625    def color(self, value=None) -> Union[np.ndarray, Self]:
626        """Set/Get the object color."""
627        if value is None:
628            return self.properties.GetColor()
629        self.properties.SetColor(colors.get_color(value))
630        return self
631
632    def c(self, value=None) -> Union[np.ndarray, Self]:
633        """Set/Get the object color."""
634        return self.color(value)
635
636    def alpha(self, value=None) -> Union[float, Self]:
637        """Set/Get the object opacity."""
638        if value is None:
639            return self.properties.GetOpacity()
640        self.properties.SetOpacity(value)
641        return self
642
643    def ps(self, point_size=None) -> Union[int, Self]:
644        if point_size is None:
645            return self.properties.GetPointSize()
646        self.properties.SetPointSize(point_size)
647        return self
648
649    def lw(self, line_width=None) -> Union[int, Self]:
650        if line_width is None:
651            return self.properties.GetLineWidth()
652        self.properties.SetLineWidth(line_width)
653        return self
654
655    def ontop(self, value=True) -> Self:
656        """Keep the object always on top of everything else."""
657        if value:
658            self.properties.SetDisplayLocationToForeground()
659        else:
660            self.properties.SetDisplayLocationToBackground()
661        return self
662
663    def add_observer(self, event_name, func, priority=0) -> int:
664        """Add a callback function that will be called when an event occurs."""
665        event_name = utils.get_vtk_name_event(event_name)
666        idd = self.AddObserver(event_name, func, priority)
667        return idd

Wrapping of vtkActor2D.

Actor2D()
542    def __init__(self):
543        """Manage 2D objects."""
544        super().__init__()
545
546        self.dataset = None
547        self.properties = self.GetProperty()
548        self.name = ""
549        self.filename = ""
550        self.file_size = 0
551        self.pipeline = None
552        self.shape = [] # for images

Manage 2D objects.

mapper
554    @property
555    def mapper(self):
556        """Get the internal vtkMapper."""
557        return self.GetMapper()

Get the internal vtkMapper.

def layer(self, value=None):
564    def layer(self, value=None):
565        """Set/Get the layer number in the overlay planes into which to render."""
566        if value is None:
567            return self.GetLayerNumber()
568        self.SetLayerNumber(value)
569        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]:
571    def pos(self, px=None, py=None) -> Union[np.ndarray, Self]:
572        """Set/Get the screen-coordinate position."""
573        if isinstance(px, str):
574            vedo.logger.error("Use string descriptors only inside the constructor")
575            return self
576        if px is None:
577            return np.array(self.GetPosition(), dtype=int)
578        if py is not None:
579            p = [px, py]
580        else:
581            p = px
582        assert len(p) == 2, "Error: len(pos) must be 2 for Actor2D"
583        self.SetPosition(p)
584        return self

Set/Get the screen-coordinate position.

def coordinate_system(self, value=None) -> Self:
586    def coordinate_system(self, value=None) -> Self:
587        """
588        Set/get the coordinate system which this coordinate is defined in.
589
590        The options are:
591            0. Display
592            1. Normalized Display
593            2. Viewport
594            3. Normalized Viewport
595            4. View
596            5. Pose
597            6. World
598        """
599        coor = self.GetPositionCoordinate()
600        if value is None:
601            return coor.GetCoordinateSystem()
602        coor.SetCoordinateSystem(value)
603        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 on(self) -> Self:
605    def on(self) -> Self:
606        """Set object visibility."""
607        self.VisibilityOn()
608        return self

Set object visibility.

def off(self) -> Self:
610    def off(self) -> Self:
611        """Set object visibility."""
612        self.VisibilityOn()
613        return self

Set object visibility.

def toggle(self) -> Self:
615    def toggle(self) -> Self:
616        """Toggle object visibility."""
617        self.SetVisibility(not self.GetVisibility())
618        return self

Toggle object visibility.

def pickable(self, value=True) -> Self:
620    def pickable(self, value=True) -> Self:
621        """Set object pickability."""
622        self.SetPickable(value)
623        return self

Set object pickability.

def color(self, value=None) -> Union[numpy.ndarray, Self]:
625    def color(self, value=None) -> Union[np.ndarray, Self]:
626        """Set/Get the object color."""
627        if value is None:
628            return self.properties.GetColor()
629        self.properties.SetColor(colors.get_color(value))
630        return self

Set/Get the object color.

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

Set/Get the object color.

def alpha(self, value=None) -> Union[float, Self]:
636    def alpha(self, value=None) -> Union[float, Self]:
637        """Set/Get the object opacity."""
638        if value is None:
639            return self.properties.GetOpacity()
640        self.properties.SetOpacity(value)
641        return self

Set/Get the object opacity.

def ps(self, point_size=None) -> Union[int, Self]:
643    def ps(self, point_size=None) -> Union[int, Self]:
644        if point_size is None:
645            return self.properties.GetPointSize()
646        self.properties.SetPointSize(point_size)
647        return self
def lw(self, line_width=None) -> Union[int, Self]:
649    def lw(self, line_width=None) -> Union[int, Self]:
650        if line_width is None:
651            return self.properties.GetLineWidth()
652        self.properties.SetLineWidth(line_width)
653        return self
def ontop(self, value=True) -> Self:
655    def ontop(self, value=True) -> Self:
656        """Keep the object always on top of everything else."""
657        if value:
658            self.properties.SetDisplayLocationToForeground()
659        else:
660            self.properties.SetDisplayLocationToBackground()
661        return self

Keep the object always on top of everything else.

def add_observer(self, event_name, func, priority=0) -> int:
663    def add_observer(self, event_name, func, priority=0) -> int:
664        """Add a callback function that will be called when an event occurs."""
665        event_name = utils.get_vtk_name_event(event_name)
666        idd = self.AddObserver(event_name, func, priority)
667        return idd

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

class LightKit:
2905class LightKit:
2906    """
2907    A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'.
2908    
2909    The main light is the key light. It is usually positioned so that it appears like
2910    an overhead light (like the sun, or a ceiling light).
2911    It is generally positioned to shine down on the scene from about a 45 degree angle vertically
2912    and at least a little offset side to side. The key light usually at least about twice as bright
2913    as the total of all other lights in the scene to provide good modeling of object features.
2914
2915    The other lights in the kit (the fill light, headlight, and a pair of back lights)
2916    are weaker sources that provide extra illumination to fill in the spots that the key light misses.
2917    The fill light is usually positioned across from or opposite from the key light
2918    (though still on the same side of the object as the camera) in order to simulate diffuse reflections
2919    from other objects in the scene. 
2920    
2921    The headlight, always located at the position of the camera, reduces the contrast between areas lit
2922    by the key and fill light. The two back lights, one on the left of the object as seen from the observer
2923    and one on the right, fill on the high-contrast areas behind the object.
2924    To enforce the relationship between the different lights, the intensity of the fill, back and headlights
2925    are set as a ratio to the key light brightness.
2926    Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity.
2927
2928    All lights are directional lights, infinitely far away with no falloff. Lights move with the camera.
2929
2930    For simplicity, the position of lights in the LightKit can only be specified using angles:
2931    the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees.
2932    For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight).
2933    A light at (elevation=90, azimuth=0) is above the lookat point, shining down.
2934    Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise.
2935    So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining
2936    slightly from the left side.
2937
2938    LightKit limits the colors that can be assigned to any light to those of incandescent sources such as
2939    light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors
2940    can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red.
2941    Colors close to 0.5 are "cool whites" and "warm whites," respectively.
2942
2943    Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight
2944    ratios are skewed by key, fill, and headlight colors. If `maintain_luminance` is set, LightKit will
2945    attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors.
2946
2947    To specify the color of a light, positioning etc you can pass a dictionary with the following keys:
2948        - `intensity` : (float) The intensity of the key light. Default is 1.
2949        - `ratio`     : (float) The ratio of the light intensity wrt key light.
2950        - `warmth`    : (float) The warmth of the light. Default is 0.5.
2951        - `elevation` : (float) The elevation of the light in degrees.
2952        - `azimuth`   : (float) The azimuth of the light in degrees.
2953
2954    Example:
2955        ```python
2956        from vedo import *
2957        lightkit = LightKit(head={"warmth":0.6))
2958        mesh = Mesh(dataurl+"bunny.obj")
2959        plt = Plotter()
2960        plt.remove_lights().add(mesh, lightkit)
2961        plt.show().close()
2962        ```
2963    """
2964    def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None:
2965
2966        self.lightkit = vtki.new("LightKit")
2967        self.lightkit.SetMaintainLuminance(maintain_luminance)
2968        self.key  = dict(key)
2969        self.head = dict(head)
2970        self.fill = dict(fill)
2971        self.back = dict(back)
2972        self.update()
2973
2974    def update(self) -> None:
2975        """Update the LightKit properties."""
2976        if "warmth" in self.key:
2977            self.lightkit.SetKeyLightWarmth(self.key["warmth"])
2978        if "warmth" in self.fill:
2979            self.lightkit.SetFillLightWarmth(self.fill["warmth"])
2980        if "warmth" in self.head:
2981            self.lightkit.SetHeadLightWarmth(self.head["warmth"])
2982        if "warmth" in self.back:
2983            self.lightkit.SetBackLightWarmth(self.back["warmth"])
2984
2985        if "intensity" in self.key:
2986            self.lightkit.SetKeyLightIntensity(self.key["intensity"])
2987        if "ratio" in self.fill:
2988            self.lightkit.SetKeyToFillRatio(self.key["ratio"])
2989        if "ratio" in self.head:
2990            self.lightkit.SetKeyToHeadRatio(self.key["ratio"])
2991        if "ratio" in self.back:
2992            self.lightkit.SetKeyToBackRatio(self.key["ratio"])
2993
2994        if "elevation" in self.key:
2995            self.lightkit.SetKeyLightElevation(self.key["elevation"])
2996        if "elevation" in self.fill:
2997            self.lightkit.SetFillLightElevation(self.fill["elevation"])
2998        if "elevation" in self.head:
2999            self.lightkit.SetHeadLightElevation(self.head["elevation"])
3000        if "elevation" in self.back:
3001            self.lightkit.SetBackLightElevation(self.back["elevation"])
3002
3003        if "azimuth" in self.key:
3004            self.lightkit.SetKeyLightAzimuth(self.key["azimuth"])
3005        if "azimuth" in self.fill:
3006            self.lightkit.SetFillLightAzimuth(self.fill["azimuth"])
3007        if "azimuth" in self.head:
3008            self.lightkit.SetHeadLightAzimuth(self.head["azimuth"])
3009        if "azimuth" in self.back:
3010            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)
2964    def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None:
2965
2966        self.lightkit = vtki.new("LightKit")
2967        self.lightkit.SetMaintainLuminance(maintain_luminance)
2968        self.key  = dict(key)
2969        self.head = dict(head)
2970        self.fill = dict(fill)
2971        self.back = dict(back)
2972        self.update()
def update(self) -> None:
2974    def update(self) -> None:
2975        """Update the LightKit properties."""
2976        if "warmth" in self.key:
2977            self.lightkit.SetKeyLightWarmth(self.key["warmth"])
2978        if "warmth" in self.fill:
2979            self.lightkit.SetFillLightWarmth(self.fill["warmth"])
2980        if "warmth" in self.head:
2981            self.lightkit.SetHeadLightWarmth(self.head["warmth"])
2982        if "warmth" in self.back:
2983            self.lightkit.SetBackLightWarmth(self.back["warmth"])
2984
2985        if "intensity" in self.key:
2986            self.lightkit.SetKeyLightIntensity(self.key["intensity"])
2987        if "ratio" in self.fill:
2988            self.lightkit.SetKeyToFillRatio(self.key["ratio"])
2989        if "ratio" in self.head:
2990            self.lightkit.SetKeyToHeadRatio(self.key["ratio"])
2991        if "ratio" in self.back:
2992            self.lightkit.SetKeyToBackRatio(self.key["ratio"])
2993
2994        if "elevation" in self.key:
2995            self.lightkit.SetKeyLightElevation(self.key["elevation"])
2996        if "elevation" in self.fill:
2997            self.lightkit.SetFillLightElevation(self.fill["elevation"])
2998        if "elevation" in self.head:
2999            self.lightkit.SetHeadLightElevation(self.head["elevation"])
3000        if "elevation" in self.back:
3001            self.lightkit.SetBackLightElevation(self.back["elevation"])
3002
3003        if "azimuth" in self.key:
3004            self.lightkit.SetKeyLightAzimuth(self.key["azimuth"])
3005        if "azimuth" in self.fill:
3006            self.lightkit.SetFillLightAzimuth(self.fill["azimuth"])
3007        if "azimuth" in self.head:
3008            self.lightkit.SetHeadLightAzimuth(self.head["azimuth"])
3009        if "azimuth" in self.back:
3010            self.lightkit.SetBackLightAzimuth(self.back["azimuth"])

Update the LightKit properties.