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

Class to manage the visual aspects of a Points object.

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

Copy properties from another Points object.

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

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

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

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

def force_opaque(self, value=True) -> Self:
1013    def force_opaque(self, value=True) -> Self:
1014        """ Force the Mesh, Line or point cloud to be treated as opaque"""
1015        ## force the opaque pass, fixes picking in vtk9
1016        # but causes other bad troubles with lines..
1017        self.actor.SetForceOpaque(value)
1018        return self

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

def force_translucent(self, value=True) -> Self:
1020    def force_translucent(self, value=True) -> Self:
1021        """ Force the Mesh, Line or point cloud to be treated as translucent"""
1022        self.actor.SetForceTranslucent(value)
1023        return self

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

def point_size(self, value=None) -> Union[int, Self]:
1025    def point_size(self, value=None) -> Union[int, Self]:
1026        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
1027        if value is None:
1028            return self.properties.GetPointSize()
1029            # self.properties.SetRepresentationToSurface()
1030        else:
1031            self.properties.SetRepresentationToPoints()
1032            self.properties.SetPointSize(value)
1033        return self

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

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

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

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

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

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

Class to manage the visual aspects of a Volume object.

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

Set interpolation type.

0=nearest neighbour, 1=linear

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

Class to manage the visual aspects of a Maesh object.

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

Set mesh's representation as wireframe or solid surface.

def flat(self) -> Self:
2369    def flat(self)  -> Self:
2370        """Set surface interpolation to flat.
2371
2372        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700">
2373        """
2374        self.properties.SetInterpolationToFlat()
2375        return self

Set surface interpolation to flat.

def phong(self) -> Self:
2377    def phong(self) -> Self:
2378        """Set surface interpolation to "phong"."""
2379        self.properties.SetInterpolationToPhong()
2380        return self

Set surface interpolation to "phong".

def backface_culling(self, value=True) -> Self:
2382    def backface_culling(self, value=True) -> Self:
2383        """Set culling of polygons based on orientation of normal with respect to camera."""
2384        self.properties.SetBackfaceCulling(value)
2385        return self

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

def render_lines_as_tubes(self, value=True) -> Self:
2387    def render_lines_as_tubes(self, value=True) -> Self:
2388        """Wrap a fake tube around a simple line for visualization"""
2389        self.properties.SetRenderLinesAsTubes(value)
2390        return self

Wrap a fake tube around a simple line for visualization

def frontface_culling(self, value=True) -> Self:
2392    def frontface_culling(self, value=True) -> Self:
2393        """Set culling of polygons based on orientation of normal with respect to camera."""
2394        self.properties.SetFrontfaceCulling(value)
2395        return self

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

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

Set/get mesh's backface color.

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

Shortcut for mesh.backcolor().

def linewidth(self, lw=None) -> Union[Self, int]:
2424    def linewidth(self, lw=None) -> Union[Self, int]:
2425        """Set/get width of mesh edges. Same as `lw()`."""
2426        if lw is not None:
2427            if lw == 0:
2428                self.properties.EdgeVisibilityOff()
2429                self.properties.SetRepresentationToSurface()
2430                return self
2431            self.properties.EdgeVisibilityOn()
2432            self.properties.SetLineWidth(lw)
2433        else:
2434            return self.properties.GetLineWidth()
2435        return self

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

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

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

def linecolor(self, lc=None) -> Union[Self, numpy.ndarray]:
2441    def linecolor(self, lc=None) -> Union[Self, np.ndarray]:
2442        """Set/get color of mesh edges. Same as `lc()`."""
2443        if lc is None:
2444            return np.array(self.properties.GetEdgeColor())
2445        self.properties.EdgeVisibilityOn()
2446        self.properties.SetEdgeColor(colors.get_color(lc))
2447        return self

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

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

Class to manage the visual aspects common to all objects.

ImageVisual()
2890    def __init__(self) -> None:
2891        # print("init ImageVisual")
2892        super().__init__()
def memory_size(self) -> int:
2894    def memory_size(self) -> int:
2895        """
2896        Return the size in bytes of the object in memory.
2897        """
2898        return self.dataset.GetActualMemorySize()

Return the size in bytes of the object in memory.

def scalar_range(self) -> numpy.ndarray:
2900    def scalar_range(self) -> np.ndarray:
2901        """
2902        Return the scalar range of the image.
2903        """
2904        return np.array(self.dataset.GetScalarRange())

Return the scalar range of the image.

def alpha(self, a=None) -> Union[Self, float]:
2906    def alpha(self, a=None) -> Union[Self, float]:
2907        """Set/get image's transparency in the rendering scene."""
2908        if a is not None:
2909            self.properties.SetOpacity(a)
2910            return self
2911        return self.properties.GetOpacity()

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

def level(self, value=None) -> Union[Self, float]:
2913    def level(self, value=None) -> Union[Self, float]:
2914        """Get/Set the image color level (brightness) in the rendering scene."""
2915        if value is None:
2916            return self.properties.GetColorLevel()
2917        self.properties.SetColorLevel(value)
2918        return self

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

def window(self, value=None) -> Union[Self, float]:
2920    def window(self, value=None) -> Union[Self, float]:
2921        """Get/Set the image color window (contrast) in the rendering scene."""
2922        if value is None:
2923            return self.properties.GetColorWindow()
2924        self.properties.SetColorWindow(value)
2925        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 visibility(value=None) -> bool:
624        """Get/Set object visibility."""
625        if value is not None:
626            self.SetVisibility(value)
627        return self.GetVisibility()
628
629    def pickable(self, value=True) -> Self:
630        """Set object pickability."""
631        self.SetPickable(value)
632        return self
633
634    def color(self, value=None) -> Union[np.ndarray, Self]:
635        """Set/Get the object color."""
636        if value is None:
637            return self.properties.GetColor()
638        self.properties.SetColor(colors.get_color(value))
639        return self
640
641    def c(self, value=None) -> Union[np.ndarray, Self]:
642        """Set/Get the object color."""
643        return self.color(value)
644
645    def alpha(self, value=None) -> Union[float, Self]:
646        """Set/Get the object opacity."""
647        if value is None:
648            return self.properties.GetOpacity()
649        self.properties.SetOpacity(value)
650        return self
651
652    def ps(self, point_size=None) -> Union[int, Self]:
653        if point_size is None:
654            return self.properties.GetPointSize()
655        self.properties.SetPointSize(point_size)
656        return self
657
658    def lw(self, line_width=None) -> Union[int, Self]:
659        if line_width is None:
660            return self.properties.GetLineWidth()
661        self.properties.SetLineWidth(line_width)
662        return self
663
664    def ontop(self, value=True) -> Self:
665        """Keep the object always on top of everything else."""
666        if value:
667            self.properties.SetDisplayLocationToForeground()
668        else:
669            self.properties.SetDisplayLocationToBackground()
670        return self
671
672    def add_observer(self, event_name, func, priority=0) -> int:
673        """Add a callback function that will be called when an event occurs."""
674        event_name = utils.get_vtk_name_event(event_name)
675        idd = self.AddObserver(event_name, func, priority)
676        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 visibility(value=None) -> bool:
623    def visibility(value=None) -> bool:
624        """Get/Set object visibility."""
625        if value is not None:
626            self.SetVisibility(value)
627        return self.GetVisibility()

Get/Set object visibility.

def pickable(self, value=True) -> Self:
629    def pickable(self, value=True) -> Self:
630        """Set object pickability."""
631        self.SetPickable(value)
632        return self

Set object pickability.

def color(self, value=None) -> Union[numpy.ndarray, Self]:
634    def color(self, value=None) -> Union[np.ndarray, Self]:
635        """Set/Get the object color."""
636        if value is None:
637            return self.properties.GetColor()
638        self.properties.SetColor(colors.get_color(value))
639        return self

Set/Get the object color.

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

Set/Get the object color.

def alpha(self, value=None) -> Union[float, Self]:
645    def alpha(self, value=None) -> Union[float, Self]:
646        """Set/Get the object opacity."""
647        if value is None:
648            return self.properties.GetOpacity()
649        self.properties.SetOpacity(value)
650        return self

Set/Get the object opacity.

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

Keep the object always on top of everything else.

def add_observer(self, event_name, func, priority=0) -> int:
672    def add_observer(self, event_name, func, priority=0) -> int:
673        """Add a callback function that will be called when an event occurs."""
674        event_name = utils.get_vtk_name_event(event_name)
675        idd = self.AddObserver(event_name, func, priority)
676        return idd

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

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

Update the LightKit properties.