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

Class to manage the visual aspects common to all objects.

CommonVisual()
35    def __init__(self):
36        # print("init CommonVisual")
37        self.mapper = None
38        self.properties = None
39        self.actor = None
40        self.scalarbar = None       
41        self.trail = None
42        self.shadows = [] 
def print(self):
44    def print(self):
45        """Print object info."""
46        print(self.__str__())
47        return self

Print object info.

LUT

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

def add_observer(self, event_name, func, priority=0):
81    def add_observer(self, event_name, func, priority=0):
82        """Add a callback function that will be called when an event occurs."""
83        event_name = utils.get_vtk_name_event(event_name)
84        idd = self.actor.AddObserver(event_name, func, priority)
85        return idd

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

def show(self, **options):
87    def show(self, **options):
88        """
89        Create on the fly an instance of class `Plotter` or use the last existing one to
90        show one single object.
91
92        This method is meant as a shortcut. If more than one object needs to be visualised
93        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
94
95        Returns the `Plotter` class instance.
96        """
97        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):
 99    def thumbnail(self, zoom=1.25, size=(200, 200), bg="white", azimuth=0, elevation=0, axes=False):
100        """Build a thumbnail of the object and return it as an array."""
101        # speed is about 20Hz for size=[200,200]
102        ren = vtk.vtkRenderer()
103
104        actor = self.actor
105        if isinstance(self, vedo.UnstructuredGrid):
106            geo = vtk.new("GeometryFilter")
107            geo.SetInputData(self.dataset)
108            geo.Update()
109            actor = vedo.Mesh(geo.GetOutput()).cmap("rainbow").actor
110
111        ren.AddActor(actor)
112        if axes:
113            axes = vedo.addons.Axes(self)
114            ren.AddActor(axes.actor)
115        ren.ResetCamera()
116        cam = ren.GetActiveCamera()
117        cam.Zoom(zoom)
118        cam.Elevation(elevation)
119        cam.Azimuth(azimuth)
120
121        ren_win = vtk.vtkRenderWindow()
122        ren_win.SetOffScreenRendering(True)
123        ren_win.SetSize(size)
124        ren.SetBackground(colors.get_color(bg))
125        ren_win.AddRenderer(ren)
126        ren_win.Render()
127
128        nx, ny = ren_win.GetSize()
129        arr = vtk.vtkUnsignedCharArray()
130        ren_win.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr)
131        narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
132        narr = np.ascontiguousarray(np.flip(narr, axis=0))
133
134        ren.RemoveActor(actor)
135        if axes:
136            ren.RemoveActor(axes.actor)
137        ren_win.Finalize()
138        del ren_win
139        return narr

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

def pickable(self, value=None):
141    def pickable(self, value=None):
142        """Set/get the pickability property of an object."""
143        if value is None:
144            return self.actor.GetPickable()
145        self.actor.SetPickable(value)
146        return self

Set/get the pickability property of an object.

def use_bounds(self, value=True):
148    def use_bounds(self, value=True):
149        """
150        Instruct the current camera to either take into account or ignore
151        the object bounds when resetting.
152        """
153        self.actor.SetUseBounds(value)
154        return self

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

def draggable(self, value=None):
156    def draggable(self, value=None):  # NOT FUNCTIONAL?
157        """Set/get the draggability property of an object."""
158        if value is None:
159            return self.actor.GetDragable()
160        self.actor.SetDragable(value)
161        return self

Set/get the draggability property of an object.

def on(self):
163    def on(self):
164        """Switch on  object visibility. Object is not removed."""
165        self.actor.VisibilityOn()
166        try:
167            self.scalarbar.actor.VisibilityOn()
168        except AttributeError:
169            pass
170        try:
171            self.trail.actor.VisibilityOn()
172        except AttributeError:
173            pass
174        try:
175            for sh in self.shadows:
176                sh.actor.VisibilityOn()
177        except AttributeError:
178            pass
179        return self

Switch on object visibility. Object is not removed.

def off(self):
181    def off(self):
182        """Switch off object visibility. Object is not removed."""
183        self.actor.VisibilityOff()
184        try:
185            self.scalarbar.actor.VisibilityOff()
186        except AttributeError:
187            pass
188        try:
189            self.trail.actor.VisibilityOff()
190        except AttributeError:
191            pass
192        try:
193            for sh in self.shadows:
194                sh.actor.VisibilityOff()
195        except AttributeError:
196            pass
197        return self

Switch off object visibility. Object is not removed.

def toggle(self):
199    def toggle(self):
200        """Toggle object visibility on/off."""
201        v = self.actor.GetVisibility()
202        if v:
203            self.off()
204        else:
205            self.on()
206        return self

Toggle object visibility on/off.

def add_scalarbar( self, title='', pos=(0.775, 0.05), title_yoffset=15, font_size=12, size=(60, 350), nlabels=None, c=None, horizontal=False, use_alpha=True, label_format=':6.3g'):
208    def add_scalarbar(
209        self,
210        title="",
211        pos=(0.775, 0.05),
212        title_yoffset=15,
213        font_size=12,
214        size=(60, 350),
215        nlabels=None,
216        c=None,
217        horizontal=False,
218        use_alpha=True,
219        label_format=":6.3g",
220    ):
221        """
222        Add a 2D scalar bar for the specified obj.
223
224        Arguments:
225            title : (str)
226                scalar bar title
227            pos : (float,float)
228                position coordinates of the bottom left corner
229            title_yoffset : (float)
230                vertical space offset between title and color scalarbar
231            font_size : (float)
232                size of font for title and numeric labels
233            size : (float,float)
234                size of the scalarbar in number of pixels (width, height)
235            nlabels : (int)
236                number of numeric labels
237            c : (list)
238                color of the scalar bar text
239            horizontal : (bool)
240                lay the scalarbar horizontally
241            use_alpha : (bool)
242                render transparency in the color bar itself
243            label_format : (str)
244                c-style format string for numeric labels
245
246        Examples:
247            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
248            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
249        """
250        plt = vedo.plotter_instance
251
252        if plt and plt.renderer:
253            c = (0.9, 0.9, 0.9)
254            if np.sum(plt.renderer.GetBackground()) > 1.5:
255                c = (0.1, 0.1, 0.1)
256            if isinstance(self.scalarbar, vtk.vtkActor):
257                plt.renderer.RemoveActor(self.scalarbar)
258            elif isinstance(self.scalarbar, vedo.Assembly):
259                for a in self.scalarbar.unpack():
260                    plt.renderer.RemoveActor(a)
261        if c is None:
262            c = "gray"
263
264        sb = vedo.addons.ScalarBar(
265            self,
266            title,
267            pos,
268            title_yoffset,
269            font_size,
270            size,
271            nlabels,
272            c,
273            horizontal,
274            use_alpha,
275            label_format,
276        )
277        self.scalarbar = sb
278        return self

Add a 2D scalar bar for the specified obj.

Arguments:
  • title : (str) scalar bar title
  • pos : (float,float) position coordinates of the bottom left corner
  • title_yoffset : (float) vertical space offset between title and color scalarbar
  • font_size : (float) size of font for title and numeric labels
  • size : (float,float) size of the scalarbar in number of pixels (width, height)
  • nlabels : (int) number of numeric labels
  • c : (list) color of the scalar bar text
  • horizontal : (bool) lay the scalarbar horizontally
  • use_alpha : (bool) render transparency in the color bar itself
  • label_format : (str) c-style format string for numeric labels
Examples:
def add_scalarbar3d( self, title='', pos=None, size=(0, 0), title_font='', title_xoffset=-1.2, title_yoffset=0.0, title_size=1.5, title_rotation=0.0, nlabels=9, label_font='', label_size=1, label_offset=0.375, label_rotation=0, label_format='', italic=0, c=None, draw_box=True, above_text=None, below_text=None, nan_text='NaN', categories=None):
280    def add_scalarbar3d(
281        self,
282        title="",
283        pos=None,
284        size=(0, 0),
285        title_font="",
286        title_xoffset=-1.2,
287        title_yoffset=0.0,
288        title_size=1.5,
289        title_rotation=0.0,
290        nlabels=9,
291        label_font="",
292        label_size=1,
293        label_offset=0.375,
294        label_rotation=0,
295        label_format="",
296        italic=0,
297        c=None,
298        draw_box=True,
299        above_text=None,
300        below_text=None,
301        nan_text="NaN",
302        categories=None,
303    ):
304        """
305        Associate a 3D scalar bar to the object and add it to the scene.
306        The new scalarbar object (Assembly) will be accessible as obj.scalarbar
307
308        Arguments:
309            size : (list)
310                (thickness, length) of scalarbar
311            title : (str)
312                scalar bar title
313            title_xoffset : (float)
314                horizontal space btw title and color scalarbar
315            title_yoffset : (float)
316                vertical space offset
317            title_size : (float)
318                size of title wrt numeric labels
319            title_rotation : (float)
320                title rotation in degrees
321            nlabels : (int)
322                number of numeric labels
323            label_font : (str)
324                font type for labels
325            label_size : (float)
326                label scale factor
327            label_offset : (float)
328                space btw numeric labels and scale
329            label_rotation : (float)
330                label rotation in degrees
331            label_format : (str)
332                label format for floats and integers (e.g. `':.2f'`)
333            draw_box : (bool)
334                draw a box around the colorbar
335            categories : (list)
336                make a categorical scalarbar,
337                the input list will have the format `[value, color, alpha, textlabel]`
338
339        Examples:
340            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
341        """
342        plt = vedo.plotter_instance
343        if plt and c is None:  # automatic black or white
344            c = (0.9, 0.9, 0.9)
345            if np.sum(vedo.get_color(plt.backgrcol)) > 1.5:
346                c = (0.1, 0.1, 0.1)
347        if c is None:
348            c = (0, 0, 0)
349        c = vedo.get_color(c)
350
351        self.scalarbar = vedo.addons.ScalarBar3D(
352            self,
353            title,
354            pos,
355            size,
356            title_font,
357            title_xoffset,
358            title_yoffset,
359            title_size,
360            title_rotation,
361            nlabels,
362            label_font,
363            label_size,
364            label_offset,
365            label_rotation,
366            label_format,
367            italic,
368            c,
369            draw_box,
370            above_text,
371            below_text,
372            nan_text,
373            categories,
374        )
375        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):
377    def color(self, col, alpha=None, vmin=None, vmax=None):
378        """
379        Assign a color or a set of colors along the range of the scalar value.
380        A single constant color can also be assigned.
381        Any matplotlib color map name is also accepted, e.g. `volume.color('jet')`.
382
383        E.g.: say that your cells scalar runs from -3 to 6,
384        and you want -3 to show red and 1.5 violet and 6 green, then just set:
385
386        `volume.color(['red', 'violet', 'green'])`
387
388        You can also assign a specific color to a aspecific value with eg.:
389
390        `volume.color([(0,'red'), (0.5,'violet'), (1,'green')])`
391
392        Arguments:
393            alpha : (list)
394                use a list to specify transparencies along the scalar range
395            vmin : (float)
396                force the min of the scalar range to be this value
397            vmax : (float)
398                force the max of the scalar range to be this value
399        """
400        # supersedes method in Points, Mesh
401
402        if col is None:
403            return self
404
405        if vmin is None:
406            vmin, _ = self.dataset.GetScalarRange()
407        if vmax is None:
408            _, vmax = self.dataset.GetScalarRange()
409        ctf = self.properties.GetRGBTransferFunction()
410        ctf.RemoveAllPoints()
411
412        if utils.is_sequence(col):
413            if utils.is_sequence(col[0]) and len(col[0]) == 2:
414                # user passing [(value1, color1), ...]
415                for x, ci in col:
416                    r, g, b = colors.get_color(ci)
417                    ctf.AddRGBPoint(x, r, g, b)
418                    # colors.printc('color at', round(x, 1),
419                    #               'set to', colors.get_color_name((r, g, b)), bold=0)
420            else:
421                # user passing [color1, color2, ..]
422                for i, ci in enumerate(col):
423                    r, g, b = colors.get_color(ci)
424                    x = vmin + (vmax - vmin) * i / (len(col) - 1)
425                    ctf.AddRGBPoint(x, r, g, b)
426        elif isinstance(col, str):
427            if col in colors.colors.keys() or col in colors.color_nicks.keys():
428                r, g, b = colors.get_color(col)
429                ctf.AddRGBPoint(vmin, r, g, b)  # constant color
430                ctf.AddRGBPoint(vmax, r, g, b)
431            else:  # assume it's a colormap
432                for x in np.linspace(vmin, vmax, num=64, endpoint=True):
433                    r, g, b = colors.color_map(x, name=col, vmin=vmin, vmax=vmax)
434                    ctf.AddRGBPoint(x, r, g, b)
435        elif isinstance(col, int):
436            r, g, b = colors.get_color(col)
437            ctf.AddRGBPoint(vmin, r, g, b)  # constant color
438            ctf.AddRGBPoint(vmax, r, g, b)
439        else:
440            vedo.logger.warning(f"in color() unknown input type {type(col)}")
441
442        if alpha is not None:
443            self.alpha(alpha, vmin=vmin, vmax=vmax)
444        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):
446    def alpha(self, alpha, vmin=None, vmax=None):
447        """
448        Assign a set of tranparencies along the range of the scalar value.
449        A single constant value can also be assigned.
450
451        E.g.: say `alpha=(0.0, 0.3, 0.9, 1)` and the scalar range goes from -10 to 150.
452        Then all cells with a value close to -10 will be completely transparent, cells at 1/4
453        of the range will get an alpha equal to 0.3 and voxels with value close to 150
454        will be completely opaque.
455
456        As a second option one can set explicit (x, alpha_x) pairs to define the transfer function.
457
458        E.g.: say `alpha=[(-5, 0), (35, 0.4) (123,0.9)]` and the scalar range goes from -10 to 150.
459        Then all cells below -5 will be completely transparent, cells with a scalar value of 35
460        will get an opacity of 40% and above 123 alpha is set to 90%.
461        """
462        if vmin is None:
463            vmin, _ = self.dataset.GetScalarRange()
464        if vmax is None:
465            _, vmax = self.dataset.GetScalarRange()
466        otf = self.properties.GetScalarOpacity()
467        otf.RemoveAllPoints()
468
469        if utils.is_sequence(alpha):
470            alpha = np.array(alpha)
471            if len(alpha.shape) == 1:  # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
472                for i, al in enumerate(alpha):
473                    xalpha = vmin + (vmax - vmin) * i / (len(alpha) - 1)
474                    # Create transfer mapping scalar value to opacity
475                    otf.AddPoint(xalpha, al)
476                    # print("alpha at", round(xalpha, 1), "\tset to", al)
477            elif len(alpha.shape) == 2:  # user passing [(x0,alpha0), ...]
478                otf.AddPoint(vmin, alpha[0][1])
479                for xalpha, al in alpha:
480                    # Create transfer mapping scalar value to opacity
481                    otf.AddPoint(xalpha, al)
482                otf.AddPoint(vmax, alpha[-1][1])
483
484        else:
485
486            otf.AddPoint(vmin, alpha)  # constant alpha
487            otf.AddPoint(vmax, alpha)
488
489        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):
 746class PointsVisual(CommonVisual):
 747    """Class to manage the visual aspects of a ``Points`` object."""
 748
 749    def __init__(self):
 750        # print("init PointsVisual")
 751        super().__init__()
 752
 753    def clone2d(self, scale=None):
 754        """
 755        Copy a 3D Mesh into a flat 2D image.
 756        Returns a `Actor2D`.
 757
 758        Examples:
 759            - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py)
 760
 761                ![](https://vedo.embl.es/images/other/clone2d.png)
 762        """
 763        if scale is None:
 764            # work out a reasonable scale
 765            msiz = self.diagonal_size()
 766            if vedo.plotter_instance and vedo.plotter_instance.window:
 767                sz = vedo.plotter_instance.window.GetSize()
 768                dsiz = utils.mag(sz)
 769                scale = dsiz / msiz / 10
 770            else:
 771                scale = 350 / msiz
 772
 773        cmsh = self.clone()
 774        poly = cmsh.pos([0, 0, 0]).scale(scale).dataset
 775
 776        cm = self.mapper.GetColorMode()
 777        lut = self.mapper.GetLookupTable()
 778        sv = self.mapper.GetScalarVisibility()
 779        use_lut = self.mapper.GetUseLookupTableScalarRange()
 780        vrange = self.mapper.GetScalarRange()
 781        sm = self.mapper.GetScalarMode()
 782
 783        mapper2d = vtk.new("PolyDataMapper2D")
 784        mapper2d.ShallowCopy(self.mapper)
 785        mapper2d.SetInputData(poly)
 786        mapper2d.SetColorMode(cm)
 787        mapper2d.SetLookupTable(lut)
 788        mapper2d.SetScalarVisibility(sv)
 789        mapper2d.SetUseLookupTableScalarRange(use_lut)
 790        mapper2d.SetScalarRange(vrange)
 791        mapper2d.SetScalarMode(sm)
 792
 793        act2d = Actor2D()
 794        act2d.properties = act2d.GetProperty()
 795        act2d.mapper = mapper2d
 796        act2d.dataset = poly
 797
 798        act2d.SetMapper(mapper2d)
 799        csys = act2d.GetPositionCoordinate()
 800        csys.SetCoordinateSystem(4)
 801        act2d.properties.SetColor(cmsh.color())
 802        act2d.properties.SetOpacity(cmsh.alpha())
 803        return act2d
 804
 805    ##################################################
 806    def copy_properties_from(self, source, deep=True, actor_related=True):
 807        """
 808        Copy properties from another ``Points`` object.
 809        """
 810        pr = vtk.vtkProperty()
 811        try:
 812            sp = source.properties
 813            mp = source.mapper
 814            sa = source.actor
 815        except AttributeError:
 816            sp = source.GetProperty()
 817            mp = source.GetMapper()
 818            sa = source
 819            
 820        if deep:
 821            pr.DeepCopy(sp)
 822        else:
 823            pr.ShallowCopy(sp)
 824        self.actor.SetProperty(pr)
 825        self.properties = pr
 826
 827        if self.actor.GetBackfaceProperty():
 828            bfpr = vtk.vtkProperty()
 829            bfpr.DeepCopy(sa.GetBackfaceProperty())
 830            self.actor.SetBackfaceProperty(bfpr)
 831            self.properties_backface = bfpr
 832
 833        if not actor_related:
 834            return self
 835
 836        # mapper related:
 837        self.mapper.SetScalarVisibility(mp.GetScalarVisibility())
 838        self.mapper.SetScalarMode(mp.GetScalarMode())
 839        self.mapper.SetScalarRange(mp.GetScalarRange())
 840        self.mapper.SetLookupTable(mp.GetLookupTable())
 841        self.mapper.SetColorMode(mp.GetColorMode())
 842        self.mapper.SetInterpolateScalarsBeforeMapping(
 843            mp.GetInterpolateScalarsBeforeMapping()
 844        )
 845        self.mapper.SetUseLookupTableScalarRange(
 846            mp.GetUseLookupTableScalarRange()
 847        )
 848
 849        self.actor.SetPickable(sa.GetPickable())
 850        self.actor.SetDragable(sa.GetDragable())
 851        self.actor.SetTexture(sa.GetTexture())
 852        self.actor.SetVisibility(sa.GetVisibility())
 853        return self
 854
 855    def color(self, c=False, alpha=None):
 856        """
 857        Set/get mesh's color.
 858        If None is passed as input, will use colors from active scalars.
 859        Same as `mesh.c()`.
 860        """
 861        if c is False:
 862            return np.array(self.properties.GetColor())
 863        if c is None:
 864            self.mapper.ScalarVisibilityOn()
 865            return self
 866        self.mapper.ScalarVisibilityOff()
 867        cc = colors.get_color(c)
 868        self.properties.SetColor(cc)
 869        if self.trail:
 870            self.trail.properties.SetColor(cc)
 871        if alpha is not None:
 872            self.alpha(alpha)
 873        return self
 874
 875    def c(self, color=False, alpha=None):
 876        """
 877        Shortcut for `color()`.
 878        If None is passed as input, will use colors from current active scalars.
 879        """
 880        return self.color(color, alpha)
 881
 882    def alpha(self, opacity=None):
 883        """Set/get mesh's transparency. Same as `mesh.opacity()`."""
 884        if opacity is None:
 885            return self.properties.GetOpacity()
 886
 887        self.properties.SetOpacity(opacity)
 888        bfp = self.actor.GetBackfaceProperty()
 889        if bfp:
 890            if opacity < 1:
 891                self.properties_backface = bfp
 892                self.actor.SetBackfaceProperty(None)
 893            else:
 894                self.actor.SetBackfaceProperty(self.properties_backface)
 895        return self
 896
 897    def lut_color_at(self, value):
 898        """
 899        Return the color of the lookup table at value.
 900        """
 901        lut = self.mapper.GetLookupTable()
 902        if not lut:
 903            return None
 904        rgb = [0,0,0]
 905        lut.GetColor(value, rgb)
 906        alpha = lut.GetOpacity(value)
 907        return np.array(rgb + [alpha])
 908
 909    def opacity(self, alpha=None):
 910        """Set/get mesh's transparency. Same as `mesh.alpha()`."""
 911        return self.alpha(alpha)
 912
 913    def force_opaque(self, value=True):
 914        """ Force the Mesh, Line or point cloud to be treated as opaque"""
 915        ## force the opaque pass, fixes picking in vtk9
 916        # but causes other bad troubles with lines..
 917        self.actor.SetForceOpaque(value)
 918        return self
 919
 920    def force_translucent(self, value=True):
 921        """ Force the Mesh, Line or point cloud to be treated as translucent"""
 922        self.actor.SetForceTranslucent(value)
 923        return self
 924
 925    def point_size(self, value=None):
 926        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
 927        if value is None:
 928            return self.properties.GetPointSize()
 929            # self.properties.SetRepresentationToSurface()
 930        else:
 931            self.properties.SetRepresentationToPoints()
 932            self.properties.SetPointSize(value)
 933        return self
 934
 935    def ps(self, pointsize=None):
 936        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
 937        return self.point_size(pointsize)
 938
 939    def render_points_as_spheres(self, value=True):
 940        """Make points look spheric or else make them look as squares."""
 941        self.properties.SetRenderPointsAsSpheres(value)
 942        return self
 943
 944    def lighting(
 945        self,
 946        style="",
 947        ambient=None,
 948        diffuse=None,
 949        specular=None,
 950        specular_power=None,
 951        specular_color=None,
 952        metallicity=None,
 953        roughness=None,
 954    ):
 955        """
 956        Set the ambient, diffuse, specular and specular_power lighting constants.
 957
 958        Arguments:
 959            style : (str)
 960                preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]`
 961            ambient : (float)
 962                ambient fraction of emission [0-1]
 963            diffuse : (float)
 964                emission of diffused light in fraction [0-1]
 965            specular : (float)
 966                fraction of reflected light [0-1]
 967            specular_power : (float)
 968                precision of reflection [1-100]
 969            specular_color : (color)
 970                color that is being reflected by the surface
 971
 972        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px>
 973
 974        Examples:
 975            - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py)
 976        """
 977        pr = self.properties
 978
 979        if style:
 980
 981            if style != "off":
 982                pr.LightingOn()
 983
 984            if style == "off":
 985                pr.SetInterpolationToFlat()
 986                pr.LightingOff()
 987                return self  ##############
 988
 989            if hasattr(pr, "GetColor"):  # could be Volume
 990                c = pr.GetColor()
 991            else:
 992                c = (1, 1, 0.99)
 993            mpr = self.mapper
 994            if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility():
 995                c = (1,1,0.99)
 996            if   style=='metallic': pars = [0.1, 0.3, 1.0, 10, c]
 997            elif style=='plastic' : pars = [0.3, 0.4, 0.3,  5, c]
 998            elif style=='shiny'   : pars = [0.2, 0.6, 0.8, 50, c]
 999            elif style=='glossy'  : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)]
1000            elif style=='ambient' : pars = [0.8, 0.1, 0.0,  1, (1,1,1)]
1001            elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c]
1002            else:
1003                vedo.logger.error("in lighting(): Available styles are")
1004                vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]")
1005                raise RuntimeError()
1006            pr.SetAmbient(pars[0])
1007            pr.SetDiffuse(pars[1])
1008            pr.SetSpecular(pars[2])
1009            pr.SetSpecularPower(pars[3])
1010            if hasattr(pr, "GetColor"):
1011                pr.SetSpecularColor(pars[4])
1012
1013        if ambient is not None: pr.SetAmbient(ambient)
1014        if diffuse is not None: pr.SetDiffuse(diffuse)
1015        if specular is not None: pr.SetSpecular(specular)
1016        if specular_power is not None: pr.SetSpecularPower(specular_power)
1017        if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color))
1018        if utils.vtk_version_at_least(9):
1019            if metallicity is not None:
1020                pr.SetInterpolationToPBR()
1021                pr.SetMetallic(metallicity)
1022            if roughness is not None:
1023                pr.SetInterpolationToPBR()
1024                pr.SetRoughness(roughness)
1025
1026        return self
1027
1028    def point_blurring(self, r=1, emissive=False):
1029        """Set point blurring.
1030        Apply a gaussian convolution filter to the points.
1031        In this case the radius `r` is in absolute units of the mesh coordinates.
1032        With emissive set, the halo of point becomes light-emissive.
1033        """
1034        self.properties.SetRepresentationToPoints()
1035        if emissive:
1036            self.mapper.SetEmissive(bool(emissive))
1037        self.mapper.SetScaleFactor(r * 1.4142)
1038
1039        # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
1040        if alpha < 1:
1041            self.mapper.SetSplatShaderCode(
1042                "//VTK::Color::Impl\n"
1043                "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
1044                "if (dist > 1.0) {\n"
1045                "   discard;\n"
1046                "} else {\n"
1047                f"  float scale = ({alpha} - dist);\n"
1048                "   ambientColor *= scale;\n"
1049                "   diffuseColor *= scale;\n"
1050                "}\n"
1051            )
1052            alpha = 1
1053
1054        self.mapper.Modified()
1055        self.actor.Modified()
1056        self.properties.SetOpacity(alpha)
1057        self.actor.SetMapper(self.mapper)
1058        return self
1059
1060    @property
1061    def cellcolors(self):
1062        """
1063        Colorize each cell (face) of a mesh by passing
1064        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1065        Colors levels and opacities must be in the range [0,255].
1066
1067        A single constant color can also be passed as string or RGBA.
1068
1069        A cell array named "CellsRGBA" is automatically created.
1070
1071        Examples:
1072            - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py)
1073            - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py)
1074
1075            ![](https://vedo.embl.es/images/basic/colorMeshCells.png)
1076        """
1077        if "CellsRGBA" not in self.celldata.keys():
1078            lut = self.mapper.GetLookupTable()
1079            vscalars = self.dataset.GetCellData().GetScalars()
1080            if vscalars is None or lut is None:
1081                arr = np.zeros([self.ncells, 4], dtype=np.uint8)
1082                col = np.array(self.properties.GetColor())
1083                col = np.round(col * 255).astype(np.uint8)
1084                alf = self.properties.GetOpacity()
1085                alf = np.round(alf * 255).astype(np.uint8)
1086                arr[:, (0, 1, 2)] = col
1087                arr[:, 3] = alf
1088            else:
1089                cols = lut.MapScalars(vscalars, 0, 0)
1090                arr = utils.vtk2numpy(cols)
1091            self.celldata["CellsRGBA"] = arr
1092        self.celldata.select("CellsRGBA")
1093        return self.celldata["CellsRGBA"]
1094
1095    @cellcolors.setter
1096    def cellcolors(self, value):
1097        if isinstance(value, str):
1098            c = colors.get_color(value)
1099            value = np.array([*c, 1]) * 255
1100            value = np.round(value)
1101
1102        value = np.asarray(value)
1103        n = self.ncells
1104
1105        if value.ndim == 1:
1106            value = np.repeat([value], n, axis=0)
1107
1108        if value.shape[1] == 3:
1109            z = np.zeros((n, 1), dtype=np.uint8)
1110            value = np.append(value, z + 255, axis=1)
1111
1112        assert n == value.shape[0]
1113
1114        self.celldata["CellsRGBA"] = value.astype(np.uint8)
1115        # self.mapper.SetColorModeToDirectScalars() # done in select()
1116        self.celldata.select("CellsRGBA")
1117
1118    @property
1119    def pointcolors(self):
1120        """
1121        Colorize each point (or vertex of a mesh) by passing
1122        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
1123        Colors levels and opacities must be in the range [0,255].
1124
1125        A single constant color can also be passed as string or RGBA.
1126
1127        A point array named "PointsRGBA" is automatically created.
1128        """
1129        if "PointsRGBA" not in self.pointdata.keys():
1130            lut = self.mapper.GetLookupTable()
1131            vscalars = self.dataset.GetPointData().GetScalars()
1132            if vscalars is None or lut is None:
1133                # create a constant array
1134                arr = np.zeros([self.npoints, 4], dtype=np.uint8)
1135                col = np.array(self.properties.GetColor())
1136                col = np.round(col * 255).astype(np.uint8)
1137                alf = self.properties.GetOpacity()
1138                alf = np.round(alf * 255).astype(np.uint8)
1139                arr[:, (0, 1, 2)] = col
1140                arr[:, 3] = alf
1141            else:
1142                cols = lut.MapScalars(vscalars, 0, 0)
1143                arr = utils.vtk2numpy(cols)
1144            self.pointdata["PointsRGBA"] = arr
1145        self.pointdata.select("PointsRGBA")
1146        return self.pointdata["PointsRGBA"]
1147
1148    @pointcolors.setter
1149    def pointcolors(self, value):
1150        if isinstance(value, str):
1151            c = colors.get_color(value)
1152            value = np.array([*c, 1]) * 255
1153            value = np.round(value)
1154
1155        value = np.asarray(value)
1156        n = self.npoints
1157
1158        if value.ndim == 1:
1159            value = np.repeat([value], n, axis=0)
1160
1161        if value.shape[1] == 3:
1162            z = np.zeros((n, 1), dtype=np.uint8)
1163            value = np.append(value, z + 255, axis=1)
1164
1165        assert n == value.shape[0]
1166
1167        self.pointdata["PointsRGBA"] = value.astype(np.uint8)
1168        # self.mapper.SetColorModeToDirectScalars() # done in select()
1169        self.pointdata.select("PointsRGBA")
1170
1171    #####################################################################################
1172    def cmap(
1173        self,
1174        input_cmap,
1175        input_array=None,
1176        on="",
1177        name="Scalars",
1178        vmin=None,
1179        vmax=None,
1180        n_colors=256,
1181        alpha=1.0,
1182        logscale=False,
1183    ):
1184        """
1185        Set individual point/cell colors by providing a list of scalar values and a color map.
1186
1187        Arguments:
1188            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
1189                color map scheme to transform a real number into a color.
1190            input_array : (str, list, vtkArray)
1191                can be the string name of an existing array, a new array or a `vtkArray`.
1192            on : (str)
1193                either 'points' or 'cells' or blank (automatic).
1194                Apply the color map to data which is defined on either points or cells.
1195            name : (str)
1196                give a name to the provided array (if input_array is an array)
1197            vmin : (float)
1198                clip scalars to this minimum value
1199            vmax : (float)
1200                clip scalars to this maximum value
1201            n_colors : (int)
1202                number of distinct colors to be used in colormap table.
1203            alpha : (float, list)
1204                Mesh transparency. Can be a `list` of values one for each vertex.
1205            logscale : (bool)
1206                Use logscale
1207
1208        Examples:
1209            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
1210            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
1211            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
1212            - (and many others)
1213
1214                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
1215        """
1216        self._cmap_name = input_cmap
1217
1218        if on == "":
1219            try:
1220                on = self.mapper.GetScalarModeAsString().replace("Use", "")
1221                if on not in ["PointData", "CellData"]: # can be "Default"
1222                    on = "points"
1223                    self.mapper.SetScalarModeToUsePointData()
1224            except AttributeError:
1225                on = "points"
1226        elif on == "Default":
1227            on = "points"
1228            self.mapper.SetScalarModeToUsePointData()
1229
1230        if input_array is None:
1231            if not self.pointdata.keys() and self.celldata.keys():
1232                on = "cells"
1233                if not self.dataset.GetCellData().GetScalars():
1234                    input_array = 0  # pick the first at hand
1235
1236        if "point" in on.lower():
1237            data = self.dataset.GetPointData()
1238            n = self.dataset.GetNumberOfPoints()
1239        elif "cell" in on.lower():
1240            data = self.dataset.GetCellData()
1241            n = self.dataset.GetNumberOfCells()
1242        else:
1243            vedo.logger.error(
1244                f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}")
1245            raise RuntimeError()
1246
1247        if input_array is None:  # if None try to fetch the active scalars
1248            arr = data.GetScalars()
1249            if not arr:
1250                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
1251                return self
1252
1253            if not arr.GetName():  # sometimes arrays dont have a name..
1254                arr.SetName(name)
1255
1256        elif isinstance(input_array, str):  # if a string is passed
1257            arr = data.GetArray(input_array)
1258            if not arr:
1259                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
1260                return self
1261
1262        elif isinstance(input_array, int):  # if an int is passed
1263            if input_array < data.GetNumberOfArrays():
1264                arr = data.GetArray(input_array)
1265            else:
1266                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
1267                return self
1268
1269        elif utils.is_sequence(input_array):  # if a numpy array is passed
1270            npts = len(input_array)
1271            if npts != n:
1272                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
1273                return self
1274            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
1275            data.AddArray(arr)
1276            data.Modified()
1277
1278        elif isinstance(input_array, vtk.vtkArray):  # if a vtkArray is passed
1279            arr = input_array
1280            data.AddArray(arr)
1281            data.Modified()
1282
1283        else:
1284            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
1285            raise RuntimeError()
1286
1287        # Now we have array "arr"
1288        array_name = arr.GetName()
1289
1290        if arr.GetNumberOfComponents() == 1:
1291            if vmin is None:
1292                vmin = arr.GetRange()[0]
1293            if vmax is None:
1294                vmax = arr.GetRange()[1]
1295        else:
1296            if vmin is None or vmax is None:
1297                vn = utils.mag(utils.vtk2numpy(arr))
1298            if vmin is None:
1299                vmin = vn.min()
1300            if vmax is None:
1301                vmax = vn.max()
1302
1303        # interpolate alphas if they are not constant
1304        if not utils.is_sequence(alpha):
1305            alpha = [alpha] * n_colors
1306        else:
1307            v = np.linspace(0, 1, n_colors, endpoint=True)
1308            xp = np.linspace(0, 1, len(alpha), endpoint=True)
1309            alpha = np.interp(v, xp, alpha)
1310
1311        ########################### build the look-up table
1312        if isinstance(input_cmap, vtk.vtkLookupTable):  # vtkLookupTable
1313            lut = input_cmap
1314
1315        elif utils.is_sequence(input_cmap):  # manual sequence of colors
1316            lut = vtk.vtkLookupTable()
1317            if logscale:
1318                lut.SetScaleToLog10()
1319            lut.SetRange(vmin, vmax)
1320            ncols = len(input_cmap)
1321            lut.SetNumberOfTableValues(ncols)
1322
1323            for i, c in enumerate(input_cmap):
1324                r, g, b = colors.get_color(c)
1325                lut.SetTableValue(i, r, g, b, alpha[i])
1326            lut.Build()
1327
1328        else:  
1329            # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
1330            lut = vtk.vtkLookupTable()
1331            if logscale:
1332                lut.SetScaleToLog10()
1333            lut.SetVectorModeToMagnitude()
1334            lut.SetRange(vmin, vmax)
1335            lut.SetNumberOfTableValues(n_colors)
1336            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
1337            for i, c in enumerate(mycols):
1338                r, g, b = c
1339                lut.SetTableValue(i, r, g, b, alpha[i])
1340            lut.Build()
1341
1342        # TEST NEW WAY
1343        self.mapper.