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            self.trail.initilized = False # so the first update will be a reset
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        if not self.trail.initilized:
1511            self.trail_points = [currentpos] * self.trail.npoints
1512            self.trail.initilized = True
1513            return self
1514        self.trail_points.append(currentpos)  # cycle
1515        self.trail_points.pop(0)
1516
1517        data = np.array(self.trail_points) + self.trail_offset
1518        tpoly = self.trail.dataset
1519        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1520        return self
1521
1522    def _compute_shadow(self, plane, point, direction):
1523        shad = self.clone()
1524        shad.name = "Shadow"
1525
1526        tarr = shad.dataset.GetPointData().GetTCoords()
1527        if tarr:  # remove any texture coords
1528            tname = tarr.GetName()
1529            shad.pointdata.remove(tname)
1530            shad.dataset.GetPointData().SetTCoords(None)
1531            shad.actor.SetTexture(None)
1532
1533        pts = shad.vertices
1534        if plane == "x":
1535            # shad = shad.project_on_plane('x')
1536            # instead do it manually so in case of alpha<1
1537            # we dont see glitches due to coplanar points
1538            # we leave a small tolerance of 0.1% in thickness
1539            x0, x1 = self.xbounds()
1540            pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[0]
1541            shad.vertices = pts
1542            shad.x(point)
1543        elif plane == "y":
1544            x0, x1 = self.ybounds()
1545            pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[1]
1546            shad.vertices = pts
1547            shad.y(point)
1548        elif plane == "z":
1549            x0, x1 = self.zbounds()
1550            pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[2]
1551            shad.vertices = pts
1552            shad.z(point)
1553        else:
1554            shad = shad.project_on_plane(plane, point, direction)
1555        return shad
1556
1557    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1558        """
1559        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1560        The output is a new `Mesh` representing the shadow.
1561        This new mesh is accessible through `mesh.shadow`.
1562        By default the shadow mesh is placed on the bottom wall of the bounding box.
1563
1564        See also `pointcloud.project_on_plane()`.
1565
1566        Arguments:
1567            plane : (str, Plane)
1568                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1569                represents x-plane, y-plane and z-plane, respectively.
1570                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1571            point : (float, array)
1572                if plane is `str`, point should be a float represents the intercept.
1573                Otherwise, point is the camera point of perspective projection
1574            direction : (list)
1575                direction of oblique projection
1576            culling : (int)
1577                choose between front [1] or backface [-1] culling or None.
1578
1579        Examples:
1580            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1581            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1582            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1583
1584            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1585        """
1586        shad = self._compute_shadow(plane, point, direction)
1587        shad.c(c).alpha(alpha)
1588
1589        try:
1590            # Points dont have these methods
1591            shad.flat()
1592            if culling in (1, True):
1593                shad.frontface_culling()
1594            elif culling == -1:
1595                shad.backface_culling()
1596        except AttributeError:
1597            pass
1598
1599        shad.properties.LightingOff()
1600        shad.actor.SetPickable(False)
1601        shad.actor.SetUseBounds(True)
1602
1603        if shad not in self.shadows:
1604            self.shadows.append(shad)
1605            shad.info = dict(plane=plane, point=point, direction=direction)
1606            # shad.metadata["plane"] = plane
1607            # shad.metadata["point"] = point
1608            # print("AAAA", direction, plane, point)
1609            # if direction is None:
1610            #     direction = [0,0,0]
1611            # shad.metadata["direction"] = direction
1612        return self
1613
1614    def update_shadows(self) -> Self:
1615        """Update the shadows of a moving object."""
1616        for sha in self.shadows:
1617            plane = sha.info["plane"]
1618            point = sha.info["point"]
1619            direction = sha.info["direction"]
1620            # print("update_shadows direction", direction,plane,point )
1621            # plane = sha.metadata["plane"]
1622            # point = sha.metadata["point"]
1623            # direction = sha.metadata["direction"]
1624            # if direction[0]==0 and direction[1]==0 and direction[2]==0:
1625            #     direction = None
1626            # print("BBBB", sha.metadata["direction"], 
1627            #       sha.metadata["plane"], sha.metadata["point"])
1628            new_sha = self._compute_shadow(plane, point, direction)
1629            sha._update(new_sha.dataset)
1630        if self.trail:
1631            self.trail.update_shadows()
1632        return self
1633
1634    def labels(
1635        self,
1636        content=None,
1637        on="points",
1638        scale=None,
1639        xrot=0.0,
1640        yrot=0.0,
1641        zrot=0.0,
1642        ratio=1,
1643        precision=None,
1644        italic=False,
1645        font="",
1646        justify="",
1647        c="black",
1648        alpha=1.0,
1649    ) -> Union["vedo.Mesh", None]:
1650        """
1651        Generate value or ID labels for mesh cells or points.
1652        For large nr. of labels use `font="VTK"` which is much faster.
1653
1654        See also:
1655            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1656
1657        Arguments:
1658            content : (list,int,str)
1659                either 'id', 'cellid', array name or array number.
1660                A array can also be passed (must match the nr. of points or cells).
1661            on : (str)
1662                generate labels for "cells" instead of "points"
1663            scale : (float)
1664                absolute size of labels, if left as None it is automatic
1665            zrot : (float)
1666                local rotation angle of label in degrees
1667            ratio : (int)
1668                skipping ratio, to reduce nr of labels for large meshes
1669            precision : (int)
1670                numeric precision of labels
1671
1672        ```python
1673        from vedo import *
1674        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1675        point_ids = s.labels('id', on="points").c('green')
1676        cell_ids  = s.labels('id', on="cells" ).c('black')
1677        show(s, point_ids, cell_ids)
1678        ```
1679        ![](https://vedo.embl.es/images/feats/labels.png)
1680
1681        Examples:
1682            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1683
1684                ![](https://vedo.embl.es/images/basic/boundaries.png)
1685        """
1686        
1687        cells = False
1688        if "cell" in on or "face" in on:
1689            cells = True
1690            justify = "centered" if justify == "" else justify
1691
1692        if isinstance(content, str):
1693            if content in ("pointid", "pointsid"):
1694                cells = False
1695                content = "id"
1696                justify = "bottom-left" if justify == "" else justify
1697            if content in ("cellid", "cellsid"):
1698                cells = True
1699                content = "id"
1700                justify = "centered" if justify == "" else justify
1701
1702        try:
1703            if cells:
1704                ns = np.sqrt(self.ncells)
1705                elems = self.cell_centers
1706                norms = self.cell_normals
1707                justify = "centered" if justify == "" else justify
1708            else:
1709                ns = np.sqrt(self.npoints)
1710                elems = self.vertices
1711                norms = self.vertex_normals
1712        except AttributeError:
1713            norms = []
1714        
1715        if not justify:
1716            justify = "bottom-left"
1717
1718        hasnorms = False
1719        if len(norms) > 0:
1720            hasnorms = True
1721
1722        if scale is None:
1723            if not ns:
1724                ns = 100
1725            scale = self.diagonal_size() / ns / 10
1726
1727        arr = None
1728        mode = 0
1729        if content is None:
1730            mode = 0
1731            if cells:
1732                if self.dataset.GetCellData().GetScalars():
1733                    name = self.dataset.GetCellData().GetScalars().GetName()
1734                    arr = self.celldata[name]
1735            else:
1736                if self.dataset.GetPointData().GetScalars():
1737                    name = self.dataset.GetPointData().GetScalars().GetName()
1738                    arr = self.pointdata[name]
1739        elif isinstance(content, (str, int)):
1740            if content == "id":
1741                mode = 1
1742            elif cells:
1743                mode = 0
1744                arr = self.celldata[content]
1745            else:
1746                mode = 0
1747                arr = self.pointdata[content]
1748        elif utils.is_sequence(content):
1749            mode = 0
1750            arr = content
1751
1752        if arr is None and mode == 0:
1753            vedo.logger.error("in labels(), array not found in point or cell data")
1754            return None
1755
1756        ratio = int(ratio+0.5)
1757        tapp = vtki.new("AppendPolyData")
1758        has_inputs = False
1759
1760        for i, e in enumerate(elems):
1761            if i % ratio:
1762                continue
1763
1764            if mode == 1:
1765                txt_lab = str(i)
1766            else:
1767                if precision:
1768                    txt_lab = utils.precision(arr[i], precision)
1769                else:
1770                    txt_lab = str(arr[i])
1771
1772            if not txt_lab:
1773                continue
1774
1775            if font == "VTK":
1776                tx = vtki.new("VectorText")
1777                tx.SetText(txt_lab)
1778                tx.Update()
1779                tx_poly = tx.GetOutput()
1780            else:
1781                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset
1782
1783            if tx_poly.GetNumberOfPoints() == 0:
1784                continue  ######################
1785
1786            T = vtki.vtkTransform()
1787            T.PostMultiply()
1788            if italic:
1789                T.Concatenate([1, 0.2, 0, 0,
1790                               0, 1  , 0, 0,
1791                               0, 0  , 1, 0,
1792                               0, 0  , 0, 1])
1793            if hasnorms:
1794                ni = norms[i]
1795                if cells and font=="VTK":  # center-justify
1796                    bb = tx_poly.GetBounds()
1797                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1798                    T.Translate(-dx, -dy, 0)
1799                if xrot: T.RotateX(xrot)
1800                if yrot: T.RotateY(yrot)
1801                if zrot: T.RotateZ(zrot)
1802                crossvec = np.cross([0, 0, 1], ni)
1803                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1804                T.RotateWXYZ(float(angle), crossvec.tolist())
1805                T.Translate(ni / 100)
1806            else:
1807                if xrot: T.RotateX(xrot)
1808                if yrot: T.RotateY(yrot)
1809                if zrot: T.RotateZ(zrot)
1810            T.Scale(scale, scale, scale)
1811            T.Translate(e)
1812            tf = vtki.new("TransformPolyDataFilter")
1813            tf.SetInputData(tx_poly)
1814            tf.SetTransform(T)
1815            tf.Update()
1816            tapp.AddInputData(tf.GetOutput())
1817            has_inputs = True
1818
1819        if has_inputs:
1820            tapp.Update()
1821            lpoly = tapp.GetOutput()
1822        else:
1823            lpoly = vtki.vtkPolyData()
1824        ids = vedo.Mesh(lpoly, c=c, alpha=alpha)
1825        ids.properties.LightingOff()
1826        ids.actor.PickableOff()
1827        ids.actor.SetUseBounds(False)
1828        ids.name = "Labels"
1829        return ids
1830
1831    def labels2d(
1832        self,
1833        content="id",
1834        on="points",
1835        scale=1.0,
1836        precision=4,
1837        font="Calco",
1838        justify="bottom-left",
1839        angle=0.0,
1840        frame=False,
1841        c="black",
1842        bc=None,
1843        alpha=1.0,
1844    ) -> Union["Actor2D", None]:
1845        """
1846        Generate value or ID bi-dimensional labels for mesh cells or points.
1847
1848        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
1849
1850        Arguments:
1851            content : (str)
1852                either 'id', 'cellid', or array name
1853            on : (str)
1854                generate labels for "cells" instead of "points" (the default)
1855            scale : (float)
1856                size scaling of labels
1857            precision : (int)
1858                precision of numeric labels
1859            angle : (float)
1860                local rotation angle of label in degrees
1861            frame : (bool)
1862                draw a frame around the label
1863            bc : (str)
1864                background color of the label
1865
1866        ```python
1867        from vedo import Sphere, show
1868        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
1869        sph.celldata["zvals"] = sph.cell_centers[:,2]
1870        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
1871        show(sph, l2d, axes=1).close()
1872        ```
1873        ![](https://vedo.embl.es/images/feats/labels2d.png)
1874        """
1875        cells = False
1876        if "cell" in on:
1877            cells = True
1878
1879        if isinstance(content, str):
1880            if content in ("id", "pointid", "pointsid"):
1881                cells = False
1882                content = "id"
1883            if content in ("cellid", "cellsid"):
1884                cells = True
1885                content = "id"
1886
1887        if cells:
1888            if content != "id" and content not in self.celldata.keys():
1889                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
1890                return None
1891            cellcloud = vedo.Points(self.cell_centers)
1892            arr = self.dataset.GetCellData().GetScalars()
1893            poly = cellcloud.dataset
1894            poly.GetPointData().SetScalars(arr)
1895        else:
1896            poly = self.dataset
1897            if content != "id" and content not in self.pointdata.keys():
1898                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
1899                return None
1900
1901        mp = vtki.new("LabeledDataMapper")
1902
1903        if content == "id":
1904            mp.SetLabelModeToLabelIds()
1905        else:
1906            mp.SetLabelModeToLabelScalars()
1907            if precision is not None:
1908                mp.SetLabelFormat(f"%-#.{precision}g")
1909
1910        pr = mp.GetLabelTextProperty()
1911        c = colors.get_color(c)
1912        pr.SetColor(c)
1913        pr.SetOpacity(alpha)
1914        pr.SetFrame(frame)
1915        pr.SetFrameColor(c)
1916        pr.SetItalic(False)
1917        pr.BoldOff()
1918        pr.ShadowOff()
1919        pr.UseTightBoundingBoxOn()
1920        pr.SetOrientation(angle)
1921        pr.SetFontFamily(vtki.VTK_FONT_FILE)
1922        fl = utils.get_font_path(font)
1923        pr.SetFontFile(fl)
1924        pr.SetFontSize(int(20 * scale))
1925
1926        if "cent" in justify or "mid" in justify:
1927            pr.SetJustificationToCentered()
1928        elif "rig" in justify:
1929            pr.SetJustificationToRight()
1930        elif "left" in justify:
1931            pr.SetJustificationToLeft()
1932        # ------
1933        if "top" in justify:
1934            pr.SetVerticalJustificationToTop()
1935        else:
1936            pr.SetVerticalJustificationToBottom()
1937
1938        if bc is not None:
1939            bc = colors.get_color(bc)
1940            pr.SetBackgroundColor(bc)
1941            pr.SetBackgroundOpacity(alpha)
1942
1943        mp.SetInputData(poly)
1944        a2d = Actor2D()
1945        a2d.PickableOff()
1946        a2d.SetMapper(mp)
1947        return a2d
1948
1949    def legend(self, txt) -> Self:
1950        """Book a legend text."""
1951        self.info["legend"] = txt
1952        # self.metadata["legend"] = txt
1953        return self
1954
1955    def flagpole(
1956        self,
1957        txt=None,
1958        point=None,
1959        offset=None,
1960        s=None,
1961        font="Calco",
1962        rounded=True,
1963        c=None,
1964        alpha=1.0,
1965        lw=2,
1966        italic=0.0,
1967        padding=0.1,
1968    ) -> Union["vedo.Mesh", None]:
1969        """
1970        Generate a flag pole style element to describe an object.
1971        Returns a `Mesh` object.
1972
1973        Use flagpole.follow_camera() to make it face the camera in the scene.
1974
1975        Consider using `settings.use_parallel_projection = True` 
1976        to avoid perspective distortions.
1977
1978        See also `flagpost()`.
1979
1980        Arguments:
1981            txt : (str)
1982                Text to display. The default is the filename or the object name.
1983            point : (list)
1984                position of the flagpole pointer. 
1985            offset : (list)
1986                text offset wrt the application point. 
1987            s : (float)
1988                size of the flagpole.
1989            font : (str)
1990                font face. Check [available fonts here](https://vedo.embl.es/fonts).
1991            rounded : (bool)
1992                draw a rounded or squared box around the text.
1993            c : (list)
1994                text and box color.
1995            alpha : (float)
1996                opacity of text and box.
1997            lw : (float)
1998                line with of box frame.
1999            italic : (float)
2000                italicness of text.
2001
2002        Examples:
2003            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2004
2005                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2006
2007            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2008            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2009            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2010        """
2011        objs = []
2012
2013        if txt is None:
2014            if self.filename:
2015                txt = self.filename.split("/")[-1]
2016            elif self.name:
2017                txt = self.name
2018            else:
2019                return None
2020
2021        x0, x1, y0, y1, z0, z1 = self.bounds()
2022        d = self.diagonal_size()
2023        if point is None:
2024            if d:
2025                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2026                # point = self.closest_point([x1, y0, z1])
2027            else:  # it's a Point
2028                point = self.transform.position
2029
2030        pt = utils.make3d(point)
2031
2032        if offset is None:
2033            offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0]
2034        offset = utils.make3d(offset)
2035
2036        if s is None:
2037            s = d / 20
2038
2039        sph = None
2040        if d and (z1 - z0) / d > 0.1:
2041            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2042
2043        if c is None:
2044            c = np.array(self.color()) / 1.4
2045
2046        lab = vedo.shapes.Text3D(
2047            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center"
2048        )
2049        objs.append(lab)
2050
2051        if d and not sph:
2052            sph = vedo.shapes.Circle(pt, r=s / 3, res=16)
2053        objs.append(sph)
2054
2055        x0, x1, y0, y1, z0, z1 = lab.bounds()
2056        aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)]
2057        if rounded:
2058            box = vedo.shapes.KSpline(aline, closed=True)
2059        else:
2060            box = vedo.shapes.Line(aline, closed=True)
2061
2062        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2063
2064        # box.actor.SetOrigin(cnt)
2065        box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt)
2066        objs.append(box)
2067
2068        x0, x1, y0, y1, z0, z1 = box.bounds()
2069        if x0 < pt[0] < x1:
2070            c0 = box.closest_point(pt)
2071            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2072        elif (pt[0] - x0) < (x1 - pt[0]):
2073            c0 = [x0, (y0 + y1) / 2, pt[2]]
2074            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2075        else:
2076            c0 = [x1, (y0 + y1) / 2, pt[2]]
2077            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2078
2079        con = vedo.shapes.Line([c0, c1, pt])
2080        objs.append(con)
2081
2082        mobjs = vedo.merge(objs).c(c).alpha(alpha)
2083        mobjs.name = "FlagPole"
2084        mobjs.bc("tomato").pickable(False)
2085        mobjs.properties.LightingOff()
2086        mobjs.properties.SetLineWidth(lw)
2087        mobjs.actor.UseBoundsOff()
2088        mobjs.actor.SetPosition([0,0,0])
2089        mobjs.actor.SetOrigin(pt)
2090        return mobjs
2091
2092        # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha)
2093        # mobjs.name = "FlagPole"
2094        # # mobjs.bc("tomato").pickable(False)
2095        # # mobjs.properties.LightingOff()
2096        # # mobjs.properties.SetLineWidth(lw)
2097        # # mobjs.actor.UseBoundsOff()
2098        # # mobjs.actor.SetPosition([0,0,0])
2099        # # mobjs.actor.SetOrigin(pt)
2100        # # print(pt)
2101        # return mobjs
2102
2103    def flagpost(
2104        self,
2105        txt=None,
2106        point=None,
2107        offset=None,
2108        s=1.0,
2109        c="k9",
2110        bc="k1",
2111        alpha=1,
2112        lw=0,
2113        font="Calco",
2114        justify="center-left",
2115        vspacing=1.0,
2116    ) -> Union["vedo.addons.Flagpost", None]:
2117        """
2118        Generate a flag post style element to describe an object.
2119
2120        Arguments:
2121            txt : (str)
2122                Text to display. The default is the filename or the object name.
2123            point : (list)
2124                position of the flag anchor point. The default is None.
2125            offset : (list)
2126                a 3D displacement or offset. The default is None.
2127            s : (float)
2128                size of the text to be shown
2129            c : (list)
2130                color of text and line
2131            bc : (list)
2132                color of the flag background
2133            alpha : (float)
2134                opacity of text and box.
2135            lw : (int)
2136                line with of box frame. The default is 0.
2137            font : (str)
2138                font name. Use a monospace font for better rendering. The default is "Calco".
2139                Type `vedo -r fonts` for a font demo.
2140                Check [available fonts here](https://vedo.embl.es/fonts).
2141            justify : (str)
2142                internal text justification. The default is "center-left".
2143            vspacing : (float)
2144                vertical spacing between lines.
2145
2146        Examples:
2147            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
2148
2149            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2150        """
2151        if txt is None:
2152            if self.filename:
2153                txt = self.filename.split("/")[-1]
2154            elif self.name:
2155                txt = self.name
2156            else:
2157                return None
2158
2159        x0, x1, y0, y1, z0, z1 = self.bounds()
2160        d = self.diagonal_size()
2161        if point is None:
2162            if d:
2163                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2164            else:  # it's a Point
2165                point = self.transform.position
2166
2167        point = utils.make3d(point)
2168
2169        if offset is None:
2170            offset = [0, 0, (z1 - z0) / 2]
2171        offset = utils.make3d(offset)
2172
2173        fpost = vedo.addons.Flagpost(
2174            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2175        )
2176        self._caption = fpost
2177        return fpost
2178
2179    def caption(
2180        self,
2181        txt=None,
2182        point=None,
2183        size=(0.30, 0.15),
2184        padding=5,
2185        font="Calco",
2186        justify="center-right",
2187        vspacing=1.0,
2188        c=None,
2189        alpha=1.0,
2190        lw=1,
2191        ontop=True,
2192    ) -> Union["vtki.vtkCaptionActor2D", None]:
2193        """
2194        Create a 2D caption to an object which follows the camera movements.
2195        Latex is not supported. Returns the same input object for concatenation.
2196
2197        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2198        with similar functionality.
2199
2200        Arguments:
2201            txt : (str)
2202                text to be rendered. The default is the file name.
2203            point : (list)
2204                anchoring point. The default is None.
2205            size : (list)
2206                (width, height) of the caption box. The default is (0.30, 0.15).
2207            padding : (float)
2208                padding space of the caption box in pixels. The default is 5.
2209            font : (str)
2210                font name. Use a monospace font for better rendering. The default is "VictorMono".
2211                Type `vedo -r fonts` for a font demo.
2212                Check [available fonts here](https://vedo.embl.es/fonts).
2213            justify : (str)
2214                internal text justification. The default is "center-right".
2215            vspacing : (float)
2216                vertical spacing between lines. The default is 1.
2217            c : (str)
2218                text and box color. The default is 'lb'.
2219            alpha : (float)
2220                text and box transparency. The default is 1.
2221            lw : (int)
2222                line width in pixels. The default is 1.
2223            ontop : (bool)
2224                keep the 2d caption always on top. The default is True.
2225
2226        Examples:
2227            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2228
2229                ![](https://vedo.embl.es/images/pyplot/caption.png)
2230
2231            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2232            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2233        """
2234        if txt is None:
2235            if self.filename:
2236                txt = self.filename.split("/")[-1]
2237            elif self.name:
2238                txt = self.name
2239
2240        if not txt:  # disable it
2241            self._caption = None
2242            return None
2243
2244        for r in vedo.shapes._reps:
2245            txt = txt.replace(r[0], r[1])
2246
2247        if c is None:
2248            c = np.array(self.properties.GetColor()) / 2
2249        else:
2250            c = colors.get_color(c)
2251
2252        if point is None:
2253            x0, x1, y0, y1, _, z1 = self.dataset.GetBounds()
2254            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2255            point = self.closest_point(pt)
2256
2257        capt = vtki.vtkCaptionActor2D()
2258        capt.SetAttachmentPoint(point)
2259        capt.SetBorder(True)
2260        capt.SetLeader(True)
2261        sph = vtki.new("SphereSource")
2262        sph.Update()
2263        capt.SetLeaderGlyphData(sph.GetOutput())
2264        capt.SetMaximumLeaderGlyphSize(5)
2265        capt.SetPadding(int(padding))
2266        capt.SetCaption(txt)
2267        capt.SetWidth(size[0])
2268        capt.SetHeight(size[1])
2269        capt.SetThreeDimensionalLeader(not ontop)
2270
2271        pra = capt.GetProperty()
2272        pra.SetColor(c)
2273        pra.SetOpacity(alpha)
2274        pra.SetLineWidth(lw)
2275
2276        pr = capt.GetCaptionTextProperty()
2277        pr.SetFontFamily(vtki.VTK_FONT_FILE)
2278        fl = utils.get_font_path(font)
2279        pr.SetFontFile(fl)
2280        pr.ShadowOff()
2281        pr.BoldOff()
2282        pr.FrameOff()
2283        pr.SetColor(c)
2284        pr.SetOpacity(alpha)
2285        pr.SetJustificationToLeft()
2286        if "top" in justify:
2287            pr.SetVerticalJustificationToTop()
2288        if "bottom" in justify:
2289            pr.SetVerticalJustificationToBottom()
2290        if "cent" in justify:
2291            pr.SetVerticalJustificationToCentered()
2292            pr.SetJustificationToCentered()
2293        if "left" in justify:
2294            pr.SetJustificationToLeft()
2295        if "right" in justify:
2296            pr.SetJustificationToRight()
2297        pr.SetLineSpacing(vspacing)
2298        return capt
2299
2300
2301#####################################################################
2302class MeshVisual(PointsVisual):
2303    """Class to manage the visual aspects of a ``Maesh`` object."""
2304
2305    def __init__(self) -> None:
2306        # print("INIT MeshVisual", super())
2307        super().__init__()
2308
2309    def follow_camera(self, camera=None, origin=None) -> Self:
2310        """
2311        Return an object that will follow camera movements and stay locked to it.
2312        Use `mesh.follow_camera(False)` to disable it.
2313
2314        A `vtkCamera` object can also be passed.
2315        """
2316        if camera is False:
2317            try:
2318                self.SetCamera(None)
2319                return self
2320            except AttributeError:
2321                return self
2322
2323        factor = vtki.vtkFollower()
2324        factor.SetMapper(self.mapper)
2325        factor.SetProperty(self.properties)
2326        factor.SetBackfaceProperty(self.actor.GetBackfaceProperty())
2327        factor.SetTexture(self.actor.GetTexture())
2328        factor.SetScale(self.actor.GetScale())
2329        # factor.SetOrientation(self.actor.GetOrientation())
2330        factor.SetPosition(self.actor.GetPosition())
2331        factor.SetUseBounds(self.actor.GetUseBounds())
2332
2333        if origin is None:
2334            factor.SetOrigin(self.actor.GetOrigin())
2335        else:
2336            factor.SetOrigin(origin)
2337
2338        factor.PickableOff()
2339
2340        if isinstance(camera, vtki.vtkCamera):
2341            factor.SetCamera(camera)
2342        else:
2343            plt = vedo.plotter_instance
2344            if plt and plt.renderer and plt.renderer.GetActiveCamera():
2345                factor.SetCamera(plt.renderer.GetActiveCamera())
2346
2347        self.actor = None
2348        factor.retrieve_object = weak_ref_to(self)
2349        self.actor = factor
2350        return self
2351
2352    def wireframe(self, value=True) -> Self:
2353        """Set mesh's representation as wireframe or solid surface."""
2354        if value:
2355            self.properties.SetRepresentationToWireframe()
2356        else:
2357            self.properties.SetRepresentationToSurface()
2358        return self
2359
2360    def flat(self)  -> Self:
2361        """Set surface interpolation to flat.
2362
2363        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700">
2364        """
2365        self.properties.SetInterpolationToFlat()
2366        return self
2367
2368    def phong(self) -> Self:
2369        """Set surface interpolation to "phong"."""
2370        self.properties.SetInterpolationToPhong()
2371        return self
2372
2373    def backface_culling(self, value=True) -> Self:
2374        """Set culling of polygons based on orientation of normal with respect to camera."""
2375        self.properties.SetBackfaceCulling(value)
2376        return self
2377
2378    def render_lines_as_tubes(self, value=True) -> Self:
2379        """Wrap a fake tube around a simple line for visualization"""
2380        self.properties.SetRenderLinesAsTubes(value)
2381        return self
2382
2383    def frontface_culling(self, value=True) -> Self:
2384        """Set culling of polygons based on orientation of normal with respect to camera."""
2385        self.properties.SetFrontfaceCulling(value)
2386        return self
2387
2388    def backcolor(self, bc=None) -> Union[Self, np.ndarray]:
2389        """
2390        Set/get mesh's backface color.
2391        """
2392        back_prop = self.actor.GetBackfaceProperty()
2393
2394        if bc is None:
2395            if back_prop:
2396                return back_prop.GetDiffuseColor()
2397            return self
2398
2399        if self.properties.GetOpacity() < 1:
2400            return self
2401
2402        if not back_prop:
2403            back_prop = vtki.vtkProperty()
2404
2405        back_prop.SetDiffuseColor(colors.get_color(bc))
2406        back_prop.SetOpacity(self.properties.GetOpacity())
2407        self.actor.SetBackfaceProperty(back_prop)
2408        self.mapper.ScalarVisibilityOff()
2409        return self
2410
2411    def bc(self, backcolor=False) -> Union[Self, np.ndarray]:
2412        """Shortcut for `mesh.backcolor()`."""
2413        return self.backcolor(backcolor)
2414
2415    def linewidth(self, lw=None) -> Union[Self, int]:
2416        """Set/get width of mesh edges. Same as `lw()`."""
2417        if lw is not None:
2418            if lw == 0:
2419                self.properties.EdgeVisibilityOff()
2420                self.properties.SetRepresentationToSurface()
2421                return self
2422            self.properties.EdgeVisibilityOn()
2423            self.properties.SetLineWidth(lw)
2424        else:
2425            return self.properties.GetLineWidth()
2426        return self
2427
2428    def lw(self, linewidth=None) -> Union[Self, int]:
2429        """Set/get width of mesh edges. Same as `linewidth()`."""
2430        return self.linewidth(linewidth)
2431
2432    def linecolor(self, lc=None) -> Union[Self, np.ndarray]:
2433        """Set/get color of mesh edges. Same as `lc()`."""
2434        if lc is None:
2435            return np.array(self.properties.GetEdgeColor())
2436        self.properties.EdgeVisibilityOn()
2437        self.properties.SetEdgeColor(colors.get_color(lc))
2438        return self
2439
2440    def lc(self, linecolor=None) -> Union[Self, np.ndarray]:
2441        """Set/get color of mesh edges. Same as `linecolor()`."""
2442        return self.linecolor(linecolor)
2443
2444    def texture(
2445        self,
2446        tname,
2447        tcoords=None,
2448        interpolate=True,
2449        repeat=True,
2450        edge_clamp=False,
2451        scale=None,
2452        ushift=None,
2453        vshift=None,
2454    ) -> Self:
2455        """
2456        Assign a texture to mesh from image file or predefined texture `tname`.
2457        If tname is set to `None` texture is disabled.
2458        Input tname can also be an array or a `vtkTexture`.
2459
2460        Arguments:
2461            tname : (numpy.array, str, Image, vtkTexture, None)
2462                the input texture to be applied. Can be a numpy array, a path to an image file,
2463                a vedo Image. The None value disables texture.
2464            tcoords : (numpy.array, str)
2465                this is the (u,v) texture coordinate array. Can also be a string of an existing array
2466                in the mesh.
2467            interpolate : (bool)
2468                turn on/off linear interpolation of the texture map when rendering.
2469            repeat : (bool)
2470                repeat of the texture when tcoords extend beyond the [0,1] range.
2471            edge_clamp : (bool)
2472                turn on/off the clamping of the texture map when
2473                the texture coords extend beyond the [0,1] range.
2474                Only used when repeat is False, and edge clamping is supported by the graphics card.
2475            scale : (bool)
2476                scale the texture image by this factor
2477            ushift : (bool)
2478                shift u-coordinates of texture by this amount
2479            vshift : (bool)
2480                shift v-coordinates of texture by this amount
2481
2482        Examples:
2483            - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py)
2484
2485            ![](https://vedo.embl.es/images/basic/texturecubes.png)
2486        """
2487        pd = self.dataset
2488        out_img = None
2489
2490        if tname is None:  # disable texture
2491            pd.GetPointData().SetTCoords(None)
2492            pd.GetPointData().Modified()
2493            return self  ######################################
2494
2495        if isinstance(tname, vtki.vtkTexture):
2496            tu = tname
2497
2498        elif isinstance(tname, vedo.Image):
2499            tu = vtki.vtkTexture()
2500            out_img = tname.dataset
2501
2502        elif utils.is_sequence(tname):
2503            tu = vtki.vtkTexture()
2504            out_img = vedo.image._get_img(tname)
2505
2506        elif isinstance(tname, str):
2507            tu = vtki.vtkTexture()
2508
2509            if "https://" in tname:
2510                try:
2511                    tname = vedo.file_io.download(tname, verbose=False)
2512                except:
2513                    vedo.logger.error(f"texture {tname} could not be downloaded")
2514                    return self
2515
2516            fn = tname + ".jpg"
2517            if os.path.exists(tname):
2518                fn = tname
2519            else:
2520                vedo.logger.error(f"texture file {tname} does not exist")
2521                return self
2522
2523            fnl = fn.lower()
2524            if ".jpg" in fnl or ".jpeg" in fnl:
2525                reader = vtki.new("JPEGReader")
2526            elif ".png" in fnl:
2527                reader = vtki.new("PNGReader")
2528            elif ".bmp" in fnl:
2529                reader = vtki.new("BMPReader")
2530            else:
2531                vedo.logger.error("in texture() supported files are only PNG, BMP or JPG")
2532                return self
2533            reader.SetFileName(fn)
2534            reader.Update()
2535            out_img = reader.GetOutput()
2536
2537        else:
2538            vedo.logger.error(f"in texture() cannot understand input {type(tname)}")
2539            return self
2540
2541        if tcoords is not None:
2542
2543            if isinstance(tcoords, str):
2544                vtarr = pd.GetPointData().GetArray(tcoords)
2545
2546            else:
2547                tcoords = np.asarray(tcoords)
2548                if tcoords.ndim != 2:
2549                    vedo.logger.error("tcoords must be a 2-dimensional array")
2550                    return self
2551                if tcoords.shape[0] != pd.GetNumberOfPoints():
2552                    vedo.logger.error("nr of texture coords must match nr of points")
2553                    return self
2554                if tcoords.shape[1] != 2:
2555                    vedo.logger.error("tcoords texture vector must have 2 components")
2556                vtarr = utils.numpy2vtk(tcoords)
2557                vtarr.SetName("TCoordinates")
2558
2559            pd.GetPointData().SetTCoords(vtarr)
2560            pd.GetPointData().Modified()
2561
2562        elif not pd.GetPointData().GetTCoords():
2563
2564            # TCoords still void..
2565            # check that there are no texture-like arrays:
2566            names = self.pointdata.keys()
2567            candidate_arr = ""
2568            for name in names:
2569                vtarr = pd.GetPointData().GetArray(name)
2570                if vtarr.GetNumberOfComponents() != 2:
2571                    continue
2572                t0, t1 = vtarr.GetRange()
2573                if t0 >= 0 and t1 <= 1:
2574                    candidate_arr = name
2575
2576            if candidate_arr:
2577
2578                vtarr = pd.GetPointData().GetArray(candidate_arr)
2579                pd.GetPointData().SetTCoords(vtarr)
2580                pd.GetPointData().Modified()
2581
2582            else:
2583                # last resource is automatic mapping
2584                tmapper = vtki.new("TextureMapToPlane")
2585                tmapper.AutomaticPlaneGenerationOn()
2586                tmapper.SetInputData(pd)
2587                tmapper.Update()
2588                tc = tmapper.GetOutput().GetPointData().GetTCoords()
2589                if scale or ushift or vshift:
2590                    ntc = utils.vtk2numpy(tc)
2591                    if scale:
2592                        ntc *= scale
2593                    if ushift:
2594                        ntc[:, 0] += ushift
2595                    if vshift:
2596                        ntc[:, 1] += vshift
2597                    tc = utils.numpy2vtk(tc)
2598                pd.GetPointData().SetTCoords(tc)
2599                pd.GetPointData().Modified()
2600
2601        if out_img:
2602            tu.SetInputData(out_img)
2603        tu.SetInterpolate(interpolate)
2604        tu.SetRepeat(repeat)
2605        tu.SetEdgeClamp(edge_clamp)
2606
2607        self.properties.SetColor(1, 1, 1)
2608        self.mapper.ScalarVisibilityOff()
2609        self.actor.SetTexture(tu)
2610
2611        # if seam_threshold is not None:
2612        #     tname = self.dataset.GetPointData().GetTCoords().GetName()
2613        #     grad = self.gradient(tname)
2614        #     ugrad, vgrad = np.split(grad, 2, axis=1)
2615        #     ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad)
2616        #     gradm = np.log(ugradm + vgradm)
2617        #     largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4]
2618        #     uvmap = self.pointdata[tname]
2619        #     # collapse triangles that have large gradient
2620        #     new_points = self.vertices.copy()
2621        #     for f in self.cells:
2622        #         if np.isin(f, largegrad_ids).all():
2623        #             id1, id2, id3 = f
2624        #             uv1, uv2, uv3 = uvmap[f]
2625        #             d12 = utils.mag2(uv1 - uv2)
2626        #             d23 = utils.mag2(uv2 - uv3)
2627        #             d31 = utils.mag2(uv3 - uv1)
2628        #             idm = np.argmin([d12, d23, d31])
2629        #             if idm == 0:
2630        #                 new_points[id1] = new_points[id3]
2631        #                 new_points[id2] = new_points[id3]
2632        #             elif idm == 1:
2633        #                 new_points[id2] = new_points[id1]
2634        #                 new_points[id3] = new_points[id1]
2635        #     self.vertices = new_points
2636
2637        self.dataset.Modified()
2638        return self
2639
2640########################################################################################
2641class VolumeVisual(CommonVisual):
2642    """Class to manage the visual aspects of a ``Volume`` object."""
2643
2644    def __init__(self) -> None:
2645        # print("INIT VolumeVisual")
2646        super().__init__()
2647
2648    def alpha_unit(self, u=None) -> Union[Self, float]:
2649        """
2650        Defines light attenuation per unit length. Default is 1.
2651        The larger the unit length, the further light has to travel to attenuate the same amount.
2652
2653        E.g., if you set the unit distance to 0, you will get full opacity.
2654        It means that when light travels 0 distance it's already attenuated a finite amount.
2655        Thus, any finite distance should attenuate all light.
2656        The larger you make the unit distance, the more transparent the rendering becomes.
2657        """
2658        if u is None:
2659            return self.properties.GetScalarOpacityUnitDistance()
2660        self.properties.SetScalarOpacityUnitDistance(u)
2661        return self
2662
2663    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2664        """
2665        Assign a set of tranparencies to a volume's gradient
2666        along the range of the scalar value.
2667        A single constant value can also be assigned.
2668        The gradient function is used to decrease the opacity
2669        in the "flat" regions of the volume while maintaining the opacity
2670        at the boundaries between material types.  The gradient is measured
2671        as the amount by which the intensity changes over unit distance.
2672
2673        The format for alpha_grad is the same as for method `volume.alpha()`.
2674        """
2675        if vmin is None:
2676            vmin, _ = self.dataset.GetScalarRange()
2677        if vmax is None:
2678            _, vmax = self.dataset.GetScalarRange()
2679
2680        if alpha_grad is None:
2681            self.properties.DisableGradientOpacityOn()
2682            return self
2683
2684        self.properties.DisableGradientOpacityOff()
2685
2686        gotf = self.properties.GetGradientOpacity()
2687        if utils.is_sequence(alpha_grad):
2688            alpha_grad = np.array(alpha_grad)
2689            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2690                for i, al in enumerate(alpha_grad):
2691                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2692                    # Create transfer mapping scalar value to gradient opacity
2693                    gotf.AddPoint(xalpha, al)
2694            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2695                gotf.AddPoint(vmin, alpha_grad[0][1])
2696                for xalpha, al in alpha_grad:
2697                    # Create transfer mapping scalar value to opacity
2698                    gotf.AddPoint(xalpha, al)
2699                gotf.AddPoint(vmax, alpha_grad[-1][1])
2700            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2701        else:
2702            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2703            gotf.AddPoint(vmax, alpha_grad)
2704        return self
2705
2706    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2707        """Same as `color()`.
2708
2709        Arguments:
2710            alpha : (list)
2711                use a list to specify transparencies along the scalar range
2712            vmin : (float)
2713                force the min of the scalar range to be this value
2714            vmax : (float)
2715                force the max of the scalar range to be this value
2716        """
2717        return self.color(c, alpha, vmin, vmax)
2718
2719    def jittering(self, status=None) -> Union[Self, bool]:
2720        """
2721        If `True`, each ray traversal direction will be perturbed slightly
2722        using a noise-texture to get rid of wood-grain effects.
2723        """
2724        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2725            if status is None:
2726                return self.mapper.GetUseJittering()
2727            self.mapper.SetUseJittering(status)
2728        return self
2729
2730    def hide_voxels(self, ids) -> Self:
2731        """
2732        Hide voxels (cells) from visualization.
2733
2734        Example:
2735            ```python
2736            from vedo import *
2737            embryo = Volume(dataurl+'embryo.tif')
2738            embryo.hide_voxels(list(range(400000)))
2739            show(embryo, axes=1).close()
2740            ```
2741
2742        See also:
2743            `volume.mask()`
2744        """
2745        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2746        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2747        name = vtki.vtkDataSetAttributes.GhostArrayName()
2748        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2749        self.dataset.GetCellData().AddArray(garr)
2750        self.dataset.GetCellData().Modified()
2751        return self
2752
2753    def mask(self, data) -> Self:
2754        """
2755        Mask a volume visualization with a binary value.
2756        Needs to specify `volume.mapper = "gpu"`.
2757
2758        Example:
2759        ```python
2760        from vedo import np, Volume, show
2761        data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2762        # all voxels have value zero except:
2763        data_matrix[ 0:35,  0:35,  0:35] = 1
2764        data_matrix[35:55, 35:55, 35:55] = 2
2765        data_matrix[55:74, 55:74, 55:74] = 3
2766        vol = Volume(data_matrix).cmap('Blues')
2767        vol.mapper = "gpu"
2768        data_mask = np.zeros_like(data_matrix)
2769        data_mask[10:65, 10:60, 20:70] = 1
2770        vol.mask(data_mask)
2771        show(vol, axes=1).close()
2772        ```
2773        See also:
2774            `volume.hide_voxels()`
2775        """
2776        rdata = data.astype(np.uint8).ravel(order="F")
2777        varr = utils.numpy2vtk(rdata, name="input_mask")
2778
2779        img = vtki.vtkImageData()
2780        img.SetDimensions(self.dimensions())
2781        img.GetPointData().AddArray(varr)
2782        img.GetPointData().SetActiveScalars(varr.GetName())
2783
2784        try:
2785            self.mapper.SetMaskTypeToBinary()
2786            self.mapper.SetMaskInput(img)
2787        except AttributeError:
2788            vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'")
2789        return self
2790
2791
2792    def mode(self, mode=None) -> Union[Self, int]:
2793        """
2794        Define the volumetric rendering mode following this:
2795            - 0, composite rendering
2796            - 1, maximum projection rendering
2797            - 2, minimum projection rendering
2798            - 3, average projection rendering
2799            - 4, additive mode
2800
2801        The default mode is "composite" where the scalar values are sampled through
2802        the volume and composited in a front-to-back scheme through alpha blending.
2803        The final color and opacity is determined using the color and opacity transfer
2804        functions specified in alpha keyword.
2805
2806        Maximum and minimum intensity blend modes use the maximum and minimum
2807        scalar values, respectively, along the sampling ray.
2808        The final color and opacity is determined by passing the resultant value
2809        through the color and opacity transfer functions.
2810
2811        Additive blend mode accumulates scalar values by passing each value
2812        through the opacity transfer function and then adding up the product
2813        of the value and its opacity. In other words, the scalar values are scaled
2814        using the opacity transfer function and summed to derive the final color.
2815        Note that the resulting image is always grayscale i.e. aggregated values
2816        are not passed through the color transfer function.
2817        This is because the final value is a derived value and not a real data value
2818        along the sampling ray.
2819
2820        Average intensity blend mode works similar to the additive blend mode where
2821        the scalar values are multiplied by opacity calculated from the opacity
2822        transfer function and then added.
2823        The additional step here is to divide the sum by the number of samples
2824        taken through the volume.
2825        As is the case with the additive intensity projection, the final image will
2826        always be grayscale i.e. the aggregated values are not passed through the
2827        color transfer function.
2828        """
2829        if mode is None:
2830            return self.mapper.GetBlendMode()
2831
2832        if isinstance(mode, str):
2833            if "comp" in mode:
2834                mode = 0
2835            elif "proj" in mode:
2836                if "max" in mode:
2837                    mode = 1
2838                elif "min" in mode:
2839                    mode = 2
2840                elif "ave" in mode:
2841                    mode = 3
2842                else:
2843                    vedo.logger.warning(f"unknown mode {mode}")
2844                    mode = 0
2845            elif "add" in mode:
2846                mode = 4
2847            else:
2848                vedo.logger.warning(f"unknown mode {mode}")
2849                mode = 0
2850
2851        self.mapper.SetBlendMode(mode)
2852        return self
2853
2854    def shade(self, status=None) -> Union[Self, bool]:
2855        """
2856        Set/Get the shading of a Volume.
2857        Shading can be further controlled with `volume.lighting()` method.
2858
2859        If shading is turned on, the mapper may perform shading calculations.
2860        In some cases shading does not apply
2861        (for example, in maximum intensity projection mode).
2862        """
2863        if status is None:
2864            return self.properties.GetShade()
2865        self.properties.SetShade(status)
2866        return self
2867
2868    def interpolation(self, itype) -> Self:
2869        """
2870        Set interpolation type.
2871
2872        0=nearest neighbour, 1=linear
2873        """
2874        self.properties.SetInterpolationType(itype)
2875        return self
2876
2877
2878########################################################################################
2879class ImageVisual(CommonVisual, Actor3DHelper):
2880
2881    def __init__(self) -> None:
2882        # print("init ImageVisual")
2883        super().__init__()
2884
2885    def memory_size(self) -> int:
2886        """
2887        Return the size in bytes of the object in memory.
2888        """
2889        return self.dataset.GetActualMemorySize()
2890
2891    def scalar_range(self) -> np.ndarray:
2892        """
2893        Return the scalar range of the image.
2894        """
2895        return np.array(self.dataset.GetScalarRange())
2896
2897    def alpha(self, a=None) -> Union[Self, float]:
2898        """Set/get image's transparency in the rendering scene."""
2899        if a is not None:
2900            self.properties.SetOpacity(a)
2901            return self
2902        return self.properties.GetOpacity()
2903
2904    def level(self, value=None) -> Union[Self, float]:
2905        """Get/Set the image color level (brightness) in the rendering scene."""
2906        if value is None:
2907            return self.properties.GetColorLevel()
2908        self.properties.SetColorLevel(value)
2909        return self
2910
2911    def window(self, value=None) -> Union[Self, float]:
2912        """Get/Set the image color window (contrast) in the rendering scene."""
2913        if value is None:
2914            return self.properties.GetColorWindow()
2915        self.properties.SetColorWindow(value)
2916        return self
2917
2918
2919class LightKit:
2920    """
2921    A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'.
2922    
2923    The main light is the key light. It is usually positioned so that it appears like
2924    an overhead light (like the sun, or a ceiling light).
2925    It is generally positioned to shine down on the scene from about a 45 degree angle vertically
2926    and at least a little offset side to side. The key light usually at least about twice as bright
2927    as the total of all other lights in the scene to provide good modeling of object features.
2928
2929    The other lights in the kit (the fill light, headlight, and a pair of back lights)
2930    are weaker sources that provide extra illumination to fill in the spots that the key light misses.
2931    The fill light is usually positioned across from or opposite from the key light
2932    (though still on the same side of the object as the camera) in order to simulate diffuse reflections
2933    from other objects in the scene. 
2934    
2935    The headlight, always located at the position of the camera, reduces the contrast between areas lit
2936    by the key and fill light. The two back lights, one on the left of the object as seen from the observer
2937    and one on the right, fill on the high-contrast areas behind the object.
2938    To enforce the relationship between the different lights, the intensity of the fill, back and headlights
2939    are set as a ratio to the key light brightness.
2940    Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity.
2941
2942    All lights are directional lights, infinitely far away with no falloff. Lights move with the camera.
2943
2944    For simplicity, the position of lights in the LightKit can only be specified using angles:
2945    the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees.
2946    For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight).
2947    A light at (elevation=90, azimuth=0) is above the lookat point, shining down.
2948    Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise.
2949    So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining
2950    slightly from the left side.
2951
2952    LightKit limits the colors that can be assigned to any light to those of incandescent sources such as
2953    light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors
2954    can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red.
2955    Colors close to 0.5 are "cool whites" and "warm whites," respectively.
2956
2957    Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight
2958    ratios are skewed by key, fill, and headlight colors. If `maintain_luminance` is set, LightKit will
2959    attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors.
2960
2961    To specify the color of a light, positioning etc you can pass a dictionary with the following keys:
2962        - `intensity` : (float) The intensity of the key light. Default is 1.
2963        - `ratio`     : (float) The ratio of the light intensity wrt key light.
2964        - `warmth`    : (float) The warmth of the light. Default is 0.5.
2965        - `elevation` : (float) The elevation of the light in degrees.
2966        - `azimuth`   : (float) The azimuth of the light in degrees.
2967
2968    Example:
2969        ```python
2970        from vedo import *
2971        lightkit = LightKit(head={"warmth":0.6})
2972        mesh = Mesh(dataurl+"bunny.obj")
2973        plt = Plotter()
2974        plt.remove_lights().add(mesh, lightkit)
2975        plt.show().close()
2976        ```
2977    """
2978    def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None:
2979
2980        self.lightkit = vtki.new("LightKit")
2981        self.lightkit.SetMaintainLuminance(maintain_luminance)
2982        self.key  = dict(key)
2983        self.head = dict(head)
2984        self.fill = dict(fill)
2985        self.back = dict(back)
2986        self.update()
2987
2988    def update(self) -> None:
2989        """Update the LightKit properties."""
2990        if "warmth" in self.key:
2991            self.lightkit.SetKeyLightWarmth(self.key["warmth"])
2992        if "warmth" in self.fill:
2993            self.lightkit.SetFillLightWarmth(self.fill["warmth"])
2994        if "warmth" in self.head:
2995            self.lightkit.SetHeadLightWarmth(self.head["warmth"])
2996        if "warmth" in self.back:
2997            self.lightkit.SetBackLightWarmth(self.back["warmth"])
2998
2999        if "intensity" in self.key:
3000            self.lightkit.SetKeyLightIntensity(self.key["intensity"])
3001        if "ratio" in self.fill:
3002            self.lightkit.SetKeyToFillRatio(self.key["ratio"])
3003        if "ratio" in self.head:
3004            self.lightkit.SetKeyToHeadRatio(self.key["ratio"])
3005        if "ratio" in self.back:
3006            self.lightkit.SetKeyToBackRatio(self.key["ratio"])
3007
3008        if "elevation" in self.key:
3009            self.lightkit.SetKeyLightElevation(self.key["elevation"])
3010        if "elevation" in self.fill:
3011            self.lightkit.SetFillLightElevation(self.fill["elevation"])
3012        if "elevation" in self.head:
3013            self.lightkit.SetHeadLightElevation(self.head["elevation"])
3014        if "elevation" in self.back:
3015            self.lightkit.SetBackLightElevation(self.back["elevation"])
3016
3017        if "azimuth" in self.key:
3018            self.lightkit.SetKeyLightAzimuth(self.key["azimuth"])
3019        if "azimuth" in self.fill:
3020            self.lightkit.SetFillLightAzimuth(self.fill["azimuth"])
3021        if "azimuth" in self.head:
3022            self.lightkit.SetHeadLightAzimuth(self.head["azimuth"])
3023        if "azimuth" in self.back:
3024            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            self.trail.initilized = False # so the first update will be a reset
1504        return self
1505
1506    def update_trail(self) -> Self:
1507        """
1508        Update the trailing line of a moving object.
1509        """
1510        currentpos = self.pos()
1511        if not self.trail.initilized:
1512            self.trail_points = [currentpos] * self.trail.npoints
1513            self.trail.initilized = True
1514            return self
1515        self.trail_points.append(currentpos)  # cycle
1516        self.trail_points.pop(0)
1517
1518        data = np.array(self.trail_points) + self.trail_offset
1519        tpoly = self.trail.dataset
1520        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1521        return self
1522
1523    def _compute_shadow(self, plane, point, direction):
1524        shad = self.clone()
1525        shad.name = "Shadow"
1526
1527        tarr = shad.dataset.GetPointData().GetTCoords()
1528        if tarr:  # remove any texture coords
1529            tname = tarr.GetName()
1530            shad.pointdata.remove(tname)
1531            shad.dataset.GetPointData().SetTCoords(None)
1532            shad.actor.SetTexture(None)
1533
1534        pts = shad.vertices
1535        if plane == "x":
1536            # shad = shad.project_on_plane('x')
1537            # instead do it manually so in case of alpha<1
1538            # we dont see glitches due to coplanar points
1539            # we leave a small tolerance of 0.1% in thickness
1540            x0, x1 = self.xbounds()
1541            pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[0]
1542            shad.vertices = pts
1543            shad.x(point)
1544        elif plane == "y":
1545            x0, x1 = self.ybounds()
1546            pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[1]
1547            shad.vertices = pts
1548            shad.y(point)
1549        elif plane == "z":
1550            x0, x1 = self.zbounds()
1551            pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[2]
1552            shad.vertices = pts
1553            shad.z(point)
1554        else:
1555            shad = shad.project_on_plane(plane, point, direction)
1556        return shad
1557
1558    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1559        """
1560        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1561        The output is a new `Mesh` representing the shadow.
1562        This new mesh is accessible through `mesh.shadow`.
1563        By default the shadow mesh is placed on the bottom wall of the bounding box.
1564
1565        See also `pointcloud.project_on_plane()`.
1566
1567        Arguments:
1568            plane : (str, Plane)
1569                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1570                represents x-plane, y-plane and z-plane, respectively.
1571                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1572            point : (float, array)
1573                if plane is `str`, point should be a float represents the intercept.
1574                Otherwise, point is the camera point of perspective projection
1575            direction : (list)
1576                direction of oblique projection
1577            culling : (int)
1578                choose between front [1] or backface [-1] culling or None.
1579
1580        Examples:
1581            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1582            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1583            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1584
1585            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1586        """
1587        shad = self._compute_shadow(plane, point, direction)
1588        shad.c(c).alpha(alpha)
1589
1590        try:
1591            # Points dont have these methods
1592            shad.flat()
1593            if culling in (1, True):
1594                shad.frontface_culling()
1595            elif culling == -1:
1596                shad.backface_culling()
1597        except AttributeError:
1598            pass
1599
1600        shad.properties.LightingOff()
1601        shad.actor.SetPickable(False)
1602        shad.actor.SetUseBounds(True)
1603
1604        if shad not in self.shadows:
1605            self.shadows.append(shad)
1606            shad.info = dict(plane=plane, point=point, direction=direction)
1607            # shad.metadata["plane"] = plane
1608            # shad.metadata["point"] = point
1609            # print("AAAA", direction, plane, point)
1610            # if direction is None:
1611            #     direction = [0,0,0]
1612            # shad.metadata["direction"] = direction
1613        return self
1614
1615    def update_shadows(self) -> Self:
1616        """Update the shadows of a moving object."""
1617        for sha in self.shadows:
1618            plane = sha.info["plane"]
1619            point = sha.info["point"]
1620            direction = sha.info["direction"]
1621            # print("update_shadows direction", direction,plane,point )
1622            # plane = sha.metadata["plane"]
1623            # point = sha.metadata["point"]
1624            # direction = sha.metadata["direction"]
1625            # if direction[0]==0 and direction[1]==0 and direction[2]==0:
1626            #     direction = None
1627            # print("BBBB", sha.metadata["direction"], 
1628            #       sha.metadata["plane"], sha.metadata["point"])
1629            new_sha = self._compute_shadow(plane, point, direction)
1630            sha._update(new_sha.dataset)
1631        if self.trail:
1632            self.trail.update_shadows()
1633        return self
1634
1635    def labels(
1636        self,
1637        content=None,
1638        on="points",
1639        scale=None,
1640        xrot=0.0,
1641        yrot=0.0,
1642        zrot=0.0,
1643        ratio=1,
1644        precision=None,
1645        italic=False,
1646        font="",
1647        justify="",
1648        c="black",
1649        alpha=1.0,
1650    ) -> Union["vedo.Mesh", None]:
1651        """
1652        Generate value or ID labels for mesh cells or points.
1653        For large nr. of labels use `font="VTK"` which is much faster.
1654
1655        See also:
1656            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1657
1658        Arguments:
1659            content : (list,int,str)
1660                either 'id', 'cellid', array name or array number.
1661                A array can also be passed (must match the nr. of points or cells).
1662            on : (str)
1663                generate labels for "cells" instead of "points"
1664            scale : (float)
1665                absolute size of labels, if left as None it is automatic
1666            zrot : (float)
1667                local rotation angle of label in degrees
1668            ratio : (int)
1669                skipping ratio, to reduce nr of labels for large meshes
1670            precision : (int)
1671                numeric precision of labels
1672
1673        ```python
1674        from vedo import *
1675        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1676        point_ids = s.labels('id', on="points").c('green')
1677        cell_ids  = s.labels('id', on="cells" ).c('black')
1678        show(s, point_ids, cell_ids)
1679        ```
1680        ![](https://vedo.embl.es/images/feats/labels.png)
1681
1682        Examples:
1683            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1684
1685                ![](https://vedo.embl.es/images/basic/boundaries.png)
1686        """
1687        
1688        cells = False
1689        if "cell" in on or "face" in on:
1690            cells = True
1691            justify = "centered" if justify == "" else justify
1692
1693        if isinstance(content, str):
1694            if content in ("pointid", "pointsid"):
1695                cells = False
1696                content = "id"
1697                justify = "bottom-left" if justify == "" else justify
1698            if content in ("cellid", "cellsid"):
1699                cells = True
1700                content = "id"
1701                justify = "centered" if justify == "" else justify
1702
1703        try:
1704            if cells:
1705                ns = np.sqrt(self.ncells)
1706                elems = self.cell_centers
1707                norms = self.cell_normals
1708                justify = "centered" if justify == "" else justify
1709            else:
1710                ns = np.sqrt(self.npoints)
1711                elems = self.vertices
1712                norms = self.vertex_normals
1713        except AttributeError:
1714            norms = []
1715        
1716        if not justify:
1717            justify = "bottom-left"
1718
1719        hasnorms = False
1720        if len(norms) > 0:
1721            hasnorms = True
1722
1723        if scale is None:
1724            if not ns:
1725                ns = 100
1726            scale = self.diagonal_size() / ns / 10
1727
1728        arr = None
1729        mode = 0
1730        if content is None:
1731            mode = 0
1732            if cells:
1733                if self.dataset.GetCellData().GetScalars():
1734                    name = self.dataset.GetCellData().GetScalars().GetName()
1735                    arr = self.celldata[name]
1736            else:
1737                if self.dataset.GetPointData().GetScalars():
1738                    name = self.dataset.GetPointData().GetScalars().GetName()
1739                    arr = self.pointdata[name]
1740        elif isinstance(content, (str, int)):
1741            if content == "id":
1742                mode = 1
1743            elif cells:
1744                mode = 0
1745                arr = self.celldata[content]
1746            else:
1747                mode = 0
1748                arr = self.pointdata[content]
1749        elif utils.is_sequence(content):
1750            mode = 0
1751            arr = content
1752
1753        if arr is None and mode == 0:
1754            vedo.logger.error("in labels(), array not found in point or cell data")
1755            return None
1756
1757        ratio = int(ratio+0.5)
1758        tapp = vtki.new("AppendPolyData")
1759        has_inputs = False
1760
1761        for i, e in enumerate(elems):
1762            if i % ratio:
1763                continue
1764
1765            if mode == 1:
1766                txt_lab = str(i)
1767            else:
1768                if precision:
1769                    txt_lab = utils.precision(arr[i], precision)
1770                else:
1771                    txt_lab = str(arr[i])
1772
1773            if not txt_lab:
1774                continue
1775
1776            if font == "VTK":
1777                tx = vtki.new("VectorText")
1778                tx.SetText(txt_lab)
1779                tx.Update()
1780                tx_poly = tx.GetOutput()
1781            else:
1782                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset
1783
1784            if tx_poly.GetNumberOfPoints() == 0:
1785                continue  ######################
1786
1787            T = vtki.vtkTransform()
1788            T.PostMultiply()
1789            if italic:
1790                T.Concatenate([1, 0.2, 0, 0,
1791                               0, 1  , 0, 0,
1792                               0, 0  , 1, 0,
1793                               0, 0  , 0, 1])
1794            if hasnorms:
1795                ni = norms[i]
1796                if cells and font=="VTK":  # center-justify
1797                    bb = tx_poly.GetBounds()
1798                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1799                    T.Translate(-dx, -dy, 0)
1800                if xrot: T.RotateX(xrot)
1801                if yrot: T.RotateY(yrot)
1802                if zrot: T.RotateZ(zrot)
1803                crossvec = np.cross([0, 0, 1], ni)
1804                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1805                T.RotateWXYZ(float(angle), crossvec.tolist())
1806                T.Translate(ni / 100)
1807            else:
1808                if xrot: T.RotateX(xrot)
1809                if yrot: T.RotateY(yrot)
1810                if zrot: T.RotateZ(zrot)
1811            T.Scale(scale, scale, scale)
1812            T.Translate(e)
1813            tf = vtki.new("TransformPolyDataFilter")
1814            tf.SetInputData(tx_poly)
1815            tf.SetTransform(T)
1816            tf.Update()
1817            tapp.AddInputData(tf.GetOutput())
1818            has_inputs = True
1819
1820        if has_inputs:
1821            tapp.Update()
1822            lpoly = tapp.GetOutput()
1823        else:
1824            lpoly = vtki.vtkPolyData()
1825        ids = vedo.Mesh(lpoly, c=c, alpha=alpha)
1826        ids.properties.LightingOff()
1827        ids.actor.PickableOff()
1828        ids.actor.SetUseBounds(False)
1829        ids.name = "Labels"
1830        return ids
1831
1832    def labels2d(
1833        self,
1834        content="id",
1835        on="points",
1836        scale=1.0,
1837        precision=4,
1838        font="Calco",
1839        justify="bottom-left",
1840        angle=0.0,
1841        frame=False,
1842        c="black",
1843        bc=None,
1844        alpha=1.0,
1845    ) -> Union["Actor2D", None]:
1846        """
1847        Generate value or ID bi-dimensional labels for mesh cells or points.
1848
1849        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
1850
1851        Arguments:
1852            content : (str)
1853                either 'id', 'cellid', or array name
1854            on : (str)
1855                generate labels for "cells" instead of "points" (the default)
1856            scale : (float)
1857                size scaling of labels
1858            precision : (int)
1859                precision of numeric labels
1860            angle : (float)
1861                local rotation angle of label in degrees
1862            frame : (bool)
1863                draw a frame around the label
1864            bc : (str)
1865                background color of the label
1866
1867        ```python
1868        from vedo import Sphere, show
1869        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
1870        sph.celldata["zvals"] = sph.cell_centers[:,2]
1871        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
1872        show(sph, l2d, axes=1).close()
1873        ```
1874        ![](https://vedo.embl.es/images/feats/labels2d.png)
1875        """
1876        cells = False
1877        if "cell" in on:
1878            cells = True
1879
1880        if isinstance(content, str):
1881            if content in ("id", "pointid", "pointsid"):
1882                cells = False
1883                content = "id"
1884            if content in ("cellid", "cellsid"):
1885                cells = True
1886                content = "id"
1887
1888        if cells:
1889            if content != "id" and content not in self.celldata.keys():
1890                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
1891                return None
1892            cellcloud = vedo.Points(self.cell_centers)
1893            arr = self.dataset.GetCellData().GetScalars()
1894            poly = cellcloud.dataset
1895            poly.GetPointData().SetScalars(arr)
1896        else:
1897            poly = self.dataset
1898            if content != "id" and content not in self.pointdata.keys():
1899                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
1900                return None
1901
1902        mp = vtki.new("LabeledDataMapper")
1903
1904        if content == "id":
1905            mp.SetLabelModeToLabelIds()
1906        else:
1907            mp.SetLabelModeToLabelScalars()
1908            if precision is not None:
1909                mp.SetLabelFormat(f"%-#.{precision}g")
1910
1911        pr = mp.GetLabelTextProperty()
1912        c = colors.get_color(c)
1913        pr.SetColor(c)
1914        pr.SetOpacity(alpha)
1915        pr.SetFrame(frame)
1916        pr.SetFrameColor(c)
1917        pr.SetItalic(False)
1918        pr.BoldOff()
1919        pr.ShadowOff()
1920        pr.UseTightBoundingBoxOn()
1921        pr.SetOrientation(angle)
1922        pr.SetFontFamily(vtki.VTK_FONT_FILE)
1923        fl = utils.get_font_path(font)
1924        pr.SetFontFile(fl)
1925        pr.SetFontSize(int(20 * scale))
1926
1927        if "cent" in justify or "mid" in justify:
1928            pr.SetJustificationToCentered()
1929        elif "rig" in justify:
1930            pr.SetJustificationToRight()
1931        elif "left" in justify:
1932            pr.SetJustificationToLeft()
1933        # ------
1934        if "top" in justify:
1935            pr.SetVerticalJustificationToTop()
1936        else:
1937            pr.SetVerticalJustificationToBottom()
1938
1939        if bc is not None:
1940            bc = colors.get_color(bc)
1941            pr.SetBackgroundColor(bc)
1942            pr.SetBackgroundOpacity(alpha)
1943
1944        mp.SetInputData(poly)
1945        a2d = Actor2D()
1946        a2d.PickableOff()
1947        a2d.SetMapper(mp)
1948        return a2d
1949
1950    def legend(self, txt) -> Self:
1951        """Book a legend text."""
1952        self.info["legend"] = txt
1953        # self.metadata["legend"] = txt
1954        return self
1955
1956    def flagpole(
1957        self,
1958        txt=None,
1959        point=None,
1960        offset=None,
1961        s=None,
1962        font="Calco",
1963        rounded=True,
1964        c=None,
1965        alpha=1.0,
1966        lw=2,
1967        italic=0.0,
1968        padding=0.1,
1969    ) -> Union["vedo.Mesh", None]:
1970        """
1971        Generate a flag pole style element to describe an object.
1972        Returns a `Mesh` object.
1973
1974        Use flagpole.follow_camera() to make it face the camera in the scene.
1975
1976        Consider using `settings.use_parallel_projection = True` 
1977        to avoid perspective distortions.
1978
1979        See also `flagpost()`.
1980
1981        Arguments:
1982            txt : (str)
1983                Text to display. The default is the filename or the object name.
1984            point : (list)
1985                position of the flagpole pointer. 
1986            offset : (list)
1987                text offset wrt the application point. 
1988            s : (float)
1989                size of the flagpole.
1990            font : (str)
1991                font face. Check [available fonts here](https://vedo.embl.es/fonts).
1992            rounded : (bool)
1993                draw a rounded or squared box around the text.
1994            c : (list)
1995                text and box color.
1996            alpha : (float)
1997                opacity of text and box.
1998            lw : (float)
1999                line with of box frame.
2000            italic : (float)
2001                italicness of text.
2002
2003        Examples:
2004            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2005
2006                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2007
2008            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2009            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2010            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2011        """
2012        objs = []
2013
2014        if txt is None:
2015            if self.filename:
2016                txt = self.filename.split("/")[-1]
2017            elif self.name:
2018                txt = self.name
2019            else:
2020                return None
2021
2022        x0, x1, y0, y1, z0, z1 = self.bounds()
2023        d = self.diagonal_size()
2024        if point is None:
2025            if d:
2026                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2027                # point = self.closest_point([x1, y0, z1])
2028            else:  # it's a Point
2029                point = self.transform.position
2030
2031        pt = utils.make3d(point)
2032
2033        if offset is None:
2034            offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0]
2035        offset = utils.make3d(offset)
2036
2037        if s is None:
2038            s = d / 20
2039
2040        sph = None
2041        if d and (z1 - z0) / d > 0.1:
2042            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2043
2044        if c is None:
2045            c = np.array(self.color()) / 1.4
2046
2047        lab = vedo.shapes.Text3D(
2048            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center"
2049        )
2050        objs.append(lab)
2051
2052        if d and not sph:
2053            sph = vedo.shapes.Circle(pt, r=s / 3, res=16)
2054        objs.append(sph)
2055
2056        x0, x1, y0, y1, z0, z1 = lab.bounds()
2057        aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)]
2058        if rounded:
2059            box = vedo.shapes.KSpline(aline, closed=True)
2060        else:
2061            box = vedo.shapes.Line(aline, closed=True)
2062
2063        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2064
2065        # box.actor.SetOrigin(cnt)
2066        box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt)
2067        objs.append(box)
2068
2069        x0, x1, y0, y1, z0, z1 = box.bounds()
2070        if x0 < pt[0] < x1:
2071            c0 = box.closest_point(pt)
2072            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2073        elif (pt[0] - x0) < (x1 - pt[0]):
2074            c0 = [x0, (y0 + y1) / 2, pt[2]]
2075            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2076        else:
2077            c0 = [x1, (y0 + y1) / 2, pt[2]]
2078            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2079
2080        con = vedo.shapes.Line([c0, c1, pt])
2081        objs.append(con)
2082
2083        mobjs = vedo.merge(objs).c(c).alpha(alpha)
2084        mobjs.name = "FlagPole"
2085        mobjs.bc("tomato").pickable(False)
2086        mobjs.properties.LightingOff()
2087        mobjs.properties.SetLineWidth(lw)
2088        mobjs.actor.UseBoundsOff()
2089        mobjs.actor.SetPosition([0,0,0])
2090        mobjs.actor.SetOrigin(pt)
2091        return mobjs
2092
2093        # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha)
2094        # mobjs.name = "FlagPole"
2095        # # mobjs.bc("tomato").pickable(False)
2096        # # mobjs.properties.LightingOff()
2097        # # mobjs.properties.SetLineWidth(lw)
2098        # # mobjs.actor.UseBoundsOff()
2099        # # mobjs.actor.SetPosition([0,0,0])
2100        # # mobjs.actor.SetOrigin(pt)
2101        # # print(pt)
2102        # return mobjs
2103
2104    def flagpost(
2105        self,
2106        txt=None,
2107        point=None,
2108        offset=None,
2109        s=1.0,
2110        c="k9",
2111        bc="k1",
2112        alpha=1,
2113        lw=0,
2114        font="Calco",
2115        justify="center-left",
2116        vspacing=1.0,
2117    ) -> Union["vedo.addons.Flagpost", None]:
2118        """
2119        Generate a flag post style element to describe an object.
2120
2121        Arguments:
2122            txt : (str)
2123                Text to display. The default is the filename or the object name.
2124            point : (list)
2125                position of the flag anchor point. The default is None.
2126            offset : (list)
2127                a 3D displacement or offset. The default is None.
2128            s : (float)
2129                size of the text to be shown
2130            c : (list)
2131                color of text and line
2132            bc : (list)
2133                color of the flag background
2134            alpha : (float)
2135                opacity of text and box.
2136            lw : (int)
2137                line with of box frame. The default is 0.
2138            font : (str)
2139                font name. Use a monospace font for better rendering. The default is "Calco".
2140                Type `vedo -r fonts` for a font demo.
2141                Check [available fonts here](https://vedo.embl.es/fonts).
2142            justify : (str)
2143                internal text justification. The default is "center-left".
2144            vspacing : (float)
2145                vertical spacing between lines.
2146
2147        Examples:
2148            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
2149
2150            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2151        """
2152        if txt is None:
2153            if self.filename:
2154                txt = self.filename.split("/")[-1]
2155            elif self.name:
2156                txt = self.name
2157            else:
2158                return None
2159
2160        x0, x1, y0, y1, z0, z1 = self.bounds()
2161        d = self.diagonal_size()
2162        if point is None:
2163            if d:
2164                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2165            else:  # it's a Point
2166                point = self.transform.position
2167
2168        point = utils.make3d(point)
2169
2170        if offset is None:
2171            offset = [0, 0, (z1 - z0) / 2]
2172        offset = utils.make3d(offset)
2173
2174        fpost = vedo.addons.Flagpost(
2175            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2176        )
2177        self._caption = fpost
2178        return fpost
2179
2180    def caption(
2181        self,
2182        txt=None,
2183        point=None,
2184        size=(0.30, 0.15),
2185        padding=5,
2186        font="Calco",
2187        justify="center-right",
2188        vspacing=1.0,
2189        c=None,
2190        alpha=1.0,
2191        lw=1,
2192        ontop=True,
2193    ) -> Union["vtki.vtkCaptionActor2D", None]:
2194        """
2195        Create a 2D caption to an object which follows the camera movements.
2196        Latex is not supported. Returns the same input object for concatenation.
2197
2198        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2199        with similar functionality.
2200
2201        Arguments:
2202            txt : (str)
2203                text to be rendered. The default is the file name.
2204            point : (list)
2205                anchoring point. The default is None.
2206            size : (list)
2207                (width, height) of the caption box. The default is (0.30, 0.15).
2208            padding : (float)
2209                padding space of the caption box in pixels. The default is 5.
2210            font : (str)
2211                font name. Use a monospace font for better rendering. The default is "VictorMono".
2212                Type `vedo -r fonts` for a font demo.
2213                Check [available fonts here](https://vedo.embl.es/fonts).
2214            justify : (str)
2215                internal text justification. The default is "center-right".
2216            vspacing : (float)
2217                vertical spacing between lines. The default is 1.
2218            c : (str)
2219                text and box color. The default is 'lb'.
2220            alpha : (float)
2221                text and box transparency. The default is 1.
2222            lw : (int)
2223                line width in pixels. The default is 1.
2224            ontop : (bool)
2225                keep the 2d caption always on top. The default is True.
2226
2227        Examples:
2228            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2229
2230                ![](https://vedo.embl.es/images/pyplot/caption.png)
2231
2232            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2233            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2234        """
2235        if txt is None:
2236            if self.filename:
2237                txt = self.filename.split("/")[-1]
2238            elif self.name:
2239                txt = self.name
2240
2241        if not txt:  # disable it
2242            self._caption = None
2243            return None
2244
2245        for r in vedo.shapes._reps:
2246            txt = txt.replace(r[0], r[1])
2247
2248        if c is None:
2249            c = np.array(self.properties.GetColor()) / 2
2250        else:
2251            c = colors.get_color(c)
2252
2253        if point is None:
2254            x0, x1, y0, y1, _, z1 = self.dataset.GetBounds()
2255            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2256            point = self.closest_point(pt)
2257
2258        capt = vtki.vtkCaptionActor2D()
2259        capt.SetAttachmentPoint(point)
2260        capt.SetBorder(True)
2261        capt.SetLeader(True)
2262        sph = vtki.new("SphereSource")
2263        sph.Update()
2264        capt.SetLeaderGlyphData(sph.GetOutput())
2265        capt.SetMaximumLeaderGlyphSize(5)
2266        capt.SetPadding(int(padding))
2267        capt.SetCaption(txt)
2268        capt.SetWidth(size[0])
2269        capt.SetHeight(size[1])
2270        capt.SetThreeDimensionalLeader(not ontop)
2271
2272        pra = capt.GetProperty()
2273        pra.SetColor(c)
2274        pra.SetOpacity(alpha)
2275        pra.SetLineWidth(lw)
2276
2277        pr = capt.GetCaptionTextProperty()
2278        pr.SetFontFamily(vtki.VTK_FONT_FILE)
2279        fl = utils.get_font_path(font)
2280        pr.SetFontFile(fl)
2281        pr.ShadowOff()
2282        pr.BoldOff()
2283        pr.FrameOff()
2284        pr.SetColor(c)
2285        pr.SetOpacity(alpha)
2286        pr.SetJustificationToLeft()
2287        if "top" in justify:
2288            pr.SetVerticalJustificationToTop()
2289        if "bottom" in justify:
2290            pr.SetVerticalJustificationToBottom()
2291        if "cent" in justify:
2292            pr.SetVerticalJustificationToCentered()
2293            pr.SetJustificationToCentered()
2294        if "left" in justify:
2295            pr.SetJustificationToLeft()
2296        if "right" in justify:
2297            pr.SetJustificationToRight()
2298        pr.SetLineSpacing(vspacing)
2299        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            self.trail.initilized = False # so the first update will be a reset
1504        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:
1506    def update_trail(self) -> Self:
1507        """
1508        Update the trailing line of a moving object.
1509        """
1510        currentpos = self.pos()
1511        if not self.trail.initilized:
1512            self.trail_points = [currentpos] * self.trail.npoints
1513            self.trail.initilized = True
1514            return self
1515        self.trail_points.append(currentpos)  # cycle
1516        self.trail_points.pop(0)
1517
1518        data = np.array(self.trail_points) + self.trail_offset
1519        tpoly = self.trail.dataset
1520        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1521        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:
1558    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self:
1559        """
1560        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1561        The output is a new `Mesh` representing the shadow.
1562        This new mesh is accessible through `mesh.shadow`.
1563        By default the shadow mesh is placed on the bottom wall of the bounding box.
1564
1565        See also `pointcloud.project_on_plane()`.
1566
1567        Arguments:
1568            plane : (str, Plane)
1569                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1570                represents x-plane, y-plane and z-plane, respectively.
1571                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1572            point : (float, array)
1573                if plane is `str`, point should be a float represents the intercept.
1574                Otherwise, point is the camera point of perspective projection
1575            direction : (list)
1576                direction of oblique projection
1577            culling : (int)
1578                choose between front [1] or backface [-1] culling or None.
1579
1580        Examples:
1581            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1582            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1583            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1584
1585            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1586        """
1587        shad = self._compute_shadow(plane, point, direction)
1588        shad.c(c).alpha(alpha)
1589
1590        try:
1591            # Points dont have these methods
1592            shad.flat()
1593            if culling in (1, True):
1594                shad.frontface_culling()
1595            elif culling == -1:
1596                shad.backface_culling()
1597        except AttributeError:
1598            pass
1599
1600        shad.properties.LightingOff()
1601        shad.actor.SetPickable(False)
1602        shad.actor.SetUseBounds(True)
1603
1604        if shad not in self.shadows:
1605            self.shadows.append(shad)
1606            shad.info = dict(plane=plane, point=point, direction=direction)
1607            # shad.metadata["plane"] = plane
1608            # shad.metadata["point"] = point
1609            # print("AAAA", direction, plane, point)
1610            # if direction is None:
1611            #     direction = [0,0,0]
1612            # shad.metadata["direction"] = direction
1613        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:
1615    def update_shadows(self) -> Self:
1616        """Update the shadows of a moving object."""
1617        for sha in self.shadows:
1618            plane = sha.info["plane"]
1619            point = sha.info["point"]
1620            direction = sha.info["direction"]
1621            # print("update_shadows direction", direction,plane,point )
1622            # plane = sha.metadata["plane"]
1623            # point = sha.metadata["point"]
1624            # direction = sha.metadata["direction"]
1625            # if direction[0]==0 and direction[1]==0 and direction[2]==0:
1626            #     direction = None
1627            # print("BBBB", sha.metadata["direction"], 
1628            #       sha.metadata["plane"], sha.metadata["point"])
1629            new_sha = self._compute_shadow(plane, point, direction)
1630            sha._update(new_sha.dataset)
1631        if self.trail:
1632            self.trail.update_shadows()
1633        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]:
1635    def labels(
1636        self,
1637        content=None,
1638        on="points",
1639        scale=None,
1640        xrot=0.0,
1641        yrot=0.0,
1642        zrot=0.0,
1643        ratio=1,
1644        precision=None,
1645        italic=False,
1646        font="",
1647        justify="",
1648        c="black",
1649        alpha=1.0,
1650    ) -> Union["vedo.Mesh", None]:
1651        """
1652        Generate value or ID labels for mesh cells or points.
1653        For large nr. of labels use `font="VTK"` which is much faster.
1654
1655        See also:
1656            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1657
1658        Arguments:
1659            content : (list,int,str)
1660                either 'id', 'cellid', array name or array number.
1661                A array can also be passed (must match the nr. of points or cells).
1662            on : (str)
1663                generate labels for "cells" instead of "points"
1664            scale : (float)
1665                absolute size of labels, if left as None it is automatic
1666            zrot : (float)
1667                local rotation angle of label in degrees
1668            ratio : (int)
1669                skipping ratio, to reduce nr of labels for large meshes
1670            precision : (int)
1671                numeric precision of labels
1672
1673        ```python
1674        from vedo import *
1675        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1676        point_ids = s.labels('id', on="points").c('green')
1677        cell_ids  = s.labels('id', on="cells" ).c('black')
1678        show(s, point_ids, cell_ids)
1679        ```
1680        ![](https://vedo.embl.es/images/feats/labels.png)
1681
1682        Examples:
1683            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1684
1685                ![](https://vedo.embl.es/images/basic/boundaries.png)
1686        """
1687        
1688        cells = False
1689        if "cell" in on or "face" in on:
1690            cells = True
1691            justify = "centered" if justify == "" else justify
1692
1693        if isinstance(content, str):
1694            if content in ("pointid", "pointsid"):
1695                cells = False
1696                content = "id"
1697                justify = "bottom-left" if justify == "" else justify
1698            if content in ("cellid", "cellsid"):
1699                cells = True
1700                content = "id"
1701                justify = "centered" if justify == "" else justify
1702
1703        try:
1704            if cells:
1705                ns = np.sqrt(self.ncells)
1706                elems = self.cell_centers
1707                norms = self.cell_normals
1708                justify = "centered" if justify == "" else justify
1709            else:
1710                ns = np.sqrt(self.npoints)
1711                elems = self.vertices
1712                norms = self.vertex_normals
1713        except AttributeError:
1714            norms = []
1715        
1716        if not justify:
1717            justify = "bottom-left"
1718
1719        hasnorms = False
1720        if len(norms) > 0:
1721            hasnorms = True
1722
1723        if scale is None:
1724            if not ns:
1725                ns = 100
1726            scale = self.diagonal_size() / ns / 10
1727
1728        arr = None
1729        mode = 0
1730        if content is None:
1731            mode = 0
1732            if cells:
1733                if self.dataset.GetCellData().GetScalars():
1734                    name = self.dataset.GetCellData().GetScalars().GetName()
1735                    arr = self.celldata[name]
1736            else:
1737                if self.dataset.GetPointData().GetScalars():
1738                    name = self.dataset.GetPointData().GetScalars().GetName()
1739                    arr = self.pointdata[name]
1740        elif isinstance(content, (str, int)):
1741            if content == "id":
1742                mode = 1
1743            elif cells:
1744                mode = 0
1745                arr = self.celldata[content]
1746            else:
1747                mode = 0
1748                arr = self.pointdata[content]
1749        elif utils.is_sequence(content):
1750            mode = 0
1751            arr = content
1752
1753        if arr is None and mode == 0:
1754            vedo.logger.error("in labels(), array not found in point or cell data")
1755            return None
1756
1757        ratio = int(ratio+0.5)
1758        tapp = vtki.new("AppendPolyData")
1759        has_inputs = False
1760
1761        for i, e in enumerate(elems):
1762            if i % ratio:
1763                continue
1764
1765            if mode == 1:
1766                txt_lab = str(i)
1767            else:
1768                if precision:
1769                    txt_lab = utils.precision(arr[i], precision)
1770                else:
1771                    txt_lab = str(arr[i])
1772
1773            if not txt_lab:
1774                continue
1775
1776            if font == "VTK":
1777                tx = vtki.new("VectorText")
1778                tx.SetText(txt_lab)
1779                tx.Update()
1780                tx_poly = tx.GetOutput()
1781            else:
1782                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset
1783
1784            if tx_poly.GetNumberOfPoints() == 0:
1785                continue  ######################
1786
1787            T = vtki.vtkTransform()
1788            T.PostMultiply()
1789            if italic:
1790                T.Concatenate([1, 0.2, 0, 0,
1791                               0, 1  , 0, 0,
1792                               0, 0  , 1, 0,
1793                               0, 0  , 0, 1])
1794            if hasnorms:
1795                ni = norms[i]
1796                if cells and font=="VTK":  # center-justify
1797                    bb = tx_poly.GetBounds()
1798                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1799                    T.Translate(-dx, -dy, 0)
1800                if xrot: T.RotateX(xrot)
1801                if yrot: T.RotateY(yrot)
1802                if zrot: T.RotateZ(zrot)
1803                crossvec = np.cross([0, 0, 1], ni)
1804                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1805                T.RotateWXYZ(float(angle), crossvec.tolist())
1806                T.Translate(ni / 100)
1807            else:
1808                if xrot: T.RotateX(xrot)
1809                if yrot: T.RotateY(yrot)
1810                if zrot: T.RotateZ(zrot)
1811            T.Scale(scale, scale, scale)
1812            T.Translate(e)
1813            tf = vtki.new("TransformPolyDataFilter")
1814            tf.SetInputData(tx_poly)
1815            tf.SetTransform(T)
1816            tf.Update()
1817            tapp.AddInputData(tf.GetOutput())
1818            has_inputs = True
1819
1820        if has_inputs:
1821            tapp.Update()
1822            lpoly = tapp.GetOutput()
1823        else:
1824            lpoly = vtki.vtkPolyData()
1825        ids = vedo.Mesh(lpoly, c=c, alpha=alpha)
1826        ids.properties.LightingOff()
1827        ids.actor.PickableOff()
1828        ids.actor.SetUseBounds(False)
1829        ids.name = "Labels"
1830        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]:
1832    def labels2d(
1833        self,
1834        content="id",
1835        on="points",
1836        scale=1.0,
1837        precision=4,
1838        font="Calco",
1839        justify="bottom-left",
1840        angle=0.0,
1841        frame=False,
1842        c="black",
1843        bc=None,
1844        alpha=1.0,
1845    ) -> Union["Actor2D", None]:
1846        """
1847        Generate value or ID bi-dimensional labels for mesh cells or points.
1848
1849        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
1850
1851        Arguments:
1852            content : (str)
1853                either 'id', 'cellid', or array name
1854            on : (str)
1855                generate labels for "cells" instead of "points" (the default)
1856            scale : (float)
1857                size scaling of labels
1858            precision : (int)
1859                precision of numeric labels
1860            angle : (float)
1861                local rotation angle of label in degrees
1862            frame : (bool)
1863                draw a frame around the label
1864            bc : (str)
1865                background color of the label
1866
1867        ```python
1868        from vedo import Sphere, show
1869        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
1870        sph.celldata["zvals"] = sph.cell_centers[:,2]
1871        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
1872        show(sph, l2d, axes=1).close()
1873        ```
1874        ![](https://vedo.embl.es/images/feats/labels2d.png)
1875        """
1876        cells = False
1877        if "cell" in on:
1878            cells = True
1879
1880        if isinstance(content, str):
1881            if content in ("id", "pointid", "pointsid"):
1882                cells = False
1883                content = "id"
1884            if content in ("cellid", "cellsid"):
1885                cells = True
1886                content = "id"
1887
1888        if cells:
1889            if content != "id" and content not in self.celldata.keys():
1890                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
1891                return None
1892            cellcloud = vedo.Points(self.cell_centers)
1893            arr = self.dataset.GetCellData().GetScalars()
1894            poly = cellcloud.dataset
1895            poly.GetPointData().SetScalars(arr)
1896        else:
1897            poly = self.dataset
1898            if content != "id" and content not in self.pointdata.keys():
1899                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
1900                return None
1901
1902        mp = vtki.new("LabeledDataMapper")
1903
1904        if content == "id":
1905            mp.SetLabelModeToLabelIds()
1906        else:
1907            mp.SetLabelModeToLabelScalars()
1908            if precision is not None:
1909                mp.SetLabelFormat(f"%-#.{precision}g")
1910
1911        pr = mp.GetLabelTextProperty()
1912        c = colors.get_color(c)
1913        pr.SetColor(c)
1914        pr.SetOpacity(alpha)
1915        pr.SetFrame(frame)
1916        pr.SetFrameColor(c)
1917        pr.SetItalic(False)
1918        pr.BoldOff()
1919        pr.ShadowOff()
1920        pr.UseTightBoundingBoxOn()
1921        pr.SetOrientation(angle)
1922        pr.SetFontFamily(vtki.VTK_FONT_FILE)
1923        fl = utils.get_font_path(font)
1924        pr.SetFontFile(fl)
1925        pr.SetFontSize(int(20 * scale))
1926
1927        if "cent" in justify or "mid" in justify:
1928            pr.SetJustificationToCentered()
1929        elif "rig" in justify:
1930            pr.SetJustificationToRight()
1931        elif "left" in justify:
1932            pr.SetJustificationToLeft()
1933        # ------
1934        if "top" in justify:
1935            pr.SetVerticalJustificationToTop()
1936        else:
1937            pr.SetVerticalJustificationToBottom()
1938
1939        if bc is not None:
1940            bc = colors.get_color(bc)
1941            pr.SetBackgroundColor(bc)
1942            pr.SetBackgroundOpacity(alpha)
1943
1944        mp.SetInputData(poly)
1945        a2d = Actor2D()
1946        a2d.PickableOff()
1947        a2d.SetMapper(mp)
1948        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:
1950    def legend(self, txt) -> Self:
1951        """Book a legend text."""
1952        self.info["legend"] = txt
1953        # self.metadata["legend"] = txt
1954        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]:
1956    def flagpole(
1957        self,
1958        txt=None,
1959        point=None,
1960        offset=None,
1961        s=None,
1962        font="Calco",
1963        rounded=True,
1964        c=None,
1965        alpha=1.0,
1966        lw=2,
1967        italic=0.0,
1968        padding=0.1,
1969    ) -> Union["vedo.Mesh", None]:
1970        """
1971        Generate a flag pole style element to describe an object.
1972        Returns a `Mesh` object.
1973
1974        Use flagpole.follow_camera() to make it face the camera in the scene.
1975
1976        Consider using `settings.use_parallel_projection = True` 
1977        to avoid perspective distortions.
1978
1979        See also `flagpost()`.
1980
1981        Arguments:
1982            txt : (str)
1983                Text to display. The default is the filename or the object name.
1984            point : (list)
1985                position of the flagpole pointer. 
1986            offset : (list)
1987                text offset wrt the application point. 
1988            s : (float)
1989                size of the flagpole.
1990            font : (str)
1991                font face. Check [available fonts here](https://vedo.embl.es/fonts).
1992            rounded : (bool)
1993                draw a rounded or squared box around the text.
1994            c : (list)
1995                text and box color.
1996            alpha : (float)
1997                opacity of text and box.
1998            lw : (float)
1999                line with of box frame.
2000            italic : (float)
2001                italicness of text.
2002
2003        Examples:
2004            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2005
2006                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2007
2008            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2009            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2010            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2011        """
2012        objs = []
2013
2014        if txt is None:
2015            if self.filename:
2016                txt = self.filename.split("/")[-1]
2017            elif self.name:
2018                txt = self.name
2019            else:
2020                return None
2021
2022        x0, x1, y0, y1, z0, z1 = self.bounds()
2023        d = self.diagonal_size()
2024        if point is None:
2025            if d:
2026                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2027                # point = self.closest_point([x1, y0, z1])
2028            else:  # it's a Point
2029                point = self.transform.position
2030
2031        pt = utils.make3d(point)
2032
2033        if offset is None:
2034            offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0]
2035        offset = utils.make3d(offset)
2036
2037        if s is None:
2038            s = d / 20
2039
2040        sph = None
2041        if d and (z1 - z0) / d > 0.1:
2042            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2043
2044        if c is None:
2045            c = np.array(self.color()) / 1.4
2046
2047        lab = vedo.shapes.Text3D(
2048            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center"
2049        )
2050        objs.append(lab)
2051
2052        if d and not sph:
2053            sph = vedo.shapes.Circle(pt, r=s / 3, res=16)
2054        objs.append(sph)
2055
2056        x0, x1, y0, y1, z0, z1 = lab.bounds()
2057        aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)]
2058        if rounded:
2059            box = vedo.shapes.KSpline(aline, closed=True)
2060        else:
2061            box = vedo.shapes.Line(aline, closed=True)
2062
2063        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2064
2065        # box.actor.SetOrigin(cnt)
2066        box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt)
2067        objs.append(box)
2068
2069        x0, x1, y0, y1, z0, z1 = box.bounds()
2070        if x0 < pt[0] < x1:
2071            c0 = box.closest_point(pt)
2072            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2073        elif (pt[0] - x0) < (x1 - pt[0]):
2074            c0 = [x0, (y0 + y1) / 2, pt[2]]
2075            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2076        else:
2077            c0 = [x1, (y0 + y1) / 2, pt[2]]
2078            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2079
2080        con = vedo.shapes.Line([c0, c1, pt])
2081        objs.append(con)
2082
2083        mobjs = vedo.merge(objs).c(c).alpha(alpha)
2084        mobjs.name = "FlagPole"
2085        mobjs.bc("tomato").pickable(False)
2086        mobjs.properties.LightingOff()
2087        mobjs.properties.SetLineWidth(lw)
2088        mobjs.actor.UseBoundsOff()
2089        mobjs.actor.SetPosition([0,0,0])
2090        mobjs.actor.SetOrigin(pt)
2091        return mobjs
2092
2093        # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha)
2094        # mobjs.name = "FlagPole"
2095        # # mobjs.bc("tomato").pickable(False)
2096        # # mobjs.properties.LightingOff()
2097        # # mobjs.properties.SetLineWidth(lw)
2098        # # mobjs.actor.UseBoundsOff()
2099        # # mobjs.actor.SetPosition([0,0,0])
2100        # # mobjs.actor.SetOrigin(pt)
2101        # # print(pt)
2102        # 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]:
2104    def flagpost(
2105        self,
2106        txt=None,
2107        point=None,
2108        offset=None,
2109        s=1.0,
2110        c="k9",
2111        bc="k1",
2112        alpha=1,
2113        lw=0,
2114        font="Calco",
2115        justify="center-left",
2116        vspacing=1.0,
2117    ) -> Union["vedo.addons.Flagpost", None]:
2118        """
2119        Generate a flag post style element to describe an object.
2120
2121        Arguments:
2122            txt : (str)
2123                Text to display. The default is the filename or the object name.
2124            point : (list)
2125                position of the flag anchor point. The default is None.
2126            offset : (list)
2127                a 3D displacement or offset. The default is None.
2128            s : (float)
2129                size of the text to be shown
2130            c : (list)
2131                color of text and line
2132            bc : (list)
2133                color of the flag background
2134            alpha : (float)
2135                opacity of text and box.
2136            lw : (int)
2137                line with of box frame. The default is 0.
2138            font : (str)
2139                font name. Use a monospace font for better rendering. The default is "Calco".
2140                Type `vedo -r fonts` for a font demo.
2141                Check [available fonts here](https://vedo.embl.es/fonts).
2142            justify : (str)
2143                internal text justification. The default is "center-left".
2144            vspacing : (float)
2145                vertical spacing between lines.
2146
2147        Examples:
2148            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
2149
2150            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2151        """
2152        if txt is None:
2153            if self.filename:
2154                txt = self.filename.split("/")[-1]
2155            elif self.name:
2156                txt = self.name
2157            else:
2158                return None
2159
2160        x0, x1, y0, y1, z0, z1 = self.bounds()
2161        d = self.diagonal_size()
2162        if point is None:
2163            if d:
2164                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2165            else:  # it's a Point
2166                point = self.transform.position
2167
2168        point = utils.make3d(point)
2169
2170        if offset is None:
2171            offset = [0, 0, (z1 - z0) / 2]
2172        offset = utils.make3d(offset)
2173
2174        fpost = vedo.addons.Flagpost(
2175            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2176        )
2177        self._caption = fpost
2178        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]:
2180    def caption(
2181        self,
2182        txt=None,
2183        point=None,
2184        size=(0.30, 0.15),
2185        padding=5,
2186        font="Calco",
2187        justify="center-right",
2188        vspacing=1.0,
2189        c=None,
2190        alpha=1.0,
2191        lw=1,
2192        ontop=True,
2193    ) -> Union["vtki.vtkCaptionActor2D", None]:
2194        """
2195        Create a 2D caption to an object which follows the camera movements.
2196        Latex is not supported. Returns the same input object for concatenation.
2197
2198        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2199        with similar functionality.
2200
2201        Arguments:
2202            txt : (str)
2203                text to be rendered. The default is the file name.
2204            point : (list)
2205                anchoring point. The default is None.
2206            size : (list)
2207                (width, height) of the caption box. The default is (0.30, 0.15).
2208            padding : (float)
2209                padding space of the caption box in pixels. The default is 5.
2210            font : (str)
2211                font name. Use a monospace font for better rendering. The default is "VictorMono".
2212                Type `vedo -r fonts` for a font demo.
2213                Check [available fonts here](https://vedo.embl.es/fonts).
2214            justify : (str)
2215                internal text justification. The default is "center-right".
2216            vspacing : (float)
2217                vertical spacing between lines. The default is 1.
2218            c : (str)
2219                text and box color. The default is 'lb'.
2220            alpha : (float)
2221                text and box transparency. The default is 1.
2222            lw : (int)
2223                line width in pixels. The default is 1.
2224            ontop : (bool)
2225                keep the 2d caption always on top. The default is True.
2226
2227        Examples:
2228            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2229
2230                ![](https://vedo.embl.es/images/pyplot/caption.png)
2231
2232            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2233            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2234        """
2235        if txt is None:
2236            if self.filename:
2237                txt = self.filename.split("/")[-1]
2238            elif self.name:
2239                txt = self.name
2240
2241        if not txt:  # disable it
2242            self._caption = None
2243            return None
2244
2245        for r in vedo.shapes._reps:
2246            txt = txt.replace(r[0], r[1])
2247
2248        if c is None:
2249            c = np.array(self.properties.GetColor()) / 2
2250        else:
2251            c = colors.get_color(c)
2252
2253        if point is None:
2254            x0, x1, y0, y1, _, z1 = self.dataset.GetBounds()
2255            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2256            point = self.closest_point(pt)
2257
2258        capt = vtki.vtkCaptionActor2D()
2259        capt.SetAttachmentPoint(point)
2260        capt.SetBorder(True)
2261        capt.SetLeader(True)
2262        sph = vtki.new("SphereSource")
2263        sph.Update()
2264        capt.SetLeaderGlyphData(sph.GetOutput())
2265        capt.SetMaximumLeaderGlyphSize(5)
2266        capt.SetPadding(int(padding))
2267        capt.SetCaption(txt)
2268        capt.SetWidth(size[0])
2269        capt.SetHeight(size[1])
2270        capt.SetThreeDimensionalLeader(not ontop)
2271
2272        pra = capt.GetProperty()
2273        pra.SetColor(c)
2274        pra.SetOpacity(alpha)
2275        pra.SetLineWidth(lw)
2276
2277        pr = capt.GetCaptionTextProperty()
2278        pr.SetFontFamily(vtki.VTK_FONT_FILE)
2279        fl = utils.get_font_path(font)
2280        pr.SetFontFile(fl)
2281        pr.ShadowOff()
2282        pr.BoldOff()
2283        pr.FrameOff()
2284        pr.SetColor(c)
2285        pr.SetOpacity(alpha)
2286        pr.SetJustificationToLeft()
2287        if "top" in justify:
2288            pr.SetVerticalJustificationToTop()
2289        if "bottom" in justify:
2290            pr.SetVerticalJustificationToBottom()
2291        if "cent" in justify:
2292            pr.SetVerticalJustificationToCentered()
2293            pr.SetJustificationToCentered()
2294        if "left" in justify:
2295            pr.SetJustificationToLeft()
2296        if "right" in justify:
2297            pr.SetJustificationToRight()
2298        pr.SetLineSpacing(vspacing)
2299        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):
2642class VolumeVisual(CommonVisual):
2643    """Class to manage the visual aspects of a ``Volume`` object."""
2644
2645    def __init__(self) -> None:
2646        # print("INIT VolumeVisual")
2647        super().__init__()
2648
2649    def alpha_unit(self, u=None) -> Union[Self, float]:
2650        """
2651        Defines light attenuation per unit length. Default is 1.
2652        The larger the unit length, the further light has to travel to attenuate the same amount.
2653
2654        E.g., if you set the unit distance to 0, you will get full opacity.
2655        It means that when light travels 0 distance it's already attenuated a finite amount.
2656        Thus, any finite distance should attenuate all light.
2657        The larger you make the unit distance, the more transparent the rendering becomes.
2658        """
2659        if u is None:
2660            return self.properties.GetScalarOpacityUnitDistance()
2661        self.properties.SetScalarOpacityUnitDistance(u)
2662        return self
2663
2664    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2665        """
2666        Assign a set of tranparencies to a volume's gradient
2667        along the range of the scalar value.
2668        A single constant value can also be assigned.
2669        The gradient function is used to decrease the opacity
2670        in the "flat" regions of the volume while maintaining the opacity
2671        at the boundaries between material types.  The gradient is measured
2672        as the amount by which the intensity changes over unit distance.
2673
2674        The format for alpha_grad is the same as for method `volume.alpha()`.
2675        """
2676        if vmin is None:
2677            vmin, _ = self.dataset.GetScalarRange()
2678        if vmax is None:
2679            _, vmax = self.dataset.GetScalarRange()
2680
2681        if alpha_grad is None:
2682            self.properties.DisableGradientOpacityOn()
2683            return self
2684
2685        self.properties.DisableGradientOpacityOff()
2686
2687        gotf = self.properties.GetGradientOpacity()
2688        if utils.is_sequence(alpha_grad):
2689            alpha_grad = np.array(alpha_grad)
2690            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2691                for i, al in enumerate(alpha_grad):
2692                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2693                    # Create transfer mapping scalar value to gradient opacity
2694                    gotf.AddPoint(xalpha, al)
2695            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2696                gotf.AddPoint(vmin, alpha_grad[0][1])
2697                for xalpha, al in alpha_grad:
2698                    # Create transfer mapping scalar value to opacity
2699                    gotf.AddPoint(xalpha, al)
2700                gotf.AddPoint(vmax, alpha_grad[-1][1])
2701            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2702        else:
2703            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2704            gotf.AddPoint(vmax, alpha_grad)
2705        return self
2706
2707    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2708        """Same as `color()`.
2709
2710        Arguments:
2711            alpha : (list)
2712                use a list to specify transparencies along the scalar range
2713            vmin : (float)
2714                force the min of the scalar range to be this value
2715            vmax : (float)
2716                force the max of the scalar range to be this value
2717        """
2718        return self.color(c, alpha, vmin, vmax)
2719
2720    def jittering(self, status=None) -> Union[Self, bool]:
2721        """
2722        If `True`, each ray traversal direction will be perturbed slightly
2723        using a noise-texture to get rid of wood-grain effects.
2724        """
2725        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2726            if status is None:
2727                return self.mapper.GetUseJittering()
2728            self.mapper.SetUseJittering(status)
2729        return self
2730
2731    def hide_voxels(self, ids) -> Self:
2732        """
2733        Hide voxels (cells) from visualization.
2734
2735        Example:
2736            ```python
2737            from vedo import *
2738            embryo = Volume(dataurl+'embryo.tif')
2739            embryo.hide_voxels(list(range(400000)))
2740            show(embryo, axes=1).close()
2741            ```
2742
2743        See also:
2744            `volume.mask()`
2745        """
2746        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2747        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2748        name = vtki.vtkDataSetAttributes.GhostArrayName()
2749        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2750        self.dataset.GetCellData().AddArray(garr)
2751        self.dataset.GetCellData().Modified()
2752        return self
2753
2754    def mask(self, data) -> Self:
2755        """
2756        Mask a volume visualization with a binary value.
2757        Needs to specify `volume.mapper = "gpu"`.
2758
2759        Example:
2760        ```python
2761        from vedo import np, Volume, show
2762        data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2763        # all voxels have value zero except:
2764        data_matrix[ 0:35,  0:35,  0:35] = 1
2765        data_matrix[35:55, 35:55, 35:55] = 2
2766        data_matrix[55:74, 55:74, 55:74] = 3
2767        vol = Volume(data_matrix).cmap('Blues')
2768        vol.mapper = "gpu"
2769        data_mask = np.zeros_like(data_matrix)
2770        data_mask[10:65, 10:60, 20:70] = 1
2771        vol.mask(data_mask)
2772        show(vol, axes=1).close()
2773        ```
2774        See also:
2775            `volume.hide_voxels()`
2776        """
2777        rdata = data.astype(np.uint8).ravel(order="F")
2778        varr = utils.numpy2vtk(rdata, name="input_mask")
2779
2780        img = vtki.vtkImageData()
2781        img.SetDimensions(self.dimensions())
2782        img.GetPointData().AddArray(varr)
2783        img.GetPointData().SetActiveScalars(varr.GetName())
2784
2785        try:
2786            self.mapper.SetMaskTypeToBinary()
2787            self.mapper.SetMaskInput(img)
2788        except AttributeError:
2789            vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'")
2790        return self
2791
2792
2793    def mode(self, mode=None) -> Union[Self, int]:
2794        """
2795        Define the volumetric rendering mode following this:
2796            - 0, composite rendering
2797            - 1, maximum projection rendering
2798            - 2, minimum projection rendering
2799            - 3, average projection rendering
2800            - 4, additive mode
2801
2802        The default mode is "composite" where the scalar values are sampled through
2803        the volume and composited in a front-to-back scheme through alpha blending.
2804        The final color and opacity is determined using the color and opacity transfer
2805        functions specified in alpha keyword.
2806
2807        Maximum and minimum intensity blend modes use the maximum and minimum
2808        scalar values, respectively, along the sampling ray.
2809        The final color and opacity is determined by passing the resultant value
2810        through the color and opacity transfer functions.
2811
2812        Additive blend mode accumulates scalar values by passing each value
2813        through the opacity transfer function and then adding up the product
2814        of the value and its opacity. In other words, the scalar values are scaled
2815        using the opacity transfer function and summed to derive the final color.
2816        Note that the resulting image is always grayscale i.e. aggregated values
2817        are not passed through the color transfer function.
2818        This is because the final value is a derived value and not a real data value
2819        along the sampling ray.
2820
2821        Average intensity blend mode works similar to the additive blend mode where
2822        the scalar values are multiplied by opacity calculated from the opacity
2823        transfer function and then added.
2824        The additional step here is to divide the sum by the number of samples
2825        taken through the volume.
2826        As is the case with the additive intensity projection, the final image will
2827        always be grayscale i.e. the aggregated values are not passed through the
2828        color transfer function.
2829        """
2830        if mode is None:
2831            return self.mapper.GetBlendMode()
2832
2833        if isinstance(mode, str):
2834            if "comp" in mode:
2835                mode = 0
2836            elif "proj" in mode:
2837                if "max" in mode:
2838                    mode = 1
2839                elif "min" in mode:
2840                    mode = 2
2841                elif "ave" in mode:
2842                    mode = 3
2843                else:
2844                    vedo.logger.warning(f"unknown mode {mode}")
2845                    mode = 0
2846            elif "add" in mode:
2847                mode = 4
2848            else:
2849                vedo.logger.warning(f"unknown mode {mode}")
2850                mode = 0
2851
2852        self.mapper.SetBlendMode(mode)
2853        return self
2854
2855    def shade(self, status=None) -> Union[Self, bool]:
2856        """
2857        Set/Get the shading of a Volume.
2858        Shading can be further controlled with `volume.lighting()` method.
2859
2860        If shading is turned on, the mapper may perform shading calculations.
2861        In some cases shading does not apply
2862        (for example, in maximum intensity projection mode).
2863        """
2864        if status is None:
2865            return self.properties.GetShade()
2866        self.properties.SetShade(status)
2867        return self
2868
2869    def interpolation(self, itype) -> Self:
2870        """
2871        Set interpolation type.
2872
2873        0=nearest neighbour, 1=linear
2874        """
2875        self.properties.SetInterpolationType(itype)
2876        return self

Class to manage the visual aspects of a Volume object.

VolumeVisual()
2645    def __init__(self) -> None:
2646        # print("INIT VolumeVisual")
2647        super().__init__()
def alpha_unit(self, u=None) -> Union[Self, float]:
2649    def alpha_unit(self, u=None) -> Union[Self, float]:
2650        """
2651        Defines light attenuation per unit length. Default is 1.
2652        The larger the unit length, the further light has to travel to attenuate the same amount.
2653
2654        E.g., if you set the unit distance to 0, you will get full opacity.
2655        It means that when light travels 0 distance it's already attenuated a finite amount.
2656        Thus, any finite distance should attenuate all light.
2657        The larger you make the unit distance, the more transparent the rendering becomes.
2658        """
2659        if u is None:
2660            return self.properties.GetScalarOpacityUnitDistance()
2661        self.properties.SetScalarOpacityUnitDistance(u)
2662        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:
2664    def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self:
2665        """
2666        Assign a set of tranparencies to a volume's gradient
2667        along the range of the scalar value.
2668        A single constant value can also be assigned.
2669        The gradient function is used to decrease the opacity
2670        in the "flat" regions of the volume while maintaining the opacity
2671        at the boundaries between material types.  The gradient is measured
2672        as the amount by which the intensity changes over unit distance.
2673
2674        The format for alpha_grad is the same as for method `volume.alpha()`.
2675        """
2676        if vmin is None:
2677            vmin, _ = self.dataset.GetScalarRange()
2678        if vmax is None:
2679            _, vmax = self.dataset.GetScalarRange()
2680
2681        if alpha_grad is None:
2682            self.properties.DisableGradientOpacityOn()
2683            return self
2684
2685        self.properties.DisableGradientOpacityOff()
2686
2687        gotf = self.properties.GetGradientOpacity()
2688        if utils.is_sequence(alpha_grad):
2689            alpha_grad = np.array(alpha_grad)
2690            if len(alpha_grad.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2691                for i, al in enumerate(alpha_grad):
2692                    xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1)
2693                    # Create transfer mapping scalar value to gradient opacity
2694                    gotf.AddPoint(xalpha, al)
2695            elif len(alpha_grad.shape) == 2:  # user passing [(x0,alpha0), ...]
2696                gotf.AddPoint(vmin, alpha_grad[0][1])
2697                for xalpha, al in alpha_grad:
2698                    # Create transfer mapping scalar value to opacity
2699                    gotf.AddPoint(xalpha, al)
2700                gotf.AddPoint(vmax, alpha_grad[-1][1])
2701            # print("alpha_grad at", round(xalpha, 1), "\tset to", al)
2702        else:
2703            gotf.AddPoint(vmin, alpha_grad)  # constant alpha_grad
2704            gotf.AddPoint(vmax, alpha_grad)
2705        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:
2707    def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self:
2708        """Same as `color()`.
2709
2710        Arguments:
2711            alpha : (list)
2712                use a list to specify transparencies along the scalar range
2713            vmin : (float)
2714                force the min of the scalar range to be this value
2715            vmax : (float)
2716                force the max of the scalar range to be this value
2717        """
2718        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]:
2720    def jittering(self, status=None) -> Union[Self, bool]:
2721        """
2722        If `True`, each ray traversal direction will be perturbed slightly
2723        using a noise-texture to get rid of wood-grain effects.
2724        """
2725        if hasattr(self.mapper, "SetUseJittering"):  # tetmesh doesnt have it
2726            if status is None:
2727                return self.mapper.GetUseJittering()
2728            self.mapper.SetUseJittering(status)
2729        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:
2731    def hide_voxels(self, ids) -> Self:
2732        """
2733        Hide voxels (cells) from visualization.
2734
2735        Example:
2736            ```python
2737            from vedo import *
2738            embryo = Volume(dataurl+'embryo.tif')
2739            embryo.hide_voxels(list(range(400000)))
2740            show(embryo, axes=1).close()
2741            ```
2742
2743        See also:
2744            `volume.mask()`
2745        """
2746        ghost_mask = np.zeros(self.ncells, dtype=np.uint8)
2747        ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL
2748        name = vtki.vtkDataSetAttributes.GhostArrayName()
2749        garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8)
2750        self.dataset.GetCellData().AddArray(garr)
2751        self.dataset.GetCellData().Modified()
2752        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:
2754    def mask(self, data) -> Self:
2755        """
2756        Mask a volume visualization with a binary value.
2757        Needs to specify `volume.mapper = "gpu"`.
2758
2759        Example:
2760        ```python
2761        from vedo import np, Volume, show
2762        data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
2763        # all voxels have value zero except:
2764        data_matrix[ 0:35,  0:35,  0:35] = 1
2765        data_matrix[35:55, 35:55, 35:55] = 2
2766        data_matrix[55:74, 55:74, 55:74] = 3
2767        vol = Volume(data_matrix).cmap('Blues')
2768        vol.mapper = "gpu"
2769        data_mask = np.zeros_like(data_matrix)
2770        data_mask[10:65, 10:60, 20:70] = 1
2771        vol.mask(data_mask)
2772        show(vol, axes=1).close()
2773        ```
2774        See also:
2775            `volume.hide_voxels()`
2776        """
2777        rdata = data.astype(np.uint8).ravel(order="F")
2778        varr = utils.numpy2vtk(rdata, name="input_mask")
2779
2780        img = vtki.vtkImageData()
2781        img.SetDimensions(self.dimensions())
2782        img.GetPointData().AddArray(varr)
2783        img.GetPointData().SetActiveScalars(varr.GetName())
2784
2785        try:
2786            self.mapper.SetMaskTypeToBinary()
2787            self.mapper.SetMaskInput(img)
2788        except AttributeError:
2789            vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'")
2790        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]:
2793    def mode(self, mode=None) -> Union[Self, int]:
2794        """
2795        Define the volumetric rendering mode following this:
2796            - 0, composite rendering
2797            - 1, maximum projection rendering
2798            - 2, minimum projection rendering
2799            - 3, average projection rendering
2800            - 4, additive mode
2801
2802        The default mode is "composite" where the scalar values are sampled through
2803        the volume and composited in a front-to-back scheme through alpha blending.
2804        The final color and opacity is determined using the color and opacity transfer
2805        functions specified in alpha keyword.
2806
2807        Maximum and minimum intensity blend modes use the maximum and minimum
2808        scalar values, respectively, along the sampling ray.
2809        The final color and opacity is determined by passing the resultant value
2810        through the color and opacity transfer functions.
2811
2812        Additive blend mode accumulates scalar values by passing each value
2813        through the opacity transfer function and then adding up the product
2814        of the value and its opacity. In other words, the scalar values are scaled
2815        using the opacity transfer function and summed to derive the final color.
2816        Note that the resulting image is always grayscale i.e. aggregated values
2817        are not passed through the color transfer function.
2818        This is because the final value is a derived value and not a real data value
2819        along the sampling ray.
2820
2821        Average intensity blend mode works similar to the additive blend mode where
2822        the scalar values are multiplied by opacity calculated from the opacity
2823        transfer function and then added.
2824        The additional step here is to divide the sum by the number of samples
2825        taken through the volume.
2826        As is the case with the additive intensity projection, the final image will
2827        always be grayscale i.e. the aggregated values are not passed through the
2828        color transfer function.
2829        """
2830        if mode is None:
2831            return self.mapper.GetBlendMode()
2832
2833        if isinstance(mode, str):
2834            if "comp" in mode:
2835                mode = 0
2836            elif "proj" in mode:
2837                if "max" in mode:
2838                    mode = 1
2839                elif "min" in mode:
2840                    mode = 2
2841                elif "ave" in mode:
2842                    mode = 3
2843                else:
2844                    vedo.logger.warning(f"unknown mode {mode}")
2845                    mode = 0
2846            elif "add" in mode:
2847                mode = 4
2848            else:
2849                vedo.logger.warning(f"unknown mode {mode}")
2850                mode = 0
2851
2852        self.mapper.SetBlendMode(mode)
2853        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]:
2855    def shade(self, status=None) -> Union[Self, bool]:
2856        """
2857        Set/Get the shading of a Volume.
2858        Shading can be further controlled with `volume.lighting()` method.
2859
2860        If shading is turned on, the mapper may perform shading calculations.
2861        In some cases shading does not apply
2862        (for example, in maximum intensity projection mode).
2863        """
2864        if status is None:
2865            return self.properties.GetShade()
2866        self.properties.SetShade(status)
2867        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:
2869    def interpolation(self, itype) -> Self:
2870        """
2871        Set interpolation type.
2872
2873        0=nearest neighbour, 1=linear
2874        """
2875        self.properties.SetInterpolationType(itype)
2876        return self

Set interpolation type.

0=nearest neighbour, 1=linear

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

Class to manage the visual aspects of a Maesh object.

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

Set mesh's representation as wireframe or solid surface.

def flat(self) -> Self:
2361    def flat(self)  -> Self:
2362        """Set surface interpolation to flat.
2363
2364        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700">
2365        """
2366        self.properties.SetInterpolationToFlat()
2367        return self

Set surface interpolation to flat.

def phong(self) -> Self:
2369    def phong(self) -> Self:
2370        """Set surface interpolation to "phong"."""
2371        self.properties.SetInterpolationToPhong()
2372        return self

Set surface interpolation to "phong".

def backface_culling(self, value=True) -> Self:
2374    def backface_culling(self, value=True) -> Self:
2375        """Set culling of polygons based on orientation of normal with respect to camera."""
2376        self.properties.SetBackfaceCulling(value)
2377        return self

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

def render_lines_as_tubes(self, value=True) -> Self:
2379    def render_lines_as_tubes(self, value=True) -> Self:
2380        """Wrap a fake tube around a simple line for visualization"""
2381        self.properties.SetRenderLinesAsTubes(value)
2382        return self

Wrap a fake tube around a simple line for visualization

def frontface_culling(self, value=True) -> Self:
2384    def frontface_culling(self, value=True) -> Self:
2385        """Set culling of polygons based on orientation of normal with respect to camera."""
2386        self.properties.SetFrontfaceCulling(value)
2387        return self

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

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

Set/get mesh's backface color.

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

Shortcut for mesh.backcolor().

def linewidth(self, lw=None) -> Union[Self, int]:
2416    def linewidth(self, lw=None) -> Union[Self, int]:
2417        """Set/get width of mesh edges. Same as `lw()`."""
2418        if lw is not None:
2419            if lw == 0:
2420                self.properties.EdgeVisibilityOff()
2421                self.properties.SetRepresentationToSurface()
2422                return self
2423            self.properties.EdgeVisibilityOn()
2424            self.properties.SetLineWidth(lw)
2425        else:
2426            return self.properties.GetLineWidth()
2427        return self

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

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

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

def linecolor(self, lc=None) -> Union[Self, numpy.ndarray]:
2433    def linecolor(self, lc=None) -> Union[Self, np.ndarray]:
2434        """Set/get color of mesh edges. Same as `lc()`."""
2435        if lc is None:
2436            return np.array(self.properties.GetEdgeColor())
2437        self.properties.EdgeVisibilityOn()
2438        self.properties.SetEdgeColor(colors.get_color(lc))
2439        return self

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

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

Class to manage the visual aspects common to all objects.

ImageVisual()
2882    def __init__(self) -> None:
2883        # print("init ImageVisual")
2884        super().__init__()
def memory_size(self) -> int:
2886    def memory_size(self) -> int:
2887        """
2888        Return the size in bytes of the object in memory.
2889        """
2890        return self.dataset.GetActualMemorySize()

Return the size in bytes of the object in memory.

def scalar_range(self) -> numpy.ndarray:
2892    def scalar_range(self) -> np.ndarray:
2893        """
2894        Return the scalar range of the image.
2895        """
2896        return np.array(self.dataset.GetScalarRange())

Return the scalar range of the image.

def alpha(self, a=None) -> Union[Self, float]:
2898    def alpha(self, a=None) -> Union[Self, float]:
2899        """Set/get image's transparency in the rendering scene."""
2900        if a is not None:
2901            self.properties.SetOpacity(a)
2902            return self
2903        return self.properties.GetOpacity()

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

def level(self, value=None) -> Union[Self, float]:
2905    def level(self, value=None) -> Union[Self, float]:
2906        """Get/Set the image color level (brightness) in the rendering scene."""
2907        if value is None:
2908            return self.properties.GetColorLevel()
2909        self.properties.SetColorLevel(value)
2910        return self

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

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

Update the LightKit properties.