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

Class to manage the visual aspects common to all objects.

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

Print object info.

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

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

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

Set the range of the scalar value for visualization.

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

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

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

Invoke an event.

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

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

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

Set/get the pickability property of an object.

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

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

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

Set/get the draggability property of an object.

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

Switch on object visibility. Object is not removed.

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

Switch off object visibility. Object is not removed.

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

Toggle object visibility on/off.

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

Add a 2D scalar bar for the specified object.

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

Class to manage the visual aspects of a Points object.

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

Copy properties from another Points object.

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

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

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

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

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

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

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

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

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

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

def ps(self, pointsize=None) -> Union[int, Self]:
1029    def ps(self, pointsize=None) -> Union[int, Self]:
1030        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
1031        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:
1033    def render_points_as_spheres(self, value=True) -> Self:
1034        """Make points look spheric or else make them look as squares."""
1035        self.properties.SetRenderPointsAsSpheres(value)
1036        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:
1038    def lighting(
1039        self,
1040        style="",
1041        ambient=None,
1042        diffuse=None,
1043        specular=None,
1044        specular_power=None,
1045        specular_color=None,
1046        metallicity=None,
1047        roughness=None,
1048    ) -> Self:
1049        """
1050        Set the ambient, diffuse, specular and specular_power lighting constants.
1051
1052        Arguments:
1053            style : (str)
1054                preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]`
1055            ambient : (float)
1056                ambient fraction of emission [0-1]
1057            diffuse : (float)
1058                emission of diffused light in fraction [0-1]
1059            specular : (float)
1060                fraction of reflected light [0-1]
1061            specular_power : (float)
1062                precision of reflection [1-100]
1063            specular_color : (color)
1064                color that is being reflected by the surface
1065
1066        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px>
1067
1068        Examples:
1069            - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py)
1070        """
1071        pr = self.properties
1072
1073        if style:
1074
1075            if style != "off":
1076                pr.LightingOn()
1077
1078            if style == "off":
1079                pr.SetInterpolationToFlat()
1080                pr.LightingOff()
1081                return self  ##############
1082
1083            if hasattr(pr, "GetColor"):  # could be Volume
1084                c = pr.GetColor()
1085            else:
1086                c = (1, 1, 0.99)
1087            mpr = self.mapper
1088            if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility():
1089                c = (1,1,0.99)
1090            if   style=='metallic': pars = [0.1, 0.3, 1.0, 10, c]
1091            elif style=='plastic' : pars = [0.3, 0.4, 0.3,  5, c]
1092            elif style=='shiny'   : pars = [0.2, 0.6, 0.8, 50, c]
1093            elif style=='glossy'  : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)]
1094            elif style=='ambient' : pars = [0.8, 0.1, 0.0,  1, (1,1,1)]
1095            elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c]
1096            else:
1097                vedo.logger.error("in lighting(): Available styles are")
1098                vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]")
1099                raise RuntimeError()
1100            pr.SetAmbient(pars[0])
1101            pr.SetDiffuse(pars[1])
1102            pr.SetSpecular(pars[2])
1103            pr.SetSpecularPower(pars[3])
1104            if hasattr(pr, "GetColor"):
1105                pr.SetSpecularColor(pars[4])
1106
1107        if ambient is not None: pr.SetAmbient(ambient)
1108        if diffuse is not None: pr.SetDiffuse(diffuse)
1109        if specular is not None: pr.SetSpecular(specular)
1110        if specular_power is not None: pr.SetSpecularPower(specular_power)
1111        if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color))
1112        if metallicity is not None:
1113            pr.SetInterpolationToPBR()
1114            pr.SetMetallic(metallicity)
1115        if roughness is not None:
1116            pr.SetInterpolationToPBR()
1117            pr.SetRoughness(roughness)
1118
1119        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:
1121    def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self:
1122        """Set point blurring.
1123        Apply a gaussian convolution filter to the points.
1124        In this case the radius `r` is in absolute units of the mesh coordinates.
1125        With emissive set, the halo of point becomes light-emissive.
1126        """
1127        self.properties.SetRepresentationToPoints()
1128        if emissive:
1129            self.mapper.SetEmissive(bool(emissive))
1130        self.mapper.SetScaleFactor(r * 1.4142)
1131
1132        # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
1133        if alpha < 1:
1134            self.mapper.SetSplatShaderCode(
1135                "//VTK::Color::Impl\n"
1136                "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
1137                "if (dist > 1.0) {\n"
1138                "   discard;\n"
1139                "} else {\n"
1140                f"  float scale = ({alpha} - dist);\n"
1141                "   ambientColor *= scale;\n"
1142                "   diffuseColor *= scale;\n"
1143                "}\n"
1144            )
1145            alpha = 1
1146
1147        self.mapper.Modified()
1148        self.actor.Modified()
1149        self.properties.SetOpacity(alpha)
1150        self.actor.SetMapper(self.mapper)
1151        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
1153    @property
1154    def cellcolors(self):
1155        """
1156        Colorize each cell (face) of a mesh by passing
1157        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1158        Colors levels and opacities must be in the range [0,255].
1159
1160        A single constant color can also be passed as string or RGBA.
1161
1162        A cell array named "CellsRGBA" is automatically created.
1163
1164        Examples:
1165            - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py)
1166            - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py)
1167
1168            ![](https://vedo.embl.es/images/basic/colorMeshCells.png)
1169        """
1170        if "CellsRGBA" not in self.celldata.keys():
1171            lut = self.mapper.GetLookupTable()
1172            vscalars = self.dataset.GetCellData().GetScalars()
1173            if vscalars is None or lut is None:
1174                arr = np.zeros([self.ncells, 4], dtype=np.uint8)
1175                col = np.array(self.properties.GetColor())
1176                col = np.round(col * 255).astype(np.uint8)
1177                alf = self.properties.GetOpacity()
1178                alf = np.round(alf * 255).astype(np.uint8)
1179                arr[:, (0, 1, 2)] = col
1180                arr[:, 3] = alf
1181            else:
1182                cols = lut.MapScalars(vscalars, 0, 0)
1183                arr = utils.vtk2numpy(cols)
1184            self.celldata["CellsRGBA"] = arr
1185        self.celldata.select("CellsRGBA")
1186        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
1211    @property
1212    def pointcolors(self):
1213        """
1214        Colorize each point (or vertex of a mesh) by passing
1215        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1216        Colors levels and opacities must be in the range [0,255].
1217
1218        A single constant color can also be passed as string or RGBA.
1219
1220        A point array named "PointsRGBA" is automatically created.
1221        """
1222        if "PointsRGBA" not in self.pointdata.keys():
1223            lut = self.mapper.GetLookupTable()
1224            vscalars = self.dataset.GetPointData().GetScalars()
1225            if vscalars is None or lut is None:
1226                # create a constant array
1227                arr = np.zeros([self.npoints, 4], dtype=np.uint8)
1228                col = np.array(self.properties.GetColor())
1229                col = np.round(col * 255).astype(np.uint8)
1230                alf = self.properties.GetOpacity()
1231                alf = np.round(alf * 255).astype(np.uint8)
1232                arr[:, (0, 1, 2)] = col
1233                arr[:, 3] = alf
1234            else:
1235                cols = lut.MapScalars(vscalars, 0, 0)
1236                arr = utils.vtk2numpy(cols)
1237            self.pointdata["PointsRGBA"] = arr
1238        self.pointdata.select("PointsRGBA")
1239        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:
1265    def cmap(
1266        self,
1267        input_cmap,
1268        input_array=None,
1269        on="",
1270        name="Scalars",
1271        vmin=None,
1272        vmax=None,
1273        n_colors=256,
1274        alpha=1.0,
1275        logscale=False,
1276    ) -> Self:
1277        """
1278        Set individual point/cell colors by providing a list of scalar values and a color map.
1279
1280        Arguments:
1281            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
1282                color map scheme to transform a real number into a color.
1283            input_array : (str, list, vtkArray)
1284                can be the string name of an existing array, a new array or a `vtkArray`.
1285            on : (str)
1286                either 'points' or 'cells' or blank (automatic).
1287                Apply the color map to data which is defined on either points or cells.
1288            name : (str)
1289                give a name to the provided array (if input_array is an array)
1290            vmin : (float)
1291                clip scalars to this minimum value
1292            vmax : (float)
1293                clip scalars to this maximum value
1294            n_colors : (int)
1295                number of distinct colors to be used in colormap table.
1296            alpha : (float, list)
1297                Mesh transparency. Can be a `list` of values one for each vertex.
1298            logscale : (bool)
1299                Use logscale
1300
1301        Examples:
1302            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
1303            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
1304            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
1305            - (and many others)
1306
1307                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
1308        """
1309        self._cmap_name = input_cmap
1310
1311        if on == "":
1312            try:
1313                on = self.mapper.GetScalarModeAsString().replace("Use", "")
1314                if on not in ["PointData", "CellData"]: # can be "Default"
1315                    on = "points"
1316                    self.mapper.SetScalarModeToUsePointData()
1317            except AttributeError:
1318                on = "points"
1319        elif on == "Default":
1320            on = "points"
1321            self.mapper.SetScalarModeToUsePointData()
1322
1323        if input_array is None:
1324            if not self.pointdata.keys() and self.celldata.keys():
1325                on = "cells"
1326                if not self.dataset.GetCellData().GetScalars():
1327                    input_array = 0  # pick the first at hand
1328
1329        if "point" in on.lower():
1330            data = self.dataset.GetPointData()
1331            n = self.dataset.GetNumberOfPoints()
1332        elif "cell" in on.lower():
1333            data = self.dataset.GetCellData()
1334            n = self.dataset.GetNumberOfCells()
1335        else:
1336            vedo.logger.error(
1337                f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}")
1338            raise RuntimeError()
1339
1340        if input_array is None:  # if None try to fetch the active scalars
1341            arr = data.GetScalars()
1342            if not arr:
1343                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
1344                return self
1345
1346            if not arr.GetName():  # sometimes arrays dont have a name..
1347                arr.SetName(name)
1348
1349        elif isinstance(input_array, str):  # if a string is passed
1350            arr = data.GetArray(input_array)
1351            if not arr:
1352                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
1353                return self
1354
1355        elif isinstance(input_array, int):  # if an int is passed
1356            if input_array < data.GetNumberOfArrays():
1357                arr = data.GetArray(input_array)
1358            else:
1359                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
1360                return self
1361
1362        elif utils.is_sequence(input_array):  # if a numpy array is passed
1363            npts = len(input_array)
1364            if npts != n:
1365                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
1366                return self
1367            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
1368            data.AddArray(arr)
1369            data.Modified()
1370
1371        elif isinstance(input_array, vtki.vtkArray):  # if a vtkArray is passed
1372            arr = input_array
1373            data.AddArray(arr)
1374            data.Modified()
1375
1376        else:
1377            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
1378            raise RuntimeError()
1379
1380        # Now we have array "arr"
1381        array_name = arr.GetName()
1382
1383        if arr.GetNumberOfComponents() == 1:
1384            if vmin is None:
1385                vmin = arr.GetRange()[0]
1386            if vmax is None:
1387                vmax = arr.GetRange()[1]
1388        else:
1389            if vmin is None or vmax is None:
1390                vn = utils.mag(utils.vtk2numpy(arr))
1391            if vmin is None:
1392                vmin = vn.min()
1393            if vmax is None:
1394                vmax = vn.max()
1395
1396        # interpolate alphas if they are not constant
1397        if not utils.is_sequence(alpha):
1398            alpha = [alpha] * n_colors
1399        else:
1400            v = np.linspace(0, 1, n_colors, endpoint=True)
1401            xp = np.linspace(0, 1, len(alpha), endpoint=True)
1402            alpha = np.interp(v, xp, alpha)
1403
1404        ########################### build the look-up table
1405        if isinstance(input_cmap, vtki.vtkLookupTable):  # vtkLookupTable
1406            lut = input_cmap
1407
1408        elif utils.is_sequence(input_cmap):  # manual sequence of colors
1409            lut = vtki.vtkLookupTable()
1410            if logscale:
1411                lut.SetScaleToLog10()
1412            lut.SetRange(vmin, vmax)
1413            ncols = len(input_cmap)
1414            lut.SetNumberOfTableValues(ncols)
1415
1416            for i, c in enumerate(input_cmap):
1417                r, g, b = colors.get_color(c)
1418                lut.SetTableValue(i, r, g, b, alpha[i])
1419            lut.Build()
1420
1421        else:  
1422            # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
1423            lut = vtki.vtkLookupTable()
1424            if logscale:
1425                lut.SetScaleToLog10()
1426            lut.SetVectorModeToMagnitude()
1427            lut.SetRange(vmin, vmax)
1428            lut.SetNumberOfTableValues(n_colors)
1429            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
1430            for i, c in enumerate(mycols):
1431                r, g, b = c
1432                lut.SetTableValue(i, r, g, b, alpha[i])
1433            lut.Build()
1434
1435        # TEST NEW WAY
1436        self.mapper.SetLookupTable(lut)
1437        self.mapper.ScalarVisibilityOn()
1438        self.mapper.SetColorModeToMapScalars()
1439        self.mapper.SetScalarRange(lut.GetRange())
1440        if "point" in on.lower():
1441            self.pointdata.select(array_name)
1442        else:
1443            self.celldata.select(array_name)
1444        return self
1445
1446        # # TEST this is the old way:
1447        # # arr.SetLookupTable(lut) # wrong! causes weird instabilities with LUT
1448        # # if data.GetScalars():
1449        # #     data.GetScalars().SetLookupTable(lut)
1450        # #     data.GetScalars().Modified()
1451
1452        # data.SetActiveScalars(array_name)
1453        # # data.SetScalars(arr)  # wrong! it deletes array in position 0, never use SetScalars
1454        # # data.SetActiveAttribute(array_name, 0) # boh!
1455
1456        # self.mapper.SetLookupTable(lut)
1457        # self.mapper.SetColorModeToMapScalars()  # so we dont need to convert uint8 scalars
1458
1459        # self.mapper.ScalarVisibilityOn()
1460        # self.mapper.SetScalarRange(lut.GetRange())
1461
1462        # if on.startswith("point"):
1463        #     self.mapper.SetScalarModeToUsePointData()
1464        # else:
1465        #     self.mapper.SetScalarModeToUseCellData()
1466        # if hasattr(self.mapper, "SetArrayName"):
1467        #     self.mapper.SetArrayName(array_name)
1468        # 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:
1470    def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self:
1471        """
1472        Add a trailing line to mesh.
1473        This new mesh is accessible through `mesh.trail`.
1474
1475        Arguments:
1476            offset : (float)
1477                set an offset vector from the object center.
1478            n : (int)
1479                number of segments
1480            lw : (float)
1481                line width of the trail
1482
1483        Examples:
1484            - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py)
1485
1486                ![](https://vedo.embl.es/images/simulations/trail.gif)
1487
1488            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1489            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1490        """
1491        if self.trail is None:
1492            pos = self.pos()
1493            self.trail_offset = np.asarray(offset)
1494            self.trail_points = [pos] * n
1495
1496            if c is None:
1497                col = self.properties.GetColor()
1498            else:
1499                col = colors.get_color(c)
1500
1501            tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw)
1502            self.trail = tline  # holds the Line
1503        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:
1505    def update_trail(self) -> Self:
1506        """
1507        Update the trailing line of a moving object.
1508        """
1509        currentpos = self.pos()
1510        self.trail_points.append(currentpos)  # cycle
1511        self.trail_points.pop(0)
1512        data = np.array(self.trail_points) + self.trail_offset
1513        tpoly = self.trail.dataset
1514        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1515        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:
1552    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1553        """
1554        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1555        The output is a new `Mesh` representing the shadow.
1556        This new mesh is accessible through `mesh.shadow`.
1557        By default the shadow mesh is placed on the bottom wall of the bounding box.
1558
1559        See also `pointcloud.project_on_plane()`.
1560
1561        Arguments:
1562            plane : (str, Plane)
1563                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1564                represents x-plane, y-plane and z-plane, respectively.
1565                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1566            point : (float, array)
1567                if plane is `str`, point should be a float represents the intercept.
1568                Otherwise, point is the camera point of perspective projection
1569            direction : (list)
1570                direction of oblique projection
1571            culling : (int)
1572                choose between front [1] or backface [-1] culling or None.
1573
1574        Examples:
1575            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1576            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1577            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1578
1579            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1580        """
1581        shad = self._compute_shadow(plane, point, direction)
1582        shad.c(c).alpha(alpha)
1583
1584        try:
1585            # Points dont have these methods
1586            shad.flat()
1587            if culling in (1, True):
1588                shad.frontface_culling()
1589            elif culling == -1:
1590                shad.backface_culling()
1591        except AttributeError:
1592            pass
1593
1594        shad.properties.LightingOff()
1595        shad.actor.SetPickable(False)
1596        shad.actor.SetUseBounds(True)
1597
1598        if shad not in self.shadows:
1599            self.shadows.append(shad)
1600            shad.info = dict(plane=plane, point=point, direction=direction)
1601            # shad.metadata["plane"] = plane
1602            # shad.metadata["point"] = point
1603            # print("AAAA", direction, plane, point)
1604            # if direction is None:
1605            #     direction = [0,0,0]
1606            # shad.metadata["direction"] = direction
1607        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:
1609    def update_shadows(self) -> Self:
1610        """Update the shadows of a moving object."""
1611        for sha in self.shadows:
1612            plane = sha.info["plane"]
1613            point = sha.info["point"]
1614            direction = sha.info["direction"]
1615            # print("update_shadows direction", direction,plane,point )
1616            # plane = sha.metadata["plane"]
1617            # point = sha.metadata["point"]
1618            # direction = sha.metadata["direction"]
1619            # if direction[0]==0 and direction[1]==0 and direction[2]==0:
1620            #     direction = None
1621            # print("BBBB", sha.metadata["direction"], 
1622            #       sha.metadata["plane"], sha.metadata["point"])
1623            new_sha = self._compute_shadow(plane, point, direction)
1624            sha._update(new_sha.dataset)
1625        if self.trail:
1626            self.trail.update_shadows()
1627        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]:
1629    def labels(
1630        self,
1631        content=None,
1632        on="points",
1633        scale=None,
1634        xrot=0.0,
1635        yrot=0.0,
1636        zrot=0.0,
1637        ratio=1,
1638        precision=None,
1639        italic=False,
1640        font="",
1641        justify="",
1642        c="black",
1643        alpha=1.0,
1644    ) -> Union["vedo.Mesh", None]:
1645        """
1646        Generate value or ID labels for mesh cells or points.
1647        For large nr. of labels use `font="VTK"` which is much faster.
1648
1649        See also:
1650            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1651
1652        Arguments:
1653            content : (list,int,str)
1654                either 'id', 'cellid', array name or array number.
1655                A array can also be passed (must match the nr. of points or cells).
1656            on : (str)
1657                generate labels for "cells" instead of "points"
1658            scale : (float)
1659                absolute size of labels, if left as None it is automatic
1660            zrot : (float)
1661                local rotation angle of label in degrees
1662            ratio : (int)
1663                skipping ratio, to reduce nr of labels for large meshes
1664            precision : (int)
1665                numeric precision of labels
1666
1667        ```python
1668        from vedo import *
1669        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1670        point_ids = s.labels('id', on="points").c('green')
1671        cell_ids  = s.labels('id', on="cells" ).c('black')
1672        show(s, point_ids, cell_ids)
1673        ```
1674        ![](https://vedo.embl.es/images/feats/labels.png)
1675
1676        Examples:
1677            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1678
1679                ![](https://vedo.embl.es/images/basic/boundaries.png)
1680        """
1681        
1682        cells = False
1683        if "cell" in on or "face" in on:
1684            cells = True
1685            justify = "centered" if justify == "" else justify
1686
1687        if isinstance(content, str):
1688            if content in ("pointid", "pointsid"):
1689                cells = False
1690                content = "id"
1691                justify = "bottom-left" if justify == "" else justify
1692            if content in ("cellid", "cellsid"):
1693                cells = True
1694                content = "id"
1695                justify = "centered" if justify == "" else justify
1696
1697        try:
1698            if cells:
1699                ns = np.sqrt(self.ncells)
1700                elems = self.cell_centers
1701                norms = self.cell_normals
1702                justify = "centered" if justify == "" else justify
1703            else:
1704                ns = np.sqrt(self.npoints)
1705                elems = self.vertices
1706                norms = self.vertex_normals
1707        except AttributeError:
1708            norms = []
1709        
1710        if not justify:
1711            justify = "bottom-left"
1712
1713        hasnorms = False
1714        if len(norms) > 0:
1715            hasnorms = True
1716
1717        if scale is None:
1718            if not ns:
1719                ns = 100
1720            scale = self.diagonal_size() / ns / 10
1721
1722        arr = None
1723        mode = 0
1724        if content is None:
1725            mode = 0
1726            if cells:
1727                if self.dataset.GetCellData().GetScalars():
1728                    name = self.dataset.GetCellData().GetScalars().GetName()
1729                    arr = self.celldata[name]
1730            else:
1731                if self.dataset.GetPointData().GetScalars():
1732                    name = self.dataset.GetPointData().GetScalars().GetName()
1733                    arr = self.pointdata[name]
1734        elif isinstance(content, (str, int)):
1735            if content == "id":
1736                mode = 1
1737            elif cells:
1738                mode = 0
1739                arr = self.celldata[content]
1740            else:
1741                mode = 0
1742                arr = self.pointdata[content]
1743        elif utils.is_sequence(content):
1744            mode = 0
1745            arr = content
1746
1747        if arr is None and mode == 0:
1748            vedo.logger.error("in labels(), array not found in point or cell data")
1749            return None
1750
1751        ratio = int(ratio+0.5)
1752        tapp = vtki.new("AppendPolyData")
1753        has_inputs = False
1754
1755        for i, e in enumerate(elems):
1756            if i % ratio:
1757                continue
1758
1759            if mode == 1:
1760                txt_lab = str(i)
1761            else:
1762                if precision:
1763                    txt_lab = utils.precision(arr[i], precision)
1764                else:
1765                    txt_lab = str(arr[i])
1766
1767            if not txt_lab:
1768                continue
1769
1770            if font == "VTK":
1771                tx = vtki.new("VectorText")
1772                tx.SetText(txt_lab)
1773                tx.Update()
1774                tx_poly = tx.GetOutput()
1775            else:
1776                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset
1777
1778            if tx_poly.GetNumberOfPoints() == 0:
1779                continue  ######################
1780
1781            T = vtki.vtkTransform()
1782            T.PostMultiply()
1783            if italic:
1784                T.Concatenate([1, 0.2, 0, 0,
1785                               0, 1  , 0, 0,
1786                               0, 0  , 1, 0,
1787                               0, 0  , 0, 1])
1788            if hasnorms:
1789                ni = norms[i]
1790                if cells and font=="VTK":  # center-justify
1791                    bb = tx_poly.GetBounds()
1792                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1793                    T.Translate(-dx, -dy, 0)
1794                if xrot: T.RotateX(xrot)
1795                if yrot: T.RotateY(yrot)
1796                if zrot: T.RotateZ(zrot)
1797                crossvec = np.cross([0, 0, 1], ni)
1798                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1799                T.RotateWXYZ(float(angle), crossvec.tolist())
1800                T.Translate(ni / 100)
1801            else:
1802                if xrot: T.RotateX(xrot)
1803                if yrot: T.RotateY(yrot)
1804                if zrot: T.RotateZ(zrot)
1805            T.Scale(scale, scale, scale)
1806            T.Translate(e)
1807            tf = vtki.new("TransformPolyDataFilter")
1808            tf.SetInputData(tx_poly)
1809            tf.SetTransform(T)
1810            tf.Update()
1811            tapp.AddInputData(tf.GetOutput())
1812            has_inputs = True
1813
1814        if has_inputs:
1815            tapp.Update()
1816            lpoly = tapp.GetOutput()
1817        else:
1818            lpoly = vtki.vtkPolyData()
1819        ids = vedo.Mesh(lpoly, c=c, alpha=alpha)
1820        ids.properties.LightingOff()
1821        ids.actor.PickableOff()
1822        ids.actor.SetUseBounds(False)
1823        ids.name = "Labels"
1824        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]:
1826    def labels2d(
1827        self,
1828        content="id",
1829        on="points",
1830        scale=1.0,
1831        precision=4,
1832        font="Calco",
1833        justify="bottom-left",
1834        angle=0.0,
1835        frame=False,
1836        c="black",
1837        bc=None,
1838        alpha=1.0,
1839    ) -> Union["Actor2D", None]:
1840        """
1841        Generate value or ID bi-dimensional labels for mesh cells or points.
1842
1843        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
1844
1845        Arguments:
1846            content : (str)
1847                either 'id', 'cellid', or array name
1848            on : (str)
1849                generate labels for "cells" instead of "points" (the default)
1850            scale : (float)
1851                size scaling of labels
1852            precision : (int)
1853                precision of numeric labels
1854            angle : (float)
1855                local rotation angle of label in degrees
1856            frame : (bool)
1857                draw a frame around the label
1858            bc : (str)
1859                background color of the label
1860
1861        ```python
1862        from vedo import Sphere, show
1863        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
1864        sph.celldata["zvals"] = sph.cell_centers[:,2]
1865        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
1866        show(sph, l2d, axes=1).close()
1867        ```
1868        ![](https://vedo.embl.es/images/feats/labels2d.png)
1869        """
1870        cells = False
1871        if "cell" in on:
1872            cells = True
1873
1874        if isinstance(content, str):
1875            if content in ("id", "pointid", "pointsid"):
1876                cells = False
1877                content = "id"
1878            if content in ("cellid", "cellsid"):
1879                cells = True
1880                content = "id"
1881
1882        if cells:
1883            if content != "id" and content not in self.celldata.keys():
1884                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
1885                return None
1886            cellcloud = vedo.Points(self.cell_centers)
1887            arr = self.dataset.GetCellData().GetScalars()
1888            poly = cellcloud.dataset
1889            poly.GetPointData().SetScalars(arr)
1890        else:
1891            poly = self.dataset
1892            if content != "id" and content not in self.pointdata.keys():
1893                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
1894                return None
1895
1896        mp = vtki.new("LabeledDataMapper")
1897
1898        if content == "id":
1899            mp.SetLabelModeToLabelIds()
1900        else:
1901            mp.SetLabelModeToLabelScalars()
1902            if precision is not None:
1903                mp.SetLabelFormat(f"%-#.{precision}g")
1904
1905        pr = mp.GetLabelTextProperty()
1906        c = colors.get_color(c)
1907        pr.SetColor(c)
1908        pr.SetOpacity(alpha)
1909        pr.SetFrame(frame)
1910        pr.SetFrameColor(c)
1911        pr.SetItalic(False)
1912        pr.BoldOff()
1913        pr.ShadowOff()
1914        pr.UseTightBoundingBoxOn()
1915        pr.SetOrientation(angle)
1916        pr.SetFontFamily(vtki.VTK_FONT_FILE)
1917        fl = utils.get_font_path(font)
1918        pr.SetFontFile(fl)
1919        pr.SetFontSize(int(20 * scale))
1920
1921        if "cent" in justify or "mid" in justify:
1922            pr.SetJustificationToCentered()
1923        elif "rig" in justify:
1924            pr.SetJustificationToRight()
1925        elif "left" in justify:
1926            pr.SetJustificationToLeft()
1927        # ------
1928        if "top" in justify:
1929            pr.SetVerticalJustificationToTop()
1930        else:
1931            pr.SetVerticalJustificationToBottom()
1932
1933        if bc is not None:
1934            bc = colors.get_color(bc)
1935            pr.SetBackgroundColor(bc)
1936            pr.SetBackgroundOpacity(alpha)
1937
1938        mp.SetInputData(poly)
1939        a2d = Actor2D()
1940        a2d.PickableOff()
1941        a2d.SetMapper(mp)
1942        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:
1944    def legend(self, txt) -> Self:
1945        """Book a legend text."""
1946        self.info["legend"] = txt
1947        # self.metadata["legend"] = txt
1948        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]:
1950    def flagpole(
1951        self,
1952        txt=None,
1953        point=None,
1954        offset=None,
1955        s=None,
1956        font="Calco",
1957        rounded=True,
1958        c=None,
1959        alpha=1.0,
1960        lw=2,
1961        italic=0.0,
1962        padding=0.1,
1963    ) -> Union["vedo.Mesh", None]:
1964        """
1965        Generate a flag pole style element to describe an object.
1966        Returns a `Mesh` object.
1967
1968        Use flagpole.follow_camera() to make it face the camera in the scene.
1969
1970        Consider using `settings.use_parallel_projection = True` 
1971        to avoid perspective distortions.
1972
1973        See also `flagpost()`.
1974
1975        Arguments:
1976            txt : (str)
1977                Text to display. The default is the filename or the object name.
1978            point : (list)
1979                position of the flagpole pointer. 
1980            offset : (list)
1981                text offset wrt the application point. 
1982            s : (float)
1983                size of the flagpole.
1984            font : (str)
1985                font face. Check [available fonts here](https://vedo.embl.es/fonts).
1986            rounded : (bool)
1987                draw a rounded or squared box around the text.
1988            c : (list)
1989                text and box color.
1990            alpha : (float)
1991                opacity of text and box.
1992            lw : (float)
1993                line with of box frame.
1994            italic : (float)
1995                italicness of text.
1996
1997        Examples:
1998            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
1999
2000                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2001
2002            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2003            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2004            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2005        """
2006        objs = []
2007
2008        if txt is None:
2009            if self.filename:
2010                txt = self.filename.split("/")[-1]
2011            elif self.name:
2012                txt = self.name
2013            else:
2014                return None
2015
2016        x0, x1, y0, y1, z0, z1 = self.bounds()
2017        d = self.diagonal_size()
2018        if point is None:
2019            if d:
2020                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2021                # point = self.closest_point([x1, y0, z1])
2022            else:  # it's a Point
2023                point = self.transform.position
2024
2025        pt = utils.make3d(point)
2026
2027        if offset is None:
2028            offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0]
2029        offset = utils.make3d(offset)
2030
2031        if s is None:
2032            s = d / 20
2033
2034        sph = None
2035        if d and (z1 - z0) / d > 0.1:
2036            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2037
2038        if c is None:
2039            c = np.array(self.color()) / 1.4
2040
2041        lab = vedo.shapes.Text3D(
2042            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center"
2043        )
2044        objs.append(lab)
2045
2046        if d and not sph:
2047            sph = vedo.shapes.Circle(pt, r=s / 3, res=16)
2048        objs.append(sph)
2049
2050        x0, x1, y0, y1, z0, z1 = lab.bounds()
2051        aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)]
2052        if rounded:
2053            box = vedo.shapes.KSpline(aline, closed=True)
2054        else:
2055            box = vedo.shapes.Line(aline, closed=True)
2056
2057        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2058
2059        # box.actor.SetOrigin(cnt)
2060        box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt)
2061        objs.append(box)
2062
2063        x0, x1, y0, y1, z0, z1 = box.bounds()
2064        if x0 < pt[0] < x1:
2065            c0 = box.closest_point(pt)
2066            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2067        elif (pt[0] - x0) < (x1 - pt[0]):
2068            c0 = [x0, (y0 + y1) / 2, pt[2]]
2069            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2070        else:
2071            c0 = [x1, (y0 + y1) / 2, pt[2]]
2072            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2073
2074        con = vedo.shapes.Line([c0, c1, pt])
2075        objs.append(con)
2076
2077        mobjs = vedo.merge(objs).c(c).alpha(alpha)
2078        mobjs.name = "FlagPole"
2079        mobjs.bc("tomato").pickable(False)
2080        mobjs.properties.LightingOff()
2081        mobjs.properties.SetLineWidth(lw)
2082        mobjs.actor.UseBoundsOff()
2083        mobjs.actor.SetPosition([0,0,0])
2084        mobjs.actor.SetOrigin(pt)
2085        return mobjs
2086
2087        # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha)
2088        # mobjs.name = "FlagPole"
2089        # # mobjs.bc("tomato").pickable(False)
2090        # # mobjs.properties.LightingOff()
2091        # # mobjs.properties.SetLineWidth(lw)
2092        # # mobjs.actor.UseBoundsOff()
2093        # # mobjs.actor.SetPosition([0,0,0])
2094        # # mobjs.actor.SetOrigin(pt)
2095        # # print(pt)
2096        # 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]:
2098    def flagpost(
2099        self,
2100        txt=None,
2101        point=None,
2102        offset=None,
2103        s=1.0,
2104        c="k9",
2105        bc="k1",
2106        alpha=1,
2107        lw=0,
2108        font="Calco",
2109        justify="center-left",
2110        vspacing=1.0,
2111    ) -> Union["vedo.addons.Flagpost", None]:
2112        """
2113        Generate a flag post style element to describe an object.
2114
2115        Arguments:
2116            txt : (str)
2117                Text to display. The default is the filename or the object name.
2118            point : (list)
2119                position of the flag anchor point. The default is None.
2120            offset : (list)
2121                a 3D displacement or offset. The default is None.
2122            s : (float)
2123                size of the text to be shown
2124            c : (list)
2125                color of text and line
2126            bc : (list)
2127                color of the flag background
2128            alpha : (float)
2129                opacity of text and box.
2130            lw : (int)
2131                line with of box frame. The default is 0.
2132            font : (str)
2133                font name. Use a monospace font for better rendering. The default is "Calco".
2134                Type `vedo -r fonts` for a font demo.
2135                Check [available fonts here](https://vedo.embl.es/fonts).
2136            justify : (str)
2137                internal text justification. The default is "center-left".
2138            vspacing : (float)
2139                vertical spacing between lines.
2140
2141        Examples:
2142            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
2143
2144            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2145        """
2146        if txt is None:
2147            if self.filename:
2148                txt = self.filename.split("/")[-1]
2149            elif self.name:
2150                txt = self.name
2151            else:
2152                return None
2153
2154        x0, x1, y0, y1, z0, z1 = self.bounds()
2155        d = self.diagonal_size()
2156        if point is None:
2157            if d:
2158                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2159            else:  # it's a Point
2160                point = self.transform.position
2161
2162        point = utils.make3d(point)
2163
2164        if offset is None:
2165            offset = [0, 0, (z1 - z0) / 2]
2166        offset = utils.make3d(offset)
2167
2168        fpost = vedo.addons.Flagpost(
2169            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2170        )
2171        self._caption = fpost
2172        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]:
2174    def caption(
2175        self,
2176        txt=None,
2177        point=None,
2178        size=(0.30, 0.15),
2179        padding=5,
2180        font="Calco",
2181        justify="center-right",
2182        vspacing=1.0,
2183        c=None,
2184        alpha=1.0,
2185        lw=1,
2186        ontop=True,
2187    ) -> Union["vtki.vtkCaptionActor2D", None]:
2188        """
2189        Create a 2D caption to an object which follows the camera movements.
2190        Latex is not supported. Returns the same input object for concatenation.
2191
2192        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2193        with similar functionality.
2194
2195        Arguments:
2196            txt : (str)
2197                text to be rendered. The default is the file name.
2198            point : (list)
2199                anchoring point. The default is None.
2200            size : (list)
2201                (width, height) of the caption box. The default is (0.30, 0.15).
2202            padding : (float)
2203                padding space of the caption box in pixels. The default is 5.
2204            font : (str)
2205                font name. Use a monospace font for better rendering. The default is "VictorMono".
2206                Type `vedo -r fonts` for a font demo.
2207                Check [available fonts here](https://vedo.embl.es/fonts).
2208            justify : (str)
2209                internal text justification. The default is "center-right".
2210            vspacing : (float)
2211                vertical spacing between lines. The default is 1.
2212            c : (str)
2213                text and box color. The default is 'lb'.
2214            alpha : (float)
2215                text and box transparency. The default is 1.
2216            lw : (int)
2217                line width in pixels. The default is 1.
2218            ontop : (bool)
2219                keep the 2d caption always on top. The default is True.
2220
2221        Examples:
2222            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2223
2224                ![](https://vedo.embl.es/images/pyplot/caption.png)
2225
2226            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2227            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2228        """
2229        if txt is None:
2230            if self.filename:
2231                txt = self.filename.split("/")[-1]
2232            elif self.name:
2233                txt = self.name
2234
2235        if not txt:  # disable it
2236            self._caption = None
2237            return None
2238
2239        for r in vedo.shapes._reps:
2240            txt = txt.replace(r[0], r[1])
2241
2242        if c is None:
2243            c = np.array(self.properties.GetColor()) / 2
2244        else:
2245            c = colors.get_color(c)
2246
2247        if point is None:
2248            x0, x1, y0, y1, _, z1 = self.dataset.GetBounds()
2249            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2250            point = self.closest_point(pt)
2251
2252        capt = vtki.vtkCaptionActor2D()
2253        capt.SetAttachmentPoint(point)
2254        capt.SetBorder(True)
2255        capt.SetLeader(True)
2256        sph = vtki.new("SphereSource")
2257        sph.Update()
2258        capt.SetLeaderGlyphData(sph.GetOutput())
2259        capt.SetMaximumLeaderGlyphSize(5)
2260        capt.SetPadding(int(padding))
2261        capt.SetCaption(txt)
2262        capt.SetWidth(size[0])
2263        capt.SetHeight(size[1])
2264        capt.SetThreeDimensionalLeader(not ontop)
2265
2266        pra = capt.GetProperty()
2267        pra.SetColor(c)
2268        pra.SetOpacity(alpha)
2269        pra.SetLineWidth(lw)
2270
2271        pr = capt.GetCaptionTextProperty()
2272        pr.SetFontFamily(vtki.VTK_FONT_FILE)
2273        fl = utils.get_font_path(font)
2274        pr.SetFontFile(fl)
2275        pr.ShadowOff()
2276        pr.BoldOff()
2277        pr.FrameOff()
2278        pr.SetColor(c)
2279        pr.SetOpacity(alpha)
2280        pr.SetJustificationToLeft()
2281        if "top" in justify:
2282            pr.SetVerticalJustificationToTop()
2283        if "bottom" in justify:
2284            pr.SetVerticalJustificationToBottom()
2285        if "cent" in justify:
2286            pr.SetVerticalJustificationToCentered()
2287            pr.SetJustificationToCentered()
2288        if "left" in justify:
2289            pr.SetJustificationToLeft()
2290        if "right" in justify:
2291            pr.SetJustificationToRight()
2292        pr.SetLineSpacing(vspacing)
2293        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):
2636class VolumeVisual(CommonVisual):
2637    """Class to manage the visual aspects of a ``Volume`` object."""
2638
2639    def __init__(self) -> None:
2640        # print("INIT VolumeVisual")
2641        super().__init__()
2642
2643    def alpha_unit(self, u=None) -> Union[Self, float]:
2644        """
2645        Defines light attenuation per unit length. Default is 1.
2646        The larger the unit length, the further light has to travel to attenuate the same amount.
2647
2648        E.g., if you set the unit distance to 0, you will get full opacity.
2649        It means that when light travels 0 distance it's already attenuated a finite amount.
2650        Thus, any finite distance should attenuate all light.
2651        The larger you make the unit distance, the more transparent the rendering becomes.
2652        """
2653        if u is None:
2654            return self.properties.GetScalarOpacityUnitDistance()
2655        self.properties.SetScalarOpacityUnitDistance(u)
2656        return self
2657
2658    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2659        """
2660        Assign a set of tranparencies to a volume's gradient
2661        along the range of the scalar value.
2662        A single constant value can also be assigned.
2663        The gradient function is used to decrease the opacity
2664        in the "flat" regions of the volume while maintaining the opacity
2665        at the boundaries between material types.  The gradient is measured
2666        as the amount by which the intensity changes over unit distance.
2667
2668        The format for alpha_grad is the same as for method `volume.alpha()`.
2669        """
2670        if vmin is None:
2671            vmin, _ = self.dataset.GetScalarRange()
2672        if vmax is None:
2673            _, vmax = self.dataset.GetScalarRange()
2674
2675        if alpha_grad is None:
2676            self.properties.DisableGradientOpacityOn()
2677            return self
2678
2679        self.properties.DisableGradientOpacityOff()
2680
2681        gotf = self.properties.GetGradientOpacity()
2682        if utils.is_sequence(alpha_grad):
2683            alpha_grad = np.array(alpha_grad)
2684            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2685                for i, al in enumerate(alpha_grad):
2686                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2687                    # Create transfer mapping scalar value to gradient opacity
2688                    gotf.AddPoint(xalpha, al)
2689            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2690                gotf.AddPoint(vmin, alpha_grad[0][1])
2691                for xalpha, al in alpha_grad:
2692                    # Create transfer mapping scalar value to opacity
2693                    gotf.AddPoint(xalpha, al)
2694                gotf.AddPoint(vmax, alpha_grad[-1][1])
2695            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2696        else:
2697            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2698            gotf.AddPoint(vmax, alpha_grad)
2699        return self
2700
2701    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2702        """Same as `color()`.
2703
2704        Arguments:
2705            alpha : (list)
2706                use a list to specify transparencies along the scalar range
2707            vmin : (float)
2708                force the min of the scalar range to be this value
2709            vmax : (float)
2710                force the max of the scalar range to be this value
2711        """
2712        return self.color(c, alpha, vmin, vmax)
2713
2714    def jittering(self, status=None) -> Union[Self, bool]:
2715        """
2716        If `True`, each ray traversal direction will be perturbed slightly
2717        using a noise-texture to get rid of wood-grain effects.
2718        """
2719        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2720            if status is None:
2721                return self.mapper.GetUseJittering()
2722            self.mapper.SetUseJittering(status)
2723        return self
2724
2725    def hide_voxels(self, ids) -> Self:
2726        """
2727        Hide voxels (cells) from visualization.
2728
2729        Example:
2730            ```python
2731            from vedo import *
2732            embryo = Volume(dataurl+'embryo.tif')
2733            embryo.hide_voxels(list(range(400000)))
2734            show(embryo, axes=1).close()
2735            ```
2736
2737        See also:
2738            `volume.mask()`
2739        """
2740        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2741        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2742        name = vtki.vtkDataSetAttributes.GhostArrayName()
2743        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2744        self.dataset.GetCellData().AddArray(garr)
2745        self.dataset.GetCellData().Modified()
2746        return self
2747
2748    def mask(self, data) -> Self:
2749        """
2750        Mask a volume visualization with a binary value.
2751        Needs to specify `volume.mapper = "gpu"`.
2752
2753        Example:
2754        ```python
2755        from vedo import np, Volume, show
2756        data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2757        # all voxels have value zero except:
2758        data_matrix[ 0:35,  0:35,  0:35] = 1
2759        data_matrix[35:55, 35:55, 35:55] = 2
2760        data_matrix[55:74, 55:74, 55:74] = 3
2761        vol = Volume(data_matrix).cmap('Blues')
2762        vol.mapper = "gpu"
2763        data_mask = np.zeros_like(data_matrix)
2764        data_mask[10:65, 10:60, 20:70] = 1
2765        vol.mask(data_mask)
2766        show(vol, axes=1).close()
2767        ```
2768        See also:
2769            `volume.hide_voxels()`
2770        """
2771        rdata = data.astype(np.uint8).ravel(order="F")
2772        varr = utils.numpy2vtk(rdata, name="input_mask")
2773
2774        img = vtki.vtkImageData()
2775        img.SetDimensions(self.dimensions())
2776        img.GetPointData().AddArray(varr)
2777        img.GetPointData().SetActiveScalars(varr.GetName())
2778
2779        try:
2780            self.mapper.SetMaskTypeToBinary()
2781            self.mapper.SetMaskInput(img)
2782        except AttributeError:
2783            vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'")
2784        return self
2785
2786
2787    def mode(self, mode=None) -> Union[Self, int]:
2788        """
2789        Define the volumetric rendering mode following this:
2790            - 0, composite rendering
2791            - 1, maximum projection rendering
2792            - 2, minimum projection rendering
2793            - 3, average projection rendering
2794            - 4, additive mode
2795
2796        The default mode is "composite" where the scalar values are sampled through
2797        the volume and composited in a front-to-back scheme through alpha blending.
2798        The final color and opacity is determined using the color and opacity transfer
2799        functions specified in alpha keyword.
2800
2801        Maximum and minimum intensity blend modes use the maximum and minimum
2802        scalar values, respectively, along the sampling ray.
2803        The final color and opacity is determined by passing the resultant value
2804        through the color and opacity transfer functions.
2805
2806        Additive blend mode accumulates scalar values by passing each value
2807        through the opacity transfer function and then adding up the product
2808        of the value and its opacity. In other words, the scalar values are scaled
2809        using the opacity transfer function and summed to derive the final color.
2810        Note that the resulting image is always grayscale i.e. aggregated values
2811        are not passed through the color transfer function.
2812        This is because the final value is a derived value and not a real data value
2813        along the sampling ray.
2814
2815        Average intensity blend mode works similar to the additive blend mode where
2816        the scalar values are multiplied by opacity calculated from the opacity
2817        transfer function and then added.
2818        The additional step here is to divide the sum by the number of samples
2819        taken through the volume.
2820        As is the case with the additive intensity projection, the final image will
2821        always be grayscale i.e. the aggregated values are not passed through the
2822        color transfer function.
2823        """
2824        if mode is None:
2825            return self.mapper.GetBlendMode()
2826
2827        if isinstance(mode, str):
2828            if "comp" in mode:
2829                mode = 0
2830            elif "proj" in mode:
2831                if "max" in mode:
2832                    mode = 1
2833                elif "min" in mode:
2834                    mode = 2
2835                elif "ave" in mode:
2836                    mode = 3
2837                else:
2838                    vedo.logger.warning(f"unknown mode {mode}")
2839                    mode = 0
2840            elif "add" in mode:
2841                mode = 4
2842            else:
2843                vedo.logger.warning(f"unknown mode {mode}")
2844                mode = 0
2845
2846        self.mapper.SetBlendMode(mode)
2847        return self
2848
2849    def shade(self, status=None) -> Union[Self, bool]:
2850        """
2851        Set/Get the shading of a Volume.
2852        Shading can be further controlled with `volume.lighting()` method.
2853
2854        If shading is turned on, the mapper may perform shading calculations.
2855        In some cases shading does not apply
2856        (for example, in maximum intensity projection mode).
2857        """
2858        if status is None:
2859            return self.properties.GetShade()
2860        self.properties.SetShade(status)
2861        return self
2862
2863    def interpolation(self, itype) -> Self:
2864        """
2865        Set interpolation type.
2866
2867        0=nearest neighbour, 1=linear
2868        """
2869        self.properties.SetInterpolationType(itype)
2870        return self

Class to manage the visual aspects of a Volume object.

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

Hide voxels (cells) from visualization.

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

volume.mask()

def mask(self, data) -> Self:
2748    def mask(self, data) -> Self:
2749        """
2750        Mask a volume visualization with a binary value.
2751        Needs to specify `volume.mapper = "gpu"`.
2752
2753        Example:
2754        ```python
2755        from vedo import np, Volume, show
2756        data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2757        # all voxels have value zero except:
2758        data_matrix[ 0:35,  0:35,  0:35] = 1
2759        data_matrix[35:55, 35:55, 35:55] = 2
2760        data_matrix[55:74, 55:74, 55:74] = 3
2761        vol = Volume(data_matrix).cmap('Blues')
2762        vol.mapper = "gpu"
2763        data_mask = np.zeros_like(data_matrix)
2764        data_mask[10:65, 10:60, 20:70] = 1
2765        vol.mask(data_mask)
2766        show(vol, axes=1).close()
2767        ```
2768        See also:
2769            `volume.hide_voxels()`
2770        """
2771        rdata = data.astype(np.uint8).ravel(order="F")
2772        varr = utils.numpy2vtk(rdata, name="input_mask")
2773
2774        img = vtki.vtkImageData()
2775        img.SetDimensions(self.dimensions())
2776        img.GetPointData().AddArray(varr)
2777        img.GetPointData().SetActiveScalars(varr.GetName())
2778
2779        try:
2780            self.mapper.SetMaskTypeToBinary()
2781            self.mapper.SetMaskInput(img)
2782        except AttributeError:
2783            vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'")
2784        return self

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

Example:

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

volume.hide_voxels()

def mode(self, mode=None) -> Union[Self, int]:
2787    def mode(self, mode=None) -> Union[Self, int]:
2788        """
2789        Define the volumetric rendering mode following this:
2790            - 0, composite rendering
2791            - 1, maximum projection rendering
2792            - 2, minimum projection rendering
2793            - 3, average projection rendering
2794            - 4, additive mode
2795
2796        The default mode is "composite" where the scalar values are sampled through
2797        the volume and composited in a front-to-back scheme through alpha blending.
2798        The final color and opacity is determined using the color and opacity transfer
2799        functions specified in alpha keyword.
2800
2801        Maximum and minimum intensity blend modes use the maximum and minimum
2802        scalar values, respectively, along the sampling ray.
2803        The final color and opacity is determined by passing the resultant value
2804        through the color and opacity transfer functions.
2805
2806        Additive blend mode accumulates scalar values by passing each value
2807        through the opacity transfer function and then adding up the product
2808        of the value and its opacity. In other words, the scalar values are scaled
2809        using the opacity transfer function and summed to derive the final color.
2810        Note that the resulting image is always grayscale i.e. aggregated values
2811        are not passed through the color transfer function.
2812        This is because the final value is a derived value and not a real data value
2813        along the sampling ray.
2814
2815        Average intensity blend mode works similar to the additive blend mode where
2816        the scalar values are multiplied by opacity calculated from the opacity
2817        transfer function and then added.
2818        The additional step here is to divide the sum by the number of samples
2819        taken through the volume.
2820        As is the case with the additive intensity projection, the final image will
2821        always be grayscale i.e. the aggregated values are not passed through the
2822        color transfer function.
2823        """
2824        if mode is None:
2825            return self.mapper.GetBlendMode()
2826
2827        if isinstance(mode, str):
2828            if "comp" in mode:
2829                mode = 0
2830            elif "proj" in mode:
2831                if "max" in mode:
2832                    mode = 1
2833                elif "min" in mode:
2834                    mode = 2
2835                elif "ave" in mode:
2836                    mode = 3
2837                else:
2838                    vedo.logger.warning(f"unknown mode {mode}")
2839                    mode = 0
2840            elif "add" in mode:
2841                mode = 4
2842            else:
2843                vedo.logger.warning(f"unknown mode {mode}")
2844                mode = 0
2845
2846        self.mapper.SetBlendMode(mode)
2847        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]:
2849    def shade(self, status=None) -> Union[Self, bool]:
2850        """
2851        Set/Get the shading of a Volume.
2852        Shading can be further controlled with `volume.lighting()` method.
2853
2854        If shading is turned on, the mapper may perform shading calculations.
2855        In some cases shading does not apply
2856        (for example, in maximum intensity projection mode).
2857        """
2858        if status is None:
2859            return self.properties.GetShade()
2860        self.properties.SetShade(status)
2861        return self

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

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

def interpolation(self, itype) -> Self:
2863    def interpolation(self, itype) -> Self:
2864        """
2865        Set interpolation type.
2866
2867        0=nearest neighbour, 1=linear
2868        """
2869        self.properties.SetInterpolationType(itype)
2870        return self

Set interpolation type.

0=nearest neighbour, 1=linear

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

Class to manage the visual aspects of a Maesh object.

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

Set mesh's representation as wireframe or solid surface.

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

Set surface interpolation to flat.

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

Set surface interpolation to "phong".

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

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

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

Wrap a fake tube around a simple line for visualization

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

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

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

Set/get mesh's backface color.

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

Shortcut for mesh.backcolor().

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

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

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

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

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

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

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

Class to manage the visual aspects common to all objects.

ImageVisual()
2876    def __init__(self) -> None:
2877        # print("init ImageVisual")
2878        super().__init__()
def memory_size(self) -> int:
2880    def memory_size(self) -> int:
2881        """
2882        Return the size in bytes of the object in memory.
2883        """
2884        return self.dataset.GetActualMemorySize()

Return the size in bytes of the object in memory.

def scalar_range(self) -> numpy.ndarray:
2886    def scalar_range(self) -> np.ndarray:
2887        """
2888        Return the scalar range of the image.
2889        """
2890        return np.array(self.dataset.GetScalarRange())

Return the scalar range of the image.

def alpha(self, a=None) -> Union[Self, float]:
2892    def alpha(self, a=None) -> Union[Self, float]:
2893        """Set/get image's transparency in the rendering scene."""
2894        if a is not None:
2895            self.properties.SetOpacity(a)
2896            return self
2897        return self.properties.GetOpacity()

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

def level(self, value=None) -> Union[Self, float]:
2899    def level(self, value=None) -> Union[Self, float]:
2900        """Get/Set the image color level (brightness) in the rendering scene."""
2901        if value is None:
2902            return self.properties.GetColorLevel()
2903        self.properties.SetColorLevel(value)
2904        return self

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

def window(self, value=None) -> Union[Self, float]:
2906    def window(self, value=None) -> Union[Self, float]:
2907        """Get/Set the image color window (contrast) in the rendering scene."""
2908        if value is None:
2909            return self.properties.GetColorWindow()
2910        self.properties.SetColorWindow(value)
2911        return self

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

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

Wrapping of vtkActor2D.

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

Manage 2D objects.

mapper
557    @property
558    def mapper(self):
559        """Get the internal vtkMapper."""
560        return self.GetMapper()

Get the internal vtkMapper.

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

Set/Get the screen-coordinate position.

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

Set object visibility.

def off(self) -> Self:
613    def off(self) -> Self:
614        """Set object visibility."""
615        self.VisibilityOn()
616        return self

Set object visibility.

def toggle(self) -> Self:
618    def toggle(self) -> Self:
619        """Toggle object visibility."""
620        self.SetVisibility(not self.GetVisibility())
621        return self

Toggle object visibility.

def pickable(self, value=True) -> Self:
623    def pickable(self, value=True) -> Self:
624        """Set object pickability."""
625        self.SetPickable(value)
626        return self

Set object pickability.

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

Set/Get the object color.

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

Set/Get the object color.

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

Set/Get the object opacity.

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

Keep the object always on top of everything else.

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

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

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

Update the LightKit properties.