vedo.base

Base classes. Do not instantiate.

   1#!/usr/bin/env python3
   2# -*- coding: utf-8 -*-
   3import time
   4import numpy as np
   5from deprecated import deprecated
   6
   7try:
   8    import vedo.vtkclasses as vtk
   9except ImportError:
  10    import vtkmodules.all as vtk
  11
  12import vedo
  13from vedo import colors
  14from vedo import utils
  15
  16__docformat__ = "google"
  17
  18__doc__ = "Base classes. Do not instantiate."
  19
  20__all__ = [
  21    "Base3DProp",
  22    "BaseActor",
  23    "BaseActor2D",
  24    "BaseGrid",
  25    "probe_points",
  26    "probe_line",
  27    "probe_plane",
  28]
  29
  30
  31###############################################################################
  32class _DataArrayHelper:
  33    # Helper class to manage data associated to either
  34    # points (or vertices) and cells (or faces).
  35    # Internal use only.
  36    def __init__(self, actor, association):
  37        self.actor = actor
  38        self.association = association
  39
  40    def __getitem__(self, key):
  41
  42        if self.association == 0:
  43            data = self.actor.inputdata().GetPointData()
  44
  45        elif self.association == 1:
  46            data = self.actor.inputdata().GetCellData()
  47
  48        elif self.association == 2:
  49            data = self.actor.inputdata().GetFieldData()
  50
  51            varr = data.GetAbstractArray(key)
  52            if isinstance(varr, vtk.vtkStringArray):
  53                if isinstance(key, int):
  54                    key = data.GetArrayName(key)
  55                n = varr.GetNumberOfValues()
  56                narr = [varr.GetValue(i) for i in range(n)]
  57                return narr
  58                ###########
  59
  60        else:
  61            raise RuntimeError()
  62
  63        if isinstance(key, int):
  64            key = data.GetArrayName(key)
  65
  66        arr = data.GetArray(key)
  67        if not arr:
  68            return None
  69        return utils.vtk2numpy(arr)
  70
  71    def __setitem__(self, key, input_array):
  72
  73        if self.association == 0:
  74            data = self.actor.inputdata().GetPointData()
  75            n = self.actor.inputdata().GetNumberOfPoints()
  76            self.actor._mapper.SetScalarModeToUsePointData()
  77
  78        elif self.association == 1:
  79            data = self.actor.inputdata().GetCellData()
  80            n = self.actor.inputdata().GetNumberOfCells()
  81            self.actor._mapper.SetScalarModeToUseCellData()
  82
  83        elif self.association == 2:
  84            data = self.actor.inputdata().GetFieldData()
  85            if not utils.is_sequence(input_array):
  86                input_array = [input_array]
  87
  88            if isinstance(input_array[0], str):
  89                varr = vtk.vtkStringArray()
  90                varr.SetName(key)
  91                varr.SetNumberOfComponents(1)
  92                varr.SetNumberOfTuples(len(input_array))
  93                for i, iarr in enumerate(input_array):
  94                    if isinstance(iarr, np.ndarray):
  95                        iarr = iarr.tolist()  # better format
  96                        # Note: a string k can be converted to numpy with
  97                        # import json; k = np.array(json.loads(k))
  98                    varr.InsertValue(i, str(iarr))
  99            else:
 100                try:
 101                    varr = utils.numpy2vtk(input_array, name=key)
 102                except TypeError as e:
 103                    vedo.logger.error(
 104                        f"cannot create metadata with input object:\n"
 105                        f"{input_array}"
 106                        f"\n\nAllowed content examples are:\n"
 107                        f"- flat list of strings ['a','b', 1, [1,2,3], ...]"
 108                        f" (first item must be a string in this case)\n"
 109                        f"  hint: use k = np.array(json.loads(k)) to convert strings\n"
 110                        f"- numpy arrays of any shape"
 111                    )
 112                    raise e
 113
 114            data.AddArray(varr)
 115            return ############
 116
 117        else:
 118            raise RuntimeError()
 119
 120        if len(input_array) != n:
 121            vedo.logger.error(
 122                f"Error in point/cell data: length of input {len(input_array)}"
 123                f" !=  {n} nr. of elements"
 124            )
 125            raise RuntimeError()
 126
 127        input_array = np.asarray(input_array)
 128        varr = utils.numpy2vtk(input_array, name=key)
 129        data.AddArray(varr)
 130
 131        if len(input_array.shape) == 1:  # scalars
 132            data.SetActiveScalars(key)
 133        elif len(input_array.shape) == 2 and input_array.shape[1] == 3:  # vectors
 134            if key.lower() == "normals":
 135                data.SetActiveNormals(key)
 136            else:
 137                data.SetActiveVectors(key)
 138
 139    def keys(self):
 140        """Return the list of available data array names"""
 141        if self.association == 0:
 142            data = self.actor.inputdata().GetPointData()
 143        elif self.association == 1:
 144            data = self.actor.inputdata().GetCellData()
 145        elif  self.association == 2:
 146            data = self.actor.inputdata().GetFieldData()
 147        arrnames = []
 148        for i in range(data.GetNumberOfArrays()):
 149            name = data.GetArray(i).GetName()
 150            if name:
 151                arrnames.append(name)
 152        return arrnames
 153
 154    def remove(self, key):
 155        """Remove a data array by name or number"""
 156        if self.association == 0:
 157            self.actor.inputdata().GetPointData().RemoveArray(key)
 158        elif self.association == 1:
 159            self.actor.inputdata().GetCellData().RemoveArray(key)
 160        elif  self.association == 2:
 161            self.actor.inputdata().GetFieldData().RemoveArray(key)
 162
 163    def rename(self, oldname, newname):
 164        """Rename an array"""
 165        if self.association == 0:
 166            varr = self.actor.inputdata().GetPointData().GetArray(oldname)
 167        elif self.association == 1:
 168            varr = self.actor.inputdata().GetCellData().GetArray(oldname)
 169        elif self.association == 2:
 170            varr = self.actor.inputdata().GetFieldData().GetArray(oldname)
 171        if varr:
 172            varr.SetName(newname)
 173        else:
 174            vedo.logger.warning(
 175                f"Cannot rename non existing array {oldname} to {newname}"
 176            )
 177
 178    def select(self, key):
 179        """Select one specific array by its name to make it the `active` one."""
 180        if self.association == 0:
 181            data = self.actor.inputdata().GetPointData()
 182            self.actor.mapper().SetScalarModeToUsePointData()
 183        else:
 184            data = self.actor.inputdata().GetCellData()
 185            self.actor.mapper().SetScalarModeToUseCellData()
 186
 187        if isinstance(key, int):
 188            key = data.GetArrayName(key)
 189        data.SetActiveScalars(key)
 190
 191        if hasattr(self.actor.mapper(), "SetArrayName"):
 192            self.actor.mapper().SetArrayName(key)
 193
 194        if hasattr(self.actor.mapper(), "ScalarVisibilityOn"):  # could be volume mapper
 195            self.actor.mapper().ScalarVisibilityOn()
 196
 197    def print(self, **kwargs):
 198        """Print the array names available to terminal"""
 199        colors.printc(self.keys(), **kwargs)
 200
 201
 202###############################################################################
 203class Base3DProp:
 204    """
 205    Base class to manage positioning and size of the objects in space and other properties.
 206
 207    .. warning:: Do not use this class to instanciate objects
 208    """
 209    def __init__(self):
 210        """
 211        Base class to manage positioning and size of the objects in space and other properties.
 212        """
 213        self.filename = ""
 214        self.name = ""
 215        self.file_size = ""
 216        self.created = ""
 217        self.trail = None
 218        self.trail_points = []
 219        self.trail_segment_size = 0
 220        self.trail_offset = None
 221        self.shadows = []
 222        self.axes = None
 223        self.picked3d = None
 224        self.units = None
 225        self.top = np.array([0, 0, 1])
 226        self.base = np.array([0, 0, 0])
 227        self.info = {}
 228        self.time = time.time()
 229        self.rendered_at = set()
 230        self.transform = None
 231        self._isfollower = False  # set by mesh.follow_camera()
 232
 233        self.point_locator = None
 234        self.cell_locator = None
 235
 236        self.scalarbar = None
 237        # self.scalarbars = dict() #TODO
 238        self.pipeline = None
 239
 240
 241    def address(self):
 242        """
 243        Return a unique memory address integer which may serve as the ID of the
 244        object, or passed to c++ code.
 245        """
 246        # https://www.linkedin.com/pulse/speedup-your-code-accessing-python-vtk-objects-from-c-pletzer/
 247        # https://github.com/tfmoraes/polydata_connectivity
 248        return int(self.inputdata().GetAddressAsString("")[5:], 16)
 249
 250    def pickable(self, value=None):
 251        """Set/get the pickability property of an object."""
 252        if value is None:
 253            return self.GetPickable()
 254        self.SetPickable(value)
 255        return self
 256
 257    def draggable(self, value=None):  # NOT FUNCTIONAL?
 258        """Set/get the draggability property of an object."""
 259        if value is None:
 260            return self.GetDragable()
 261        self.SetDragable(value)
 262        return self
 263
 264    def origin(self, x=None, y=None, z=None):
 265        """
 266        Set/get object's origin.
 267
 268        Relevant to control the scaling with `scale()` and rotations.
 269        Has no effect on position.
 270        """
 271        if x is None:
 272            return np.array(self.GetOrigin()) + self.GetPosition()
 273
 274        if z is None and y is None:  # assume x is of the form (x,y,z)
 275            if len(x) == 3:
 276                x, y, z = x
 277            else:
 278                x, y = x
 279                z = 0
 280        elif z is None:  # assume x,y is of the form x, y
 281            z = 0
 282        self.SetOrigin([x, y, z] - np.array(self.GetPosition()))
 283        return self
 284
 285    def pos(self, x=None, y=None, z=None):
 286        """Set/Get object position."""
 287        if x is None:  # get functionality
 288            return np.array(self.GetPosition())
 289
 290        if z is None and y is None:  # assume x is of the form (x,y,z)
 291            if len(x) == 3:
 292                x, y, z = x
 293            else:
 294                x, y = x
 295                z = 0
 296        elif z is None:  # assume x,y is of the form x, y
 297            z = 0
 298        self.SetPosition(x, y, z)
 299
 300        self.point_locator = None
 301        self.cell_locator = None
 302        return self  # return itself to concatenate methods
 303
 304    def shift(self, dx=0, dy=0, dz=0):
 305        """Add a vector to the current object position."""
 306        p = np.array(self.GetPosition())
 307
 308        if utils.is_sequence(dx):
 309            if len(dx) == 2:
 310                self.SetPosition(p + [dx[0], dx[1], 0])
 311            else:
 312                self.SetPosition(p + dx)
 313        else:
 314            self.SetPosition(p + [dx, dy, dz])
 315
 316        self.point_locator = None
 317        self.cell_locator = None
 318        return self
 319
 320    def x(self, val=None):
 321        """Set/Get object position along x axis."""
 322        p = self.GetPosition()
 323        if val is None:
 324            return p[0]
 325        self.pos(val, p[1], p[2])
 326        return self
 327
 328    def y(self, val=None):
 329        """Set/Get object position along y axis."""
 330        p = self.GetPosition()
 331        if val is None:
 332            return p[1]
 333        self.pos(p[0], val, p[2])
 334        return self
 335
 336    def z(self, val=None):
 337        """Set/Get object position along z axis."""
 338        p = self.GetPosition()
 339        if val is None:
 340            return p[2]
 341        self.pos(p[0], p[1], val)
 342        return self
 343
 344    def rotate(self, angle, axis=(1, 0, 0), point=(0, 0, 0), rad=False):
 345        """
 346        Rotate around an arbitrary `axis` passing through `point`.
 347
 348        Example:
 349            ```python
 350            from vedo import *
 351            c1 = Cube()
 352            c2 = c1.clone().c('violet').alpha(0.5) # copy of c1
 353            v = vector(0.2,1,0)
 354            p = vector(1,0,0)  # axis passes through this point
 355            c2.rotate(90, axis=v, point=p)
 356            l = Line(-v+p, v+p).lw(3).c('red')
 357            show(c1, l, c2, axes=1).close()
 358            ```
 359            ![](https://vedo.embl.es/images/feats/rotate_axis.png)
 360        """
 361        if rad:
 362            anglerad = angle
 363        else:
 364            anglerad = np.deg2rad(angle)
 365        axis = utils.versor(axis)
 366        a = np.cos(anglerad / 2)
 367        b, c, d = -axis * np.sin(anglerad / 2)
 368        aa, bb, cc, dd = a * a, b * b, c * c, d * d
 369        bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d
 370        R = np.array(
 371            [
 372                [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)],
 373                [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)],
 374                [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc],
 375            ]
 376        )
 377        rv = np.dot(R, self.GetPosition() - np.asarray(point)) + point
 378
 379        if rad:
 380            angle *= 180.0 / np.pi
 381        # this vtk method only rotates in the origin of the object:
 382        self.RotateWXYZ(angle, axis[0], axis[1], axis[2])
 383        self.pos(rv)
 384        return self
 385
 386    def _rotatexyz(self, a, angle, rad, around):
 387        if rad:
 388            angle *= 180 / np.pi
 389
 390        T = vtk.vtkTransform()
 391        T.SetMatrix(self.GetMatrix())
 392        T.PostMultiply()
 393
 394        rot = dict(x=T.RotateX, y=T.RotateY, z=T.RotateZ)
 395
 396        if around is None:
 397            # rotate around its origin
 398            rot[a](angle)
 399        else:
 400            if around == "itself":
 401                around = self.GetPosition()
 402            # displacement needed to bring it back to the origin
 403            # and disregard origin
 404            disp = around - np.array(self.GetOrigin())
 405            T.Translate(-disp)
 406            rot[a](angle)
 407            T.Translate(disp)
 408
 409        self.SetOrientation(T.GetOrientation())
 410        self.SetPosition(T.GetPosition())
 411
 412        self.point_locator = None
 413        self.cell_locator = None
 414        return self
 415
 416    @deprecated(reason=vedo.colors.red + "Please use rotate_x()" + vedo.colors.reset)
 417    def rotateX(self, *a, **b):
 418        """Deprecated. Please use `rotate_x()`."""
 419        return self.rotate_x(*a, **b)
 420    @deprecated(reason=vedo.colors.red + "Please use rotate_y()" + vedo.colors.reset)
 421    def rotateY(self, *a, **b):
 422        """Deprecated. Please use `rotate_y()`."""
 423        return self.rotate_y(*a, **b)
 424    @deprecated(reason=vedo.colors.red + "Please use rotate_z()" + vedo.colors.reset)
 425    def rotateZ(self, *a, **b):
 426        """Deprecated. Please use `rotate_z()`."""
 427        return self.rotate_z(*a, **b)
 428
 429    def rotate_x(self, angle, rad=False, around=None):
 430        """
 431        Rotate around x-axis. If angle is in radians set `rad=True`.
 432
 433        Use `around` to define a pivoting point.
 434        """
 435        return self._rotatexyz("x", angle, rad, around)
 436
 437    def rotate_y(self, angle, rad=False, around=None):
 438        """
 439        Rotate around y-axis. If angle is in radians set `rad=True`.
 440
 441        Use `around` to define a pivoting point.
 442        """
 443        return self._rotatexyz("y", angle, rad, around)
 444
 445    def rotate_z(self, angle, rad=False, around=None):
 446        """
 447        Rotate around z-axis. If angle is in radians set `rad=True`.
 448
 449        Use `around` to define a pivoting point.
 450        """
 451        return self._rotatexyz("z", angle, rad, around)
 452
 453    def orientation(self, newaxis=None, rotation=0, concatenate=False, xyplane=False, rad=False):
 454        """
 455        Set/Get object orientation.
 456
 457        Arguments:
 458            rotation : (float)
 459                rotate object around newaxis.
 460            concatenate : (bool)
 461                concatenate the orietation operation with the previous existing transform (if any)
 462            xyplane : (bool)
 463                make an extra rotation to keep the object aligned to the xy-plane
 464            rad : (bool)
 465                set to True if angle is expressed in radians.
 466
 467        Example:
 468            ```python
 469            from vedo import *
 470            center = np.array([581/2,723/2,0])
 471            objs = []
 472            for a in np.linspace(0, 6.28, 7):
 473                v = vector(cos(a), sin(a), 0)*1000
 474                pic = Picture(dataurl+"images/dog.jpg").rotate_z(10)
 475                pic.orientation(v, xyplane=True)
 476                pic.origin(center)
 477                pic.pos(v - center)
 478                objs += [pic, Arrow(v, v+v)]
 479            show(objs, Point(), axes=1).close()
 480            ```
 481            ![](https://vedo.embl.es/images/feats/orientation.png)
 482
 483        Examples:
 484            - [gyroscope2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope2.py)
 485
 486            ![](https://vedo.embl.es/images/simulations/50738942-687b5780-11d9-11e9-97f0-72bbd63f7d6e.gif)
 487        """
 488        if self.top is None or self.base is None:
 489            initaxis = (0, 0, 1)
 490        else:
 491            initaxis = utils.versor(self.top - self.base)
 492
 493        newaxis = utils.versor(newaxis)
 494        p = np.array(self.GetPosition())
 495        crossvec = np.cross(initaxis, newaxis)
 496
 497        angleth = np.arccos(np.dot(initaxis, newaxis))
 498
 499        T = vtk.vtkTransform()
 500        if concatenate:
 501            try:
 502                M = self.GetMatrix()
 503                T.SetMatrix(M)
 504            except:
 505                pass
 506        T.PostMultiply()
 507        T.Translate(-p)
 508        if rotation:
 509            if rad:
 510                rotation *= 180.0 / np.pi
 511            T.RotateWXYZ(rotation, initaxis)
 512        if xyplane:
 513            angleph = np.arctan2(newaxis[1], newaxis[0])
 514            T.RotateWXYZ(np.rad2deg(angleph + angleth), initaxis) # compensation
 515        T.RotateWXYZ(np.rad2deg(angleth), crossvec)
 516        T.Translate(p)
 517
 518        self.SetOrientation(T.GetOrientation())
 519
 520        self.point_locator = None
 521        self.cell_locator = None
 522        return self
 523
 524        # newaxis = utils.versor(newaxis)
 525        # pos = np.array(self.GetPosition())
 526        # crossvec = np.cross(initaxis, newaxis)
 527        # angle = np.arccos(np.dot(initaxis, newaxis))
 528        # T = vtk.vtkTransform()
 529        # T.PostMultiply()
 530        # T.Translate(-pos)
 531        # if rotation:
 532        #     T.RotateWXYZ(rotation, initaxis)
 533        # T.RotateWXYZ(np.rad2deg(angle), crossvec)
 534        # T.Translate(pos)
 535        # self.SetUserTransform(T)
 536        # self.transform = T
 537
 538
 539    def scale(self, s=None, reset=False):
 540        """
 541        Set/get object's scaling factor.
 542
 543        Arguments:
 544            s : (list, float)
 545                scaling factor(s).
 546            reset : (bool)
 547                if True previous scaling factors are ignored.
 548
 549        Note:
 550            use `s=(sx,sy,sz)` to scale differently in the three coordinates.
 551        """
 552        if s is None:
 553            return np.array(self.GetScale())
 554
 555        # assert s[0] != 0
 556        # assert s[1] != 0
 557        # assert s[2] != 0
 558
 559        if reset:
 560            self.SetScale(s)
 561        else:
 562            self.SetScale(np.multiply(self.GetScale(), s))
 563
 564        self.point_locator = None
 565        self.cell_locator = None
 566        return self
 567
 568    def get_transform(self, invert=False):
 569        """
 570        Check if `object.transform` exists and returns a `vtkTransform`.
 571        Otherwise return current user transformation (where the object is currently placed).
 572
 573        Use `invert` to return the inverse of the current transformation
 574
 575        Example:
 576            ```python
 577            from vedo import *
 578
 579            c1 = Cube()
 580            c2 = c1.clone().c('violet').alpha(0.5) # copy of c1
 581            v = vector(0.2,1,0)
 582            p = vector(1,0,0)  # axis passes through this point
 583            c2.rotate(90, axis=v, point=p)
 584
 585            # get the inverse of the current transformation
 586            T = c2.get_transform(invert=True)
 587            c2.apply_transform(T)  # put back c2 in place
 588
 589            l = Line(p-v, p+v).lw(3).c('red')
 590            show(c1.wireframe().lw(3), l, c2, axes=1).close()
 591            ```
 592            ![](https://vedo.embl.es/images/feats/get_transf.png)
 593        """
 594        if self.transform:
 595            tr = self.transform
 596            if invert:
 597                tr = tr.GetInverse()
 598            return tr
 599
 600        T = self.GetMatrix()
 601        tr = vtk.vtkTransform()
 602        tr.SetMatrix(T)
 603        if invert:
 604            tr = tr.GetInverse()
 605        return tr
 606
 607
 608    @deprecated(reason=vedo.colors.red + "Please use apply_transform()" + vedo.colors.reset)
 609    def applyTransform(self, T, reset=False, concatenate=False):
 610        """Deprecated. Please use `apply_transform()`"""
 611        return self.apply_transform(T,reset,concatenate)
 612
 613    def apply_transform(self, T, reset=False, concatenate=False):
 614        """
 615        Transform object position and orientation.
 616
 617        Arguments:
 618            reset : (bool)
 619                no effect, this is superseded by `pointcloud.apply_transform()`
 620            concatenate : (bool)
 621                no effect, this is superseded by `pointcloud.apply_transform()`
 622        """
 623        if isinstance(T, vtk.vtkMatrix4x4):
 624            self.SetUserMatrix(T)
 625        elif utils.is_sequence(T):
 626            vm = vtk.vtkMatrix4x4()
 627            for i in [0, 1, 2, 3]:
 628                for j in [0, 1, 2, 3]:
 629                    vm.SetElement(i, j, T[i][j])
 630            self.SetUserMatrix(vm)
 631        else:
 632            self.SetUserTransform(T)
 633        self.transform = T
 634
 635        self.point_locator = None
 636        self.cell_locator = None
 637        return self
 638
 639    def align_to_bounding_box(self, msh, rigid=False):
 640        """
 641        Align the current object's bounding box to the bounding box
 642        of the input object.
 643
 644        Use `rigid` to disable scaling.
 645
 646        Examples:
 647            - [align6.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align6.py)
 648        """
 649        lmt = vtk.vtkLandmarkTransform()
 650        ss = vtk.vtkPoints()
 651        xss0, xss1, yss0, yss1, zss0, zss1 = self.bounds()
 652        for p in [
 653            [xss0, yss0, zss0],
 654            [xss1, yss0, zss0],
 655            [xss1, yss1, zss0],
 656            [xss0, yss1, zss0],
 657            [xss0, yss0, zss1],
 658            [xss1, yss0, zss1],
 659            [xss1, yss1, zss1],
 660            [xss0, yss1, zss1],
 661        ]:
 662            ss.InsertNextPoint(p)
 663        st = vtk.vtkPoints()
 664        xst0, xst1, yst0, yst1, zst0, zst1 = msh.bounds()
 665        for p in [
 666            [xst0, yst0, zst0],
 667            [xst1, yst0, zst0],
 668            [xst1, yst1, zst0],
 669            [xst0, yst1, zst0],
 670            [xst0, yst0, zst1],
 671            [xst1, yst0, zst1],
 672            [xst1, yst1, zst1],
 673            [xst0, yst1, zst1],
 674        ]:
 675            st.InsertNextPoint(p)
 676
 677        lmt.SetSourceLandmarks(ss)
 678        lmt.SetTargetLandmarks(st)
 679        lmt.SetModeToAffine()
 680        if rigid:
 681            lmt.SetModeToRigidBody()
 682        lmt.Update()
 683        self.apply_transform(lmt)
 684        self.transform = lmt
 685
 686        self.point_locator = None
 687        self.cell_locator = None
 688        return self
 689
 690    def on(self):
 691        """Switch on  object visibility. Object is not removed."""
 692        self.VisibilityOn()
 693        try:
 694            self.scalarbar.VisibilityOn()
 695        except AttributeError:
 696            pass
 697        try:
 698            self.trail.VisibilityOn()
 699        except AttributeError:
 700            pass
 701        try:
 702            for sh in self.shadows:
 703                sh.VisibilityOn()
 704        except AttributeError:
 705            pass        
 706        return self
 707
 708    def off(self):
 709        """Switch off object visibility. Object is not removed."""
 710        self.VisibilityOff()
 711        try:
 712            self.scalarbar.VisibilityOff()
 713        except AttributeError:
 714            pass
 715        try:
 716            self.trail.VisibilityOff()
 717        except AttributeError:
 718            pass
 719        try:
 720            for sh in self.shadows:
 721                sh.VisibilityOff()
 722        except AttributeError:
 723            pass        
 724        return self
 725    
 726    def toggle(self):
 727        """Toggle object visibility on/off."""
 728        v = self.GetVisibility()
 729        if v:
 730            self.off()
 731        else:
 732            self.on()
 733        return self
 734
 735    def box(self, scale=1, padding=0, fill=False):
 736        """
 737        Return the bounding box as a new `Mesh`.
 738
 739        Arguments:
 740            scale : (float)
 741                box size can be scaled by a factor
 742            padding : (float, list)
 743                a constant padding can be added (can be a list [padx,pady,padz])
 744
 745        Examples:
 746            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
 747        """
 748        b = self.bounds()
 749        if not utils.is_sequence(padding):
 750            padding = [padding, padding, padding]
 751        length, width, height = b[1] - b[0], b[3] - b[2], b[5] - b[4]
 752        tol = (length + width + height) / 30000  # useful for boxing 2D text
 753        pos = [(b[0] + b[1]) / 2, (b[3] + b[2]) / 2, (b[5] + b[4]) / 2 - tol]
 754        bx = vedo.shapes.Box(
 755            pos,
 756            length * scale + padding[0],
 757            width  * scale + padding[1],
 758            height * scale + padding[2],
 759            c="gray",
 760        )
 761        if hasattr(self, "GetProperty"):  # could be Assembly
 762            if isinstance(self.GetProperty(), vtk.vtkProperty):  # could be volume
 763                pr = vtk.vtkProperty()
 764                pr.DeepCopy(self.GetProperty())
 765                bx.SetProperty(pr)
 766                bx.property = pr
 767        bx.wireframe(not fill)
 768        bx.flat().lighting("off")
 769        return bx
 770
 771    def use_bounds(self, ub=True):
 772        """
 773        Instruct the current camera to either take into account or ignore
 774        the object bounds when resetting.
 775        """
 776        self.SetUseBounds(ub)
 777        return self
 778
 779    def bounds(self):
 780        """
 781        Get the object bounds.
 782        Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`.
 783        """
 784        try:
 785            pts = self.points()
 786            xmin, ymin, zmin = np.min(pts, axis=0)
 787            xmax, ymax, zmax = np.max(pts, axis=0)
 788            return [xmin,xmax, ymin,ymax, zmin,zmax]
 789        except (AttributeError, ValueError):
 790            return self.GetBounds()
 791
 792    def xbounds(self, i=None):
 793        """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1)."""
 794        b = self.bounds()
 795        if i is not None:
 796            return b[i]
 797        return (b[0], b[1])
 798
 799    def ybounds(self, i=None):
 800        """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1)."""
 801        b = self.bounds()
 802        if i == 0:
 803            return b[2]
 804        if i == 1:
 805            return b[3]
 806        return (b[2], b[3])
 807
 808    def zbounds(self, i=None):
 809        """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1)."""
 810        b = self.bounds()
 811        if i == 0: return b[4]
 812        if i == 1: return b[5]
 813        return (b[4], b[5])
 814
 815    @deprecated(reason=vedo.colors.red + "Please use diagonal_size()" + vedo.colors.reset)
 816    def diagonalSize(self):
 817        """Deprecated. Please use `diagonal_size()`."""
 818        return self.diagonal_size()
 819
 820    def diagonal_size(self):
 821        """Get the length of the diagonal of mesh bounding box."""
 822        b = self.bounds()
 823        return np.sqrt((b[1] - b[0]) ** 2 + (b[3] - b[2]) ** 2 + (b[5] - b[4]) ** 2)
 824        # return self.GetLength() # ???different???
 825
 826
 827    def copy_data_from(self, obj):
 828        """Copy all data (point and cell data) from this input object"""
 829        self._data.GetPointData().PassData(obj._data.GetPointData())
 830        self._data.GetCellData().PassData(obj._data.GetCellData())
 831        self.pipeline = utils.OperationNode(
 832            f"copy_data_from\n{obj.__class__.__name__}", 
 833            parents=[self, obj],
 834            shape='note',
 835            c="#ccc5b9",
 836        )
 837        return self
 838
 839    def print(self):
 840        """Print information about an object."""
 841        utils.print_info(self)
 842        return self
 843
 844    def show(self, **options):
 845        """
 846        Create on the fly an instance of class `Plotter` or use the last existing one to
 847        show one single object.
 848
 849        This method is meant as a shortcut. If more than one object needs to be visualised
 850        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
 851
 852        Returns the `Plotter` class instance.
 853        """
 854        return vedo.plotter.show(self, **options)
 855
 856    def thumbnail(
 857            self, 
 858            zoom=1.25,
 859            size=(200, 200),
 860            bg="white", 
 861            azimuth=0, 
 862            elevation=0, 
 863            axes=False,
 864        ):
 865        """Build a thumbnail of the object and return it as an array."""
 866        # speed is about 20Hz for size=[200,200]
 867        ren = vtk.vtkRenderer()
 868        ren.AddActor(self)
 869        if axes:
 870            axes = vedo.addons.Axes(self)
 871            ren.AddActor(axes)
 872        ren.ResetCamera()
 873        cam = ren.GetActiveCamera()
 874        cam.Zoom(zoom)
 875        cam.Elevation(elevation)
 876        cam.Azimuth(azimuth)
 877
 878        ren_win = vtk.vtkRenderWindow()
 879        ren_win.SetOffScreenRendering(True)
 880        ren_win.SetSize(size)
 881        ren.SetBackground(colors.get_color(bg))
 882        ren_win.AddRenderer(ren)
 883        ren_win.Render()
 884
 885        nx, ny = ren_win.GetSize()
 886        arr = vtk.vtkUnsignedCharArray()
 887        ren_win.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
 888        narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
 889        narr = np.ascontiguousarray(np.flip(narr, axis=0))
 890
 891        ren.RemoveActor(self)
 892        if axes:
 893            ren.RemoveActor(axes)
 894        ren_win.Finalize()
 895        del ren_win
 896        return narr
 897
 898
 899########################################################################################
 900class BaseActor2D(vtk.vtkActor2D):
 901    """
 902    Base class.
 903
 904    .. warning:: Do not use this class to instanciate objects.
 905    """
 906    def __init__(self):
 907        """Manage 2D objects."""
 908        super().__init__()
 909        self._mapper = None
 910        self.property = self.GetProperty()
 911        self.filename = ""
 912
 913    def layer(self, value=None):
 914        """Set/Get the layer number in the overlay planes into which to render."""
 915        if value is None:
 916            return self.GetLayerNumber()
 917        self.SetLayerNumber(value)
 918        return self
 919
 920    def pos(self, px=None, py=None):
 921        """Set/Get the screen-coordinate position."""
 922        if isinstance(px, str):
 923            vedo.logger.error("Use string descriptors only inside the contructor")
 924            return self
 925        if px is None:
 926            return np.array(self.GetPosition(), dtype=int)
 927        if  py is not None:
 928            p = [px,py]
 929        else:
 930            p = px
 931        assert len(p) == 2, "Error: len(pos) must be 2 for BaseActor2D"
 932        self.SetPosition(p)
 933        return self
 934
 935    def coordinate_system(self, value=None):
 936        """
 937        Set/get the coordinate system which this coordinate is defined in.
 938        
 939        The options are:
 940            0. Display
 941            1. Normalized Display
 942            2. Viewport
 943            3. Normalized Viewport
 944            4. View
 945            5. Pose
 946            6. World
 947        """
 948        coor = self.GetPositionCoordinate()
 949        if value is None:
 950            return coor.GetCoordinateSystem()
 951        coor.SetCoordinateSystem(value)
 952        return self
 953
 954    def on(self):
 955        """Set object visibility."""
 956        self.VisibilityOn()
 957        return self
 958
 959    def off(self):
 960        """Set object visibility."""
 961        self.VisibilityOn()
 962        return self
 963
 964    def toggle(self):
 965        """Toggle object visibility."""
 966        self.SetVisibility(not(self.GetVisibility()))
 967        return self
 968
 969    def pickable(self, value=True):
 970        self.SetPickable(value)
 971        return self
 972
 973    def alpha(self, value=None):
 974        """Set/Get the object opacity."""
 975        if value is None:
 976            return self.GetProperty().GetOpacity()
 977        self.GetProperty().SetOpacity(value)
 978        return self
 979
 980    def ps(self, point_size=None):
 981        if point_size is None:
 982            return self.GetProperty().GetPointSize()
 983        self.GetProperty().SetPointSize(point_size)
 984        return self
 985    
 986    def ontop(self, value=True):
 987        """Keep the object always on top of everithing else."""
 988        if value:
 989            self.GetProperty().SetDisplayLocationToForeground()
 990        else:
 991            self.GetProperty().SetDisplayLocationToBackground()
 992        return self
 993
 994
 995########################################################################################
 996class BaseActor(Base3DProp):
 997    """
 998    Base class.
 999
1000    .. warning:: Do not use this class to instanciate objects, use one the above instead.
1001    """
1002    def __init__(self):
1003        """
1004        Base class to add operative and data
1005        functionality to `Mesh`, `Assembly`, `Volume` and `Picture` objects.
1006        """
1007        
1008        super().__init__()
1009
1010        self._mapper = None
1011        self._caption = None
1012        self.property = None
1013
1014
1015    def mapper(self, new_mapper=None):
1016        """Return the `vtkMapper` data object, or update it with a new one."""
1017        if new_mapper:
1018            self.SetMapper(new_mapper)
1019            if self._mapper:
1020                iptdata = self._mapper.GetInput()
1021                if iptdata:
1022                    new_mapper.SetInputData(self._mapper.GetInput())
1023            self._mapper = new_mapper
1024            self._mapper.Modified()
1025        return self._mapper
1026
1027    def inputdata(self):
1028        """Return the VTK input data object."""
1029        if self._mapper:
1030            return self._mapper.GetInput()
1031        return self.GetMapper().GetInput()
1032
1033    def modified(self):
1034        """Use in conjunction with `tonumpy()`
1035        to update any modifications to the volume array"""
1036        sc = self.inputdata().GetPointData().GetScalars()
1037        if sc:
1038            sc.Modified()
1039        self.inputdata().GetPointData().Modified()
1040        return self
1041
1042    @deprecated(reason=vedo.colors.red + "Please use property object.npoints" + vedo.colors.reset)
1043    def N(self):
1044        """Deprecated. Please use property object.npoints"""
1045        return self.inputdata().GetNumberOfPoints()
1046
1047    @deprecated(reason=vedo.colors.red + "Please use property object.npoints" + vedo.colors.reset)
1048    def NPoints(self):
1049        """Deprecated. Please use property object.npoints"""
1050        return self.inputdata().GetNumberOfPoints()
1051
1052    @deprecated(reason=vedo.colors.red + "Please use property object.ncells" + vedo.colors.reset)
1053    def NCells(self):
1054        """Deprecated. Please use property object.ncells"""
1055        return self.inputdata().GetNumberOfCells()
1056
1057    @property
1058    def npoints(self):
1059        """Retrieve the number of points."""
1060        return self.inputdata().GetNumberOfPoints()
1061
1062    @property
1063    def ncells(self):
1064        """Retrieve the number of cells."""
1065        return self.inputdata().GetNumberOfCells()
1066
1067
1068    def points(self, pts=None, transformed=True):
1069        """
1070        Set/Get the vertex coordinates of a mesh or point cloud.
1071        Argument can be an index, a set of indices
1072        or a complete new set of points to update the mesh.
1073
1074        Set `transformed=False` to ignore any previous transformation applied to the mesh.
1075        """
1076        if pts is None:  ### getter
1077
1078            if isinstance(self, vedo.Points):
1079                vpts = self.polydata(transformed).GetPoints()
1080            elif isinstance(self, vedo.BaseVolume):
1081                v2p = vtk.vtkImageToPoints()
1082                v2p.SetInputData(self.imagedata())
1083                v2p.Update()
1084                vpts = v2p.GetOutput().GetPoints()
1085            else:  # tetmesh et al
1086                vpts = self.inputdata().GetPoints()
1087
1088            if vpts:
1089                return utils.vtk2numpy(vpts.GetData())
1090            return np.array([], dtype=np.float32)
1091
1092        else:  ### setter
1093
1094            if len(pts) == 3 and len(pts[0]) != 3:
1095                # assume plist is in the format [all_x, all_y, all_z]
1096                pts = np.stack((pts[0], pts[1], pts[2]), axis=1)
1097            pts = np.asarray(pts, dtype=np.float32)
1098            if pts.shape[1] == 2:
1099                pts = np.c_[pts, np.zeros(pts.shape[0], dtype=np.float32)]
1100            vpts = self.inputdata().GetPoints()
1101            arr = utils.numpy2vtk(pts, dtype=np.float32)
1102            vpts.SetData(arr)
1103            vpts.Modified()
1104            # reset mesh to identity matrix position/rotation:
1105            self.PokeMatrix(vtk.vtkMatrix4x4())
1106            self.point_locator = None
1107            self.cell_locator = None
1108            return self
1109
1110    @deprecated(reason=vedo.colors.red + "Please use cell_centers()" + vedo.colors.reset)
1111    def cellCenters(self):
1112        """Deprecated. Please use `cell_centers()`"""
1113        return self.cell_centers()
1114
1115    def cell_centers(self):
1116        """
1117        Get the coordinates of the cell centers.
1118
1119        Examples:
1120            - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py)
1121        """
1122        vcen = vtk.vtkCellCenters()
1123        if hasattr(self, "polydata"):
1124            vcen.SetInputData(self.polydata())
1125        else:
1126            vcen.SetInputData(self.inputdata())
1127        vcen.Update()
1128        return utils.vtk2numpy(vcen.GetOutput().GetPoints().GetData())
1129
1130    def delete_cells(self, ids):
1131        """
1132        Remove cells from the mesh object by their ID.
1133        Points (vertices) are not removed (you may use `.clean()` to remove those).
1134        """
1135        data = self.inputdata()
1136        data.BuildLinks()
1137        for cid in ids:
1138            data.DeleteCell(cid)
1139        data.RemoveDeletedCells()
1140        data.Modified()
1141        self._mapper.Modified()
1142        self.pipeline = utils.OperationNode(
1143            "delete_cells", parents=[self],
1144            comment=f"#cells {self._data.GetNumberOfCells()}",
1145        )
1146        return self
1147
1148    def mark_boundaries(self):
1149        """Mark cells and vertices of the mesh if they lie on a boundary."""
1150        mb = vtk.vtkMarkBoundaryFilter()
1151        mb.SetInputData(self._data)
1152        mb.Update()
1153        out = self._update(mb.GetOutput())
1154        out.pipeline = utils.OperationNode("mark_boundaries", parents=[self])
1155        return out
1156
1157    def find_cells_in(self, xbounds=(), ybounds=(), zbounds=()):
1158        """
1159        Find cells that are within the specified bounds.
1160        Setting a color will add a vtk array to colorize these cells.
1161        """
1162        if len(xbounds) == 6:
1163            bnds = xbounds
1164        else:
1165            bnds = list(self.bounds())
1166            if len(xbounds) == 2:
1167                bnds[0] = xbounds[0]
1168                bnds[1] = xbounds[1]
1169            if len(ybounds) == 2:
1170                bnds[2] = ybounds[0]
1171                bnds[3] = ybounds[1]
1172            if len(zbounds) == 2:
1173                bnds[4] = zbounds[0]
1174                bnds[5] = zbounds[1]
1175
1176        cellIds = vtk.vtkIdList()
1177        self.cell_locator = vtk.vtkCellTreeLocator()
1178        self.cell_locator.SetDataSet(self.polydata())
1179        self.cell_locator.BuildLocator()
1180        self.cell_locator.FindCellsWithinBounds(bnds, cellIds)
1181
1182        cids = []
1183        for i in range(cellIds.GetNumberOfIds()):
1184            cid = cellIds.GetId(i)
1185            cids.append(cid)
1186
1187        return np.array(cids)
1188
1189    def count_vertices(self):
1190        """Count the number of vertices each cell has and return it as a numpy array"""
1191        vc = vtk.vtkCountVertices()
1192        vc.SetInputData(self._data)
1193        vc.SetOutputArrayName("VertexCount")
1194        vc.Update()
1195        varr = vc.GetOutput().GetCellData().GetArray("VertexCount")
1196        return utils.vtk2numpy(varr)
1197
1198    def lighting(
1199        self,
1200        style="",
1201        ambient=None,
1202        diffuse=None,
1203        specular=None,
1204        specular_power=None,
1205        specular_color=None,
1206        metallicity=None,
1207        roughness=None,
1208    ):
1209        """
1210        Set the ambient, diffuse, specular and specular_power lighting constants.
1211
1212        Arguments:
1213            style : (str)
1214                preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]`
1215            ambient : (float)
1216                ambient fraction of emission [0-1]
1217            diffuse : (float)
1218                emission of diffused light in fraction [0-1]
1219            specular : (float)
1220                fraction of reflected light [0-1]
1221            specular_power : (float)
1222                precision of reflection [1-100]
1223            specular_color : (color)
1224                color that is being reflected by the surface
1225
1226        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px>
1227
1228        Examples:
1229            - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py)
1230        """
1231        pr = self.GetProperty()
1232
1233        if style:
1234
1235            if isinstance(pr, vtk.vtkVolumeProperty):
1236                self.shade(True)
1237                if style == "off":
1238                    self.shade(False)
1239                elif style == "ambient":
1240                    style = "default"
1241                    self.shade(False)
1242            else:
1243                if style != "off":
1244                    pr.LightingOn()
1245
1246            if style == "off":
1247                pr.SetInterpolationToFlat()
1248                pr.LightingOff()
1249                return self  ##############
1250
1251            if hasattr(pr, "GetColor"):  # could be Volume
1252                c = pr.GetColor()
1253            else:
1254                c = (1, 1, 0.99)
1255            mpr = self._mapper
1256            if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility():
1257                c = (1,1,0.99)
1258            if   style=='metallic': pars = [0.1, 0.3, 1.0, 10, c]
1259            elif style=='plastic' : pars = [0.3, 0.4, 0.3,  5, c]
1260            elif style=='shiny'   : pars = [0.2, 0.6, 0.8, 50, c]
1261            elif style=='glossy'  : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)]
1262            elif style=='ambient' : pars = [0.8, 0.1, 0.0,  1, (1,1,1)]
1263            elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c]
1264            else:
1265                vedo.logger.error("in lighting(): Available styles are")
1266                vedo.logger.error(
1267                    "[default, metallic, plastic, shiny, glossy, ambient, off]"
1268                )
1269                raise RuntimeError()
1270            pr.SetAmbient(pars[0])
1271            pr.SetDiffuse(pars[1])
1272            pr.SetSpecular(pars[2])
1273            pr.SetSpecularPower(pars[3])
1274            if hasattr(pr, "GetColor"):
1275                pr.SetSpecularColor(pars[4])
1276
1277        if ambient is not None: pr.SetAmbient(ambient)
1278        if diffuse is not None: pr.SetDiffuse(diffuse)
1279        if specular is not None: pr.SetSpecular(specular)
1280        if specular_power is not None: pr.SetSpecularPower(specular_power)
1281        if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color))
1282        if utils.vtk_version_at_least(9):
1283            if metallicity is not None:
1284                pr.SetInterpolationToPBR()
1285                pr.SetMetallic(metallicity)
1286            if roughness is not None:
1287                pr.SetInterpolationToPBR()
1288                pr.SetRoughness(roughness)
1289
1290        return self
1291
1292    def print_histogram(
1293        self,
1294        bins=10,
1295        height=10,
1296        logscale=False,
1297        minbin=0,
1298        horizontal=False,
1299        char="\U00002589",
1300        c=None,
1301        bold=True,
1302        title="Histogram",
1303    ):
1304        """
1305        Ascii histogram printing on terminal.
1306        Input can be `Volume` or `Mesh` (will grab the active point array).
1307
1308        Arguments:
1309            bins : (int)
1310                number of histogram bins
1311            height : (int)
1312                height of the histogram in character units
1313            logscale : (bool)
1314                use logscale for frequencies
1315            minbin : (int)
1316                ignore bins before minbin
1317            horizontal : (bool)
1318                show histogram horizontally
1319            char : (str)
1320                character to be used as marker
1321            c : (color)
1322                ascii color
1323            bold : (bool)
1324                use boldface
1325            title : (str)
1326                histogram title
1327
1328        ![](https://vedo.embl.es/images/feats/histoprint.png)
1329        """
1330        utils.print_histogram(
1331            self, bins, height, logscale, minbin, horizontal, char, c, bold, title
1332        )
1333        return self
1334
1335    def c(self, color=False, alpha=None):
1336        """
1337        Shortcut for `color()`.
1338        If None is passed as input, will use colors from current active scalars.
1339        """
1340        return self.color(color, alpha)
1341
1342    @property
1343    def pointdata(self):
1344        """
1345        Create and/or return a `numpy.array` associated to points (vertices).
1346        A data array can be indexed either as a string or by an integer number.
1347        E.g.:  `myobj.pointdata["arrayname"]`
1348
1349        Usage:
1350
1351            `myobj.pointdata.keys()` to return the available data array names
1352
1353            `myobj.pointdata.select(name)` to make this array the active one
1354
1355            `myobj.pointdata.remove(name)` to remove this array
1356        """
1357        return _DataArrayHelper(self, 0)
1358
1359    @property
1360    def celldata(self):
1361        """
1362        Create and/or return a `numpy.array` associated to cells (faces).
1363        A data array can be indexed either as a string or by an integer number.
1364        E.g.:  `myobj.celldata["arrayname"]`
1365
1366        Usage:
1367
1368            `myobj.celldata.keys()` to return the available data array names
1369
1370            `myobj.celldata.select(name)` to make this array the active one
1371
1372            `myobj.celldata.remove(name)` to remove this array
1373        """
1374        return _DataArrayHelper(self, 1)
1375
1376    @property
1377    def metadata(self):
1378        """
1379        Create and/or return a `numpy.array` associated to neither cells nor faces.
1380        A data array can be indexed either as a string or by an integer number.
1381        E.g.:  `myobj.metadata["arrayname"]`
1382
1383        Usage:
1384
1385            `myobj.metadata.keys()` to return the available data array names
1386
1387            `myobj.metadata.select(name)` to make this array the active one
1388
1389            `myobj.metadata.remove(name)` to remove this array
1390        """
1391        return _DataArrayHelper(self, 2)
1392
1393
1394    def map_cells_to_points(self, arrays=(), move=False):
1395        """
1396        Interpolate cell data (i.e., data specified per cell or face)
1397        into point data (i.e., data specified at each vertex).
1398        The method of transformation is based on averaging the data values
1399        of all cells using a particular point.
1400        
1401        A custom list of arrays to be mapped can be passed in input.
1402 
1403        Set `move=True` to delete the original `celldata` array.
1404        """
1405        c2p = vtk.vtkCellDataToPointData()
1406        c2p.SetInputData(self.inputdata())
1407        if not move:
1408            c2p.PassCellDataOn()
1409        if arrays:
1410            c2p.ClearCellDataArrays()
1411            c2p.ProcessAllArraysOff()
1412            for arr in arrays:
1413                c2p.AddCellDataArray(arr)
1414        else:
1415            c2p.ProcessAllArraysOn()
1416        c2p.Update()
1417        self._mapper.SetScalarModeToUsePointData()
1418        out = self._update(c2p.GetOutput())
1419        out.pipeline = utils.OperationNode("map cell\nto point data", parents=[self])
1420        return out
1421
1422    def map_points_to_cells(self, arrays=(), move=False):
1423        """
1424        Interpolate point data (i.e., data specified per point or vertex)
1425        into cell data (i.e., data specified per cell).
1426        The method of transformation is based on averaging the data values
1427        of all points defining a particular cell.
1428
1429        A custom list of arrays to be mapped can be passed in input.
1430
1431        Set `move=True` to delete the original `pointdata` array.
1432
1433        Examples:
1434            - [mesh_map2cell.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_map2cell.py)
1435        """
1436        p2c = vtk.vtkPointDataToCellData()
1437        p2c.SetInputData(self.inputdata())
1438        if not move:
1439            p2c.PassPointDataOn()
1440        if arrays:
1441            p2c.ClearPointDataArrays()
1442            p2c.ProcessAllArraysOff()
1443            for arr in arrays:
1444                p2c.AddPointDataArray(arr)
1445        else:
1446            p2c.ProcessAllArraysOn()
1447        p2c.Update()
1448        self._mapper.SetScalarModeToUseCellData()
1449        out = self._update(p2c.GetOutput())
1450        out.pipeline = utils.OperationNode("map point\nto cell data", parents=[self])
1451        return out
1452
1453    def resample_data_from(self, source, tol=None, categorical=False):
1454        """
1455        Resample point and cell data from another dataset.
1456        The output has the same structure but its point data have
1457        the resampled values from target.
1458
1459        Use `tol` to set the tolerance used to compute whether
1460        a point in the source is in a cell of the current object.
1461        Points without resampled values, and their cells, are marked as blank.
1462        If the data is categorical, then the resulting data will be determined
1463        by a nearest neighbor interpolation scheme.
1464
1465        Example:
1466        ```python
1467        from vedo import *
1468        m1 = Mesh(dataurl+'bunny.obj')#.add_gaussian_noise(0.1)
1469        pts = m1.points()
1470        ces = m1.cell_centers()
1471        m1.pointdata["xvalues"] = np.power(pts[:,0], 3)
1472        m1.celldata["yvalues"]  = np.power(ces[:,1], 3)
1473        m2 = Mesh(dataurl+'bunny.obj')
1474        m2.resample_arrays_from(m1)
1475        # print(m2.pointdata["xvalues"])
1476        show(m1, m2 , N=2, axes=1)
1477        ```
1478        """
1479        rs = vtk.vtkResampleWithDataSet()
1480        rs.SetInputData(self.inputdata())
1481        rs.SetSourceData(source.inputdata())
1482        
1483        rs.SetPassPointArrays(True)
1484        rs.SetPassCellArrays(True)
1485        rs.SetPassFieldArrays(True)
1486        rs.SetCategoricalData(categorical)
1487
1488        rs.SetComputeTolerance(True)
1489        if tol:
1490            rs.SetComputeTolerance(False)
1491            rs.SetTolerance(tol)
1492        rs.Update()
1493        self._update(rs.GetOutput())
1494        self.pipeline = utils.OperationNode(
1495            f"resample_data_from\n{source.__class__.__name__}",
1496            parents=[self, source])
1497        return self
1498
1499    def add_ids(self):
1500        """Generate point and cell ids arrays."""
1501        ids = vtk.vtkIdFilter()
1502        ids.SetInputData(self.inputdata())
1503        ids.PointIdsOn()
1504        ids.CellIdsOn()
1505        ids.FieldDataOff()
1506        ids.SetPointIdsArrayName("PointID")
1507        ids.SetCellIdsArrayName("CellID")
1508        ids.Update()
1509        self._update(ids.GetOutput())
1510        self.pipeline = utils.OperationNode("add_ids", parents=[self])
1511        return self
1512
1513    def gradient(self, input_array=None, on="points", fast=False):
1514        """
1515        Compute and return the gradiend of the active scalar field as a numpy array.
1516
1517        Arguments:
1518            input_array : (str)
1519                array of the scalars to compute the gradient,
1520                if None the current active array is selected
1521            on : (str)
1522                compute either on 'points' or 'cells' data
1523            fast : (bool)
1524                if True, will use a less accurate algorithm
1525                that performs fewer derivative calculations (and is therefore faster).
1526
1527        Examples:
1528            - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/isolines.py)
1529
1530            ![](https://user-images.githubusercontent.com/32848391/72433087-f00a8780-3798-11ea-9778-991f0abeca70.png)
1531        """
1532        gra = vtk.vtkGradientFilter()
1533        if on.startswith("p"):
1534            varr = self.inputdata().GetPointData()
1535            tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS
1536        else:
1537            varr = self.inputdata().GetCellData()
1538            tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS
1539
1540        if input_array is None:
1541            if varr.GetScalars():
1542                input_array = varr.GetScalars().GetName()
1543            else:
1544                vedo.logger.error(f"in gradient: no scalars found for {on}")
1545                raise RuntimeError
1546
1547        gra.SetInputData(self.inputdata())
1548        gra.SetInputScalars(tp, input_array)
1549        gra.SetResultArrayName("Gradient")
1550        gra.SetFasterApproximation(fast)
1551        gra.ComputeDivergenceOff()
1552        gra.ComputeVorticityOff()
1553        gra.ComputeGradientOn()
1554        gra.Update()
1555        if on.startswith("p"):
1556            gvecs = utils.vtk2numpy(gra.GetOutput().GetPointData().GetArray("Gradient"))
1557        else:
1558            gvecs = utils.vtk2numpy(gra.GetOutput().GetCellData().GetArray("Gradient"))
1559        return gvecs
1560
1561    def divergence(self, array_name=None, on="points", fast=False):
1562        """
1563        Compute and return the divergence of a vector field as a numpy array.
1564
1565        Arguments:
1566            array_name : (str)
1567                name of the array of vectors to compute the divergence,
1568                if None the current active array is selected
1569            on : (str)
1570                compute either on 'points' or 'cells' data
1571            fast : (bool)
1572                if True, will use a less accurate algorithm
1573                that performs fewer derivative calculations (and is therefore faster).
1574        """
1575        div = vtk.vtkGradientFilter()
1576        if on.startswith("p"):
1577            varr = self.inputdata().GetPointData()
1578            tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS
1579        else:
1580            varr = self.inputdata().GetCellData()
1581            tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS
1582
1583        if array_name is None:
1584            if varr.GetVectors():
1585                array_name = varr.GetVectors().GetName()
1586            else:
1587                vedo.logger.error(f"in divergence(): no vectors found for {on}")
1588                raise RuntimeError
1589
1590        div.SetInputData(self.inputdata())
1591        div.SetInputScalars(tp, array_name)
1592        div.ComputeDivergenceOn()
1593        div.ComputeGradientOff()
1594        div.ComputeVorticityOff()
1595        div.SetDivergenceArrayName("Divergence")
1596        div.SetFasterApproximation(fast)
1597        div.Update()
1598        if on.startswith('p'):
1599            dvecs = utils.vtk2numpy(div.GetOutput().GetPointData().GetArray('Divergence'))
1600        else:
1601            dvecs = utils.vtk2numpy(div.GetOutput().GetCellData().GetArray('Divergence'))
1602        return dvecs
1603
1604    def vorticity(self, array_name=None, on="points", fast=False):
1605        """
1606        Compute and return the vorticity of a vector field as a numpy array.
1607
1608        Arguments:
1609            array_name : (str)
1610                name of the array to compute the vorticity,
1611                if None the current active array is selected
1612            on : (str)
1613                compute either on 'points' or 'cells' data
1614            fast : (bool)
1615                if True, will use a less accurate algorithm
1616                that performs fewer derivative calculations (and is therefore faster).
1617        """
1618        vort = vtk.vtkGradientFilter()
1619        if on.startswith("p"):
1620            varr = self.inputdata().GetPointData()
1621            tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS
1622        else:
1623            varr = self.inputdata().GetCellData()
1624            tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS
1625
1626        if array_name is None:
1627            if varr.GetVectors():
1628                array_name = varr.GetVectors().GetName()
1629            else:
1630                vedo.logger.error(f"in vorticity(): no vectors found for {on}")
1631                raise RuntimeError
1632
1633        vort.SetInputData(self.inputdata())
1634        vort.SetInputScalars(tp, array_name)
1635        vort.ComputeDivergenceOff()
1636        vort.ComputeGradientOff()
1637        vort.ComputeVorticityOn()
1638        vort.SetVorticityArrayName("Vorticity")
1639        vort.SetFasterApproximation(fast)
1640        vort.Update()
1641        if on.startswith('p'):
1642            vvecs = utils.vtk2numpy(vort.GetOutput().GetPointData().GetArray('Vorticity'))
1643        else:
1644            vvecs = utils.vtk2numpy(vort.GetOutput().GetCellData().GetArray('Vorticity'))
1645        return vvecs
1646
1647
1648    @deprecated(reason=vedo.colors.red + "Please use method add_scalarbar()" + vedo.colors.reset)
1649    def addScalarBar(self, *a, **b):
1650        """Deprecated. Please use method `add_scalarbar()`"""
1651        return self.add_scalarbar(*a, **b)
1652
1653    def add_scalarbar(
1654            self,
1655            title="",
1656            pos=(0.8,0.05),
1657            title_yoffset=15,
1658            font_size=12,
1659            size=(None,None),
1660            nlabels=None,
1661            c=None,
1662            horizontal=False,
1663            use_alpha=True,
1664            label_format=':6.3g',
1665        ):
1666        """
1667        Add a 2D scalar bar for the specified obj.
1668
1669        Examples:
1670            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
1671            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
1672        """
1673        plt = vedo.plotter_instance
1674
1675        if plt and plt.renderer:
1676            c = (0.9, 0.9, 0.9)
1677            if np.sum(plt.renderer.GetBackground()) > 1.5:
1678                c = (0.1, 0.1, 0.1)
1679            if isinstance(self.scalarbar, vtk.vtkActor):
1680                plt.renderer.RemoveActor(self.scalarbar)
1681            elif isinstance(self.scalarbar, vedo.Assembly):
1682                for a in self.scalarbar.unpack():
1683                    plt.renderer.RemoveActor(a)
1684        if c is None:
1685            c = "gray"
1686
1687        sb = vedo.addons.ScalarBar(
1688            self,
1689            title,
1690            pos,
1691            title_yoffset,
1692            font_size,
1693            size,
1694            nlabels,
1695            c,
1696            horizontal,
1697            use_alpha,
1698            label_format,
1699        )
1700        self.scalarbar = sb
1701        return self
1702
1703    @deprecated(reason=vedo.colors.red + "Please use method add_scalarbar3d()" + vedo.colors.reset)
1704    def addScalarBar3D(self, *a, **b):
1705        """Deprecated. Please use method `add_scalarbar3d()`"""
1706        return self.add_scalarbar3d(*a, **b)
1707
1708    def add_scalarbar3d(
1709        self,
1710        title="",
1711        pos=None,
1712        s=(None, None),
1713        title_font="",
1714        title_xoffset=-1.5,
1715        title_yoffset=0.0,
1716        title_size=1.5,
1717        title_rotation=0.0,
1718        nlabels=9,
1719        label_font="",
1720        label_size=1,
1721        label_offset=0.375,
1722        label_rotation=0,
1723        label_format="",
1724        italic=0,
1725        c=None,
1726        draw_box=True,
1727        above_text=None,
1728        below_text=None,
1729        nan_text="NaN",
1730        categories=None,
1731    ):
1732        """
1733        Associate a 3D scalar bar to the object and add it to the scene.
1734        The new scalarbar object (Assembly) will be accessible as obj.scalarbar
1735
1736        Arguments:
1737            s : (list)
1738                (thickness, length) of scalarbar
1739            title : (str)
1740                scalar bar title
1741            title_xoffset : (float)
1742                horizontal space btw title and color scalarbar
1743            title_yoffset : (float)
1744                vertical space offset
1745            title_size : (float)
1746                size of title wrt numeric labels
1747            title_rotation : (float)
1748                title rotation in degrees
1749            nlabels : (int)
1750                number of numeric labels
1751            label_font : (str)
1752                font type for labels
1753            label_size : (float)
1754                label scale factor
1755            label_offset : (float)
1756                space btw numeric labels and scale
1757            label_rotation : (float)
1758                label rotation in degrees
1759            label_format : (str)
1760                label format for floats and integers (e.g. `':.2f'`)
1761            draw_box : (bool)
1762                draw a box around the colorbar
1763            categories : (list)
1764                make a categorical scalarbar,
1765                the input list will have the format `[value, color, alpha, textlabel]`
1766
1767        Examples:
1768            - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
1769        """
1770        plt = vedo.plotter_instance
1771        if plt and c is None:  # automatic black or white
1772            c = (0.9, 0.9, 0.9)
1773            if np.sum(vedo.get_color(plt.backgrcol)) > 1.5:
1774                c = (0.1, 0.1, 0.1)
1775        if c is None:
1776            c = (0, 0, 0)
1777        c = vedo.get_color(c)
1778
1779        self.scalarbar = vedo.addons.ScalarBar3D(
1780            self,
1781            title,
1782            pos,
1783            s,
1784            title_font,
1785            title_xoffset,
1786            title_yoffset,
1787            title_size,
1788            title_rotation,
1789            nlabels,
1790            label_font,
1791            label_size,
1792            label_offset,
1793            label_rotation,
1794            label_format,
1795            italic,
1796            c,
1797            draw_box,
1798            above_text,
1799            below_text,
1800            nan_text,
1801            categories,
1802        )
1803        return self
1804
1805    ###################################################################################
1806    def write(self, filename, binary=True):
1807        """Write object to file."""
1808        out = vedo.io.write(self, filename, binary)
1809        out.pipeline = utils.OperationNode(
1810            "write", parents=[self],
1811            comment=filename[:15],
1812            shape="folder",
1813            c="#8a817c",
1814        )
1815        return out
1816
1817
1818########################################################################################
1819class BaseGrid(BaseActor):
1820    """
1821    Base class for grid datasets.
1822
1823    .. warning:: Do not use this class to instanciate objects.
1824    """
1825    def __init__(self):
1826        """Base class for grid datasets."""
1827
1828        super().__init__()
1829
1830        self._data = None
1831        self.useCells = True
1832        self._color = None
1833        self._alpha = [0,1]
1834
1835        # -----------------------------------------------------------
1836
1837    def _update(self, data):
1838        self._data = data
1839        self._mapper.SetInputData(self.tomesh().polydata())
1840        self._mapper.Modified()
1841        return self
1842
1843    def tomesh(self, fill=True, shrink=1.0):
1844        """
1845        Build a polygonal Mesh from the current Grid object.
1846
1847        If `fill=True`, the interior faces of all the cells are created.
1848        (setting a `shrink` value slightly smaller than the default 1.0
1849        can avoid flickering due to internal adjacent faces).
1850
1851        If `fill=False`, only the boundary faces will be generated.
1852        """
1853        gf = vtk.vtkGeometryFilter()
1854        if fill:
1855            sf = vtk.vtkShrinkFilter()
1856            sf.SetInputData(self._data)
1857            sf.SetShrinkFactor(shrink)
1858            sf.Update()
1859            gf.SetInputData(sf.GetOutput())
1860            gf.Update()
1861            poly = gf.GetOutput()
1862            if shrink == 1.0:
1863                cleanPolyData = vtk.vtkCleanPolyData()
1864                cleanPolyData.PointMergingOn()
1865                cleanPolyData.ConvertLinesToPointsOn()
1866                cleanPolyData.ConvertPolysToLinesOn()
1867                cleanPolyData.ConvertStripsToPolysOn()
1868                cleanPolyData.SetInputData(poly)
1869                cleanPolyData.Update()
1870                poly = cleanPolyData.GetOutput()
1871        else:
1872            gf.SetInputData(self._data)
1873            gf.Update()
1874            poly = gf.GetOutput()
1875
1876        msh = vedo.mesh.Mesh(poly).flat()
1877        msh.scalarbar = self.scalarbar
1878        lut = utils.ctf2lut(self)
1879        if lut:
1880            msh.mapper().SetLookupTable(lut)
1881        if self.useCells:
1882            msh.mapper().SetScalarModeToUseCellData()
1883        else:
1884            msh.mapper().SetScalarModeToUsePointData()
1885        
1886        msh.pipeline = utils.OperationNode(
1887            "tomesh", parents=[self], 
1888            comment=f"fill={fill}",
1889            c="#9e2a2b:#e9c46a",
1890        )
1891        return msh
1892
1893    def cells(self):
1894        """
1895        Get the cells connectivity ids as a numpy array.
1896
1897        The output format is: `[[id0 ... idn], [id0 ... idm],  etc]`.
1898        """
1899        arr1d = utils.vtk2numpy(self._data.GetCells().GetData())
1900        if arr1d is None:
1901            return []
1902
1903        # Get cell connettivity ids as a 1D array. vtk format is:
1904        # [nids1, id0 ... idn, niids2, id0 ... idm,  etc].
1905        i = 0
1906        conn = []
1907        n = len(arr1d)
1908        if n:
1909            while True:
1910                cell = [arr1d[i + k] for k in range(1, arr1d[i] + 1)]
1911                conn.append(cell)
1912                i += arr1d[i] + 1
1913                if i >= n:
1914                    break
1915        return conn
1916
1917    def color(self, col, alpha=None, vmin=None, vmax=None):
1918        """
1919        Assign a color or a set of colors along the range of the scalar value.
1920        A single constant color can also be assigned.
1921        Any matplotlib color map name is also accepted, e.g. `volume.color('jet')`.
1922
1923        E.g.: say that your cells scalar runs from -3 to 6,
1924        and you want -3 to show red and 1.5 violet and 6 green, then just set:
1925
1926        `volume.color(['red', 'violet', 'green'])`
1927
1928        You can also assign a specific color to a aspecific value with eg.:
1929
1930        `volume.color([(0,'red', (0.5,'violet'), (1,'green')])`
1931
1932        Arguments:
1933            alpha : (list)
1934                use a list to specify transparencies along the scalar range
1935            vmin : (float)
1936                force the min of the scalar range to be this value
1937            vmax : (float)
1938                force the max of the scalar range to be this value
1939        """
1940        # supersedes method in Points, Mesh
1941        if vmin is None:
1942            vmin, _ = self._data.GetScalarRange()
1943        if vmax is None:
1944            _, vmax = self._data.GetScalarRange()
1945        ctf = self.GetProperty().GetRGBTransferFunction()
1946        ctf.RemoveAllPoints()
1947        self._color = col
1948
1949        if utils.is_sequence(col):
1950            if utils.is_sequence(col[0]) and len(col[0]) == 2:
1951                # user passing [(value1, color1), ...]
1952                for x, ci in col:
1953                    r, g, b = colors.get_color(ci)
1954                    ctf.AddRGBPoint(x, r, g, b)
1955                    # colors.printc('color at', round(x, 1),
1956                    #               'set to', colors.get_color_name((r, g, b)),
1957                    #               c='w', bold=0)
1958            else:
1959                # user passing [color1, color2, ..]
1960                for i, ci in enumerate(col):
1961                    r, g, b = colors.get_color(ci)
1962                    x = vmin + (vmax - vmin) * i / (len(col) - 1)
1963                    ctf.AddRGBPoint(x, r, g, b)
1964        elif isinstance(col, str):
1965            if col in colors.colors.keys() or col in colors.color_nicks.keys():
1966                r, g, b = colors.get_color(col)
1967                ctf.AddRGBPoint(vmin, r, g, b)  # constant color
1968                ctf.AddRGBPoint(vmax, r, g, b)
1969            else:  # assume it's a colormap
1970                for x in np.linspace(vmin, vmax, num=64, endpoint=True):
1971                    r, g, b = colors.color_map(x, name=col, vmin=vmin, vmax=vmax)
1972                    ctf.AddRGBPoint(x, r, g, b)
1973        elif isinstance(col, int):
1974            r, g, b = colors.get_color(col)
1975            ctf.AddRGBPoint(vmin, r, g, b)  # constant color
1976            ctf.AddRGBPoint(vmax, r, g, b)
1977        else:
1978            vedo.logger.warning(f"in color() unknown input type {type(col)}")
1979
1980        if alpha is not None:
1981            self.alpha(alpha, vmin=vmin, vmax=vmax)
1982        return self
1983
1984    def alpha(self, alpha, vmin=None, vmax=None):
1985        """
1986        Assign a set of tranparencies along the range of the scalar value.
1987        A single constant value can also be assigned.
1988
1989        E.g.: say `alpha=(0.0, 0.3, 0.9, 1)` and the scalar range goes from -10 to 150.
1990        Then all cells with a value close to -10 will be completely transparent, cells at 1/4
1991        of the range will get an alpha equal to 0.3 and voxels with value close to 150
1992        will be completely opaque.
1993
1994        As a second option one can set explicit (x, alpha_x) pairs to define the transfer function.
1995
1996        E.g.: say `alpha=[(-5, 0), (35, 0.4) (123,0.9)]` and the scalar range goes from -10 to 150.
1997        Then all cells below -5 will be completely transparent, cells with a scalar value of 35
1998        will get an opacity of 40% and above 123 alpha is set to 90%.
1999        """
2000        if vmin is None:
2001            vmin, _ = self._data.GetScalarRange()
2002        if vmax is None:
2003            _, vmax = self._data.GetScalarRange()
2004        otf = self.GetProperty().GetScalarOpacity()
2005        otf.RemoveAllPoints()
2006        self._alpha = alpha
2007
2008        if utils.is_sequence(alpha):
2009            alpha = np.array(alpha)
2010            if len(alpha.shape)==1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1)
2011                for i, al in enumerate(alpha):
2012                    xalpha = vmin + (vmax - vmin) * i / (len(alpha) - 1)
2013                    # Create transfer mapping scalar value to opacity
2014                    otf.AddPoint(xalpha, al)
2015                    # colors.printc("alpha at", round(xalpha, 1), "\tset to", al)
2016            elif len(alpha.shape) == 2:  # user passing [(x0,alpha0), ...]
2017                otf.AddPoint(vmin, alpha[0][1])
2018                for xalpha, al in alpha:
2019                    # Create transfer mapping scalar value to opacity
2020                    otf.AddPoint(xalpha, al)
2021                otf.AddPoint(vmax, alpha[-1][1])
2022
2023        else:
2024
2025            otf.AddPoint(vmin, alpha)  # constant alpha
2026            otf.AddPoint(vmax, alpha)
2027
2028        return self
2029
2030    def alpha_unit(self, u=None):
2031        """
2032        Defines light attenuation per unit length. Default is 1.
2033        The larger the unit length, the further light has to travel to attenuate the same amount.
2034
2035        E.g., if you set the unit distance to 0, you will get full opacity.
2036        It means that when light travels 0 distance it's already attenuated a finite amount.
2037        Thus, any finite distance should attenuate all light.
2038        The larger you make the unit distance, the more transparent the rendering becomes.
2039        """
2040        if u is None:
2041            return self.GetProperty().GetScalarOpacityUnitDistance()
2042        self.GetProperty().SetScalarOpacityUnitDistance(u)
2043        return self
2044
2045    def shrink(self, fraction=0.8):
2046        """
2047        Shrink the individual cells to improve visibility.
2048        
2049        ![](https://vedo.embl.es/images/feats/shrink_hex.png)
2050        """
2051        sf = vtk.vtkShrinkFilter()
2052        sf.SetInputData(self.inputdata())
2053        sf.SetShrinkFactor(fraction)
2054        sf.Update()
2055        self._update(sf.GetOutput())
2056        self.pipeline = utils.OperationNode(
2057            "shrink", comment=f"by {fraction}", parents=[self], c='#9e2a2b')
2058        return self
2059
2060    def isosurface(self, value=None, flying_edges=True):
2061        """
2062        Return an `Mesh` isosurface extracted from the `Volume` object.
2063
2064        Set `value` as single float or list of values to draw the isosurface(s).
2065        Use flying_edges for faster results (but sometimes can interfere with `smooth()`).
2066
2067        Examples:
2068            - [isosurfaces.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces.py)
2069
2070                ![](https://vedo.embl.es/images/volumetric/isosurfaces.png)
2071        """
2072        scrange = self._data.GetScalarRange()
2073
2074        if flying_edges:
2075            cf = vtk.vtkFlyingEdges3D()
2076            cf.InterpolateAttributesOn()
2077        else:
2078            cf = vtk.vtkContourFilter()
2079            cf.UseScalarTreeOn()
2080
2081        cf.SetInputData(self._data)
2082        cf.ComputeNormalsOn()
2083
2084        if utils.is_sequence(value):
2085            cf.SetNumberOfContours(len(value))
2086            for i, t in enumerate(value):
2087                cf.SetValue(i, t)
2088        else:
2089            if value is None:
2090                value = (2 * scrange[0] + scrange[1]) / 3.0
2091                # print("automatic isosurface value =", value)
2092            cf.SetValue(0, value)
2093
2094        cf.Update()
2095        poly = cf.GetOutput()
2096
2097        out = vedo.mesh.Mesh(poly, c=None).phong()
2098        out.mapper().SetScalarRange(scrange[0], scrange[1])
2099
2100        out.pipeline = utils.OperationNode(
2101            "isosurface", parents=[self], 
2102            comment=f"#pts {out._data.GetNumberOfPoints()}",
2103            c="#4cc9f0:#e9c46a",
2104        )
2105        return out
2106
2107    def legosurface(
2108        self, vmin=None, vmax=None, 
2109        invert=False, boundary=False,
2110        array_name='input_scalars',
2111    ):
2112        """
2113        Represent an object - typically a `Volume` - as lego blocks (voxels).
2114        By default colors correspond to the volume's scalar.
2115        Returns an `Mesh` object.
2116
2117        Arguments:
2118            vmin : (float)
2119                the lower threshold, voxels below this value are not shown.
2120            vmax : (float)
2121                the upper threshold, voxels above this value are not shown.
2122            boundary : (bool)
2123                controls whether to include cells that are partially inside
2124            array_name : (int, str)
2125                name or index of the scalar array to be considered
2126
2127        Examples:
2128            - [legosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/legosurface.py)
2129
2130                ![](https://vedo.embl.es/images/volumetric/56820682-da40e500-684c-11e9-8ea3-91cbcba24b3a.png)
2131        """
2132        dataset = vtk.vtkImplicitDataSet()
2133        dataset.SetDataSet(self._data)
2134        window = vtk.vtkImplicitWindowFunction()
2135        window.SetImplicitFunction(dataset)
2136
2137        srng = list(self._data.GetScalarRange())
2138        if vmin is not None:
2139            srng[0] = vmin
2140        if vmax is not None:
2141            srng[1] = vmax
2142        tol = 0.00001 * (srng[1] - srng[0])
2143        srng[0] -= tol
2144        srng[1] += tol
2145        window.SetWindowRange(srng)
2146
2147        extract = vtk.vtkExtractGeometry()
2148        extract.SetInputData(self._data)
2149        extract.SetImplicitFunction(window)
2150        extract.SetExtractInside(invert)
2151        extract.SetExtractBoundaryCells(boundary)
2152        extract.Update()
2153
2154        gf = vtk.vtkGeometryFilter()
2155        gf.SetInputData(extract.GetOutput())
2156        gf.Update()
2157
2158        m = vedo.mesh.Mesh(gf.GetOutput()).lw(0.1).flat()
2159        m.map_points_to_cells()
2160        m.celldata.select(array_name)
2161
2162        m.pipeline = utils.OperationNode(
2163            "legosurface", parents=[self], 
2164            comment=f"array: {array_name}",
2165            c="#4cc9f0:#e9c46a",
2166        )
2167        return m
2168
2169    @deprecated(reason=vedo.colors.red + "Please use cut_with_plane()" + vedo.colors.reset)
2170    def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0)):
2171        """Deprecated. Please use cut_with_plane()"""
2172        return self.cut_with_plane(origin, normal)
2173
2174    def cut_with_plane(self, origin=(0, 0, 0), normal="x"):
2175        """
2176        Cut the object with the plane defined by a point and a normal.
2177
2178        Arguments:
2179            origin : (list)
2180                the cutting plane goes through this point
2181            normal : (list, str)
2182                normal vector to the cutting plane
2183        """
2184        strn = str(normal)
2185        if strn   ==  "x": normal = (1, 0, 0)
2186        elif strn ==  "y": normal = (0, 1, 0)
2187        elif strn ==  "z": normal = (0, 0, 1)
2188        elif strn == "-x": normal = (-1, 0, 0)
2189        elif strn == "-y": normal = (0, -1, 0)
2190        elif strn == "-z": normal = (0, 0, -1)
2191        plane = vtk.vtkPlane()
2192        plane.SetOrigin(origin)
2193        plane.SetNormal(normal)
2194        clipper = vtk.vtkClipDataSet()
2195        clipper.SetInputData(self._data)
2196        clipper.SetClipFunction(plane)
2197        clipper.GenerateClipScalarsOff()
2198        clipper.GenerateClippedOutputOff()
2199        clipper.SetValue(0)
2200        clipper.Update()
2201        cout = clipper.GetOutput()
2202        self._update(cout)
2203        self.pipeline = utils.OperationNode(
2204            "cut_with_plane", parents=[self], c='#9e2a2b')
2205        return self
2206
2207    def cut_with_box(self, box):
2208        """
2209        Cut the grid with the specified bounding box.
2210
2211        Parameter box has format [xmin, xmax, ymin, ymax, zmin, zmax].
2212        If an object is passed, its bounding box are used.
2213
2214        Example:
2215            ```python
2216            from vedo import *
2217            tetmesh = TetMesh(dataurl+'limb_ugrid.vtk')
2218            tetmesh.color('rainbow')
2219            cu = Cube(side=500).x(500) # any Mesh works
2220            tetmesh.cut_with_box(cu).show(axes=1)
2221            ```
2222            ![](https://vedo.embl.es/images/feats/tet_cut_box.png)
2223        """
2224        bc = vtk.vtkBoxClipDataSet()
2225        bc.SetInputData(self._data)
2226        if isinstance(box, vtk.vtkProp):
2227            boxb = box.GetBounds()
2228        else:
2229            boxb = box
2230        bc.SetBoxClip(*boxb)
2231        bc.Update()
2232        self._update(bc.GetOutput())
2233        self.pipeline = utils.OperationNode(
2234            "cut_with_box", parents=[self, box], c='#9e2a2b')
2235        return self
2236
2237    def cut_with_mesh(self, mesh, invert=False, whole_cells=False, only_boundary=False):
2238        """
2239        Cut a UGrid, TetMesh or Volume with a Mesh.
2240
2241        Use `invert` to return cut off part of the input object.
2242        """
2243        polymesh = mesh.polydata()
2244        ug = self._data
2245
2246        ippd = vtk.vtkImplicitPolyDataDistance()
2247        ippd.SetInput(polymesh)
2248
2249        if whole_cells or only_boundary:
2250            clipper = vtk.vtkExtractGeometry()
2251            clipper.SetInputData(ug)
2252            clipper.SetImplicitFunction(ippd)
2253            clipper.SetExtractInside(not invert)
2254            clipper.SetExtractBoundaryCells(False)
2255            if only_boundary:
2256                clipper.SetExtractBoundaryCells(True)
2257                clipper.SetExtractOnlyBoundaryCells(True)
2258        else:
2259            signedDistances = vtk.vtkFloatArray()
2260            signedDistances.SetNumberOfComponents(1)
2261            signedDistances.SetName("SignedDistances")
2262            for pointId in range(ug.GetNumberOfPoints()):
2263                p = ug.GetPoint(pointId)
2264                signedDistance = ippd.EvaluateFunction(p)
2265                signedDistances.InsertNextValue(signedDistance)
2266            ug.GetPointData().AddArray(signedDistances)
2267            ug.GetPointData().SetActiveScalars("SignedDistances")
2268            clipper = vtk.vtkClipDataSet()
2269            clipper.SetInputData(ug)
2270            clipper.SetInsideOut(not invert)
2271            clipper.SetValue(0.0)
2272
2273        clipper.Update()
2274        cug = clipper.GetOutput()
2275
2276        if ug.GetCellData().GetScalars():  # not working
2277            scalname = ug.GetCellData().GetScalars().GetName()
2278            if scalname:  # not working
2279                if self.useCells:
2280                    self.celldata.select(scalname)
2281                else:
2282                    self.pointdata.select(scalname)
2283
2284        self._update(cug)
2285        self.pipeline = utils.OperationNode(
2286            "cut_with_mesh", parents=[self, mesh], c='#9e2a2b')
2287        return self
2288
2289    def extract_cells_on_plane(self, origin, normal):
2290        """
2291        Extract cells that are lying of the specified surface.
2292        """
2293        bf = vtk.vtk3DLinearGridCrinkleExtractor()
2294        bf.SetInputData(self._data)
2295        bf.CopyPointDataOn()
2296        bf.CopyCellDataOn()
2297        bf.RemoveUnusedPointsOff()
2298
2299        plane = vtk.vtkPlane()
2300        plane.SetOrigin(origin)
2301        plane.SetNormal(normal)
2302        bf.SetImplicitFunction(plane)
2303        bf.Update()
2304
2305        self._update(bf.GetOutput())
2306        self.pipeline = utils.OperationNode(
2307            "extract_cells_on_plane", parents=[self],
2308            comment=f"#cells {self._data.GetNumberOfCells()}", c='#9e2a2b',
2309        )
2310        return self
2311
2312    def extract_cells_on_sphere(self, center, radius):
2313        """
2314        Extract cells that are lying of the specified surface.
2315        """
2316        bf = vtk.vtk3DLinearGridCrinkleExtractor()
2317        bf.SetInputData(self._data)
2318        bf.CopyPointDataOn()
2319        bf.CopyCellDataOn()
2320        bf.RemoveUnusedPointsOff()
2321
2322        sph = vtk.vtkSphere()
2323        sph.SetRadius(radius)
2324        sph.SetCenter(center)
2325        bf.SetImplicitFunction(sph)
2326        bf.Update()
2327
2328        self._update(bf.GetOutput())
2329        self.pipeline = utils.OperationNode(
2330            "extract_cells_on_sphere", parents=[self],
2331            comment=f"#cells {self._data.GetNumberOfCells()}", c='#9e2a2b',
2332        )
2333        return self
2334
2335    def extract_cells_on_cylinder(self, center, axis, radius):
2336        """
2337        Extract cells that are lying of the specified surface.
2338        """
2339        bf = vtk.vtk3DLinearGridCrinkleExtractor()
2340        bf.SetInputData(self._data)
2341        bf.CopyPointDataOn()
2342        bf.CopyCellDataOn()
2343        bf.RemoveUnusedPointsOff()
2344
2345        cyl = vtk.vtkCylinder()
2346        cyl.SetRadius(radius)
2347        cyl.SetCenter(center)
2348        cyl.SetAxis(axis)
2349        bf.SetImplicitFunction(cyl)
2350        bf.Update()
2351
2352        self.pipeline = utils.OperationNode(
2353            "extract_cells_on_cylinder", parents=[self],
2354            comment=f"#cells {self._data.GetNumberOfCells()}", c='#9e2a2b',
2355        )
2356        self._update(bf.GetOutput())
2357        return self
2358
2359    def clean(self):
2360        """
2361        Cleanup unused points and empty cells
2362        """
2363        cl = vtk.vtkStaticCleanUnstructuredGrid()
2364        cl.SetInputData(self._data)
2365        cl.RemoveUnusedPointsOn()
2366        cl.ProduceMergeMapOff()
2367        cl.AveragePointDataOff()
2368        cl.Update()
2369        
2370        self._update(cl.GetOutput())
2371        self.pipeline = utils.OperationNode(
2372            "clean", parents=[self],
2373            comment=f"#cells {self._data.GetNumberOfCells()}",
2374            c='#9e2a2b',
2375        )
2376        return self
2377
2378
2379    def find_cell(self, p):
2380        """Locate the cell that contains a point and return the cell ID."""
2381        cell = vtk.vtkTetra()
2382        cellId = vtk.mutable(0)
2383        tol2 = vtk.mutable(0)
2384        subId = vtk.mutable(0)
2385        pcoords = [0,0,0]
2386        weights = [0,0,0]
2387        cid = self._data.FindCell(p, cell, cellId, tol2, subId, pcoords, weights)
2388        return cid
2389
2390
2391    def extract_cells_by_id(self, idlist, use_point_ids=False):
2392        """Return a new UGrid composed of the specified subset of indices."""
2393        selectionNode = vtk.vtkSelectionNode()
2394        if use_point_ids:
2395            selectionNode.SetFieldType(vtk.vtkSelectionNode.POINT)
2396            contcells = vtk.vtkSelectionNode.CONTAINING_CELLS()
2397            selectionNode.GetProperties().Set(contcells, 1)
2398        else:
2399            selectionNode.SetFieldType(vtk.vtkSelectionNode.CELL)
2400        selectionNode.SetContentType(vtk.vtkSelectionNode.INDICES)
2401        vidlist = utils.numpy2vtk(idlist, dtype="id")
2402        selectionNode.SetSelectionList(vidlist)
2403        selection = vtk.vtkSelection()
2404        selection.AddNode(selectionNode)
2405        es = vtk.vtkExtractSelection()
2406        es.SetInputData(0, self._data)
2407        es.SetInputData(1, selection)
2408        es.Update()
2409
2410        ug = vedo.ugrid.UGrid(es.GetOutput())
2411        pr = vtk.vtkProperty()
2412        pr.DeepCopy(self.GetProperty())
2413        ug.SetProperty(pr)
2414        ug.property = pr
2415
2416        # assign the same transformation to the copy
2417        ug.SetOrigin(self.GetOrigin())
2418        ug.SetScale(self.GetScale())
2419        ug.SetOrientation(self.GetOrientation())
2420        ug.SetPosition(self.GetPosition())
2421        ug.mapper().SetLookupTable(utils.ctf2lut(self))
2422        ug.pipeline = utils.OperationNode(
2423            "extract_cells_by_id", parents=[self],
2424            comment=f"#cells {self._data.GetNumberOfCells()}",
2425            c="#9e2a2b",
2426        )
2427        return ug
2428
2429
2430############################################################################### funcs
2431def _getinput(obj):
2432    if isinstance(obj, (vtk.vtkVolume, vtk.vtkActor)):
2433        return obj.GetMapper().GetInput()
2434    return obj
2435
2436
2437def probe_points(dataset, pts):
2438    """
2439    Takes a `Volume` (or any other vtk data set)
2440    and probes its scalars at the specified points in space.
2441
2442    Note that a mask is also output with valid/invalid points which can be accessed
2443    with `mesh.pointdata['vtkValidPointMask']`.
2444
2445    Examples:
2446        - [probe_points.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/probe_points.py)
2447
2448            ![](https://vedo.embl.es/images/volumetric/probePoints.png)
2449    """
2450    if isinstance(pts, vedo.pointcloud.Points):
2451        pts = pts.points()
2452
2453    def _readpoints():
2454        output = src.GetPolyDataOutput()
2455        points = vtk.vtkPoints()
2456        for p in pts:
2457            x, y, z = p
2458            points.InsertNextPoint(x, y, z)
2459        output.SetPoints(points)
2460
2461        cells = vtk.vtkCellArray()
2462        cells.InsertNextCell(len(pts))
2463        for i in range(len(pts)):
2464            cells.InsertCellPoint(i)
2465        output.SetVerts(cells)
2466
2467    src = vtk.vtkProgrammableSource()
2468    src.SetExecuteMethod(_readpoints)
2469    src.Update()
2470    img = _getinput(dataset)
2471    probeFilter = vtk.vtkProbeFilter()
2472    probeFilter.SetSourceData(img)
2473    probeFilter.SetInputConnection(src.GetOutputPort())
2474    probeFilter.Update()
2475    poly = probeFilter.GetOutput()
2476    pm = vedo.mesh.Mesh(poly)
2477    pm.name = "ProbePoints"
2478    pm.pipeline = utils.OperationNode("probe_points", parents=[dataset])
2479    return pm
2480
2481
2482def probe_line(dataset, p1, p2, res=100):
2483    """
2484    Takes a `Volume`  (or any other vtk data set)
2485    and probes its scalars along a line defined by 2 points `p1` and `p2`.
2486
2487    Note that a mask is also output with valid/invalid points which can be accessed
2488    with `mesh.pointdata['vtkValidPointMask']`.
2489
2490    Use `res` to set the nr of points along the line
2491
2492    Examples:
2493        - [probe_line1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/probe_line1.py)
2494        - [probe_line2.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/probe_line2.py)
2495
2496            ![](https://vedo.embl.es/images/volumetric/probeLine2.png) 
2497    """
2498    line = vtk.vtkLineSource()
2499    line.SetResolution(res)
2500    line.SetPoint1(p1)
2501    line.SetPoint2(p2)
2502    img = _getinput(dataset)
2503    probeFilter = vtk.vtkProbeFilter()
2504    probeFilter.SetSourceData(img)
2505    probeFilter.SetInputConnection(line.GetOutputPort())
2506    probeFilter.Update()
2507    poly = probeFilter.GetOutput()
2508    lnn = vedo.mesh.Mesh(poly)
2509    lnn.name = "ProbeLine"
2510    lnn.pipeline = utils.OperationNode("probe_line", parents=[dataset])
2511    return lnn
2512
2513
2514def probe_plane(dataset, origin=(0, 0, 0), normal=(1, 0, 0)):
2515    """
2516    Takes a `Volume` (or any other vtk data set)
2517    and probes its scalars on a plane defined by a point and a normal.
2518
2519    Examples:
2520        - [slicePlane1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slicePlane1.py)
2521        - [slicePlane2.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slicePlane2.py)
2522
2523            ![](https://vedo.embl.es/images/volumetric/slicePlane2.png)
2524    """
2525    img = _getinput(dataset)
2526    plane = vtk.vtkPlane()
2527    plane.SetOrigin(origin)
2528    plane.SetNormal(normal)
2529    planeCut = vtk.vtkCutter()
2530    planeCut.SetInputData(img)
2531    planeCut.SetCutFunction(plane)
2532    planeCut.Update()
2533    poly = planeCut.GetOutput()
2534    cutmesh = vedo.mesh.Mesh(poly)
2535    cutmesh.name = "ProbePlane"
2536    cutmesh.pipeline = utils.OperationNode("probe_plane", parents=[dataset])
2537    return cutmesh
class Base3DProp:
204class Base3DProp:
205    """
206    Base class to manage positioning and size of the objects in space and other properties.
207
208    .. warning:: Do not use this class to instanciate objects
209    """
210    def __init__(self):
211        """
212        Base class to manage positioning and size of the objects in space and other properties.
213        """
214        self.filename = ""
215        self.name = ""
216        self.file_size = ""
217        self.created = ""
218        self.trail = None
219        self.trail_points = []
220        self.trail_segment_size = 0
221        self.trail_offset = None
222        self.shadows = []
223        self.axes = None
224        self.picked3d = None
225        self.units = None
226        self.top = np.array([0, 0, 1])
227        self.base = np.array([0, 0, 0])
228        self.info = {}
229        self.time = time.time()
230        self.rendered_at = set()
231        self.transform = None
232        self._isfollower = False  # set by mesh.follow_camera()
233
234        self.point_locator = None
235        self.cell_locator = None
236
237        self.scalarbar = None
238        # self.scalarbars = dict() #TODO
239        self.pipeline = None
240
241
242    def address(self):
243        """
244        Return a unique memory address integer which may serve as the ID of the
245        object, or passed to c++ code.
246        """
247        # https://www.linkedin.com/pulse/speedup-your-code-accessing-python-vtk-objects-from-c-pletzer/
248        # https://github.com/tfmoraes/polydata_connectivity
249        return int(self.inputdata().GetAddressAsString("")[5:], 16)
250
251    def pickable(self, value=None):
252        """Set/get the pickability property of an object."""
253        if value is None:
254            return self.GetPickable()
255        self.SetPickable(value)
256        return self
257
258    def draggable(self, value=None):  # NOT FUNCTIONAL?
259        """Set/get the draggability property of an object."""
260        if value is None:
261            return self.GetDragable()
262        self.SetDragable(value)
263        return self
264
265    def origin(self, x=None, y=None, z=None):
266        """
267        Set/get object's origin.
268
269        Relevant to control the scaling with `scale()` and rotations.
270        Has no effect on position.
271        """
272        if x is None:
273            return np.array(self.GetOrigin()) + self.GetPosition()
274
275        if z is None and y is None:  # assume x is of the form (x,y,z)
276            if len(x) == 3:
277                x, y, z = x
278            else:
279                x, y = x
280                z = 0
281        elif z is None:  # assume x,y is of the form x, y
282            z = 0
283        self.SetOrigin([x, y, z] - np.array(self.GetPosition()))
284        return self
285
286    def pos(self, x=None, y=None, z=None):
287        """Set/Get object position."""
288        if x is None:  # get functionality
289            return np.array(self.GetPosition())
290
291        if z is None and y is None:  # assume x is of the form (x,y,z)
292            if len(x) == 3:
293                x, y, z = x
294            else:
295                x, y = x
296                z = 0
297        elif z is None:  # assume x,y is of the form x, y
298            z = 0
299        self.SetPosition(x, y, z)
300
301        self.point_locator = None
302        self.cell_locator = None
303        return self  # return itself to concatenate methods
304
305    def shift(self, dx=0, dy=0, dz=0):
306        """Add a vector to the current object position."""
307        p = np.array(self.GetPosition())
308
309        if utils.is_sequence(dx):
310            if len(dx) == 2:
311                self.SetPosition(p + [dx[0], dx[1], 0])
312            else:
313                self.SetPosition(p + dx)
314        else:
315            self.SetPosition(p + [dx, dy, dz])
316
317        self.point_locator = None
318        self.cell_locator = None
319        return self
320
321    def x(self, val=None):
322        """Set/Get object position along x axis."""
323        p = self.GetPosition()
324        if val is None:
325            return p[0]
326        self.pos(val, p[1], p[2])
327        return self
328
329    def y(self, val=None):
330        """Set/Get object position along y axis."""
331        p = self.GetPosition()
332        if val is None:
333            return p[1]
334        self.pos(p[0], val, p[2])
335        return self
336
337    def z(self, val=None):
338        """Set/Get object position along z axis."""
339        p = self.GetPosition()
340        if val is None:
341            return p[2]
342        self.pos(p[0], p[1], val)
343        return self
344
345    def rotate(self, angle, axis=(1, 0, 0), point=(0, 0, 0), rad=False):
346        """
347        Rotate around an arbitrary `axis` passing through `point`.
348
349        Example:
350            ```python
351            from vedo import *
352            c1 = Cube()
353            c2 = c1.clone().c('violet').alpha(0.5) # copy of c1
354            v = vector(0.2,1,0)
355            p = vector(1,0,0)  # axis passes through this point
356            c2.rotate(90, axis=v, point=p)
357            l = Line(-v+p, v+p).lw(3).c('red')
358            show(c1, l, c2, axes=1).close()
359            ```
360            ![](https://vedo.embl.es/images/feats/rotate_axis.png)
361        """
362        if rad:
363            anglerad = angle
364        else:
365            anglerad = np.deg2rad(angle)
366        axis = utils.versor(axis)
367        a = np.cos(anglerad / 2)
368        b, c, d = -axis * np.sin(anglerad / 2)
369        aa, bb, cc, dd = a * a, b * b, c * c, d * d
370        bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d
371        R = np.array(
372            [
373                [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)],
374                [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)],
375                [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc],
376            ]
377        )
378        rv = np.dot(R, self.GetPosition() - np.asarray(point)) + point
379
380        if rad:
381            angle *= 180.0 / np.pi
382        # this vtk method only rotates in the origin of the object:
383        self.RotateWXYZ(angle, axis[0], axis[1], axis[2])
384        self.pos(rv)
385        return self
386
387    def _rotatexyz(self, a, angle, rad, around):
388        if rad:
389            angle *= 180 / np.pi
390
391        T = vtk.vtkTransform()
392        T.SetMatrix(self.GetMatrix())
393        T.PostMultiply()
394
395        rot = dict(x=T.RotateX, y=T.RotateY, z=T.RotateZ)
396
397        if around is None:
398            # rotate around its origin
399            rot[a](angle)
400        else:
401            if around == "itself":
402                around = self.GetPosition()
403            # displacement needed to bring it back to the origin
404            # and disregard origin
405            disp = around - np.array(self.GetOrigin())
406            T.Translate(-disp)
407            rot[a](angle)
408            T.Translate(disp)
409
410        self.SetOrientation(T.GetOrientation())
411        self.SetPosition(T.GetPosition())
412
413        self.point_locator = None
414        self.cell_locator = None
415        return self
416
417    @deprecated(reason=vedo.colors.red + "Please use rotate_x()" + vedo.colors.reset)
418    def rotateX(self, *a, **b):
419        """Deprecated. Please use `rotate_x()`."""
420        return self.rotate_x(*a, **b)
421    @deprecated(reason=vedo.colors.red + "Please use rotate_y()" + vedo.colors.reset)
422    def rotateY(self, *a, **b):
423        """Deprecated. Please use `rotate_y()`."""
424        return self.rotate_y(*a, **b)
425    @deprecated(reason=vedo.colors.red + "Please use rotate_z()" + vedo.colors.reset)
426    def rotateZ(self, *a, **b):
427        """Deprecated. Please use `rotate_z()`."""
428        return self.rotate_z(*a, **b)
429
430    def rotate_x(self, angle, rad=False, around=None):
431        """
432        Rotate around x-axis. If angle is in radians set `rad=True`.
433
434        Use `around` to define a pivoting point.
435        """
436        return self._rotatexyz("x", angle, rad, around)
437
438    def rotate_y(self, angle, rad=False, around=None):
439        """
440        Rotate around y-axis. If angle is in radians set `rad=True`.
441
442        Use `around` to define a pivoting point.
443        """
444        return self._rotatexyz("y", angle, rad, around)
445
446    def rotate_z(self, angle, rad=False, around=None):
447        """
448        Rotate around z-axis. If angle is in radians set `rad=True`.
449
450        Use `around` to define a pivoting point.
451        """
452        return self._rotatexyz("z", angle, rad, around)
453
454    def orientation(self, newaxis=None, rotation=0, concatenate=False, xyplane=False, rad=False):
455        """
456        Set/Get object orientation.
457
458        Arguments:
459            rotation : (float)
460                rotate object around newaxis.
461            concatenate : (bool)
462                concatenate the orietation operation with the previous existing transform (if any)
463            xyplane : (bool)
464                make an extra rotation to keep the object aligned to the xy-plane
465            rad : (bool)
466                set to True if angle is expressed in radians.
467
468        Example:
469            ```python
470            from vedo import *
471            center = np.array([581/2,723/2,0])
472            objs = []
473            for a in np.linspace(0, 6.28, 7):
474                v = vector(cos(a), sin(a), 0)*1000
475                pic = Picture(dataurl+"images/dog.jpg").rotate_z(10)
476                pic.orientation(v, xyplane=True)
477                pic.origin(center)
478                pic.pos(v - center)
479                objs += [pic, Arrow(v, v+v)]
480            show(objs, Point(), axes=1).close()
481            ```
482            ![](https://vedo.embl.es/images/feats/orientation.png)
483
484        Examples:
485            - [gyroscope2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope2.py)
486
487            ![](https://vedo.embl.es/images/simulations/50738942-687b5780-11d9-11e9-97f0-72bbd63f7d6e.gif)
488        """
489        if self.top is None or self.base is None:
490            initaxis = (0, 0, 1)
491        else:
492            initaxis = utils.versor(self.top - self.base)
493
494        newaxis = utils.versor(newaxis)
495        p = np.array(self.GetPosition())
496        crossvec = np.cross(initaxis, newaxis)
497
498        angleth = np.arccos(np.dot(initaxis, newaxis))
499
500        T = vtk.vtkTransform()
501        if concatenate:
502            try:
503                M = self.GetMatrix()
504                T.SetMatrix(M)
505            except:
506                pass
507        T.PostMultiply()
508        T.Translate(-p)
509        if rotation:
510            if rad:
511                rotation *= 180.0 / np.pi
512            T.RotateWXYZ(rotation, initaxis)
513        if xyplane:
514            angleph = np.arctan2(newaxis[1], newaxis[0])
515            T.RotateWXYZ(np.rad2deg(angleph + angleth), initaxis) # compensation
516        T.RotateWXYZ(np.rad2deg(angleth), crossvec)
517        T.Translate(p)
518
519        self.SetOrientation(T.GetOrientation())
520
521        self.point_locator = None
522        self.cell_locator = None
523        return self
524
525        # newaxis = utils.versor(newaxis)
526        # pos = np.array(self.GetPosition())
527        # crossvec = np.cross(initaxis, newaxis)
528        # angle = np.arccos(np.dot(initaxis, newaxis))
529        # T = vtk.vtkTransform()
530        # T.PostMultiply()
531        # T.Translate(-pos)
532        # if rotation:
533        #     T.RotateWXYZ(rotation, initaxis)
534        # T.RotateWXYZ(np.rad2deg(angle), crossvec)
535        # T.Translate(pos)
536        # self.SetUserTransform(T)
537        # self.transform = T
538
539
540    def scale(self, s=None, reset=False):
541        """
542        Set/get object's scaling factor.
543
544        Arguments:
545            s : (list, float)
546                scaling factor(s).
547            reset : (bool)
548                if True previous scaling factors are ignored.
549
550        Note:
551            use `s=(sx,sy,sz)` to scale differently in the three coordinates.
552        """
553        if s is None:
554            return np.array(self.GetScale())
555
556        # assert s[0] != 0
557        # assert s[1] != 0
558        # assert s[2] != 0
559
560        if reset:
561            self.SetScale(s)
562        else:
563            self.SetScale(np.multiply(self.GetScale(), s))
564
565        self.point_locator = None
566        self.cell_locator = None
567        return self
568
569    def get_transform(self, invert=False):
570        """
571        Check if `object.transform` exists and returns a `vtkTransform`.
572        Otherwise return current user transformation (where the object is currently placed).
573
574        Use `invert` to return the inverse of the current transformation
575
576        Example:
577            ```python
578            from vedo import *
579
580            c1 = Cube()
581            c2 = c1.clone().c('violet').alpha(0.5) # copy of c1
582            v = vector(0.2,1,0)
583            p = vector(1,0,0)  # axis passes through this point
584            c2.rotate(90, axis=v, point=p)
585
586            # get the inverse of the current transformation
587            T = c2.get_transform(invert=True)
588            c2.apply_transform(T)  # put back c2 in place
589
590            l = Line(p-v, p+v).lw(3).c('red')
591            show(c1.wireframe().lw(3), l, c2, axes=1).close()
592            ```
593            ![](https://vedo.embl.es/images/feats/get_transf.png)
594        """
595        if self.transform:
596            tr = self.transform
597            if invert:
598                tr = tr.GetInverse()
599            return tr
600
601        T = self.GetMatrix()
602        tr = vtk.vtkTransform()
603        tr.SetMatrix(T)
604        if invert:
605            tr = tr.GetInverse()
606        return tr
607
608
609    @deprecated(reason=vedo.colors.red + "Please use apply_transform()" + vedo.colors.reset)
610    def applyTransform(self, T, reset=False, concatenate=False):
611        """Deprecated. Please use `apply_transform()`"""
612        return self.apply_transform(T,reset,concatenate)
613
614    def apply_transform(self, T, reset=False, concatenate=False):
615        """
616        Transform object position and orientation.
617
618        Arguments:
619            reset : (bool)
620                no effect, this is superseded by `pointcloud.apply_transform()`
621            concatenate : (bool)
622                no effect, this is superseded by `pointcloud.apply_transform()`
623        """
624        if isinstance(T, vtk.vtkMatrix4x4):
625            self.SetUserMatrix(T)
626        elif utils.is_sequence(T):
627            vm = vtk.vtkMatrix4x4()
628            for i in [0, 1, 2, 3]:
629                for j in [0, 1, 2, 3]:
630                    vm.SetElement(i, j, T[i][j])
631            self.SetUserMatrix(vm)
632        else:
633            self.SetUserTransform(T)
634        self.transform = T
635
636        self.point_locator = None
637        self.cell_locator = None
638        return self
639
640    def align_to_bounding_box(self, msh, rigid=False):
641        """
642        Align the current object's bounding box to the bounding box
643        of the input object.
644
645        Use `rigid` to disable scaling.
646
647        Examples:
648            - [align6.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align6.py)
649        """
650        lmt = vtk.vtkLandmarkTransform()
651        ss = vtk.vtkPoints()
652        xss0, xss1, yss0, yss1, zss0, zss1 = self.bounds()
653        for p in [
654            [xss0, yss0, zss0],
655            [xss1, yss0, zss0],
656            [xss1, yss1, zss0],
657            [xss0, yss1, zss0],
658            [xss0, yss0, zss1],
659            [xss1, yss0, zss1],
660            [xss1, yss1, zss1],
661            [xss0, yss1, zss1],
662        ]:
663            ss.InsertNextPoint(p)
664        st = vtk.vtkPoints()
665        xst0, xst1, yst0, yst1, zst0, zst1 = msh.bounds()
666        for p in [
667            [xst0, yst0, zst0],
668            [xst1, yst0, zst0],
669            [xst1, yst1, zst0],
670            [xst0, yst1, zst0],
671            [xst0, yst0, zst1],
672            [xst1, yst0, zst1],
673            [xst1, yst1, zst1],
674            [xst0, yst1, zst1],
675        ]:
676            st.InsertNextPoint(p)
677
678        lmt.SetSourceLandmarks(ss)
679        lmt.SetTargetLandmarks(st)
680        lmt.SetModeToAffine()
681        if rigid:
682            lmt.SetModeToRigidBody()
683        lmt.Update()
684        self.apply_transform(lmt)
685        self.transform = lmt
686
687        self.point_locator = None
688        self.cell_locator = None
689        return self
690
691    def on(self):
692        """Switch on  object visibility. Object is not removed."""
693        self.VisibilityOn()
694        try:
695            self.scalarbar.VisibilityOn()
696        except AttributeError:
697            pass
698        try:
699            self.trail.VisibilityOn()
700        except AttributeError:
701            pass
702        try:
703            for sh in self.shadows:
704                sh.VisibilityOn()
705        except AttributeError:
706            pass        
707        return self
708
709    def off(self):
710        """Switch off object visibility. Object is not removed."""
711        self.VisibilityOff()
712        try:
713            self.scalarbar.VisibilityOff()
714        except AttributeError:
715            pass
716        try:
717            self.trail.VisibilityOff()
718        except AttributeError:
719            pass
720        try:
721            for sh in self.shadows:
722                sh.VisibilityOff()
723        except AttributeError:
724            pass        
725        return self
726    
727    def toggle(self):
728        """Toggle object visibility on/off."""
729        v = self.GetVisibility()
730        if v:
731            self.off()
732        else:
733            self.on()
734        return self
735
736    def box(self, scale=1, padding=0, fill=False):
737        """
738        Return the bounding box as a new `Mesh`.
739
740        Arguments:
741            scale : (float)
742                box size can be scaled by a factor
743            padding : (float, list)
744                a constant padding can be added (can be a list [padx,pady,padz])
745
746        Examples:
747            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
748        """
749        b = self.bounds()
750        if not utils.is_sequence(padding):
751            padding = [padding, padding, padding]
752        length, width, height = b[1] - b[0], b[3] - b[2], b[5] - b[4]
753        tol = (length + width + height) / 30000  # useful for boxing 2D text
754        pos = [(b[0] + b[1]) / 2, (b[3] + b[2]) / 2, (b[5] + b[4]) / 2 - tol]
755        bx = vedo.shapes.Box(
756            pos,
757            length * scale + padding[0],
758            width  * scale + padding[1],
759            height * scale + padding[2],
760            c="gray",
761        )
762        if hasattr(self, "GetProperty"):  # could be Assembly
763            if isinstance(self.GetProperty(), vtk.vtkProperty):  # could be volume
764                pr = vtk.vtkProperty()
765                pr.DeepCopy(self.GetProperty())
766                bx.SetProperty(pr)
767                bx.property = pr
768        bx.wireframe(not fill)
769        bx.flat().lighting("off")
770        return bx
771
772    def use_bounds(self, ub=True):
773        """
774        Instruct the current camera to either take into account or ignore
775        the object bounds when resetting.
776        """
777        self.SetUseBounds(ub)
778        return self
779
780    def bounds(self):
781        """
782        Get the object bounds.
783        Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`.
784        """
785        try:
786            pts = self.points()
787            xmin, ymin, zmin = np.min(pts, axis=0)
788            xmax, ymax, zmax = np.max(pts, axis=0)
789            return [xmin,xmax, ymin,ymax, zmin,zmax]
790        except (AttributeError, ValueError):
791            return self.GetBounds()
792
793    def xbounds(self, i=None):
794        """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1)."""
795        b = self.bounds()
796        if i is not None:
797            return b[i]
798        return (b[0], b[1])
799
800    def ybounds(self, i=None):
801        """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1)."""
802        b = self.bounds()
803        if i == 0:
804            return b[2]
805        if i == 1:
806            return b[3]
807        return (b[2], b[3])
808
809    def zbounds(self, i=None):
810        """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1)."""
811        b = self.bounds()
812        if i == 0: return b[4]
813        if i == 1: return b[5]
814        return (b[4], b[5])
815
816    @deprecated(reason=vedo.colors.red + "Please use diagonal_size()" + vedo.colors.reset)
817    def diagonalSize(self):
818        """Deprecated. Please use `diagonal_size()`."""
819        return self.diagonal_size()
820
821    def diagonal_size(self):
822        """Get the length of the diagonal of mesh bounding box."""
823        b = self.bounds()
824        return np.sqrt((b[1] - b[0]) ** 2 + (b[3] - b[2]) ** 2 + (b[5] - b[4]) ** 2)
825        # return self.GetLength() # ???different???
826
827
828    def copy_data_from(self, obj):
829        """Copy all data (point and cell data) from this input object"""
830        self._data.GetPointData().PassData(obj._data.GetPointData())
831        self._data.GetCellData().PassData(obj._data.GetCellData())
832        self.pipeline = utils.OperationNode(
833            f"copy_data_from\n{obj.__class__.__name__}", 
834            parents=[self, obj],
835            shape='note',
836            c="#ccc5b9",
837        )
838        return self
839
840    def print(self):
841        """Print information about an object."""
842        utils.print_info(self)
843        return self
844
845    def show(self, **options):
846        """
847        Create on the fly an instance of class `Plotter` or use the last existing one to
848        show one single object.
849
850        This method is meant as a shortcut. If more than one object needs to be visualised
851        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
852
853        Returns the `Plotter` class instance.
854        """
855        return vedo.plotter.show(self, **options)
856
857    def thumbnail(
858            self, 
859            zoom=1.25,
860            size=(200, 200),
861            bg="white", 
862            azimuth=0, 
863            elevation=0, 
864            axes=False,
865        ):
866        """Build a thumbnail of the object and return it as an array."""
867        # speed is about 20Hz for size=[200,200]
868        ren = vtk.vtkRenderer()
869        ren.AddActor(self)
870        if axes:
871            axes = vedo.addons.Axes(self)
872            ren.AddActor(axes)
873        ren.ResetCamera()
874        cam = ren.GetActiveCamera()
875        cam.Zoom(zoom)
876        cam.Elevation(elevation)
877        cam.Azimuth(azimuth)
878
879        ren_win = vtk.vtkRenderWindow()
880        ren_win.SetOffScreenRendering(True)
881        ren_win.SetSize(size)
882        ren.SetBackground(colors.get_color(bg))
883        ren_win.AddRenderer(ren)
884        ren_win.Render()
885
886        nx, ny = ren_win.GetSize()
887        arr = vtk.vtkUnsignedCharArray()
888        ren_win.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
889        narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
890        narr = np.ascontiguousarray(np.flip(narr, axis=0))
891
892        ren.RemoveActor(self)
893        if axes:
894            ren.RemoveActor(axes)
895        ren_win.Finalize()
896        del ren_win
897        return narr

Base class to manage positioning and size of the objects in space and other properties.

Do not use this class to instanciate objects
Base3DProp()
210    def __init__(self):
211        """
212        Base class to manage positioning and size of the objects in space and other properties.
213        """
214        self.filename = ""
215        self.name = ""
216        self.file_size = ""
217        self.created = ""
218        self.trail = None
219        self.trail_points = []
220        self.trail_segment_size = 0
221        self.trail_offset = None
222        self.shadows = []
223        self.axes = None
224        self.picked3d = None
225        self.units = None
226        self.top = np.array([0, 0, 1])
227        self.base = np.array([0, 0, 0])
228        self.info = {}
229        self.time = time.time()
230        self.rendered_at = set()
231        self.transform = None
232        self._isfollower = False  # set by mesh.follow_camera()
233
234        self.point_locator = None
235        self.cell_locator = None
236
237        self.scalarbar = None
238        # self.scalarbars = dict() #TODO
239        self.pipeline = None

Base class to manage positioning and size of the objects in space and other properties.

def address(self):
242    def address(self):
243        """
244        Return a unique memory address integer which may serve as the ID of the
245        object, or passed to c++ code.
246        """
247        # https://www.linkedin.com/pulse/speedup-your-code-accessing-python-vtk-objects-from-c-pletzer/
248        # https://github.com/tfmoraes/polydata_connectivity
249        return int(self.inputdata().GetAddressAsString("")[5:], 16)

Return a unique memory address integer which may serve as the ID of the object, or passed to c++ code.

def pickable(self, value=None):
251    def pickable(self, value=None):
252        """Set/get the pickability property of an object."""
253        if value is None:
254            return self.GetPickable()
255        self.SetPickable(value)
256        return self

Set/get the pickability property of an object.

def draggable(self, value=None):
258    def draggable(self, value=None):  # NOT FUNCTIONAL?
259        """Set/get the draggability property of an object."""
260        if value is None:
261            return self.GetDragable()
262        self.SetDragable(value)
263        return self

Set/get the draggability property of an object.

def origin(self, x=None, y=None, z=None):
265    def origin(self, x=None, y=None, z=None):
266        """
267        Set/get object's origin.
268
269        Relevant to control the scaling with `scale()` and rotations.
270        Has no effect on position.
271        """
272        if x is None:
273            return np.array(self.GetOrigin()) + self.GetPosition()
274
275        if z is None and y is None:  # assume x is of the form (x,y,z)
276            if len(x) == 3:
277                x, y, z = x
278            else:
279                x, y = x
280                z = 0
281        elif z is None:  # assume x,y is of the form x, y
282            z = 0
283        self.SetOrigin([x, y, z] - np.array(self.GetPosition()))
284        return self

Set/get object's origin.

Relevant to control the scaling with scale() and rotations. Has no effect on position.

def pos(self, x=None, y=None, z=None):
286    def pos(self, x=None, y=None, z=None):
287        """Set/Get object position."""
288        if x is None:  # get functionality
289            return np.array(self.GetPosition())
290
291        if z is None and y is None:  # assume x is of the form (x,y,z)
292            if len(x) == 3:
293                x, y, z = x
294            else:
295                x, y = x
296                z = 0
297        elif z is None:  # assume x,y is of the form x, y
298            z = 0
299        self.SetPosition(x, y, z)
300
301        self.point_locator = None
302        self.cell_locator = None
303        return self  # return itself to concatenate methods

Set/Get object position.

def shift(self, dx=0, dy=0, dz=0):
305    def shift(self, dx=0, dy=0, dz=0):
306        """Add a vector to the current object position."""
307        p = np.array(self.GetPosition())
308
309        if utils.is_sequence(dx):
310            if len(dx) == 2:
311                self.SetPosition(p + [dx[0], dx[1], 0])
312            else:
313                self.SetPosition(p + dx)
314        else:
315            self.SetPosition(p + [dx, dy, dz])
316
317        self.point_locator = None
318        self.cell_locator = None
319        return self

Add a vector to the current object position.

def x(self, val=None):
321    def x(self, val=None):
322        """Set/Get object position along x axis."""
323        p = self.GetPosition()
324        if val is None:
325            return p[0]
326        self.pos(val, p[1], p[2])
327        return self

Set/Get object position along x axis.

def y(self, val=None):
329    def y(self, val=None):
330        """Set/Get object position along y axis."""
331        p = self.GetPosition()
332        if val is None:
333            return p[1]
334        self.pos(p[0], val, p[2])
335        return self

Set/Get object position along y axis.

def z(self, val=None):
337    def z(self, val=None):
338        """Set/Get object position along z axis."""
339        p = self.GetPosition()
340        if val is None:
341            return p[2]
342        self.pos(p[0], p[1], val)
343        return self

Set/Get object position along z axis.

def rotate(self, angle, axis=(1, 0, 0), point=(0, 0, 0), rad=False):
345    def rotate(self, angle, axis=(1, 0, 0), point=(0, 0, 0), rad=False):
346        """
347        Rotate around an arbitrary `axis` passing through `point`.
348
349        Example:
350            ```python
351            from vedo import *
352            c1 = Cube()
353            c2 = c1.clone().c('violet').alpha(0.5) # copy of c1
354            v = vector(0.2,1,0)
355            p = vector(1,0,0)  # axis passes through this point
356            c2.rotate(90, axis=v, point=p)
357            l = Line(-v+p, v+p).lw(3).c('red')
358            show(c1, l, c2, axes=1).close()
359            ```
360            ![](https://vedo.embl.es/images/feats/rotate_axis.png)
361        """
362        if rad:
363            anglerad = angle
364        else:
365            anglerad = np.deg2rad(angle)
366        axis = utils.versor(axis)
367        a = np.cos(anglerad / 2)
368        b, c, d = -axis * np.sin(anglerad / 2)
369        aa, bb, cc, dd = a * a, b * b, c * c, d * d
370        bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d
371        R = np.array(
372            [
373                [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)],
374                [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)],
375                [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc],
376            ]
377        )
378        rv = np.dot(R, self.GetPosition() - np.asarray(point)) + point
379
380        if rad:
381            angle *= 180.0 / np.pi
382        # this vtk method only rotates in the origin of the object:
383        self.RotateWXYZ(angle, axis[0], axis[1], axis[2])
384        self.pos(rv)
385        return self

Rotate around an arbitrary axis passing through point.

Example:
from vedo import *
c1 = Cube()
c2 = c1.clone().c('violet').alpha(0.5) # copy of c1
v = vector(0.2,1,0)
p = vector(1,0,0)  # axis passes through this point
c2.rotate(90, axis=v, point=p)
l = Line(-v+p, v+p).lw(3).c('red')
show(c1, l, c2, axes=1).close()

@deprecated(reason=vedo.colors.red + 'Please use rotate_x()' + vedo.colors.reset)
def rotateX(self, *a, **b):
417    @deprecated(reason=vedo.colors.red + "Please use rotate_x()" + vedo.colors.reset)
418    def rotateX(self, *a, **b):
419        """Deprecated. Please use `rotate_x()`."""
420        return self.rotate_x(*a, **b)

Deprecated. Please use rotate_x().

@deprecated(reason=vedo.colors.red + 'Please use rotate_y()' + vedo.colors.reset)
def rotateY(self, *a, **b):
421    @deprecated(reason=vedo.colors.red + "Please use rotate_y()" + vedo.colors.reset)
422    def rotateY(self, *a, **b):
423        """Deprecated. Please use `rotate_y()`."""
424        return self.rotate_y(*a, **b)

Deprecated. Please use rotate_y().

@deprecated(reason=vedo.colors.red + 'Please use rotate_z()' + vedo.colors.reset)
def rotateZ(self, *a, **b):
425    @deprecated(reason=vedo.colors.red + "Please use rotate_z()" + vedo.colors.reset)
426    def rotateZ(self, *a, **b):
427        """Deprecated. Please use `rotate_z()`."""
428        return self.rotate_z(*a, **b)

Deprecated. Please use rotate_z().

def rotate_x(self, angle, rad=False, around=None):
430    def rotate_x(self, angle, rad=False, around=None):
431        """
432        Rotate around x-axis. If angle is in radians set `rad=True`.
433
434        Use `around` to define a pivoting point.
435        """
436        return self._rotatexyz("x", angle, rad, around)

Rotate around x-axis. If angle is in radians set rad=True.

Use around to define a pivoting point.

def rotate_y(self, angle, rad=False, around=None):
438    def rotate_y(self, angle, rad=False, around=None):
439        """
440        Rotate around y-axis. If angle is in radians set `rad=True`.
441
442        Use `around` to define a pivoting point.
443        """
444        return self._rotatexyz("y", angle, rad, around)

Rotate around y-axis. If angle is in radians set rad=True.

Use around to define a pivoting point.

def rotate_z(self, angle, rad=False, around=None):
446    def rotate_z(self, angle, rad=False, around=None):
447        """
448        Rotate around z-axis. If angle is in radians set `rad=True`.
449
450        Use `around` to define a pivoting point.
451        """
452        return self._rotatexyz("z", angle, rad, around)

Rotate around z-axis. If angle is in radians set rad=True.

Use around to define a pivoting point.

def orientation( self, newaxis=None, rotation=0, concatenate=False, xyplane=False, rad=False):
454    def orientation(self, newaxis=None, rotation=0, concatenate=False, xyplane=False, rad=False):
455        """
456        Set/Get object orientation.
457
458        Arguments:
459            rotation : (float)
460                rotate object around newaxis.
461            concatenate : (bool)
462                concatenate the orietation operation with the previous existing transform (if any)
463            xyplane : (bool)
464                make an extra rotation to keep the object aligned to the xy-plane
465            rad : (bool)
466                set to True if angle is expressed in radians.
467
468        Example:
469            ```python
470            from vedo import *
471            center = np.array([581/2,723/2,0])
472            objs = []
473            for a in np.linspace(0, 6.28, 7):
474                v = vector(cos(a), sin(a), 0)*1000
475                pic = Picture(dataurl+"images/dog.jpg").rotate_z(10)
476                pic.orientation(v, xyplane=True)
477                pic.origin(center)
478                pic.pos(v - center)
479                objs += [pic, Arrow(v, v+v)]
480            show(objs, Point(), axes=1).close()
481            ```
482            ![](https://vedo.embl.es/images/feats/orientation.png)
483
484        Examples:
485            - [gyroscope2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/gyroscope2.py)
486
487            ![](https://vedo.embl.es/images/simulations/50738942-687b5780-11d9-11e9-97f0-72bbd63f7d6e.gif)
488        """
489        if self.top is None or self.base is None:
490            initaxis = (0, 0, 1)
491        else:
492            initaxis = utils.versor(self.top - self.base)
493
494        newaxis = utils.versor(newaxis)
495        p = np.array(self.GetPosition())
496        crossvec = np.cross(initaxis, newaxis)
497
498        angleth = np.arccos(np.dot(initaxis, newaxis))
499
500        T = vtk.vtkTransform()
501        if concatenate:
502            try:
503                M = self.GetMatrix()
504                T.SetMatrix(M)
505            except:
506                pass
507        T.PostMultiply()
508        T.Translate(-p)
509        if rotation:
510            if rad:
511                rotation *= 180.0 / np.pi
512            T.RotateWXYZ(rotation, initaxis)
513        if xyplane:
514            angleph = np.arctan2(newaxis[1], newaxis[0])
515            T.RotateWXYZ(np.rad2deg(angleph + angleth), initaxis) # compensation
516        T.RotateWXYZ(np.rad2deg(angleth), crossvec)
517        T.Translate(p)
518
519        self.SetOrientation(T.GetOrientation())
520
521        self.point_locator = None
522        self.cell_locator = None
523        return self
524
525        # newaxis = utils.versor(newaxis)
526        # pos = np.array(self.GetPosition())
527        # crossvec = np.cross(initaxis, newaxis)
528        # angle = np.arccos(np.dot(initaxis, newaxis))
529        # T = vtk.vtkTransform()
530        # T.PostMultiply()
531        # T.Translate(-pos)
532        # if rotation:
533        #     T.RotateWXYZ(rotation, initaxis)
534        # T.RotateWXYZ(np.rad2deg(angle), crossvec)
535        # T.Translate(pos)
536        # self.SetUserTransform(T)
537        # self.transform = T

Set/Get object orientation.

Arguments:
  • rotation : (float) rotate object around newaxis.
  • concatenate : (bool) concatenate the orietation operation with the previous existing transform (if any)
  • xyplane : (bool) make an extra rotation to keep the object aligned to the xy-plane
  • rad : (bool) set to True if angle is expressed in radians.
Example:
from vedo import *
center = np.array([581/2,723/2,0])
objs = []
for a in np.linspace(0, 6.28, 7):
    v = vector(cos(a), sin(a), 0)*1000
    pic = Picture(dataurl+"images/dog.jpg").rotate_z(10)
    pic.orientation(v, xyplane=True)
    pic.origin(center)
    pic.pos(v - center)
    objs += [pic, Arrow(v, v+v)]
show(objs, Point(), axes=1).close()

Examples:

def scale(self, s=None, reset=False):
540    def scale(self, s=None, reset=False):
541        """
542        Set/get object's scaling factor.
543
544        Arguments:
545            s : (list, float)
546                scaling factor(s).
547            reset : (bool)
548                if True previous scaling factors are ignored.
549
550        Note:
551            use `s=(sx,sy,sz)` to scale differently in the three coordinates.
552        """
553        if s is None:
554            return np.array(self.GetScale())
555
556        # assert s[0] != 0
557        # assert s[1] != 0
558        # assert s[2] != 0
559
560        if reset:
561            self.SetScale(s)
562        else:
563            self.SetScale(np.multiply(self.GetScale(), s))
564
565        self.point_locator = None
566        self.cell_locator = None
567        return self

Set/get object's scaling factor.

Arguments:
  • s : (list, float) scaling factor(s).
  • reset : (bool) if True previous scaling factors are ignored.
Note:

use s=(sx,sy,sz) to scale differently in the three coordinates.

def get_transform(self, invert=False):
569    def get_transform(self, invert=False):
570        """
571        Check if `object.transform` exists and returns a `vtkTransform`.
572        Otherwise return current user transformation (where the object is currently placed).
573
574        Use `invert` to return the inverse of the current transformation
575
576        Example:
577            ```python
578            from vedo import *
579
580            c1 = Cube()
581            c2 = c1.clone().c('violet').alpha(0.5) # copy of c1
582            v = vector(0.2,1,0)
583            p = vector(1,0,0)  # axis passes through this point
584            c2.rotate(90, axis=v, point=p)
585
586            # get the inverse of the current transformation
587            T = c2.get_transform(invert=True)
588            c2.apply_transform(T)  # put back c2 in place
589
590            l = Line(p-v, p+v).lw(3).c('red')
591            show(c1.wireframe().lw(3), l, c2, axes=1).close()
592            ```
593            ![](https://vedo.embl.es/images/feats/get_transf.png)
594        """
595        if self.transform:
596            tr = self.transform
597            if invert:
598                tr = tr.GetInverse()
599            return tr
600
601        T = self.GetMatrix()
602        tr = vtk.vtkTransform()
603        tr.SetMatrix(T)
604        if invert:
605            tr = tr.GetInverse()
606        return tr

Check if object.transform exists and returns a vtkTransform. Otherwise return current user transformation (where the object is currently placed).

Use invert to return the inverse of the current transformation

Example:
from vedo import *

c1 = Cube()
c2 = c1.clone().c('violet').alpha(0.5) # copy of c1
v = vector(0.2,1,0)
p = vector(1,0,0)  # axis passes through this point
c2.rotate(90, axis=v, point=p)

# get the inverse of the current transformation
T = c2.get_transform(invert=True)
c2.apply_transform(T)  # put back c2 in place

l = Line(p-v, p+v).lw(3).c('red')
show(c1.wireframe().lw(3), l, c2, axes=1).close()

@deprecated(reason=vedo.colors.red + 'Please use apply_transform()' + vedo.colors.reset)
def applyTransform(self, T, reset=False, concatenate=False):
609    @deprecated(reason=vedo.colors.red + "Please use apply_transform()" + vedo.colors.reset)
610    def applyTransform(self, T, reset=False, concatenate=False):
611        """Deprecated. Please use `apply_transform()`"""
612        return self.apply_transform(T,reset,concatenate)

Deprecated. Please use apply_transform()

def apply_transform(self, T, reset=False, concatenate=False):
614    def apply_transform(self, T, reset=False, concatenate=False):
615        """
616        Transform object position and orientation.
617
618        Arguments:
619            reset : (bool)
620                no effect, this is superseded by `pointcloud.apply_transform()`
621            concatenate : (bool)
622                no effect, this is superseded by `pointcloud.apply_transform()`
623        """
624        if isinstance(T, vtk.vtkMatrix4x4):
625            self.SetUserMatrix(T)
626        elif utils.is_sequence(T):
627            vm = vtk.vtkMatrix4x4()
628            for i in [0, 1, 2, 3]:
629                for j in [0, 1, 2, 3]:
630                    vm.SetElement(i, j, T[i][j])
631            self.SetUserMatrix(vm)
632        else:
633            self.SetUserTransform(T)
634        self.transform = T
635
636        self.point_locator = None
637        self.cell_locator = None
638        return self

Transform object position and orientation.

Arguments:
  • reset : (bool) no effect, this is superseded by pointcloud.apply_transform()
  • concatenate : (bool) no effect, this is superseded by pointcloud.apply_transform()
def align_to_bounding_box(self, msh, rigid=False):
640    def align_to_bounding_box(self, msh, rigid=False):
641        """
642        Align the current object's bounding box to the bounding box
643        of the input object.
644
645        Use `rigid` to disable scaling.
646
647        Examples:
648            - [align6.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align6.py)
649        """
650        lmt = vtk.vtkLandmarkTransform()
651        ss = vtk.vtkPoints()
652        xss0, xss1, yss0, yss1, zss0, zss1 = self.bounds()
653        for p in [
654            [xss0, yss0, zss0],
655            [xss1, yss0, zss0],
656            [xss1, yss1, zss0],
657            [xss0, yss1, zss0],
658            [xss0, yss0, zss1],
659            [xss1, yss0, zss1],
660            [xss1, yss1, zss1],
661            [xss0, yss1, zss1],
662        ]:
663            ss.InsertNextPoint(p)
664        st = vtk.vtkPoints()
665        xst0, xst1, yst0, yst1, zst0, zst1 = msh.bounds()
666        for p in [
667            [xst0, yst0, zst0],
668            [xst1, yst0, zst0],
669            [xst1, yst1, zst0],
670            [xst0, yst1, zst0],
671            [xst0, yst0, zst1],
672            [xst1, yst0, zst1],
673            [xst1, yst1, zst1],
674            [xst0, yst1, zst1],
675        ]:
676            st.InsertNextPoint(p)
677
678        lmt.SetSourceLandmarks(ss)
679        lmt.SetTargetLandmarks(st)
680        lmt.SetModeToAffine()
681        if rigid:
682            lmt.SetModeToRigidBody()
683        lmt.Update()
684        self.apply_transform(lmt)
685        self.transform = lmt
686
687        self.point_locator = None
688        self.cell_locator = None
689        return self

Align the current object's bounding box to the bounding box of the input object.

Use rigid to disable scaling.

Examples:
def on(self):
691    def on(self):
692        """Switch on  object visibility. Object is not removed."""
693        self.VisibilityOn()
694        try:
695            self.scalarbar.VisibilityOn()
696        except AttributeError:
697            pass
698        try:
699            self.trail.VisibilityOn()
700        except AttributeError:
701            pass
702        try:
703            for sh in self.shadows:
704                sh.VisibilityOn()
705        except AttributeError:
706            pass        
707        return self

Switch on object visibility. Object is not removed.

def off(self):
709    def off(self):
710        """Switch off object visibility. Object is not removed."""
711        self.VisibilityOff()
712        try:
713            self.scalarbar.VisibilityOff()
714        except AttributeError:
715            pass
716        try:
717            self.trail.VisibilityOff()
718        except AttributeError:
719            pass
720        try:
721            for sh in self.shadows:
722                sh.VisibilityOff()
723        except AttributeError:
724            pass        
725        return self

Switch off object visibility. Object is not removed.

def toggle(self):
727    def toggle(self):
728        """Toggle object visibility on/off."""
729        v = self.GetVisibility()
730        if v:
731            self.off()
732        else:
733            self.on()
734        return self

Toggle object visibility on/off.

def box(self, scale=1, padding=0, fill=False):
736    def box(self, scale=1, padding=0, fill=False):
737        """
738        Return the bounding box as a new `Mesh`.
739
740        Arguments:
741            scale : (float)
742                box size can be scaled by a factor
743            padding : (float, list)
744                a constant padding can be added (can be a list [padx,pady,padz])
745
746        Examples:
747            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
748        """
749        b = self.bounds()
750        if not utils.is_sequence(padding):
751            padding = [padding, padding, padding]
752        length, width, height = b[1] - b[0], b[3] - b[2], b[5] - b[4]
753        tol = (length + width + height) / 30000  # useful for boxing 2D text
754        pos = [(b[0] + b[1]) / 2, (b[3] + b[2]) / 2, (b[5] + b[4]) / 2 - tol]
755        bx = vedo.shapes.Box(
756            pos,
757            length * scale + padding[0],
758            width  * scale + padding[1],
759            height * scale + padding[2],
760            c="gray",
761        )
762        if hasattr(self, "GetProperty"):  # could be Assembly
763            if isinstance(self.GetProperty(), vtk.vtkProperty):  # could be volume
764                pr = vtk.vtkProperty()
765                pr.DeepCopy(self.GetProperty())
766                bx.SetProperty(pr)
767                bx.property = pr
768        bx.wireframe(not fill)
769        bx.flat().lighting("off")
770        return bx

Return the bounding box as a new Mesh.

Arguments:
  • scale : (float) box size can be scaled by a factor
  • padding : (float, list) a constant padding can be added (can be a list [padx,pady,padz])
Examples:
def use_bounds(self, ub=True):
772    def use_bounds(self, ub=True):
773        """
774        Instruct the current camera to either take into account or ignore
775        the object bounds when resetting.
776        """
777        self.SetUseBounds(ub)
778        return self

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

def bounds(self):
780    def bounds(self):
781        """
782        Get the object bounds.
783        Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`.
784        """
785        try:
786            pts = self.points()
787            xmin, ymin, zmin = np.min(pts, axis=0)
788            xmax, ymax, zmax = np.max(pts, axis=0)
789            return [xmin,xmax, ymin,ymax, zmin,zmax]
790        except (AttributeError, ValueError):
791            return self.GetBounds()

Get the object bounds. Returns a list in format [xmin,xmax, ymin,ymax, zmin,zmax].

def xbounds(self, i=None):
793    def xbounds(self, i=None):
794        """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1)."""
795        b = self.bounds()
796        if i is not None:
797            return b[i]
798        return (b[0], b[1])

Get the bounds [xmin,xmax]. Can specify upper or lower with i (0,1).

def ybounds(self, i=None):
800    def ybounds(self, i=None):
801        """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1)."""
802        b = self.bounds()
803        if i == 0:
804            return b[2]
805        if i == 1:
806            return b[3]
807        return (b[2], b[3])

Get the bounds [ymin,ymax]. Can specify upper or lower with i (0,1).

def zbounds(self, i=None):
809    def zbounds(self, i=None):
810        """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1)."""
811        b = self.bounds()
812        if i == 0: return b[4]
813        if i == 1: return b[5]
814        return (b[4], b[5])

Get the bounds [zmin,zmax]. Can specify upper or lower with i (0,1).

@deprecated(reason=vedo.colors.red + 'Please use diagonal_size()' + vedo.colors.reset)
def diagonalSize(self):
816    @deprecated(reason=vedo.colors.red + "Please use diagonal_size()" + vedo.colors.reset)
817    def diagonalSize(self):
818        """Deprecated. Please use `diagonal_size()`."""
819        return self.diagonal_size()

Deprecated. Please use diagonal_size().

def diagonal_size(self):
821    def diagonal_size(self):
822        """Get the length of the diagonal of mesh bounding box."""
823        b = self.bounds()
824        return np.sqrt((b[1] - b[0]) ** 2 + (b[3] - b[2]) ** 2 + (b[5] - b[4]) ** 2)
825        # return self.GetLength() # ???different???

Get the length of the diagonal of mesh bounding box.

def copy_data_from(self, obj):
828    def copy_data_from(self, obj):
829        """Copy all data (point and cell data) from this input object"""
830        self._data.GetPointData().PassData(obj._data.GetPointData())
831        self._data.GetCellData().PassData(obj._data.GetCellData())
832        self.pipeline = utils.OperationNode(
833            f"copy_data_from\n{obj.__class__.__name__}", 
834            parents=[self, obj],
835            shape='note',
836            c="#ccc5b9",
837        )
838        return self

Copy all data (point and cell data) from this input object

def print(self):
840    def print(self):
841        """Print information about an object."""
842        utils.print_info(self)
843        return self

Print information about an object.

def show(self, **options):
845    def show(self, **options):
846        """
847        Create on the fly an instance of class `Plotter` or use the last existing one to
848        show one single object.
849
850        This method is meant as a shortcut. If more than one object needs to be visualised
851        please use the syntax `show(mesh1, mesh2, volume, ..., options)`.
852
853        Returns the `Plotter` class instance.
854        """
855        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):
857    def thumbnail(
858            self, 
859            zoom=1.25,
860            size=(200, 200),
861            bg="white", 
862            azimuth=0, 
863            elevation=0, 
864            axes=False,
865        ):
866        """Build a thumbnail of the object and return it as an array."""
867        # speed is about 20Hz for size=[200,200]
868        ren = vtk.vtkRenderer()
869        ren.AddActor(self)
870        if axes:
871            axes = vedo.addons.Axes(self)
872            ren.AddActor(axes)
873        ren.ResetCamera()
874        cam = ren.GetActiveCamera()
875        cam.Zoom(zoom)
876        cam.Elevation(elevation)
877        cam.Azimuth(azimuth)
878
879        ren_win = vtk.vtkRenderWindow()
880        ren_win.SetOffScreenRendering(True)
881        ren_win.SetSize(size)
882        ren.SetBackground(colors.get_color(bg))
883        ren_win.AddRenderer(ren)
884        ren_win.Render()
885
886        nx, ny = ren_win.GetSize()
887        arr = vtk.vtkUnsignedCharArray()
888        ren_win.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
889        narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
890        narr = np.ascontiguousarray(np.flip(narr, axis=0))
891
892        ren.RemoveActor(self)
893        if axes:
894            ren.RemoveActor(axes)
895        ren_win.Finalize()
896        del ren_win
897        return narr

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

class BaseActor(Base3DProp):
 997class BaseActor(Base3DProp):
 998    """
 999    Base class.
1000
1001    .. warning:: Do not use this class to instanciate objects, use one the above instead.
1002    """
1003    def __init__(self):
1004        """
1005        Base class to add operative and data
1006        functionality to `Mesh`, `Assembly`, `Volume` and `Picture` objects.
1007        """
1008        
1009        super().__init__()
1010
1011        self._mapper = None
1012        self._caption = None
1013        self.property = None
1014
1015
1016    def mapper(self, new_mapper=None):
1017        """Return the `vtkMapper` data object, or update it with a new one."""
1018        if new_mapper:
1019            self.SetMapper(new_mapper)
1020            if self._mapper:
1021                iptdata = self._mapper.GetInput()
1022                if iptdata:
1023                    new_mapper.SetInputData(self._mapper.GetInput())
1024            self._mapper = new_mapper
1025            self._mapper.Modified()
1026        return self._mapper
1027
1028    def inputdata(self):
1029        """Return the VTK input data object."""
1030        if self._mapper:
1031            return self._mapper.GetInput()
1032        return self.GetMapper().GetInput()
1033
1034    def modified(self):
1035        """Use in conjunction with `tonumpy()`
1036        to update any modifications to the volume array"""
1037        sc = self.inputdata().GetPointData().GetScalars()
1038        if sc:
1039            sc.Modified()
1040        self.inputdata().GetPointData().Modified()
1041        return self
1042
1043    @deprecated(reason=vedo.colors.red + "Please use property object.npoints" + vedo.colors.reset)
1044    def N(self):
1045        """Deprecated. Please use property object.npoints"""
1046        return self.inputdata().GetNumberOfPoints()
1047
1048    @deprecated(reason=vedo.colors.red + "Please use property object.npoints" + vedo.colors.reset)
1049    def NPoints(self):
1050        """Deprecated. Please use property object.npoints"""
1051        return self.inputdata().GetNumberOfPoints()
1052
1053    @deprecated(reason=vedo.colors.red + "Please use property object.ncells" + vedo.colors.reset)
1054    def NCells(self):
1055        """Deprecated. Please use property object.ncells"""
1056        return self.inputdata().GetNumberOfCells()
1057
1058    @property
1059    def npoints(self):
1060        """Retrieve the number of points."""
1061        return self.inputdata().GetNumberOfPoints()
1062
1063    @property
1064    def ncells(self):
1065        """Retrieve the number of cells."""
1066        return self.inputdata().GetNumberOfCells()
1067
1068
1069    def points(self, pts=None, transformed=True):
1070        """
1071        Set/Get the vertex coordinates of a mesh or point cloud.
1072        Argument can be an index, a set of indices
1073        or a complete new set of points to update the mesh.
1074
1075        Set `transformed=False` to ignore any previous transformation applied to the mesh.
1076        """
1077        if pts is None:  ### getter
1078
1079            if isinstance(self, vedo.Points):
1080                vpts = self.polydata(transformed).GetPoints()
1081            elif isinstance(self, vedo.BaseVolume):
1082                v2p = vtk.vtkImageToPoints()
1083                v2p.SetInputData(self.imagedata())
1084                v2p.Update()
1085                vpts = v2p.GetOutput().GetPoints()
1086            else:  # tetmesh et al
1087                vpts = self.inputdata().GetPoints()
1088
1089            if vpts:
1090                return utils.vtk2numpy(vpts.GetData())
1091            return np.array([], dtype=np.float32)
1092
1093        else:  ### setter
1094
1095            if len(pts) == 3 and len(pts[0]) != 3:
1096                # assume plist is in the format [all_x, all_y, all_z]
1097                pts = np.stack((pts[0], pts[1], pts[2]), axis=1)
1098            pts = np.asarray(pts, dtype=np.float32)
1099            if pts.shape[1] == 2:
1100                pts = np.c_[pts, np.zeros(pts.shape[0], dtype=np.float32)]
1101            vpts = self.inputdata().GetPoints()
1102            arr = utils.numpy2vtk(pts, dtype=np.float32)
1103            vpts.SetData(arr)
1104            vpts.Modified()
1105            # reset mesh to identity matrix position/rotation:
1106            self.PokeMatrix(vtk.vtkMatrix4x4())
1107            self.point_locator = None
1108            self.cell_locator = None
1109            return self
1110
1111    @deprecated(reason=vedo.colors.red + "Please use cell_centers()" + vedo.colors.reset)
1112    def cellCenters(self):
1113        """Deprecated. Please use `cell_centers()`"""
1114        return self.cell_centers()
1115
1116    def cell_centers(self):
1117        """
1118        Get the coordinates of the cell centers.
1119
1120        Examples:
1121            - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py)
1122        """
1123        vcen = vtk.vtkCellCenters()
1124        if hasattr(self, "polydata"):
1125            vcen.SetInputData(self.polydata())
1126        else:
1127            vcen.SetInputData(self.inputdata())
1128        vcen.Update()
1129        return utils.vtk2numpy(vcen.GetOutput().GetPoints().GetData())
1130
1131    def delete_cells(self, ids):
1132        """
1133        Remove cells from the mesh object by their ID.
1134        Points (vertices) are not removed (you may use `.clean()` to remove those).
1135        """
1136        data = self.inputdata()
1137        data.BuildLinks()
1138        for cid in ids:
1139            data.DeleteCell(cid)
1140        data.RemoveDeletedCells()
1141        data.Modified()
1142        self._mapper.Modified()
1143        self.pipeline = utils.OperationNode(
1144            "delete_cells", parents=[self],
1145            comment=f"#cells {self._data.GetNumberOfCells()}",
1146        )
1147        return self
1148
1149    def mark_boundaries(self):
1150        """Mark cells and vertices of the mesh if they lie on a boundary."""
1151        mb = vtk.vtkMarkBoundaryFilter()
1152        mb.SetInputData(self._data)
1153        mb.Update()
1154        out = self._update(mb.GetOutput())
1155        out.pipeline = utils.OperationNode("mark_boundaries", parents=[self])
1156        return out
1157
1158    def find_cells_in(self, xbounds=(), ybounds=(), zbounds=()):
1159        """
1160        Find cells that are within the specified bounds.
1161        Setting a color will add a vtk array to colorize these cells.
1162        """
1163        if len(xbounds) == 6:
1164            bnds = xbounds
1165        else:
1166            bnds = list(self.bounds())
1167            if len(xbounds) == 2:
1168                bnds[0] = xbounds[0]
1169                bnds[1] = xbounds[1]
1170            if len(ybounds) == 2:
1171                bnds[2] = ybounds[0]
1172                bnds[3] = ybounds[1]
1173            if len(zbounds) == 2:
1174                bnds[4] = zbounds[0]
1175                bnds[5] = zbounds[1]
1176
1177        cellIds = vtk.vtkIdList()
1178        self.cell_locator = vtk.vtkCellTreeLocator()
1179        self.cell_locator.SetDataSet(self.polydata())
1180        self.cell_locator.BuildLocator()
1181        self.cell_locator.FindCellsWithinBounds(bnds, cellIds)
1182
1183        cids = []
1184        for i in range(cellIds.GetNumberOfIds()):
1185            cid = cellIds.GetId(i)
1186            cids.append(cid)
1187
1188        return np.array(cids)
1189
1190    def count_vertices(self):
1191        """Count the number of vertices each cell has and return it as a numpy array"""
1192        vc = vtk.vtkCountVertices()
1193        vc.SetInputData(self._data)
1194        vc.SetOutputArrayName("VertexCount")
1195        vc.Update()
1196        varr = vc.GetOutput().GetCellData().GetArray("VertexCount")
1197        return utils.vtk2numpy(varr)
1198
1199    def lighting(
1200        self,
1201        style="",
1202        ambient=None,
1203        diffuse=None,
1204        specular=None,
1205        specular_power=None,
1206        specular_color=None,
1207        metallicity=None,
1208        roughness=None,
1209    ):
1210        """
1211        Set the ambient, diffuse, specular and specular_power lighting constants.
1212
1213        Arguments:
1214            style : (str)
1215                preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]`
1216            ambient : (float)
1217                ambient fraction of emission [0-1]
1218            diffuse : (float)
1219                emission of diffused light in fraction [0-1]
1220            specular : (float)
1221                fraction of reflected light [0-1]
1222            specular_power : (float)
1223                precision of reflection [1-100]
1224            specular_color : (color)
1225                color that is being reflected by the surface
1226
1227        <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px>
1228
1229        Examples:
1230            - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py)
1231        """
1232        pr = self.GetProperty()
1233
1234        if style:
1235
1236            if isinstance(pr, vtk.vtkVolumeProperty):
1237                self.shade(True)
1238                if style == "off":
1239                    self.