vedo.pointcloud

Submodule to work with point clouds

   1#!/usr/bin/env python3
   2# -*- coding: utf-8 -*-
   3import numpy as np
   4
   5try:
   6    import vedo.vtkclasses as vtk
   7except ImportError:
   8    import vtkmodules.all as vtk
   9
  10import vedo
  11from vedo import colors
  12from vedo import utils
  13from vedo.base import BaseActor
  14
  15__docformat__ = "google"
  16
  17__doc__ = """
  18Submodule to work with point clouds <br>
  19
  20![](https://vedo.embl.es/images/basic/pca.png)
  21"""
  22
  23__all__ = [
  24    "Points",
  25    "Point",
  26    "merge",
  27    "visible_points",
  28    "delaunay2d",
  29    "voronoi",
  30    "fit_line",
  31    "fit_circle",
  32    "fit_plane",
  33    "fit_sphere",
  34    "pca_ellipse",
  35    "pca_ellipsoid",
  36]
  37
  38
  39####################################################
  40def merge(*meshs, flag=False):
  41    """
  42    Build a new Mesh (or Points) formed by the fusion of the inputs.
  43
  44    Similar to Assembly, but in this case the input objects become a single entity.
  45
  46    To keep track of the original identities of the inputs you can use `flag`.
  47    In this case a point array of IDs is added to the output.
  48
  49    Examples:
  50        - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py)
  51
  52            ![](https://vedo.embl.es/images/advanced/warp1.png)
  53
  54        - [value_iteration.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/value_iteration.py)
  55
  56    """
  57    acts = [a for a in utils.flatten(meshs) if a]
  58
  59    if not acts:
  60        return None
  61
  62    idarr = []
  63    polyapp = vtk.vtkAppendPolyData()
  64    for i, a in enumerate(acts):
  65        try:
  66            poly = a.polydata()
  67        except AttributeError:
  68            # so a vtkPolydata can also be passed
  69            poly = a
  70        polyapp.AddInputData(poly)
  71        if flag:
  72            idarr += [i] * poly.GetNumberOfPoints()
  73    polyapp.Update()
  74    mpoly = polyapp.GetOutput()
  75
  76    if flag:
  77        varr = utils.numpy2vtk(idarr, dtype=np.uint16, name="OriginalMeshID")
  78        mpoly.GetPointData().AddArray(varr)
  79
  80    if isinstance(acts[0], vedo.Mesh):
  81        msh = vedo.Mesh(mpoly)
  82    else:
  83        msh = Points(mpoly)
  84
  85    if isinstance(acts[0], vtk.vtkActor):
  86        cprp = vtk.vtkProperty()
  87        cprp.DeepCopy(acts[0].GetProperty())
  88        msh.SetProperty(cprp)
  89        msh.property = cprp
  90
  91    msh.pipeline = utils.OperationNode(
  92        "merge", parents=acts,
  93        comment=f"#pts {msh.inputdata().GetNumberOfPoints()}",
  94    )
  95    return msh
  96
  97
  98####################################################
  99def visible_points(mesh, area=(), tol=None, invert=False):
 100    """
 101    Extract points based on whether they are visible or not.
 102    Visibility is determined by accessing the z-buffer of a rendering window.
 103    The position of each input point is converted into display coordinates,
 104    and then the z-value at that point is obtained.
 105    If within the user-specified tolerance, the point is considered visible.
 106    Associated data attributes are passed to the output as well.
 107
 108    This filter also allows you to specify a rectangular window in display (pixel)
 109    coordinates in which the visible points must lie.
 110
 111    Arguments:
 112        area : (list)
 113            specify a rectangular region as (xmin,xmax,ymin,ymax)
 114        tol : (float)
 115            a tolerance in normalized display coordinate system
 116        invert : (bool)
 117            select invisible points instead.
 118
 119    Example:
 120        ```python
 121        from vedo import Ellipsoid, show, visible_points
 122        s = Ellipsoid().rotate_y(30)
 123
 124        #Camera options: pos, focal_point, viewup, distance,
 125        camopts = dict(pos=(0,0,25), focal_point=(0,0,0))
 126        show(s, camera=camopts, offscreen=True)
 127
 128        m = visible_points(s)
 129        #print('visible pts:', m.points()) # numpy array
 130        show(m, new=True, axes=1) # optionally draw result on a new window
 131        ```
 132        ![](https://vedo.embl.es/images/feats/visible_points.png)
 133    """
 134    # specify a rectangular region
 135    svp = vtk.vtkSelectVisiblePoints()
 136    svp.SetInputData(mesh.polydata())
 137    svp.SetRenderer(vedo.plotter_instance.renderer)
 138
 139    if len(area) == 4:
 140        svp.SetSelection(area[0], area[1], area[2], area[3])
 141    if tol is not None:
 142        svp.SetTolerance(tol)
 143    if invert:
 144        svp.SelectInvisibleOn()
 145    svp.Update()
 146
 147    m = Points(svp.GetOutput()).point_size(5)
 148    m.name = "VisiblePoints"
 149    return m
 150
 151
 152def delaunay2d(plist, mode="scipy", boundaries=(), tol=None, alpha=0.0, offset=0.0, transform=None):
 153    """
 154    Create a mesh from points in the XY plane.
 155    If `mode='fit'` then the filter computes a best fitting
 156    plane and projects the points onto it.
 157
 158    Arguments:
 159        tol : (float)
 160            specify a tolerance to control discarding of closely spaced points.
 161            This tolerance is specified as a fraction of the diagonal length of the bounding box of the points.
 162        alpha : (float)
 163            for a non-zero alpha value, only edges or triangles contained
 164            within a sphere centered at mesh vertices will be output.
 165            Otherwise, only triangles will be output.
 166        offset : (float)
 167            multiplier to control the size of the initial, bounding Delaunay triangulation.
 168        transform: vtkTransform
 169            a VTK transformation (eg. a thinplate spline)
 170            which is applied to points to generate a 2D problem.
 171            This maps a 3D dataset into a 2D dataset where triangulation can be done on the XY plane.
 172            The points are transformed and triangulated.
 173            The topology of triangulated points is used as the output topology.
 174
 175    Examples:
 176        - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py)
 177
 178            ![](https://vedo.embl.es/images/basic/delaunay2d.png)
 179    """
 180    if isinstance(plist, Points):
 181        parents = [plist]
 182        plist = plist.points()
 183    else:
 184        parents = []
 185        plist = np.ascontiguousarray(plist)
 186        plist = utils.make3d(plist)
 187
 188    #############################################
 189    if mode == "scipy":
 190        from scipy.spatial import Delaunay as scipy_delaunay
 191
 192        tri = scipy_delaunay(plist[:, 0:2])
 193        return vedo.mesh.Mesh([plist, tri.simplices])
 194    #############################################
 195
 196    pd = vtk.vtkPolyData()
 197    vpts = vtk.vtkPoints()
 198    vpts.SetData(utils.numpy2vtk(plist, dtype=np.float32))
 199    pd.SetPoints(vpts)
 200
 201    delny = vtk.vtkDelaunay2D()
 202    delny.SetInputData(pd)
 203    if tol:
 204        delny.SetTolerance(tol)
 205    delny.SetAlpha(alpha)
 206    delny.SetOffset(offset)
 207    if transform:
 208        if hasattr(transform, "transform"):
 209            transform = transform.transform
 210        delny.SetTransform(transform)
 211
 212    if mode == "xy" and boundaries:
 213        boundary = vtk.vtkPolyData()
 214        boundary.SetPoints(vpts)
 215        cell_array = vtk.vtkCellArray()
 216        for b in boundaries:
 217            cpolygon = vtk.vtkPolygon()
 218            for idd in b:
 219                cpolygon.GetPointIds().InsertNextId(idd)
 220            cell_array.InsertNextCell(cpolygon)
 221        boundary.SetPolys(cell_array)
 222        delny.SetSourceData(boundary)
 223
 224    if mode == "fit":
 225        delny.SetProjectionPlaneMode(vtk.VTK_BEST_FITTING_PLANE)
 226    delny.Update()
 227    msh = vedo.mesh.Mesh(delny.GetOutput()).clean().lighting("off")
 228
 229    msh.pipeline = utils.OperationNode(
 230        "delaunay2d", parents=parents,
 231        comment=f"#cells {msh.inputdata().GetNumberOfCells()}"
 232    )
 233    return msh
 234
 235
 236def voronoi(pts, padding=0.0, fit=False, method="vtk"):
 237    """
 238    Generate the 2D Voronoi convex tiling of the input points (z is ignored).
 239    The points are assumed to lie in a plane. The output is a Mesh. Each output cell is a convex polygon.
 240
 241    The 2D Voronoi tessellation is a tiling of space, where each Voronoi tile represents the region nearest
 242    to one of the input points. Voronoi tessellations are important in computational geometry
 243    (and many other fields), and are the dual of Delaunay triangulations.
 244
 245    Thus the triangulation is constructed in the x-y plane, and the z coordinate is ignored
 246    (although carried through to the output).
 247    If you desire to triangulate in a different plane, you can use fit=True.
 248
 249    A brief summary is as follows. Each (generating) input point is associated with
 250    an initial Voronoi tile, which is simply the bounding box of the point set.
 251    A locator is then used to identify nearby points: each neighbor in turn generates a
 252    clipping line positioned halfway between the generating point and the neighboring point,
 253    and orthogonal to the line connecting them. Clips are readily performed by evaluationg the
 254    vertices of the convex Voronoi tile as being on either side (inside,outside) of the clip line.
 255    If two intersections of the Voronoi tile are found, the portion of the tile "outside" the clip
 256    line is discarded, resulting in a new convex, Voronoi tile. As each clip occurs,
 257    the Voronoi "Flower" error metric (the union of error spheres) is compared to the extent of the region
 258    containing the neighboring clip points. The clip region (along with the points contained in it) is grown
 259    by careful expansion (e.g., outward spiraling iterator over all candidate clip points).
 260    When the Voronoi Flower is contained within the clip region, the algorithm terminates and the Voronoi
 261    tile is output. Once complete, it is possible to construct the Delaunay triangulation from the Voronoi
 262    tessellation. Note that topological and geometric information is used to generate a valid triangulation
 263    (e.g., merging points and validating topology).
 264
 265    Arguments:
 266        pts : (list)
 267            list of input points.
 268        padding : (float)
 269            padding distance. The default is 0.
 270        fit : (bool)
 271            detect automatically the best fitting plane. The default is False.
 272
 273    Examples:
 274        - [voronoi1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/voronoi1.py)
 275
 276            ![](https://vedo.embl.es/images/basic/voronoi1.png)
 277
 278        - [voronoi2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/voronoi2.py)
 279
 280            ![](https://vedo.embl.es/images/advanced/voronoi2.png)
 281    """
 282    if method == "scipy":
 283        from scipy.spatial import Voronoi as scipy_voronoi
 284
 285        pts = np.asarray(pts)[:, (0, 1)]
 286        vor = scipy_voronoi(pts)
 287        regs = []  # filter out invalid indices
 288        for r in vor.regions:
 289            flag = True
 290            for x in r:
 291                if x < 0:
 292                    flag = False
 293                    break
 294            if flag and len(r) > 0:
 295                regs.append(r)
 296
 297        m = vedo.Mesh([vor.vertices, regs], c="orange5")
 298        m.celldata["VoronoiID"] = np.array(list(range(len(regs)))).astype(int)
 299        m.locator = None
 300
 301    elif method == "vtk":
 302        vor = vtk.vtkVoronoi2D()
 303        if isinstance(pts, Points):
 304            vor.SetInputData(pts.polydata())
 305        else:
 306            pts = np.asarray(pts)
 307            if pts.shape[1] == 2:
 308                pts = np.c_[pts, np.zeros(len(pts))]
 309            pd = vtk.vtkPolyData()
 310            vpts = vtk.vtkPoints()
 311            vpts.SetData(utils.numpy2vtk(pts, dtype=np.float32))
 312            pd.SetPoints(vpts)
 313            vor.SetInputData(pd)
 314        vor.SetPadding(padding)
 315        vor.SetGenerateScalarsToPointIds()
 316        if fit:
 317            vor.SetProjectionPlaneModeToBestFittingPlane()
 318        else:
 319            vor.SetProjectionPlaneModeToXYPlane()
 320        vor.Update()
 321        poly = vor.GetOutput()
 322        arr = poly.GetCellData().GetArray(0)
 323        if arr:
 324            arr.SetName("VoronoiID")
 325        m = vedo.Mesh(poly, c="orange5")
 326        m.locator = vor.GetLocator()
 327
 328    else:
 329        vedo.logger.error(f"Unknown method {method} in voronoi()")
 330        raise RuntimeError
 331
 332    m.lw(2).lighting("off").wireframe()
 333    m.name = "Voronoi"
 334    return m
 335
 336
 337def _rotate_points(points, n0=None, n1=(0, 0, 1)):
 338    # Rotate a set of 3D points from direction n0 to direction n1.
 339    # Return the rotated points and the normal to the fitting plane (if n0 is None).
 340    # The pointing direction of the normal in this case is arbitrary.
 341    points = np.asarray(points)
 342
 343    if points.ndim == 1:
 344        points = points[np.newaxis, :]
 345
 346    if len(points[0]) == 2:
 347        return points, (0, 0, 1)
 348
 349    if n0 is None:  # fit plane
 350        datamean = points.mean(axis=0)
 351        vv = np.linalg.svd(points - datamean)[2]
 352        n0 = np.cross(vv[0], vv[1])
 353
 354    n0 = n0 / np.linalg.norm(n0)
 355    n1 = n1 / np.linalg.norm(n1)
 356    k = np.cross(n0, n1)
 357    l = np.linalg.norm(k)
 358    if not l:
 359        k = n0
 360    k /= np.linalg.norm(k)
 361
 362    ct = np.dot(n0, n1)
 363    theta = np.arccos(ct)
 364    st = np.sin(theta)
 365    v = k * (1 - ct)
 366
 367    rpoints = []
 368    for p in points:
 369        a = p * ct
 370        b = np.cross(k, p) * st
 371        c = v * np.dot(k, p)
 372        rpoints.append(a + b + c)
 373
 374    return np.array(rpoints), n0
 375
 376
 377def fit_line(points):
 378    """
 379    Fits a line through points.
 380
 381    Extra info is stored in `Line.slope`, `Line.center`, `Line.variances`.
 382
 383    Examples:
 384        - [fitline.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitline.py)
 385
 386            ![](https://vedo.embl.es/images/advanced/fitline.png)
 387    """
 388    if isinstance(points, Points):
 389        points = points.points()
 390    data = np.array(points)
 391    datamean = data.mean(axis=0)
 392    _, dd, vv = np.linalg.svd(data - datamean)
 393    vv = vv[0] / np.linalg.norm(vv[0])
 394    # vv contains the first principal component, i.e. the direction
 395    # vector of the best fit line in the least squares sense.
 396    xyz_min = points.min(axis=0)
 397    xyz_max = points.max(axis=0)
 398    a = np.linalg.norm(xyz_min - datamean)
 399    b = np.linalg.norm(xyz_max - datamean)
 400    p1 = datamean - a * vv
 401    p2 = datamean + b * vv
 402    line = vedo.shapes.Line(p1, p2, lw=1)
 403    line.slope = vv
 404    line.center = datamean
 405    line.variances = dd
 406    return line
 407
 408
 409def fit_circle(points):
 410    """
 411    Fits a circle through a set of 3D points, with a very fast non-iterative method.
 412
 413    Returns the tuple `(center, radius, normal_to_circle)`.
 414
 415    .. warning::
 416        trying to fit s-shaped points will inevitably lead to instabilities and
 417        circles of small radius.
 418
 419    References:
 420        *J.F. Crawford, Nucl. Instr. Meth. 211, 1983, 223-225.*
 421    """
 422    data = np.asarray(points)
 423
 424    offs = data.mean(axis=0)
 425    data, n0 = _rotate_points(data - offs)
 426
 427    xi = data[:, 0]
 428    yi = data[:, 1]
 429
 430    x = sum(xi)
 431    xi2 = xi * xi
 432    xx = sum(xi2)
 433    xxx = sum(xi2 * xi)
 434
 435    y = sum(yi)
 436    yi2 = yi * yi
 437    yy = sum(yi2)
 438    yyy = sum(yi2 * yi)
 439
 440    xiyi = xi * yi
 441    xy = sum(xiyi)
 442    xyy = sum(xiyi * yi)
 443    xxy = sum(xi * xiyi)
 444
 445    N = len(xi)
 446    k = (xx + yy) / N
 447
 448    a1 = xx - x * x / N
 449    b1 = xy - x * y / N
 450    c1 = 0.5 * (xxx + xyy - x * k)
 451
 452    a2 = xy - x * y / N
 453    b2 = yy - y * y / N
 454    c2 = 0.5 * (xxy + yyy - y * k)
 455
 456    d = a2 * b1 - a1 * b2
 457    if not d:
 458        return offs, 0, n0
 459    x0 = (b1 * c2 - b2 * c1) / d
 460    y0 = (c1 - a1 * x0) / b1
 461
 462    R = np.sqrt(x0 * x0 + y0 * y0 - 1 / N * (2 * x0 * x + 2 * y0 * y - xx - yy))
 463
 464    c, _ = _rotate_points([x0, y0, 0], (0, 0, 1), n0)
 465
 466    return c[0] + offs, R, n0
 467
 468
 469def fit_plane(points, signed=False):
 470    """
 471    Fits a plane to a set of points.
 472
 473    Extra info is stored in `Plane.normal`, `Plane.center`, `Plane.variance`.
 474
 475    Arguments:
 476    signed : (bool)
 477        if True flip sign of the normal based on the ordering of the points
 478
 479    Examples:
 480        - [fitline.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitline.py)
 481
 482            ![](https://vedo.embl.es/images/advanced/fitline.png)
 483    """
 484    if isinstance(points, Points):
 485        points = points.points()
 486    data = np.asarray(points)
 487    datamean = data.mean(axis=0)
 488    pts = data - datamean
 489    res = np.linalg.svd(pts)
 490    dd, vv = res[1], res[2]
 491    n = np.cross(vv[0], vv[1])
 492    if signed:
 493        v = np.zeros_like(pts)
 494        for i in range(len(pts) - 1):
 495            vi = np.cross(pts[i], pts[i + 1])
 496            v[i] = vi / np.linalg.norm(vi)
 497        ns = np.mean(v, axis=0)  # normal to the points plane
 498        if np.dot(n, ns) < 0:
 499            n = -n
 500    xyz_min = data.min(axis=0)
 501    xyz_max = data.max(axis=0)
 502    s = np.linalg.norm(xyz_max - xyz_min)
 503    pla = vedo.shapes.Plane(datamean, n, s=[s, s])
 504    pla.normal = n
 505    pla.center = datamean
 506    pla.variance = dd[2]
 507    pla.name = "FitPlane"
 508    pla.top = n
 509    return pla
 510
 511
 512def fit_sphere(coords):
 513    """
 514    Fits a sphere to a set of points.
 515
 516    Extra info is stored in `Sphere.radius`, `Sphere.center`, `Sphere.residue`.
 517
 518    Examples:
 519        - [fitspheres1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitspheres1.py)
 520
 521            ![](https://vedo.embl.es/images/advanced/fitspheres1.jpg)
 522    """
 523    if isinstance(coords, Points):
 524        coords = coords.points()
 525    coords = np.array(coords)
 526    n = len(coords)
 527    A = np.zeros((n, 4))
 528    A[:, :-1] = coords * 2
 529    A[:, 3] = 1
 530    f = np.zeros((n, 1))
 531    x = coords[:, 0]
 532    y = coords[:, 1]
 533    z = coords[:, 2]
 534    f[:, 0] = x * x + y * y + z * z
 535    try:
 536        C, residue, rank, _ = np.linalg.lstsq(A, f, rcond=-1)  # solve AC=f
 537    except:
 538        C, residue, rank, _ = np.linalg.lstsq(A, f)  # solve AC=f
 539    if rank < 4:
 540        return None
 541    t = (C[0] * C[0]) + (C[1] * C[1]) + (C[2] * C[2]) + C[3]
 542    radius = np.sqrt(t)[0]
 543    center = np.array([C[0][0], C[1][0], C[2][0]])
 544    if len(residue) > 0:
 545        residue = np.sqrt(residue[0]) / n
 546    else:
 547        residue = 0
 548    sph = vedo.shapes.Sphere(center, radius, c=(1, 0, 0)).wireframe(1)
 549    sph.radius = radius
 550    sph.center = center
 551    sph.residue = residue
 552    sph.name = "FitSphere"
 553    return sph
 554
 555
 556def pca_ellipse(points, pvalue=0.673, res=60):
 557    """
 558    Show the oriented PCA 2D ellipse that contains the fraction `pvalue` of points.
 559
 560    Parameter `pvalue` sets the specified fraction of points inside the ellipse.
 561    Normalized directions are stored in `ellipse.axis1`, `ellipse.axis12`
 562    axes sizes are stored in `ellipse.va`, `ellipse.vb`
 563
 564    Arguments:
 565        pvalue : (float)
 566            ellipse will include this fraction of points
 567        res : (int)
 568            resolution of the ellipse
 569
 570    Examples:
 571        - [histo_pca.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_pca.py)
 572
 573            ![](https://vedo.embl.es/images/pyplot/histo_pca.png)
 574    """
 575    from scipy.stats import f
 576
 577    if isinstance(points, Points):
 578        coords = points.points()
 579    else:
 580        coords = points
 581    if len(coords) < 4:
 582        vedo.logger.warning("in pca_ellipse(), there are not enough points!")
 583        return None
 584
 585    P = np.array(coords, dtype=float)[:,(0,1)]
 586    cov = np.cov(P, rowvar=0)     # covariance matrix
 587    _, s, R = np.linalg.svd(cov)  # singular value decomposition
 588    p, n = s.size, P.shape[0]
 589    fppf = f.ppf(pvalue, p, n-p)  # f % point function
 590    ua, ub = np.sqrt(s*fppf/2)*2  # semi-axes (largest first)
 591    center = np.mean(P, axis=0)   # centroid of the ellipse
 592
 593    matri = vtk.vtkMatrix4x4()
 594    matri.DeepCopy((
 595        R[0][0] * ua, R[1][0] * ub, 0, center[0],
 596        R[0][1] * ua, R[1][1] * ub, 0, center[1],
 597                   0,            0, 1,         0,
 598        0, 0, 0, 1)
 599    )
 600    vtra = vtk.vtkTransform()
 601    vtra.SetMatrix(matri)
 602
 603    elli = vedo.shapes.Circle(alpha=0.75, res=res)
 604
 605    # assign the transformation
 606    elli.SetScale(vtra.GetScale())
 607    elli.SetOrientation(vtra.GetOrientation())
 608    elli.SetPosition(vtra.GetPosition())
 609
 610    elli.center = np.array(vtra.GetPosition())
 611    elli.nr_of_points = n
 612    elli.va = ua
 613    elli.vb = ub
 614    elli.axis1 = np.array(vtra.TransformPoint([1, 0, 0])) - elli.center
 615    elli.axis2 = np.array(vtra.TransformPoint([0, 1, 0])) - elli.center
 616    elli.axis1 /= np.linalg.norm(elli.axis1)
 617    elli.axis2 /= np.linalg.norm(elli.axis2)
 618    elli.transformation = vtra
 619    elli.name = "PCAEllipse"
 620    return elli
 621
 622
 623def pca_ellipsoid(points, pvalue=0.673):
 624    """
 625    Show the oriented PCA ellipsoid that contains fraction `pvalue` of points.
 626
 627    Parameter `pvalue` sets the specified fraction of points inside the ellipsoid.
 628
 629    Extra can be calculated with `mesh.asphericity()`, `mesh.asphericity_error()`
 630    (asphericity is equal to 0 for a perfect sphere).
 631
 632    Axes sizes can be accessed in `ellips.va`, `ellips.vb`, `ellips.vc`,
 633    normalized directions are stored in `ellips.axis1`, `ellips.axis12`
 634    and `ellips.axis3`.
 635
 636    .. warning:: the meaning of `ellips.axis1`, has changed wrt `vedo==2022.1.0`
 637        in that it's now the direction wrt the origin (e.i. the center is subtracted)
 638
 639    Examples:
 640        - [pca_ellipsoid.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/pca_ellipsoid.py)
 641
 642            ![](https://vedo.embl.es/images/basic/pca.png)
 643    """
 644    from scipy.stats import f
 645
 646    if isinstance(points, Points):
 647        coords = points.points()
 648    else:
 649        coords = points
 650    if len(coords) < 4:
 651        vedo.logger.warning("in pcaEllipsoid(), there are not enough points!")
 652        return None
 653
 654    P = np.array(coords, ndmin=2, dtype=float)
 655    cov = np.cov(P, rowvar=0)     # covariance matrix
 656    _, s, R = np.linalg.svd(cov)  # singular value decomposition
 657    p, n = s.size, P.shape[0]
 658    fppf = f.ppf(pvalue, p, n-p)*(n-1)*p*(n+1)/n/(n-p)  # f % point function
 659    cfac = 1 + 6/(n-1)            # correction factor for low statistics
 660    ua, ub, uc = np.sqrt(s*fppf)/cfac  # semi-axes (largest first)
 661    center = np.mean(P, axis=0)   # centroid of the hyperellipsoid
 662
 663    elli = vedo.shapes.Ellipsoid((0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), alpha=0.25)
 664
 665    matri = vtk.vtkMatrix4x4()
 666    matri.DeepCopy((
 667        R[0][0] * ua*2, R[1][0] * ub*2, R[2][0] * uc*2, center[0],
 668        R[0][1] * ua*2, R[1][1] * ub*2, R[2][1] * uc*2, center[1],
 669        R[0][2] * ua*2, R[1][2] * ub*2, R[2][2] * uc*2, center[2],
 670        0, 0, 0, 1)
 671    )
 672    vtra = vtk.vtkTransform()
 673    vtra.SetMatrix(matri)
 674
 675    # assign the transformation
 676    elli.SetScale(vtra.GetScale())
 677    elli.SetOrientation(vtra.GetOrientation())
 678    elli.SetPosition(vtra.GetPosition())
 679
 680    elli.center = np.array(vtra.GetPosition())
 681    elli.nr_of_points = n
 682    elli.va = ua
 683    elli.vb = ub
 684    elli.vc = uc
 685    elli.axis1 = np.array(vtra.TransformPoint([1, 0, 0])) - elli.center
 686    elli.axis2 = np.array(vtra.TransformPoint([0, 1, 0])) - elli.center
 687    elli.axis3 = np.array(vtra.TransformPoint([0, 0, 1])) - elli.center
 688    elli.axis1 /= np.linalg.norm(elli.axis1)
 689    elli.axis2 /= np.linalg.norm(elli.axis2)
 690    elli.axis3 /= np.linalg.norm(elli.axis3)
 691    elli.transformation = vtra
 692    elli.name = "PCAEllipsoid"
 693    return elli
 694
 695
 696###################################################
 697def Point(pos=(0, 0, 0), r=12, c="red", alpha=1.0):
 698    """
 699    Create a simple point in space.
 700
 701    .. note:: if you are creating many points you should definitely use class `Points` instead!
 702    """
 703    if isinstance(pos, vtk.vtkActor):
 704        pos = pos.GetPosition()
 705    pd = utils.buildPolyData([[0, 0, 0]])
 706    if len(pos) == 2:
 707        pos = (pos[0], pos[1], 0.0)
 708    pt = Points(pd, r, c, alpha)
 709    pt.SetPosition(pos)
 710    pt.name = "Point"
 711    return pt
 712
 713
 714###################################################
 715class Points(BaseActor, vtk.vtkActor):
 716    """Work with pointclouds."""
 717
 718    def __init__(self, inputobj=None, r=4, c=(0.2, 0.2, 0.2), alpha=1, blur=False, emissive=True):
 719        """
 720        Build an object made of only vertex points for a list of 2D/3D points.
 721        Both shapes (N, 3) or (3, N) are accepted as input, if N>3.
 722        For very large point clouds a list of colors and alpha can be assigned to each
 723        point in the form c=[(R,G,B,A), ... ] where 0<=R<256, ... 0<=A<256.
 724
 725        Arguments:
 726            inputobj : (list, tuple)
 727            r : (int)
 728                Point radius in units of pixels.
 729            c : (str, list)
 730                Color name or rgb tuple.
 731            alpha : (float)
 732                Transparency in range [0,1].
 733            blur : (bool)
 734                Apply a gaussian convolution filter to the points.
 735                In this case the radius `r` is in absolute units of the mesh coordinates.
 736            emissive : (bool)
 737                Halo of point becomes emissive.
 738
 739        Example:
 740            ```python
 741            from vedo import *
 742
 743            def fibonacci_sphere(n):
 744                s = np.linspace(0, n, num=n, endpoint=False)
 745                theta = s * 2.399963229728653
 746                y = 1 - s * (2/(n-1))
 747                r = np.sqrt(1 - y * y)
 748                x = np.cos(theta) * r
 749                z = np.sin(theta) * r
 750                return [x,y,z]
 751
 752            Points(fibonacci_sphere(1000)).show(axes=1).close()
 753            ```
 754            ![](https://vedo.embl.es/images/feats/fibonacci.png)
 755        """
 756
 757        vtk.vtkActor.__init__(self)
 758        BaseActor.__init__(self)
 759
 760        self._data = None
 761
 762        if blur:
 763            self._mapper = vtk.vtkPointGaussianMapper()
 764            if emissive:
 765                self._mapper.SetEmissive(bool(emissive))
 766            self._mapper.SetScaleFactor(r * 1.4142)
 767
 768            # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
 769            if alpha < 1:
 770                self._mapper.SetSplatShaderCode(
 771                    "//VTK::Color::Impl\n"
 772                    "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
 773                    "if (dist > 1.0) {\n"
 774                    "   discard;\n"
 775                    "} else {\n"
 776                    f"  float scale = ({alpha} - dist);\n"
 777                    "   ambientColor *= scale;\n"
 778                    "   diffuseColor *= scale;\n"
 779                    "}\n"
 780                )
 781                alpha = 1
 782
 783        else:
 784            self._mapper = vtk.vtkPolyDataMapper()
 785        self.SetMapper(self._mapper)
 786
 787        self._bfprop = None  # backface property holder
 788
 789        self._scals_idx = 0  # index of the active scalar changed from CLI
 790        self._ligthingnr = 0  # index of the lighting mode changed from CLI
 791        self._cmap_name = ""  # remember the name for self._keypress
 792        # self.name = "Points" # better not to give it a name here
 793
 794        self.property = self.GetProperty()
 795
 796        try:
 797            if not blur:
 798                self.property.RenderPointsAsSpheresOn()
 799        except AttributeError:
 800            pass
 801
 802        if inputobj is None:  ####################
 803            self._data = vtk.vtkPolyData()
 804            return
 805        ########################################
 806
 807        self.property.SetRepresentationToPoints()
 808        self.property.SetPointSize(r)
 809        self.property.LightingOff()
 810
 811        if isinstance(inputobj, vedo.BaseActor):
 812            inputobj = inputobj.points()  # numpy
 813
 814        ######
 815        if isinstance(inputobj, vtk.vtkActor):
 816            poly_copy = vtk.vtkPolyData()
 817            pr = vtk.vtkProperty()
 818            pr.DeepCopy(inputobj.GetProperty())
 819            poly_copy.DeepCopy(inputobj.GetMapper().GetInput())
 820            pr.SetRepresentationToPoints()
 821            pr.SetPointSize(r)
 822            self._data = poly_copy
 823            self._mapper.SetInputData(poly_copy)
 824            self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility())
 825            self.SetProperty(pr)
 826            self.property = pr
 827
 828        elif isinstance(inputobj, vtk.vtkPolyData):
 829            if inputobj.GetNumberOfCells() == 0:
 830                carr = vtk.vtkCellArray()
 831                for i in range(inputobj.GetNumberOfPoints()):
 832                    carr.InsertNextCell(1)
 833                    carr.InsertCellPoint(i)
 834                inputobj.SetVerts(carr)
 835            self._data = inputobj  # cache vtkPolyData and mapper for speed
 836
 837        elif utils.is_sequence(inputobj):  # passing point coords
 838            plist = inputobj
 839            n = len(plist)
 840
 841            if n == 3:  # assume plist is in the format [all_x, all_y, all_z]
 842                if utils.is_sequence(plist[0]) and len(plist[0]) > 3:
 843                    plist = np.stack((plist[0], plist[1], plist[2]), axis=1)
 844            elif n == 2:  # assume plist is in the format [all_x, all_y, 0]
 845                if utils.is_sequence(plist[0]) and len(plist[0]) > 3:
 846                    plist = np.stack((plist[0], plist[1], np.zeros(len(plist[0]))), axis=1)
 847
 848            # if n and len(plist[0]) == 2:  # make it 3d
 849            #     plist = np.c_[np.array(plist), np.zeros(len(plist))]
 850            plist = utils.make3d(plist)
 851
 852            if (
 853                utils.is_sequence(c)
 854                and (len(c) > 3 or (utils.is_sequence(c[0]) and len(c[0]) == 4))
 855            ) or utils.is_sequence(alpha):
 856
 857                cols = c
 858
 859                n = len(plist)
 860                if n != len(cols):
 861                    vedo.logger.error(f"mismatch in Points() colors array lengths {n} and {len(cols)}")
 862                    raise RuntimeError()
 863
 864                src = vtk.vtkPointSource()
 865                src.SetNumberOfPoints(n)
 866                src.Update()
 867
 868                vgf = vtk.vtkVertexGlyphFilter()
 869                vgf.SetInputData(src.GetOutput())
 870                vgf.Update()
 871                pd = vgf.GetOutput()
 872
 873                pd.GetPoints().SetData(utils.numpy2vtk(plist, dtype=np.float32))
 874
 875                ucols = vtk.vtkUnsignedCharArray()
 876                ucols.SetNumberOfComponents(4)
 877                ucols.SetName("Points_RGBA")
 878                if utils.is_sequence(alpha):
 879                    if len(alpha) != n:
 880                        vedo.logger.error(f"mismatch in Points() alpha array lengths {n} and {len(cols)}")
 881                        raise RuntimeError()
 882                    alphas = alpha
 883                    alpha = 1
 884                else:
 885                    alphas = (alpha,) * n
 886
 887                if utils.is_sequence(cols):
 888                    c = None
 889                    if len(cols[0]) == 4:
 890                        for i in range(n):  # FAST
 891                            rc, gc, bc, ac = cols[i]
 892                            ucols.InsertNextTuple4(rc, gc, bc, ac)
 893                    else:
 894                        for i in range(n):  # SLOW
 895                            rc, gc, bc = colors.get_color(cols[i])
 896                            ucols.InsertNextTuple4(rc * 255, gc * 255, bc * 255, alphas[i] * 255)
 897                else:
 898                    c = cols
 899
 900                pd.GetPointData().AddArray(ucols)
 901                pd.GetPointData().SetActiveScalars("Points_RGBA")
 902                self._mapper.SetInputData(pd)
 903                self._mapper.ScalarVisibilityOn()
 904                self._data = pd
 905
 906            else:
 907
 908                pd = utils.buildPolyData(plist)
 909                self._mapper.SetInputData(pd)
 910                c = colors.get_color(c)
 911                self.property.SetColor(c)
 912                self.property.SetOpacity(alpha)
 913                self._data = pd
 914
 915            ##########
 916            self.pipeline = utils.OperationNode(
 917                self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}"
 918            )
 919            return
 920            ##########
 921
 922        elif isinstance(inputobj, str):
 923            verts = vedo.file_io.load(inputobj)
 924            self.filename = inputobj
 925            self._data = verts.polydata()
 926
 927        else:
 928
 929            # try to extract the points from the VTK input data object
 930            try:
 931                vvpts = inputobj.GetPoints()
 932                pd = vtk.vtkPolyData()
 933                pd.SetPoints(vvpts)
 934                for i in range(inputobj.GetPointData().GetNumberOfArrays()):
 935                    arr = inputobj.GetPointData().GetArray(i)
 936                    pd.GetPointData().AddArray(arr)
 937
 938                self._mapper.SetInputData(pd)
 939                c = colors.get_color(c)
 940                self.property.SetColor(c)
 941                self.property.SetOpacity(alpha)
 942                self._data = pd
 943            except:
 944                vedo.logger.error(f"cannot build Points from type {type(inputobj)}")
 945                raise RuntimeError()
 946
 947        c = colors.get_color(c)
 948        self.property.SetColor(c)
 949        self.property.SetOpacity(alpha)
 950
 951        self._mapper.SetInputData(self._data)
 952
 953        self.pipeline = utils.OperationNode(
 954            self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}"
 955        )
 956        return
 957
 958    def _repr_html_(self):
 959        """
 960        HTML representation of the Point cloud object for Jupyter Notebooks.
 961
 962        Returns:
 963            HTML text with the image and some properties.
 964        """
 965        import io
 966        import base64
 967        from PIL import Image
 968
 969        library_name = "vedo.pointcloud.Points"
 970        help_url = "https://vedo.embl.es/docs/vedo/pointcloud.html"
 971
 972        arr = self.thumbnail()
 973        im = Image.fromarray(arr)
 974        buffered = io.BytesIO()
 975        im.save(buffered, format="PNG", quality=100)
 976        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
 977        url = "data:image/png;base64," + encoded
 978        image = f"<img src='{url}'></img>"
 979
 980        bounds = "<br/>".join(
 981            [
 982                utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4)
 983                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
 984            ]
 985        )
 986        average_size = "{size:.3f}".format(size=self.average_size())
 987
 988        help_text = ""
 989        if self.name:
 990            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
 991        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
 992        if self.filename:
 993            dots = ""
 994            if len(self.filename) > 30:
 995                dots = "..."
 996            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
 997
 998        pdata = ""
 999        if self._data.GetPointData().GetScalars():
1000            if self._data.GetPointData().GetScalars().GetName():
1001                name = self._data.GetPointData().GetScalars().GetName()
1002                pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>"
1003
1004        cdata = ""
1005        if self._data.GetCellData().GetScalars():
1006            if self._data.GetCellData().GetScalars().GetName():
1007                name = self._data.GetCellData().GetScalars().GetName()
1008                cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>"
1009
1010        allt = [
1011            "<table>",
1012            "<tr>",
1013            "<td>",
1014            image,
1015            "</td>",
1016            "<td style='text-align: center; vertical-align: center;'><br/>",
1017            help_text,
1018            "<table>",
1019            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
1020            "<tr><td><b> center of mass </b></td><td>"
1021            + utils.precision(self.center_of_mass(), 3)
1022            + "</td></tr>",
1023            "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>",
1024            "<tr><td><b> nr. points </b></td><td>" + str(self.npoints) + "</td></tr>",
1025            pdata,
1026            cdata,
1027            "</table>",
1028            "</table>",
1029        ]
1030        return "\n".join(allt)
1031
1032
1033    ##################################################################################
1034    def _update(self, polydata):
1035        # Overwrite the polygonal mesh with a new vtkPolyData
1036        self._data = polydata
1037        self.mapper().SetInputData(polydata)
1038        self.mapper().Modified()
1039        return self
1040
1041    def __add__(self, meshs):
1042        if isinstance(meshs, list):
1043            alist = [self]
1044            for l in meshs:
1045                if isinstance(l, vedo.Assembly):
1046                    alist += l.unpack()
1047                else:
1048                    alist += l
1049            return vedo.assembly.Assembly(alist)
1050
1051        if isinstance(meshs, vedo.Assembly):
1052            return meshs + self  # use Assembly.__add__
1053
1054        return vedo.assembly.Assembly([self, meshs])
1055
1056    def polydata(self, transformed=True):
1057        """
1058        Returns the `vtkPolyData` object associated to a `Mesh`.
1059
1060        .. note::
1061            If `transformed=True` return a copy of polydata that corresponds
1062            to the current mesh position in space.
1063        """
1064        if not self._data:
1065            self._data = self.mapper().GetInput()
1066            return self._data
1067
1068        if transformed:
1069            # if self.GetIsIdentity() or self._data.GetNumberOfPoints()==0: # commmentd out on 15th feb 2020
1070            if self._data.GetNumberOfPoints() == 0:
1071                # no need to do much
1072                return self._data
1073
1074            # otherwise make a copy that corresponds to
1075            # the actual position in space of the mesh
1076            M = self.GetMatrix()
1077            transform = vtk.vtkTransform()
1078            transform.SetMatrix(M)
1079            tp = vtk.vtkTransformPolyDataFilter()
1080            tp.SetTransform(transform)
1081            tp.SetInputData(self._data)
1082            tp.Update()
1083            return tp.GetOutput()
1084
1085        return self._data
1086
1087
1088    def clone(self, deep=True, transformed=False):
1089        """
1090        Clone a `PointCloud` or `Mesh` object to make an exact copy of it.
1091
1092        Arguments:
1093            deep : (bool)
1094                if False only build a shallow copy of the object (faster copy).
1095
1096            transformed : (bool)
1097                if True reset the current transformation of the copy to unit.
1098
1099        Examples:
1100            - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py)
1101
1102               ![](https://vedo.embl.es/images/basic/mirror.png)
1103        """
1104        poly = self.polydata(transformed)
1105        poly_copy = vtk.vtkPolyData()
1106        if deep:
1107            poly_copy.DeepCopy(poly)
1108        else:
1109            poly_copy.ShallowCopy(poly)
1110
1111        if isinstance(self, vedo.Mesh):
1112            cloned = vedo.Mesh(poly_copy)
1113        else:
1114            cloned = Points(poly_copy)
1115
1116        pr = vtk.vtkProperty()
1117        pr.DeepCopy(self.GetProperty())
1118        cloned.SetProperty(pr)
1119        cloned.property = pr
1120
1121        if self.GetBackfaceProperty():
1122            bfpr = vtk.vtkProperty()
1123            bfpr.DeepCopy(self.GetBackfaceProperty())
1124            cloned.SetBackfaceProperty(bfpr)
1125
1126        if not transformed:
1127            if self.transform:
1128                # already has a transform which can be non linear, so use that
1129                cloned.SetUserTransform(self.transform)
1130            else:
1131                # assign the same transformation to the copy
1132                cloned.SetOrigin(self.GetOrigin())
1133                cloned.SetScale(self.GetScale())
1134                cloned.SetOrientation(self.GetOrientation())
1135                cloned.SetPosition(self.GetPosition())
1136
1137        mp = cloned.mapper()
1138        sm = self.mapper()
1139        mp.SetScalarVisibility(sm.GetScalarVisibility())
1140        mp.SetScalarRange(sm.GetScalarRange())
1141        mp.SetColorMode(sm.GetColorMode())
1142        lsr = sm.GetUseLookupTableScalarRange()
1143        mp.SetUseLookupTableScalarRange(lsr)
1144        mp.SetScalarMode(sm.GetScalarMode())
1145        lut = sm.GetLookupTable()
1146        if lut:
1147            mp.SetLookupTable(lut)
1148
1149        if self.GetTexture():
1150            cloned.texture(self.GetTexture())
1151
1152        cloned.SetPickable(self.GetPickable())
1153
1154        cloned.base = np.array(self.base)
1155        cloned.top = np.array(self.top)
1156        cloned.name = str(self.name)
1157        cloned.filename = str(self.filename)
1158        cloned.info = dict(self.info)
1159
1160        # better not to share the same locators with original obj
1161        cloned.point_locator = None
1162        cloned.cell_locator = None
1163
1164        cloned.pipeline = utils.OperationNode("clone", parents=[self], shape="diamond", c="#edede9")
1165        return cloned
1166
1167    def clone2d(
1168        self,
1169        pos=(0, 0),
1170        coordsys=4,
1171        scale=None,
1172        c=None,
1173        alpha=None,
1174        ps=2,
1175        lw=1,
1176        sendback=False,
1177        layer=0,
1178    ):
1179        """
1180        Copy a 3D Mesh into a static 2D image. Returns a `vtkActor2D`.
1181
1182        Arguments:
1183            coordsys : (int)
1184                the coordinate system, options are
1185                - 0 = Displays
1186                - 1 = Normalized Display
1187                - 2 = Viewport (origin is the bottom-left corner of the window)
1188                - 3 = Normalized Viewport
1189                - 4 = View (origin is the center of the window)
1190                - 5 = World (anchor the 2d image to mesh)
1191
1192            ps : (int)
1193                point size in pixel units
1194
1195            lw : (int)
1196                line width in pixel units
1197
1198            sendback : (bool)
1199                put it behind any other 3D object
1200
1201        Examples:
1202            - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py)
1203
1204                ![](https://vedo.embl.es/images/other/clone2d.png)
1205        """
1206        if scale is None:
1207            msiz = self.diagonal_size()
1208            if vedo.plotter_instance and vedo.plotter_instance.window:
1209                sz = vedo.plotter_instance.window.GetSize()
1210                dsiz = utils.mag(sz)
1211                scale = dsiz / msiz / 10
1212            else:
1213                scale = 350 / msiz
1214
1215        cmsh = self.clone()
1216        poly = cmsh.pos(0, 0, 0).scale(scale).polydata()
1217
1218        mapper3d = self.mapper()
1219        cm = mapper3d.GetColorMode()
1220        lut = mapper3d.GetLookupTable()
1221        sv = mapper3d.GetScalarVisibility()
1222        use_lut = mapper3d.GetUseLookupTableScalarRange()
1223        vrange = mapper3d.GetScalarRange()
1224        sm = mapper3d.GetScalarMode()
1225
1226        mapper2d = vtk.vtkPolyDataMapper2D()
1227        mapper2d.ShallowCopy(mapper3d)
1228        mapper2d.SetInputData(poly)
1229        mapper2d.SetColorMode(cm)
1230        mapper2d.SetLookupTable(lut)
1231        mapper2d.SetScalarVisibility(sv)
1232        mapper2d.SetUseLookupTableScalarRange(use_lut)
1233        mapper2d.SetScalarRange(vrange)
1234        mapper2d.SetScalarMode(sm)
1235
1236        act2d = vtk.vtkActor2D()
1237        act2d.SetMapper(mapper2d)
1238        act2d.SetLayerNumber(layer)
1239        csys = act2d.GetPositionCoordinate()
1240        csys.SetCoordinateSystem(coordsys)
1241        act2d.SetPosition(pos)
1242        if c is not None:
1243            c = colors.get_color(c)
1244            act2d.GetProperty().SetColor(c)
1245            mapper2d.SetScalarVisibility(False)
1246        else:
1247            act2d.GetProperty().SetColor(cmsh.color())
1248        if alpha is not None:
1249            act2d.GetProperty().SetOpacity(alpha)
1250        else:
1251            act2d.GetProperty().SetOpacity(cmsh.alpha())
1252        act2d.GetProperty().SetPointSize(ps)
1253        act2d.GetProperty().SetLineWidth(lw)
1254        act2d.GetProperty().SetDisplayLocationToForeground()
1255        if sendback:
1256            act2d.GetProperty().SetDisplayLocationToBackground()
1257
1258        # print(csys.GetCoordinateSystemAsString())
1259        # print(act2d.GetHeight(), act2d.GetWidth(), act2d.GetLayerNumber())
1260        return act2d
1261
1262    def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2):
1263        """
1264        Add a trailing line to mesh.
1265        This new mesh is accessible through `mesh.trail`.
1266
1267        Arguments:
1268            offset : (float)
1269                set an offset vector from the object center.
1270            n : (int)
1271                number of segments
1272            lw : (float)
1273                line width of the trail
1274
1275        Examples:
1276            - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py)
1277
1278                ![](https://vedo.embl.es/images/simulations/trail.gif)
1279
1280            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1281            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1282        """
1283        if self.trail is None:
1284            pos = self.GetPosition()
1285            self.trail_offset = np.asarray(offset)
1286            self.trail_points = [pos] * n
1287
1288            if c is None:
1289                col = self.GetProperty().GetColor()
1290            else:
1291                col = colors.get_color(c)
1292
1293            tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw)
1294            self.trail = tline  # holds the Line
1295        return self
1296
1297    def update_trail(self):
1298        """
1299        Update the trailing line of a moving object.
1300        """
1301        if isinstance(self, vedo.shapes.Arrow):
1302            currentpos = self.tipPoint()  # the tip of Arrow
1303        else:
1304            currentpos = np.array(self.GetPosition())
1305
1306        self.trail_points.append(currentpos)  # cycle
1307        self.trail_points.pop(0)
1308
1309        data = np.array(self.trail_points) - currentpos + self.trail_offset
1310        tpoly = self.trail.polydata(False)
1311        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1312        self.trail.SetPosition(currentpos)
1313        return self
1314
1315
1316    def _compute_shadow(self, plane, point, direction):
1317        shad = self.clone()
1318        shad._data.GetPointData().SetTCoords(None) # remove any texture coords
1319        shad.name = "Shadow"
1320
1321        pts = shad.points()
1322        if plane == 'x':
1323            # shad = shad.project_on_plane('x')
1324            # instead do it manually so in case of alpha<1 
1325            # we dont see glitches due to coplanar points
1326            # we leave a small tolerance of 0.1% in thickness
1327            x0, x1 = self.xbounds()
1328            pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[0]
1329            shad.points(pts)
1330            shad.x(point)
1331        elif plane == 'y':
1332            x0, x1 = self.ybounds()
1333            pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[1]
1334            shad.points(pts)
1335            shad.y(point)
1336        elif plane == "z":
1337            x0, x1 = self.zbounds()
1338            pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[2]
1339            shad.points(pts)
1340            shad.z(point)
1341        else:
1342            shad = shad.project_on_plane(plane, point, direction)
1343        return shad
1344
1345    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0):
1346        """
1347        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1348        The output is a new `Mesh` representing the shadow.
1349        This new mesh is accessible through `mesh.shadow`.
1350        By default the shadow mesh is placed on the bottom wall of the bounding box.
1351
1352        See also `pointcloud.project_on_plane()`.
1353
1354        Arguments:
1355            plane : (str, Plane)
1356                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1357                represents x-plane, y-plane and z-plane, respectively.
1358                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1359            point : (float, array)
1360                if plane is `str`, point should be a float represents the intercept.
1361                Otherwise, point is the camera point of perspective projection
1362            direction : (list)
1363                direction of oblique projection
1364            culling : (int)
1365                choose between front [1] or backface [-1] culling or None.
1366
1367        Examples:
1368            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1369            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1370            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1371
1372            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1373        """
1374        shad = self._compute_shadow(plane, point, direction)
1375        shad.c(c).alpha(alpha)
1376
1377        try:
1378            # Points dont have these methods
1379            shad.flat()
1380            if culling in (1, True):
1381                shad.frontface_culling()
1382            elif culling == -1:
1383                shad.backface_culling()
1384        except AttributeError:
1385            pass
1386
1387        shad.GetProperty().LightingOff()
1388        shad.SetPickable(False)
1389        shad.SetUseBounds(True)
1390
1391        if shad not in self.shadows:
1392            self.shadows.append(shad)
1393            shad.info = dict(plane=plane, point=point, direction=direction)
1394        return self
1395
1396    def update_shadows(self):
1397        """
1398        Update the shadows of a moving object.
1399        """
1400        for sha in self.shadows:
1401            plane = sha.info['plane']
1402            point = sha.info['point']
1403            direction = sha.info['direction']
1404            new_sha = self._compute_shadow(plane, point, direction)
1405            sha._update(new_sha._data)
1406        return self
1407
1408
1409    def delete_cells_by_point_index(self, indices):
1410        """
1411        Delete a list of vertices identified by any of their vertex index.
1412
1413        See also `delete_cells()`.
1414
1415        Examples:
1416            - [elete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/elete_mesh_pts.py)
1417
1418                ![](https://vedo.embl.es/images/basic/deleteMeshPoints.png)
1419        """
1420        cell_ids = vtk.vtkIdList()
1421        data = self.inputdata()
1422        data.BuildLinks()
1423        n = 0
1424        for i in np.unique(indices):
1425            data.GetPointCells(i, cell_ids)
1426            for j in range(cell_ids.GetNumberOfIds()):
1427                data.DeleteCell(cell_ids.GetId(j))  # flag cell
1428                n += 1
1429
1430        data.RemoveDeletedCells()
1431        self.mapper().Modified()
1432        self.pipeline = utils.OperationNode(f"delete {n} cells\nby point index", parents=[self])
1433        return self
1434
1435    def compute_normals_with_pca(self, n=20, orientation_point=None, invert=False):
1436        """
1437        Generate point normals using PCA (principal component analysis).
1438        Basically this estimates a local tangent plane around each sample point p
1439        by considering a small neighborhood of points around p, and fitting a plane
1440        to the neighborhood (via PCA).
1441
1442        Arguments:
1443            n : (int)
1444                neighborhood size to calculate the normal
1445            orientation_point : (list)
1446                adjust the +/- sign of the normals so that
1447                the normals all point towards a specified point. If None, perform a traversal
1448                of the point cloud and flip neighboring normals so that they are mutually consistent.
1449            invert : (bool)
1450                flip all normals
1451        """
1452        poly = self.polydata()
1453        pcan = vtk.vtkPCANormalEstimation()
1454        pcan.SetInputData(poly)
1455        pcan.SetSampleSize(n)
1456
1457        if orientation_point is not None:
1458            pcan.SetNormalOrientationToPoint()
1459            pcan.SetOrientationPoint(orientation_point)
1460        else:
1461            pcan.SetNormalOrientationToGraphTraversal()
1462
1463        if invert:
1464            pcan.FlipNormalsOn()
1465        pcan.Update()
1466
1467        varr = pcan.GetOutput().GetPointData().GetNormals()
1468        varr.SetName("Normals")
1469        self.inputdata().GetPointData().SetNormals(varr)
1470        self.inputdata().GetPointData().Modified()
1471        return self
1472
1473    def compute_acoplanarity(self, n=25, radius=None, on="points"):
1474        """
1475        Compute acoplanarity which is a measure of how much a local region of the mesh
1476        differs from a plane.
1477        The information is stored in a `pointdata` or `celldata` array with name 'Acoplanarity'.
1478        Either `n` (number of neighbour points) or `radius` (radius of local search) can be specified.
1479        If a radius value is given and not enough points fall inside it, then a -1 is stored.
1480
1481        Example:
1482            ```python
1483            from vedo import *
1484            msh = ParametricShape('RandomHills')
1485            msh.compute_acoplanarity(radius=0.1, on='cells')
1486            msh.cmap("coolwarm", on='cells').add_scalarbar()
1487            msh.show(axes=1).close()
1488            ```
1489            ![](https://vedo.embl.es/images/feats/acoplanarity.jpg)
1490        """
1491        acoplanarities = []
1492        if "point" in on:
1493            pts = self.points()
1494        elif "cell" in on:
1495            pts = self.cell_centers()
1496        else:
1497            raise ValueError(f"In compute_acoplanarity() set on to either 'cells' or 'points', not {on}")
1498
1499        for p in utils.progressbar(pts, delay=5, width=15, title=f"{on} acoplanarity"):
1500            if n:
1501                data = self.closest_point(p, n=n)
1502                npts = n
1503            elif radius:
1504                data = self.closest_point(p, radius=radius)
1505                npts = len(data)
1506
1507            try:
1508                center = data.mean(axis=0)
1509                res = np.linalg.svd(data - center)
1510                acoplanarities.append(res[1][2] / npts)
1511            except:
1512                acoplanarities.append(-1.0)
1513
1514        if "point" in on:
1515            self.pointdata["Acoplanarity"] = np.array(acoplanarities, dtype=float)
1516        else:
1517            self.celldata["Acoplanarity"] = np.array(acoplanarities, dtype=float)
1518        return self
1519
1520    def distance_to(self, pcloud, signed=False, invert=False, name="Distance"):
1521        """
1522        Computes the distance from one point cloud or mesh to another point cloud or mesh.
1523        This new `pointdata` array is saved with default name "Distance".
1524
1525        Keywords `signed` and `invert` are used to compute signed distance,
1526        but the mesh in that case must have polygonal faces (not a simple point cloud),
1527        and normals must also be computed.
1528
1529        Examples:
1530            - [distance2mesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/distance2mesh.py)
1531
1532                ![](https://vedo.embl.es/images/basic/distance2mesh.png)
1533        """
1534        if pcloud.inputdata().GetNumberOfPolys():
1535
1536            poly1 = self.polydata()
1537            poly2 = pcloud.polydata()
1538            df = vtk.vtkDistancePolyDataFilter()
1539            df.ComputeSecondDistanceOff()
1540            df.SetInputData(0, poly1)
1541            df.SetInputData(1, poly2)
1542            df.SetSignedDistance(signed)
1543            df.SetNegateDistance(invert)
1544            df.Update()
1545            scals = df.GetOutput().GetPointData().GetScalars()
1546            dists = utils.vtk2numpy(scals)
1547
1548        else:  # has no polygons and vtkDistancePolyDataFilter wants them (dont know why)
1549
1550            if signed:
1551                vedo.logger.warning("distanceTo() called with signed=True but input object has no polygons")
1552
1553            if not pcloud.point_locator:
1554                pcloud.point_locator = vtk.vtkPointLocator()
1555                pcloud.point_locator.SetDataSet(pcloud.polydata())
1556                pcloud.point_locator.BuildLocator()
1557
1558            ids = []
1559            ps1 = self.points()
1560            ps2 = pcloud.points()
1561            for p in ps1:
1562                pid = pcloud.point_locator.FindClosestPoint(p)
1563                ids.append(pid)
1564
1565            deltas = ps2[ids] - ps1
1566            dists = np.linalg.norm(deltas, axis=1).astype(np.float32)
1567            scals = utils.numpy2vtk(dists)
1568
1569        scals.SetName(name)
1570        self.inputdata().GetPointData().AddArray(scals)  # must be self.inputdata() !
1571        self.inputdata().GetPointData().SetActiveScalars(scals.GetName())
1572        rng = scals.GetRange()
1573        self.mapper().SetScalarRange(rng[0], rng[1])
1574        self.mapper().ScalarVisibilityOn()
1575
1576        self.pipeline = utils.OperationNode(
1577            "distance_to",
1578            parents=[self, pcloud],
1579            shape="cylinder",
1580            comment=f"#pts {self._data.GetNumberOfPoints()}",
1581        )
1582        return dists
1583
1584    def alpha(self, opacity=None):
1585        """Set/get mesh's transparency. Same as `mesh.opacity()`."""
1586        if opacity is None:
1587            return self.GetProperty().GetOpacity()
1588
1589        self.GetProperty().SetOpacity(opacity)
1590        bfp = self.GetBackfaceProperty()
1591        if bfp:
1592            if opacity < 1:
1593                self._bfprop = bfp
1594                self.SetBackfaceProperty(None)
1595            else:
1596                self.SetBackfaceProperty(self._bfprop)
1597        return self
1598
1599    def opacity(self, alpha=None):
1600        """Set/get mesh's transparency. Same as `mesh.alpha()`."""
1601        return self.alpha(alpha)
1602
1603    def force_opaque(self, value=True):
1604        """ Force the Mesh, Line or point cloud to be treated as opaque"""
1605        ## force the opaque pass, fixes picking in vtk9
1606        # but causes other bad troubles with lines..
1607        self.SetForceOpaque(value)
1608        return self
1609
1610    def force_translucent(self, value=True):
1611        """ Force the Mesh, Line or point cloud to be treated as translucent"""
1612        self.SetForceTranslucent(value)
1613        return self
1614
1615    def point_size(self, value=None):
1616        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
1617        if value is None:
1618            return self.GetProperty().GetPointSize()
1619            #self.GetProperty().SetRepresentationToSurface()
1620        else:
1621            self.GetProperty().SetRepresentationToPoints()
1622            self.GetProperty().SetPointSize(value)
1623        return self
1624
1625    def ps(self, pointsize=None):
1626        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
1627        return self.point_size(pointsize)
1628
1629    def render_points_as_spheres(self, value=True):
1630        """Make points look spheric or make them look as squares."""
1631        self.GetProperty().SetRenderPointsAsSpheres(value)
1632        return self
1633
1634    def color(self, c=False, alpha=None):
1635        """
1636        Set/get mesh's color.
1637        If None is passed as input, will use colors from active scalars.
1638        Same as `mesh.c()`.
1639        """
1640        # overrides base.color()
1641        if c is False:
1642            return np.array(self.GetProperty().GetColor())
1643        if c is None:
1644            self.mapper().ScalarVisibilityOn()
1645            return self
1646        self.mapper().ScalarVisibilityOff()
1647        cc = colors.get_color(c)
1648        self.GetProperty().SetColor(cc)
1649        if self.trail:
1650            self.trail.GetProperty().SetColor(cc)
1651        if alpha is not None:
1652            self.alpha(alpha)
1653        return self
1654
1655    def clean(self):
1656        """
1657        Clean pointcloud or mesh by removing coincident points.
1658        """
1659        cpd = vtk.vtkCleanPolyData()
1660        cpd.PointMergingOn()
1661        cpd.ConvertLinesToPointsOn()
1662        cpd.ConvertPolysToLinesOn()
1663        cpd.ConvertStripsToPolysOn()
1664        cpd.SetInputData(self.inputdata())
1665        cpd.Update()
1666        out = self._update(cpd.GetOutput())
1667
1668        out.pipeline = utils.OperationNode(
1669            "clean", parents=[self],
1670            comment=f"#pts {out.inputdata().GetNumberOfPoints()}"
1671        )
1672        return out
1673
1674    def subsample(self, fraction, absolute=False):
1675        """
1676        Subsample a point cloud by requiring that the points
1677        or vertices are far apart at least by the specified fraction of the object size.
1678        If a Mesh is passed the polygonal faces are not removed
1679        but holes can appear as vertices are removed.
1680
1681        Examples:
1682            - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py)
1683
1684                ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png)
1685
1686            - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py)
1687
1688                ![](https://vedo.embl.es/images/advanced/recosurface.png)
1689        """
1690        if not absolute:
1691            if fraction > 1:
1692                vedo.logger.warning(
1693                    f"subsample(fraction=...), fraction must be < 1, but is {fraction}"
1694                )
1695            if fraction <= 0:
1696                return self
1697
1698        cpd = vtk.vtkCleanPolyData()
1699        cpd.PointMergingOn()
1700        cpd.ConvertLinesToPointsOn()
1701        cpd.ConvertPolysToLinesOn()
1702        cpd.ConvertStripsToPolysOn()
1703        cpd.SetInputData(self.inputdata())
1704        if absolute:
1705            cpd.SetTolerance(fraction / self.diagonal_size())
1706            # cpd.SetToleranceIsAbsolute(absolute)
1707        else:
1708            cpd.SetTolerance(fraction)
1709        cpd.Update()
1710
1711        ps = 2
1712        if self.GetProperty().GetRepresentation() == 0:
1713            ps = self.GetProperty().GetPointSize()
1714
1715        out = self._update(cpd.GetOutput()).ps(ps)
1716
1717        out.pipeline = utils.OperationNode(
1718            "subsample", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}"
1719        )
1720        return out
1721
1722    def threshold(self, scalars, above=None, below=None, on="points"):
1723        """
1724        Extracts cells where scalar value satisfies threshold criterion.
1725
1726        Arguments:
1727            scalars : (str)
1728                name of the scalars array.
1729            above : (float)
1730                minimum value of the scalar
1731            below : (float)
1732                maximum value of the scalar
1733            on : (str)
1734                if 'cells' assume array of scalars refers to cell data.
1735
1736        Examples:
1737            - [mesh_threshold.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_threshold.py)
1738        """
1739        thres = vtk.vtkThreshold()
1740        thres.SetInputData(self.inputdata())
1741
1742        if on.startswith("c"):
1743            asso = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS
1744        else:
1745            asso = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS
1746
1747        thres.SetInputArrayToProcess(0, 0, 0, asso, scalars)
1748
1749        if above is None and below is not None:
1750            thres.ThresholdByLower(below)
1751        elif below is None and above is not None:
1752            thres.ThresholdByUpper(above)
1753        else:
1754            thres.ThresholdBetween(above, below)
1755        thres.Update()
1756
1757        gf = vtk.vtkGeometryFilter()
1758        gf.SetInputData(thres.GetOutput())
1759        gf.Update()
1760        return self._update(gf.GetOutput())
1761
1762    def quantize(self, value):
1763        """
1764        The user should input a value and all {x,y,z} coordinates
1765        will be quantized to that absolute grain size.
1766        """
1767        poly = self.inputdata()
1768        qp = vtk.vtkQuantizePolyDataPoints()
1769        qp.SetInputData(poly)
1770        qp.SetQFactor(value)
1771        qp.Update()
1772        out = self._update(qp.GetOutput()).flat()
1773        out.pipeline = utils.OperationNode("quantize", parents=[self])
1774        return out
1775
1776    def average_size(self):
1777        """
1778        Calculate the average size of a mesh.
1779        This is the mean of the vertex distances from the center of mass.
1780        """
1781        coords = self.points()
1782        cm = np.mean(coords, axis=0)
1783        if coords.shape[0] == 0:
1784            return 0.0
1785        cc = coords - cm
1786        return np.mean(np.linalg.norm(cc, axis=1))
1787
1788    def center_of_mass(self):
1789        """Get the center of mass of mesh."""
1790        cmf = vtk.vtkCenterOfMass()
1791        cmf.SetInputData(self.polydata())
1792        cmf.Update()
1793        c = cmf.GetCenter()
1794        return np.array(c)
1795
1796    def normal_at(self, i):
1797        """Return the normal vector at vertex point `i`."""
1798        normals = self.polydata().GetPointData().GetNormals()
1799        return np.array(normals.GetTuple(i))
1800
1801    def normals(self, cells=False, recompute=True):
1802        """Retrieve vertex normals as a numpy array.
1803
1804        Arguments:
1805            cells : (bool)
1806                if `True` return cell normals.
1807
1808            recompute : (bool)
1809                if `True` normals are recalculated if not already present.
1810                Note that this might modify the number of mesh points.
1811        """
1812        if cells:
1813            vtknormals = self.polydata().GetCellData().GetNormals()
1814        else:
1815            vtknormals = self.polydata().GetPointData().GetNormals()
1816        if not vtknormals and recompute:
1817            try:
1818                self.compute_normals(cells=cells)
1819                if cells:
1820                    vtknormals = self.polydata().GetCellData().GetNormals()
1821                else:
1822                    vtknormals = self.polydata().GetPointData().GetNormals()
1823            except AttributeError:
1824                # can be that 'Points' object has no attribute 'compute_normals'
1825                pass
1826
1827        if not vtknormals:
1828            return np.array([])
1829        return utils.vtk2numpy(vtknormals)
1830
1831    def labels(
1832        self,
1833        content=None,
1834        on="points",
1835        scale=None,
1836        xrot=0.0,
1837        yrot=0.0,
1838        zrot=0.0,
1839        ratio=1,
1840        precision=None,
1841        italic=False,
1842        font="",
1843        justify="bottom-left",
1844        c="black",
1845        alpha=1.0,
1846        cells=None,
1847    ):
1848        """
1849        Generate value or ID labels for mesh cells or points.
1850        For large nr. of labels use `font="VTK"` which is much faster.
1851
1852        See also:
1853            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1854
1855        Arguments:
1856            content : (list,int,str)
1857                either 'id', 'cellid', array name or array number.
1858                A array can also be passed (must match the nr. of points or cells).
1859            on : (str)
1860                generate labels for "cells" instead of "points"
1861            scale : (float)
1862                absolute size of labels, if left as None it is automatic
1863            zrot : (float)
1864                local rotation angle of label in degrees
1865            ratio : (int)
1866                skipping ratio, to reduce nr of labels for large meshes
1867            precision : (int)
1868                numeric precision of labels
1869
1870        ```python
1871        from vedo import *
1872        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1873        point_ids = s.labels('id', on="points").c('green')
1874        cell_ids  = s.labels('id', on="cells" ).c('black')
1875        show(s, point_ids, cell_ids)
1876        ```
1877        ![](https://vedo.embl.es/images/feats/labels.png)
1878
1879        Examples:
1880            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1881
1882                ![](https://vedo.embl.es/images/basic/boundaries.png)
1883        """
1884        if cells is not None:  # deprecation message
1885            vedo.logger.warning("In labels(cells=...) please use labels(on='cells') instead")
1886
1887        if "cell" in on or "face" in on:
1888            cells = True
1889
1890        if isinstance(content, str):
1891            if content in ("cellid", "cellsid"):
1892                cells = True
1893                content = "id"
1894
1895        if cells:
1896            elems = self.cell_centers()
1897            norms = self.normals(cells=True, recompute=False)
1898            ns = np.sqrt(self.ncells)
1899        else:
1900            elems = self.points()
1901            norms = self.normals(cells=False, recompute=False)
1902            ns = np.sqrt(self.npoints)
1903
1904        hasnorms = False
1905        if len(norms) > 0:
1906            hasnorms = True
1907
1908        if scale is None:
1909            if not ns:
1910                ns = 100
1911            scale = self.diagonal_size() / ns / 10
1912
1913        arr = None
1914        mode = 0
1915        if content is None:
1916            mode = 0
1917            if cells:
1918                if self.inputdata().GetCellData().GetScalars():
1919                    name = self.inputdata().GetCellData().GetScalars().GetName()
1920                    arr = self.celldata[name]
1921            else:
1922                if self.inputdata().GetPointData().GetScalars():
1923                    name = self.inputdata().GetPointData().GetScalars().GetName()
1924                    arr = self.pointdata[name]
1925        elif isinstance(content, (str, int)):
1926            if content == "id":
1927                mode = 1
1928            elif cells:
1929                mode = 0
1930                arr = self.celldata[content]
1931            else:
1932                mode = 0
1933                arr = self.pointdata[content]
1934        elif utils.is_sequence(content):
1935            mode = 0
1936            arr = content
1937            # print('WEIRD labels() test', content)
1938            # exit()
1939
1940        if arr is None and mode == 0:
1941            vedo.logger.error("in labels(), array not found for points or cells")
1942            return None
1943
1944        tapp = vtk.vtkAppendPolyData()
1945        ninputs = 0
1946
1947        for i, e in enumerate(elems):
1948            if i % ratio:
1949                continue
1950
1951            if mode == 1:
1952                txt_lab = str(i)
1953            else:
1954                if precision:
1955                    txt_lab = utils.precision(arr[i], precision)
1956                else:
1957                    txt_lab = str(arr[i])
1958
1959            if not txt_lab:
1960                continue
1961
1962            if font == "VTK":
1963                tx = vtk.vtkVectorText()
1964                tx.SetText(txt_lab)
1965                tx.Update()
1966                tx_poly = tx.GetOutput()
1967            else:
1968                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify)
1969                tx_poly = tx_poly.inputdata()
1970
1971            if tx_poly.GetNumberOfPoints() == 0:
1972                continue  #######################
1973            ninputs += 1
1974
1975            T = vtk.vtkTransform()
1976            T.PostMultiply()
1977            if italic:
1978                T.Concatenate([1,0.2,0,0,
1979                               0,1,0,0,
1980                               0,0,1,0,
1981                               0,0,0,1])
1982            if hasnorms:
1983                ni = norms[i]
1984                if cells:  # center-justify
1985                    bb = tx_poly.GetBounds()
1986                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1987                    T.Translate(-dx, -dy, 0)
1988                if xrot:
1989                    T.RotateX(xrot)
1990                if yrot:
1991                    T.RotateY(yrot)
1992                if zrot:
1993                    T.RotateZ(zrot)
1994                crossvec = np.cross([0, 0, 1], ni)
1995                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1996                T.RotateWXYZ(angle, crossvec)
1997                if cells:  # small offset along normal only for cells
1998                    T.Translate(ni * scale / 2)
1999            else:
2000                if xrot:
2001                    T.RotateX(xrot)
2002                if yrot:
2003                    T.RotateY(yrot)
2004                if zrot:
2005                    T.RotateZ(zrot)
2006            T.Scale(scale, scale, scale)
2007            T.Translate(e)
2008            tf = vtk.vtkTransformPolyDataFilter()
2009            tf.SetInputData(tx_poly)
2010            tf.SetTransform(T)
2011            tf.Update()
2012            tapp.AddInputData(tf.GetOutput())
2013
2014        if ninputs:
2015            tapp.Update()
2016            lpoly = tapp.GetOutput()
2017        else:  # return an empty obj
2018            lpoly = vtk.vtkPolyData()
2019
2020        ids = vedo.mesh.Mesh(lpoly, c=c, alpha=alpha)
2021        ids.GetProperty().LightingOff()
2022        ids.PickableOff()
2023        ids.SetUseBounds(False)
2024        return ids
2025
2026    def labels2d(
2027        self,
2028        content="id",
2029        on="points",
2030        scale=1.0,
2031        precision=4,
2032        font="Calco",
2033        justify="bottom-left",
2034        angle=0.0,
2035        frame=False,
2036        c="black",
2037        bc=None,
2038        alpha=1.0,
2039    ):
2040        """
2041        Generate value or ID bi-dimensional labels for mesh cells or points.
2042
2043        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
2044
2045        Arguments:
2046            content : (str)
2047                either 'id', 'cellid', or array name
2048            on : (str)
2049                generate labels for "cells" instead of "points" (the default)
2050            scale : (float)
2051                size scaling of labels
2052            precision : (int)
2053                precision of numeric labels
2054            angle : (float)
2055                local rotation angle of label in degrees
2056            frame : (bool)
2057                draw a frame around the label
2058            bc : (str)
2059                background color of the label
2060
2061        ```python
2062        from vedo import Sphere, show
2063        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
2064        sph.celldata["zvals"] = sph.cell_centers()[:,2]
2065        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
2066        show(sph, l2d, axes=1).close()
2067        ```
2068        ![](https://vedo.embl.es/images/feats/labels2d.png)
2069        """
2070        cells = False
2071        if isinstance(content, str):
2072            if content in ("cellid", "cellsid"):
2073                cells = True
2074                content = "id"
2075
2076        if "cell" in on:
2077            cells = True
2078        elif "point" in on:
2079            cells = False
2080
2081        if cells:
2082            if content != "id" and content not in self.celldata.keys():
2083                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
2084                return None
2085            cellcloud = Points(self.cell_centers())
2086            arr = self.inputdata().GetCellData().GetScalars()
2087            poly = cellcloud.polydata(False)
2088            poly.GetPointData().SetScalars(arr)
2089        else:
2090            poly = self.polydata()
2091            if content != "id" and content not in self.pointdata.keys():
2092                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
2093                return None
2094            self.pointdata.select(content)
2095
2096        mp = vtk.vtkLabeledDataMapper()
2097
2098        if content == "id":
2099            mp.SetLabelModeToLabelIds()
2100        else:
2101            mp.SetLabelModeToLabelScalars()
2102            if precision is not None:
2103                mp.SetLabelFormat(f"%-#.{precision}g")
2104
2105        pr = mp.GetLabelTextProperty()
2106        c = colors.get_color(c)
2107        pr.SetColor(c)
2108        pr.SetOpacity(alpha)
2109        pr.SetFrame(frame)
2110        pr.SetFrameColor(c)
2111        pr.SetItalic(False)
2112        pr.BoldOff()
2113        pr.ShadowOff()
2114        pr.UseTightBoundingBoxOn()
2115        pr.SetOrientation(angle)
2116        pr.SetFontFamily(vtk.VTK_FONT_FILE)
2117        fl = utils.get_font_path(font)
2118        pr.SetFontFile(fl)
2119        pr.SetFontSize(int(20 * scale))
2120
2121        if "cent" in justify or "mid" in justify:
2122            pr.SetJustificationToCentered()
2123        elif "rig" in justify:
2124            pr.SetJustificationToRight()
2125        elif "left" in justify:
2126            pr.SetJustificationToLeft()
2127        # ------
2128        if "top" in justify:
2129            pr.SetVerticalJustificationToTop()
2130        else:
2131            pr.SetVerticalJustificationToBottom()
2132
2133        if bc is not None:
2134            bc = colors.get_color(bc)
2135            pr.SetBackgroundColor(bc)
2136            pr.SetBackgroundOpacity(alpha)
2137
2138        mp.SetInputData(poly)
2139        a2d = vtk.vtkActor2D()
2140        a2d.PickableOff()
2141        a2d.SetMapper(mp)
2142        return a2d
2143
2144    def legend(self, txt):
2145        """Book a legend text."""
2146        self.info["legend"] = txt
2147        return self
2148
2149    def flagpole(
2150        self,
2151        txt=None,
2152        point=None,
2153        offset=None,
2154        s=None,
2155        font="",
2156        rounded=True,
2157        c=None,
2158        alpha=1.0,
2159        lw=2,
2160        italic=0.0,
2161        padding=0.1,
2162    ):
2163        """
2164        Generate a flag pole style element to describe an object.
2165        Returns a `Mesh` object.
2166
2167        Use flagpole.follow_camera() to make it face the camera in the scene.
2168
2169        See also `flagpost()`.
2170
2171        Arguments:
2172            txt : (str)
2173                Text to display. The default is the filename or the object name.
2174            point : (list)
2175                position of the flagpole pointer. 
2176            offset : (list)
2177                text offset wrt the application point. 
2178            s : (float)
2179                size of the flagpole.
2180            font : (str)
2181                font face. Check [available fonts here](https://vedo.embl.es/fonts).
2182            rounded : (bool)
2183                draw a rounded or squared box around the text.
2184            c : (list)
2185                text and box color.
2186            alpha : (float)
2187                opacity of text and box.
2188            lw : (float)
2189                line with of box frame.
2190            italic : (float)
2191                italicness of text.
2192
2193        Examples:
2194            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2195
2196                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2197
2198            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2199            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2200            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2201        """
2202        acts = []
2203
2204        if txt is None:
2205            if self.filename:
2206                txt = self.filename.split("/")[-1]
2207            elif self.name:
2208                txt = self.name
2209            else:
2210                return None
2211
2212        x0, x1, y0, y1, z0, z1 = self.bounds()
2213        d = self.diagonal_size()
2214        if point is None:
2215            if d:
2216                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2217            else:  # it's a Point
2218                point = self.GetPosition()
2219
2220        pt = utils.make3d(point)
2221
2222        if offset is None:
2223            offset = [(x1 - x0) / 2, (y1 - y0) / 6, 0]
2224        offset = utils.make3d(offset)
2225
2226        if s is None:
2227            s = d / 20
2228
2229        sph = None
2230        if d and (z1 - z0) / d > 0.1:
2231            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2232
2233        if c is None:
2234            c = np.array(self.color()) / 1.4
2235
2236        lb = vedo.shapes.Text3D(
2237            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center-left"
2238        )
2239        acts.append(lb)
2240
2241        if d and not sph:
2242            sph = vedo.shapes.Circle(pt, r=s / 3, res=15)
2243        acts.append(sph)
2244
2245        x0, x1, y0, y1, z0, z1 = lb.GetBounds()
2246        if rounded:
2247            box = vedo.shapes.KSpline(
2248                [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)], closed=True
2249            )
2250        else:
2251            box = vedo.shapes.Line(
2252                [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z0)]
2253            )
2254
2255        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2256
2257        box.SetOrigin(cnt)
2258        box.scale([1 + padding, 1 + 2 * padding, 1])
2259        acts.append(box)
2260
2261        # pts = box.points()
2262        # bfaces = []
2263        # for i, pt in enumerate(pts):
2264        #     if i:
2265        #         face = [i-1, i, 0]
2266        #         bfaces.append(face)
2267        # bpts = [cnt] + pts.tolist()
2268        # box2 = vedo.Mesh([bpts, bfaces]).z(-cnt[0]/10)#.c('w').alpha(0.1)
2269        # #should be made assembly otherwise later merge() nullifies it
2270        # box2.SetOrigin(cnt)
2271        # acts.append(box2)
2272
2273        x0, x1, y0, y1, z0, z1 = box.bounds()
2274        if x0 < pt[0] < x1:
2275            c0 = box.closest_point(pt)
2276            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2277        elif (pt[0] - x0) < (x1 - pt[0]):
2278            c0 = [x0, (y0 + y1) / 2, pt[2]]
2279            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2280        else:
2281            c0 = [x1, (y0 + y1) / 2, pt[2]]
2282            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2283
2284        con = vedo.shapes.Line([c0, c1, pt])
2285        acts.append(con)
2286
2287        macts = vedo.merge(acts).c(c).alpha(alpha)
2288        macts.SetOrigin(pt)
2289        macts.bc("tomato").pickable(False)
2290        macts.GetProperty().LightingOff()
2291        macts.GetProperty().SetLineWidth(lw)
2292        macts.UseBoundsOff()
2293        macts.name = "FlagPole"
2294        return macts
2295
2296    def flagpost(
2297        self,
2298        txt=None,
2299        point=None,
2300        offset=None,
2301        s=1.0,
2302        c="k9",
2303        bc="k1",
2304        alpha=1,
2305        lw=0,
2306        font="Calco",
2307        justify="center-left",
2308        vspacing=1.0,
2309    ):
2310        """
2311        Generate a flag post style element to describe an object.
2312
2313        Arguments:
2314            txt : (str)
2315                Text to display. The default is the filename or the object name.
2316            point : (list)
2317                position of the flag anchor point. The default is None.
2318            offset : (list)
2319                a 3D displacement or offset. The default is None.
2320            s : (float)
2321                size of the text to be shown
2322            c : (list)
2323                color of text and line
2324            bc : (list)
2325                color of the flag background
2326            alpha : (float)
2327                opacity of text and box.
2328            lw : (int)
2329                line with of box frame. The default is 0.
2330            font : (str)
2331                font name. Use a monospace font for better rendering. The default is "Calco".
2332                Type `vedo -r fonts` for a font demo.
2333                Check [available fonts here](https://vedo.embl.es/fonts).
2334            justify : (str)
2335                internal text justification. The default is "center-left".
2336            vspacing : (float)
2337                vertical spacing between lines.
2338
2339        Examples:
2340            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
2341
2342            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2343        """
2344        if txt is None:
2345            if self.filename:
2346                txt = self.filename.split("/")[-1]
2347            elif self.name:
2348                txt = self.name
2349            else:
2350                return None
2351
2352        x0, x1, y0, y1, z0, z1 = self.bounds()
2353        d = self.diagonal_size()
2354        if point is None:
2355            if d:
2356                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2357            else:  # it's a Point
2358                point = self.GetPosition()
2359
2360        point = utils.make3d(point)
2361
2362        if offset is None:
2363            offset = [0, 0, (z1 - z0) / 2]
2364        offset = utils.make3d(offset)
2365
2366        fpost = vedo.addons.Flagpost(
2367            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2368        )
2369        self._caption = fpost
2370        return fpost
2371
2372    def caption(
2373        self,
2374        txt=None,
2375        point=None,
2376        size=(0.30, 0.15),
2377        padding=5,
2378        font="Calco",
2379        justify="center-right",
2380        vspacing=1.0,
2381        c=None,
2382        alpha=1.0,
2383        lw=1,
2384        ontop=True,
2385    ):
2386        """
2387        Add a 2D caption to an object which follows the camera movements.
2388        Latex is not supported. Returns the same input object for concatenation.
2389
2390        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2391        with similar functionality.
2392
2393        Arguments:
2394            txt : (str)
2395                text to be rendered. The default is the file name.
2396            point : (list)
2397                anchoring point. The default is None.
2398            size : (list)
2399                (width, height) of the caption box. The default is (0.30, 0.15).
2400            padding : (float)
2401                padding space of the caption box in pixels. The default is 5.
2402            font : (str)
2403                font name. Use a monospace font for better rendering. The default is "VictorMono".
2404                Type `vedo -r fonts` for a font demo.
2405                Check [available fonts here](https://vedo.embl.es/fonts).
2406            justify : (str)
2407                internal text justification. The default is "center-right".
2408            vspacing : (float)
2409                vertical spacing between lines. The default is 1.
2410            c : (str)
2411                text and box color. The default is 'lb'.
2412            alpha : (float)
2413                text and box transparency. The default is 1.
2414            lw : (int)
2415                line width in pixels. The default is 1.
2416            ontop : (bool)
2417                keep the 2d caption always on top. The default is True.
2418
2419        Examples:
2420            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2421
2422                ![](https://vedo.embl.es/images/pyplot/caption.png)
2423
2424            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2425            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2426        """
2427        if txt is None:
2428            if self.filename:
2429                txt = self.filename.split("/")[-1]
2430            elif self.name:
2431                txt = self.name
2432
2433        if not txt:  # disable it
2434            self._caption = None
2435            return self
2436
2437        for r in vedo.shapes._reps:
2438            txt = txt.replace(r[0], r[1])
2439
2440        if c is None:
2441            c = np.array(self.GetProperty().GetColor()) / 2
2442        else:
2443            c = colors.get_color(c)
2444
2445        if point is None:
2446            x0, x1, y0, y1, _, z1 = self.GetBounds()
2447            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2448            point = self.closest_point(pt)
2449
2450        capt = vtk.vtkCaptionActor2D()
2451        capt.SetAttachmentPoint(point)
2452        capt.SetBorder(True)
2453        capt.SetLeader(True)
2454        sph = vtk.vtkSphereSource()
2455        sph.Update()
2456        capt.SetLeaderGlyphData(sph.GetOutput())
2457        capt.SetMaximumLeaderGlyphSize(5)
2458        capt.SetPadding(int(padding))
2459        capt.SetCaption(txt)
2460        capt.SetWidth(size[0])
2461        capt.SetHeight(size[1])
2462        capt.SetThreeDimensionalLeader(not ontop)
2463
2464        pra = capt.GetProperty()
2465        pra.SetColor(c)
2466        pra.SetOpacity(alpha)
2467        pra.SetLineWidth(lw)
2468
2469        pr = capt.GetCaptionTextProperty()
2470        pr.SetFontFamily(vtk.VTK_FONT_FILE)
2471        fl = utils.get_font_path(font)
2472        pr.SetFontFile(fl)
2473        pr.ShadowOff()
2474        pr.BoldOff()
2475        pr.FrameOff()
2476        pr.SetColor(c)
2477        pr.SetOpacity(alpha)
2478        pr.SetJustificationToLeft()
2479        if "top" in justify:
2480            pr.SetVerticalJustificationToTop()
2481        if "bottom" in justify:
2482            pr.SetVerticalJustificationToBottom()
2483        if "cent" in justify:
2484            pr.SetVerticalJustificationToCentered()
2485            pr.SetJustificationToCentered()
2486        if "left" in justify:
2487            pr.SetJustificationToLeft()
2488        if "right" in justify:
2489            pr.SetJustificationToRight()
2490        pr.SetLineSpacing(vspacing)
2491        self._caption = capt
2492        return self
2493
2494
2495    def align_to(self, target, iters=100, rigid=False, invert=False, use_centroids=False):
2496        """
2497        Aligned to target mesh through the `Iterative Closest Point` algorithm.
2498
2499        The core of the algorithm is to match each vertex in one surface with
2500        the closest surface point on the other, then apply the transformation
2501        that modify one surface to best match the other (in the least-square sense).
2502
2503        Arguments:
2504            rigid : (bool)
2505                if True do not allow scaling
2506            invert : (bool)
2507                if True start by aligning the target to the source but
2508                invert the transformation finally. Useful when the target is smaller
2509                than the source.
2510            use_centroids : (bool)
2511                start by matching the centroids of the two objects.
2512
2513        Examples:
2514            - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py)
2515
2516                ![](https://vedo.embl.es/images/basic/align1.png)
2517
2518            - [align2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align2.py)
2519
2520                ![](https://vedo.embl.es/images/basic/align2.png)
2521        """
2522        icp = vtk.vtkIterativeClosestPointTransform()
2523        icp.SetSource(self.polydata())
2524        icp.SetTarget(target.polydata())
2525        if invert:
2526            icp.Inverse()
2527        icp.SetMaximumNumberOfIterations(iters)
2528        if rigid:
2529            icp.GetLandmarkTransform().SetModeToRigidBody()
2530        icp.SetStartByMatchingCentroids(use_centroids)
2531        icp.Update()
2532
2533        M = icp.GetMatrix()
2534        if invert:
2535            M.Invert()  # icp.GetInverse() doesnt work!
2536        # self.apply_transform(M)
2537        self.SetUserMatrix(M)
2538
2539        self.transform = self.GetUserTransform()
2540        self.point_locator = None
2541        self.cell_locator = None
2542
2543        self.pipeline = utils.OperationNode(
2544            "align_to", parents=[self, target], comment=f"rigid = {rigid}"
2545        )
2546        return self
2547
2548    def transform_with_landmarks(
2549        self, source_landmarks, target_landmarks, rigid=False, affine=False, least_squares=False
2550    ):
2551        """
2552        Transform mesh orientation and position based on a set of landmarks points.
2553        The algorithm finds the best matching of source points to target points
2554        in the mean least square sense, in one single step.
2555
2556        If affine is True the x, y and z axes can scale independently but stay collinear.
2557        With least_squares they can vary orientation.
2558
2559        Examples:
2560            - [align5.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align5.py)
2561
2562                ![](https://vedo.embl.es/images/basic/align5.png)
2563        """
2564
2565        if utils.is_sequence(source_landmarks):
2566            ss = vtk.vtkPoints()
2567            for p in source_landmarks:
2568                ss.InsertNextPoint(p)
2569        else:
2570            ss = source_landmarks.polydata().GetPoints()
2571            if least_squares:
2572                source_landmarks = source_landmarks.points()
2573
2574        if utils.is_sequence(target_landmarks):
2575            st = vtk.vtkPoints()
2576            for p in target_landmarks:
2577                st.InsertNextPoint(p)
2578        else:
2579            st = target_landmarks.polydata().GetPoints()
2580            if least_squares:
2581                target_landmarks = target_landmarks.points()
2582
2583        if ss.GetNumberOfPoints() != st.GetNumberOfPoints():
2584            n1 = ss.GetNumberOfPoints()
2585            n2 = st.GetNumberOfPoints()
2586            vedo.logger.error(f"source and target have different nr of points {n1} vs {n2}")
2587            raise RuntimeError()
2588
2589        lmt = vtk.vtkLandmarkTransform()
2590        lmt.SetSourceLandmarks(ss)
2591        lmt.SetTargetLandmarks(st)
2592        lmt.SetModeToSimilarity()
2593        if rigid:
2594            lmt.SetModeToRigidBody()
2595            lmt.Update()
2596            self.SetUserTransform(lmt)
2597
2598        elif affine:
2599            lmt.SetModeToAffine()
2600            lmt.Update()
2601            self.SetUserTransform(lmt)
2602
2603        elif least_squares:
2604            cms = source_landmarks.mean(axis=0)
2605            cmt = target_landmarks.mean(axis=0)
2606            m = np.linalg.lstsq(source_landmarks - cms, target_landmarks - cmt, rcond=None)[0]
2607            M = vtk.vtkMatrix4x4()
2608            for i in range(3):
2609                for j in range(3):
2610                    M.SetElement(j, i, m[i][j])
2611            lmt = vtk.vtkTransform()
2612            lmt.Translate(cmt)
2613            lmt.Concatenate(M)
2614            lmt.Translate(-cms)
2615            self.apply_transform(lmt, concatenate=True)
2616        else:
2617            self.SetUserTransform(lmt)
2618
2619        self.transform = lmt
2620        self.point_locator = None
2621        self.cell_locator = None
2622        self.pipeline = utils.OperationNode("transform_with_landmarks", parents=[self])
2623        return self
2624
2625
2626    def apply_transform(self, T, reset=False, concatenate=False):
2627        """
2628        Apply a linear or non-linear transformation to the mesh polygonal data.
2629
2630        Arguments:
2631            T : (matrix)
2632                `vtkTransform`, `vtkMatrix4x4` or a 4x4 or 3x3 python or numpy matrix.
2633            reset : (bool)
2634                if True reset the current transformation matrix
2635                to identity after having moved the object, otherwise the internal
2636                matrix will stay the same (to only affect visualization).
2637                It the input transformation has no internal defined matrix (ie. non linear)
2638                then reset will be assumed as True.
2639            concatenate : (bool)
2640                concatenate the transformation with the current existing one
2641
2642        Example:
2643            ```python
2644            from vedo import Cube, show
2645            c1 = Cube().rotate_z(5).x(2).y(1)
2646            print("cube1 position", c1.pos())
2647            T = c1.get_transform()  # rotate by 5 degrees, sum 2 to x and 1 to y
2648            c2 = Cube().c('r4')
2649            c2.apply_transform(T)   # ignore previous movements
2650            c2.apply_transform(T, concatenate=True)
2651            c2.apply_transform(T, concatenate=True)
2652            print("cube2 position", c2.pos())
2653            show(c1, c2, axes=1).close()
2654            ```
2655            ![](https://vedo.embl.es/images/feats/apply_transform.png)
2656        """
2657        self.point_locator = None
2658        self.cell_locator = None
2659
2660        if isinstance(T, vtk.vtkMatrix4x4):
2661            tr = vtk.vtkTransform()
2662            tr.SetMatrix(T)
2663            T = tr
2664
2665        elif utils.is_sequence(T):
2666            M = vtk.vtkMatrix4x4()
2667            n = len(T[0])
2668            for i in range(n):
2669                for j in range(n):
2670                    M.SetElement(i, j, T[i][j])
2671            tr = vtk.vtkTransform()
2672            tr.SetMatrix(M)
2673            T = tr
2674
2675        if reset or not hasattr(T, "GetScale"):  # might be non-linear
2676
2677            tf = vtk.vtkTransformPolyDataFilter()
2678            tf.SetTransform(T)
2679            tf.SetInputData(self.polydata())
2680            tf.Update()
2681
2682            I = vtk.vtkMatrix4x4()
2683            self.PokeMatrix(I)  # reset to identity
2684            self.SetUserTransform(None)
2685
2686            self._update(tf.GetOutput())  ### UPDATE
2687            self.transform = T
2688
2689        else:
2690
2691            if concatenate:
2692
2693                M = vtk.vtkTransform()
2694                M.PostMultiply()
2695                M.SetMatrix(self.GetMatrix())
2696
2697                M.Concatenate(T)
2698
2699                self.SetScale(M.GetScale())
2700                self.SetOrientation(M.GetOrientation())
2701                self.SetPosition(M.GetPosition())
2702                self.transform = M
2703                self.SetUserTransform(None)
2704
2705            else:
2706
2707                self.SetScale(T.GetScale())
2708                self.SetOrientation(T.GetOrientation())
2709                self.SetPosition(T.GetPosition())
2710                self.SetUserTransform(None)
2711
2712                self.transform = T
2713
2714        return self
2715
2716    def normalize(self):
2717        """Scale Mesh average size to unit."""
2718        coords = self.points()
2719        if not coords.shape[0]:
2720            return self
2721        cm = np.mean(coords, axis=0)
2722        pts = coords - cm
2723        xyz2 = np.sum(pts * pts, axis=0)
2724        scale = 1 / np.sqrt(np.sum(xyz2) / len(pts))
2725        t = vtk.vtkTransform()
2726        t.PostMultiply()
2727        # t.Translate(-cm)
2728        t.Scale(scale, scale, scale)
2729        # t.Translate(cm)
2730        tf = vtk.vtkTransformPolyDataFilter()
2731        tf.SetInputData(self.inputdata())
2732        tf.SetTransform(t)
2733        tf.Update()
2734        self.point_locator = None
2735        self.cell_locator = None
2736        return self._update(tf.GetOutput())
2737
2738    def mirror(self, axis="x", origin=(0, 0, 0), reset=False):
2739        """
2740        Mirror the mesh  along one of the cartesian axes
2741
2742        Arguments:
2743            axis : (str)
2744                axis to use for mirroring, must be set to x, y, z or n.
2745                Or any combination of those. Adding 'n' reverses mesh faces (hence normals).
2746            origin : (list)
2747                use this point as the origin of the mirroring transformation.
2748            reset : (bool)
2749                if True keep into account the current position of the object,
2750                and then reset its internal transformation matrix to Identity.
2751
2752        Examples:
2753            - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py)
2754
2755                ![](https://vedo.embl.es/images/basic/mirror.png)
2756        """
2757        sx, sy, sz = 1, 1, 1
2758        if "x" in axis.lower(): sx = -1
2759        if "y" in axis.lower(): sy = -1
2760        if "z" in axis.lower(): sz = -1
2761        origin = np.array(origin)
2762        tr = vtk.vtkTransform()
2763        tr.PostMultiply()
2764        tr.Translate(-origin)
2765        tr.Scale(sx, sy, sz)
2766        tr.Translate(origin)
2767        tf = vtk.vtkTransformPolyDataFilter()
2768        tf.SetInputData(self.polydata(reset))
2769        tf.SetTransform(tr)
2770        tf.Update()
2771        outpoly = tf.GetOutput()
2772        if reset:
2773            self.PokeMatrix(vtk.vtkMatrix4x4())  # reset to identity
2774        if sx * sy * sz < 0 or "n" in axis:
2775            rs = vtk.vtkReverseSense()
2776            rs.SetInputData(outpoly)
2777            rs.ReverseNormalsOff()
2778            rs.Update()
2779            outpoly = rs.GetOutput()
2780
2781        self.point_locator = None
2782        self.cell_locator = None
2783
2784        out = self._update(outpoly)
2785
2786        out.pipeline = utils.OperationNode(f"mirror\naxis = {axis}", parents=[self])
2787        return out
2788
2789    def shear(self, x=0, y=0, z=0):
2790        """Apply a shear deformation along one of the main axes"""
2791        t = vtk.vtkTransform()
2792        sx, sy, sz = self.GetScale()
2793        t.SetMatrix([sx, x, 0, 0,
2794                      y,sy, z, 0,
2795                      0, 0,sz, 0,
2796                      0, 0, 0, 1])
2797        self.apply_transform(t, reset=True)
2798        return self
2799
2800    def flip_normals(self):
2801        """Flip all mesh normals. Same as `mesh.mirror('n')`."""
2802        rs = vtk.vtkReverseSense()
2803        rs.SetInputData(self.inputdata())
2804        rs.ReverseCellsOff()
2805        rs.ReverseNormalsOn()
2806        rs.Update()
2807        out = self._update(rs.GetOutput())
2808        self.pipeline = utils.OperationNode("flip_normals", parents=[self])
2809        return out
2810
2811    #####################################################################################
2812    def cmap(
2813        self,
2814        input_cmap,
2815        input_array=None,
2816        on="points",
2817        name="Scalars",
2818        vmin=None,
2819        vmax=None,
2820        n_colors=256,
2821        alpha=1.0,
2822        logscale=False,
2823    ):
2824        """
2825        Set individual point/cell colors by providing a list of scalar values and a color map.
2826
2827        Arguments:
2828            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
2829                color map scheme to transform a real number into a color.
2830            input_array : (str, list, vtkArray)
2831                can be the string name of an existing array, a numpy array or a `vtkArray`.
2832            on : (str)
2833                either 'points' or 'cells'.
2834                Apply the color map to data which is defined on either points or cells.
2835            name : (str)
2836                give a name to the provided numpy array (if input_array is a numpy array)
2837            vmin : (float)
2838                clip scalars to this minimum value
2839            vmax : (float)
2840                clip scalars to this maximum value
2841            n_colors : (int)
2842                number of distinct colors to be used in colormap table.
2843            alpha : (float, list)
2844                Mesh transparency. Can be a `list` of values one for each vertex.
2845            logscale : (bool)
2846                Use logscale
2847
2848        Examples:
2849            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
2850            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
2851            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
2852            (and many others)
2853
2854                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
2855        """
2856        self._cmap_name = input_cmap
2857        poly = self.inputdata()
2858
2859        if input_array is None:
2860            if not self.pointdata.keys() and self.celldata.keys():
2861                on = "cells"
2862                if not poly.GetCellData().GetScalars():
2863                    input_array = 0  # pick the first at hand
2864
2865        if on.startswith("point"):
2866            data = poly.GetPointData()
2867            n = poly.GetNumberOfPoints()
2868        elif on.startswith("cell"):
2869            data = poly.GetCellData()
2870            n = poly.GetNumberOfCells()
2871        else:
2872            vedo.logger.error("Must specify in cmap(on=...) to either 'cells' or 'points'")
2873            raise RuntimeError()
2874
2875        if input_array is None:  # if None try to fetch the active scalars
2876            arr = data.GetScalars()
2877            if not arr:
2878                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
2879                return self
2880
2881            if not arr.GetName():  # sometimes arrays dont have a name..
2882                arr.SetName(name)
2883
2884        elif isinstance(input_array, str):  # if a string is passed
2885            arr = data.GetArray(input_array)
2886            if not arr:
2887                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
2888                return self
2889
2890        elif isinstance(input_array, int):  # if an int is passed
2891            if input_array < data.GetNumberOfArrays():
2892                arr = data.GetArray(input_array)
2893            else:
2894                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
2895                return self
2896
2897        elif utils.is_sequence(input_array):  # if a numpy array is passed
2898            npts = len(input_array)
2899            if npts != n:
2900                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
2901                return self
2902            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
2903            data.AddArray(arr)
2904            data.Modified()
2905
2906        elif isinstance(input_array, vtk.vtkArray):  # if a vtkArray is passed
2907            arr = input_array
2908            data.AddArray(arr)
2909            data.Modified()
2910
2911        else:
2912            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
2913            raise RuntimeError()
2914
2915        # Now we have array "arr"
2916        array_name = arr.GetName()
2917
2918        if arr.GetNumberOfComponents() == 1:
2919            if vmin is None:
2920                vmin = arr.GetRange()[0]
2921            if vmax is None:
2922                vmax = arr.GetRange()[1]
2923        else:
2924            if vmin is None or vmax is None:
2925                vn = utils.mag(utils.vtk2numpy(arr))
2926            if vmin is None:
2927                vmin = vn.min()
2928            if vmax is None:
2929                vmax = vn.max()
2930
2931        # interpolate alphas if they are not constant
2932        if not utils.is_sequence(alpha):
2933            alpha = [alpha] * n_colors
2934        else:
2935            v = np.linspace(0, 1, n_colors, endpoint=True)
2936            xp = np.linspace(0, 1, len(alpha), endpoint=True)
2937            alpha = np.interp(v, xp, alpha)
2938
2939        ########################### build the look-up table
2940        if isinstance(input_cmap, vtk.vtkLookupTable):  # vtkLookupTable
2941            lut = input_cmap
2942
2943        elif utils.is_sequence(input_cmap):  # manual sequence of colors
2944            lut = vtk.vtkLookupTable()
2945            if logscale:
2946                lut.SetScaleToLog10()
2947            lut.SetRange(vmin, vmax)
2948            ncols = len(input_cmap)
2949            lut.SetNumberOfTableValues(ncols)
2950
2951            for i, c in enumerate(input_cmap):
2952                r, g, b = colors.get_color(c)
2953                lut.SetTableValue(i, r, g, b, alpha[i])
2954            lut.Build()
2955
2956        else:  # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
2957            lut = vtk.vtkLookupTable()
2958            if logscale:
2959                lut.SetScaleToLog10()
2960            lut.SetVectorModeToMagnitude()
2961            lut.SetRange(vmin, vmax)
2962            lut.SetNumberOfTableValues(n_colors)
2963            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
2964            for i, c in enumerate(mycols):
2965                r, g, b = c
2966                lut.SetTableValue(i, r, g, b, alpha[i])
2967            lut.Build()
2968
2969        arr.SetLookupTable(lut)
2970
2971        data.SetActiveScalars(array_name)
2972        # data.SetScalars(arr)  # wrong! it deletes array in position 0, never use SetScalars
2973        # data.SetActiveAttribute(array_name, 0) # boh!
2974
2975        if data.GetScalars():
2976            data.GetScalars().SetLookupTable(lut)
2977            data.GetScalars().Modified()
2978
2979        self._mapper.SetLookupTable(lut)
2980        self._mapper.SetColorModeToMapScalars()  # so we dont need to convert uint8 scalars
2981
2982        self._mapper.ScalarVisibilityOn()
2983        self._mapper.SetScalarRange(lut.GetRange())
2984        if on.startswith("point"):
2985            self._mapper.SetScalarModeToUsePointData()
2986        else:
2987            self._mapper.SetScalarModeToUseCellData()
2988        if hasattr(self._mapper, "SetArrayName"):
2989            self._mapper.SetArrayName(array_name)
2990
2991        return self
2992
2993    def cell_individual_colors(self, colorlist):
2994        # DEPRECATED
2995        self.cellcolors = colorlist
2996        print("Please use property mesh.cellcolors=... instead of mesh.cell_individual_colors()")
2997        return self
2998
2999    @property
3000    def cellcolors(self):
3001        """
3002        Colorize each cell (face) of a mesh by passing
3003        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
3004        Colors levels and opacities must be in the range [0,255].
3005
3006        A single constant color can also be passed as string or RGBA.
3007
3008        A cell array named "CellsRGBA" is automatically created.
3009
3010        Examples:
3011            - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py)
3012            - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py)
3013
3014            ![](https://vedo.embl.es/images/basic/colorMeshCells.png)
3015        """
3016        if "CellsRGBA" not in self.celldata.keys():
3017            lut = self.mapper().GetLookupTable()
3018            vscalars = self._data.GetCellData().GetScalars()
3019            if vscalars is None or lut is None:
3020                arr = np.zeros([self.ncells, 4], dtype=np.uint8)
3021                col = np.array(self.property.GetColor())
3022                col = np.round(col * 255).astype(np.uint8)
3023                alf = self.property.GetOpacity()
3024                alf = np.round(alf * 255).astype(np.uint8)
3025                arr[:, (0, 1, 2)] = col
3026                arr[:, 3] = alf
3027            else:
3028                cols = lut.MapScalars(vscalars, 0, 0)
3029                arr = utils.vtk2numpy(cols)
3030            self.celldata["CellsRGBA"] = arr
3031        self.celldata.select("CellsRGBA")
3032        return self.celldata["CellsRGBA"]
3033
3034    @cellcolors.setter
3035    def cellcolors(self, value):
3036        if isinstance(value, str):
3037            c = colors.get_color(value)
3038            value = np.array([*c, 1]) * 255
3039            value = np.round(value)
3040
3041        value = np.asarray(value)
3042        n = self.ncells
3043
3044        if value.ndim == 1:
3045            value = np.repeat([value], n, axis=0)
3046
3047        if value.shape[1] == 3:
3048            z = np.zeros((n, 1), dtype=np.uint8)
3049            value = np.append(value, z + 255, axis=1)
3050
3051        assert n == value.shape[0]
3052
3053        self.celldata["CellsRGBA"] = value.astype(np.uint8)
3054        self.celldata.select("CellsRGBA")
3055
3056
3057    @property
3058    def pointcolors(self):
3059        """
3060        Colorize each point (or vertex of a mesh) by passing
3061        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
3062        Colors levels and opacities must be in the range [0,255].
3063
3064        A single constant color can also be passed as string or RGBA.
3065
3066        A point array named "PointsRGBA" is automatically created.
3067        """
3068        if "PointsRGBA" not in self.pointdata.keys():
3069            lut = self.mapper().GetLookupTable()
3070            vscalars = self._data.GetPointData().GetScalars()
3071            if vscalars is None or lut is None:
3072                arr = np.zeros([self.npoints, 4], dtype=np.uint8)
3073                col = np.array(self.property.GetColor())
3074                col = np.round(col * 255).astype(np.uint8)
3075                alf = self.property.GetOpacity()
3076                alf = np.round(alf * 255).astype(np.uint8)
3077                arr[:, (0, 1, 2)] = col
3078                arr[:, 3] = alf
3079            else:
3080                cols = lut.MapScalars(vscalars, 0, 0)
3081                arr = utils.vtk2numpy(cols)
3082            self.pointdata["PointsRGBA"] = arr
3083        self.pointdata.select("PointsRGBA")
3084        return self.pointdata["PointsRGBA"]
3085
3086    @pointcolors.setter
3087    def pointcolors(self, value):
3088        if isinstance(value, str):
3089            c = colors.get_color(value)
3090            value = np.array([*c, 1]) * 255
3091            value = np.round(value)
3092
3093        value = np.asarray(value)
3094        n = self.npoints
3095
3096        if value.ndim == 1:
3097            value = np.repeat([value], n, axis=0)
3098
3099        if value.shape[1] == 3:
3100            z = np.zeros((n, 1), dtype=np.uint8)
3101            value = np.append(value, z + 255, axis=1)
3102
3103        assert n == value.shape[0]
3104
3105        self.pointdata["PointsRGBA"] = value.astype(np.uint8)
3106        self.pointdata.select("PointsRGBA")
3107
3108
3109    def interpolate_data_from(
3110        self,
3111        source,
3112        radius=None,
3113        n=None,
3114        kernel="shepard",
3115        exclude=("Normals",),
3116        on="points",
3117        null_strategy=1,
3118        null_value=0,
3119    ):
3120        """
3121        Interpolate over source to port its data onto the current object using various kernels.
3122
3123        If n (number of closest points to use) is set then radius value is ignored.
3124
3125        Arguments:
3126            kernel : (str)
3127                available kernels are [shepard, gaussian, linear]
3128            null_strategy : (int)
3129                specify a strategy to use when encountering a "null" point
3130                during the interpolation process. Null points occur when the local neighborhood
3131                (of nearby points to interpolate from) is empty.
3132
3133                - Case 0: an output array is created that marks points
3134                  as being valid (=1) or null (invalid =0), and the null_value is set as well
3135                - Case 1: the output data value(s) are set to the provided null_value
3136                - Case 2: simply use the closest point to perform the interpolation.
3137            null_value : (float)
3138                see above.
3139
3140        Examples:
3141            - [interpolateMeshArray.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolateMeshArray.py)
3142
3143                ![](https://vedo.embl.es/images/advanced/interpolateMeshArray.png)
3144        """
3145        if radius is None and not n:
3146            vedo.logger.error("in interpolate_data_from(): please set either radius or n")
3147            raise RuntimeError
3148
3149        if on == "points":
3150            points = source.polydata()
3151        elif on == "cells":
3152            poly2 = vtk.vtkPolyData()
3153            poly2.ShallowCopy(source.polydata())
3154            c2p = vtk.vtkCellDataToPointData()
3155            c2p.SetInputData(poly2)
3156            c2p.Update()
3157            points = c2p.GetOutput()
3158        else:
3159            vedo.logger.error("in interpolate_data_from(), on must be on points or cells")
3160            raise RuntimeError()
3161
3162        locator = vtk.vtkPointLocator()
3163        locator.SetDataSet(points)
3164        locator.BuildLocator()
3165
3166        if kernel.lower() == "shepard":
3167            kern = vtk.vtkShepardKernel()
3168            kern.SetPowerParameter(2)
3169        elif kernel.lower() == "gaussian":
3170            kern = vtk.vtkGaussianKernel()
3171            kern.SetSharpness(2)
3172        elif kernel.lower() == "linear":
3173            kern = vtk.vtkLinearKernel()
3174        else:
3175            vedo.logger.error("available kernels are: [shepard, gaussian, linear]")
3176            raise RuntimeError()
3177
3178        if n:
3179            kern.SetNumberOfPoints(n)
3180            kern.SetKernelFootprintToNClosest()
3181        else:
3182            kern.SetRadius(radius)
3183
3184        interpolator = vtk.vtkPointInterpolator()
3185        interpolator.SetInputData(self.polydata())
3186        interpolator.SetSourceData(points)
3187        interpolator.SetKernel(kern)
3188        interpolator.SetLocator(locator)
3189        interpolator.PassFieldArraysOff()
3190        interpolator.SetNullPointsStrategy(null_strategy)
3191        interpolator.SetNullValue(null_value)
3192        interpolator.SetValidPointsMaskArrayName("ValidPointMask")
3193        for ex in exclude:
3194            interpolator.AddExcludedArray(ex)
3195        interpolator.Update()
3196
3197        if on == "cells":
3198            p2c = vtk.vtkPointDataToCellData()
3199            p2c.SetInputData(interpolator.GetOutput())
3200            p2c.Update()
3201            cpoly = p2c.GetOutput()
3202        else:
3203            cpoly = interpolator.GetOutput()
3204
3205        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3206            self._update(cpoly)
3207        else:
3208            # bring the underlying polydata to where _data is
3209            M = vtk.vtkMatrix4x4()
3210            M.DeepCopy(self.GetMatrix())
3211            M.Invert()
3212            tr = vtk.vtkTransform()
3213            tr.SetMatrix(M)
3214            tf = vtk.vtkTransformPolyDataFilter()
3215            tf.SetTransform(tr)
3216            tf.SetInputData(cpoly)
3217            tf.Update()
3218            self._update(tf.GetOutput())
3219
3220        self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source])
3221        return self
3222
3223    def add_gaussian_noise(self, sigma=1.0):
3224        """
3225        Add gaussian noise to point positions.
3226        An extra array is added named "GaussianNoise" with the shifts.
3227
3228        Arguments:
3229            sigma : (float)
3230                nr. of standard deviations, expressed in percent of the diagonal size of mesh.
3231                Can also be a list [sigma_x, sigma_y, sigma_z].
3232
3233        Examples:
3234            ```python
3235            from vedo import Sphere
3236            Sphere().add_gaussian_noise(1.0).point_size(8).show().close()
3237            ```
3238        """
3239        sz = self.diagonal_size()
3240        pts = self.points()
3241        n = len(pts)
3242        ns = (np.random.randn(n, 3) * sigma) * (sz / 100)
3243        vpts = vtk.vtkPoints()
3244        vpts.SetNumberOfPoints(n)
3245        vpts.SetData(utils.numpy2vtk(pts + ns, dtype=np.float32))
3246        self.inputdata().SetPoints(vpts)
3247        self.inputdata().GetPoints().Modified()
3248        self.pointdata["GaussianNoise"] = -ns
3249        self.pipeline = utils.OperationNode(
3250            "gaussian_noise", parents=[self], shape="egg", comment=f"sigma = {sigma}"
3251        )
3252        return self
3253
3254
3255    def closest_point(self, pt, n=1, radius=None, return_point_id=False, return_cell_id=False):
3256        """
3257        Find the closest point(s) on a mesh given from the input point `pt`.
3258
3259        Arguments:
3260            n : (int)
3261                if greater than 1, return a list of n ordered closest points
3262            radius : (float)
3263                if given, get all points within that radius. Then n is ignored.
3264            return_point_id : (bool)
3265                return point ID instead of coordinates
3266            return_cell_id : (bool)
3267                return cell ID in which the closest point sits
3268
3269        Examples:
3270            - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py)
3271            - [fitplanes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitplanes.py)
3272            - [quadratic_morphing.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/quadratic_morphing.py)
3273
3274        .. note::
3275            The appropriate tree search locator is built on the fly and cached for speed.
3276
3277            If you want to reset it use `mymesh.point_locator=None`
3278        """
3279        # NB: every time the mesh moves or is warped the locators are set to None
3280        if (n > 1 or radius) or (n == 1 and return_point_id):
3281
3282            poly = None
3283            if not self.point_locator:
3284                poly = self.polydata()
3285                self.point_locator = vtk.vtkStaticPointLocator()
3286                self.point_locator.SetDataSet(poly)
3287                self.point_locator.BuildLocator()
3288
3289            ##########
3290            if radius:
3291                vtklist = vtk.vtkIdList()
3292                self.point_locator.FindPointsWithinRadius(radius, pt, vtklist)
3293            elif n > 1:
3294                vtklist = vtk.vtkIdList()
3295                self.point_locator.FindClosestNPoints(n, pt, vtklist)
3296            else:  # n==1 hence return_point_id==True
3297                ########
3298                return self.point_locator.FindClosestPoint(pt)
3299                ########
3300
3301            if return_point_id:
3302                ########
3303                return utils.vtk2numpy(vtklist)
3304                ########
3305
3306            if not poly:
3307                poly = self.polydata()
3308            trgp = []
3309            for i in range(vtklist.GetNumberOfIds()):
3310                trgp_ = [0, 0, 0]
3311                vi = vtklist.GetId(i)
3312                poly.GetPoints().GetPoint(vi, trgp_)
3313                trgp.append(trgp_)
3314            ########
3315            return np.array(trgp)
3316            ########
3317
3318        else:
3319
3320            if not self.cell_locator:
3321                poly = self.polydata()
3322
3323                # As per Miquel example with limbs the vtkStaticCellLocator doesnt work !!
3324                # https://discourse.vtk.org/t/vtkstaticcelllocator-problem-vtk9-0-3/7854/4
3325                if vedo.vtk_version[0] >= 9 and vedo.vtk_version[0] > 0:
3326                    self.cell_locator = vtk.vtkStaticCellLocator()
3327                else:
3328                    self.cell_locator = vtk.vtkCellLocator()
3329
3330                self.cell_locator.SetDataSet(poly)
3331                self.cell_locator.BuildLocator()
3332
3333            trgp = [0, 0, 0]
3334            cid = vtk.mutable(0)
3335            dist2 = vtk.mutable(0)
3336            subid = vtk.mutable(0)
3337            self.cell_locator.FindClosestPoint(pt, trgp, cid, subid, dist2)
3338
3339            if return_cell_id:
3340                return int(cid)
3341
3342            return np.array(trgp)
3343
3344
3345    def hausdorff_distance(self, points):
3346        """
3347        Compute the Hausdorff distance to the input point set.
3348        Returns a single `float`.
3349
3350        Example:
3351            ```python
3352            from vedo import *
3353            t = np.linspace(0, 2*np.pi, 100)
3354            x = 4/3 * sin(t)**3
3355            y = cos(t) - cos(2*t)/3 - cos(3*t)/6 - cos(4*t)/12
3356            pol1 = Line(np.c_[x,y], closed=True).triangulate()
3357            pol2 = Polygon(nsides=5).pos(2,2)
3358            d12 = pol1.distance_to(pol2)
3359            d21 = pol2.distance_to(pol1)
3360            pol1.lw(0).cmap("viridis")
3361            pol2.lw(0).cmap("viridis")
3362            print("distance d12, d21 :", min(d12), min(d21))
3363            print("hausdorff distance:", pol1.hausdorff_distance(pol2))
3364            print("chamfer distance  :", pol1.chamfer_distance(pol2))
3365            show(pol1, pol2, axes=1)
3366            ```
3367            ![](https://vedo.embl.es/images/feats/heart.png)
3368        """
3369        hp = vtk.vtkHausdorffDistancePointSetFilter()
3370        hp.SetInputData(0, self.polydata())
3371        hp.SetInputData(1, points.polydata())
3372        hp.SetTargetDistanceMethodToPointToCell()
3373        hp.Update()
3374        return hp.GetHausdorffDistance()
3375
3376    def chamfer_distance(self, pcloud):
3377        """
3378        Compute the Chamfer distance to the input point set.
3379        Returns a single `float`.
3380        """
3381        if not pcloud.point_locator:
3382            pcloud.point_locator = vtk.vtkPointLocator()
3383            pcloud.point_locator.SetDataSet(pcloud.polydata())
3384            pcloud.point_locator.BuildLocator()
3385        if not self.point_locator:
3386            self.point_locator = vtk.vtkPointLocator()
3387            self.point_locator.SetDataSet(self.polydata())
3388            self.point_locator.BuildLocator()
3389
3390        ps1 = self.points()
3391        ps2 = pcloud.points()
3392
3393        ids12 = []
3394        for p in ps1:
3395            pid12 = pcloud.point_locator.FindClosestPoint(p)
3396            ids12.append(pid12)
3397        deltav = ps2[ids12] - ps1
3398        da = np.mean(np.linalg.norm(deltav, axis=1))
3399
3400        ids21 = []
3401        for p in ps2:
3402            pid21 = self.point_locator.FindClosestPoint(p)
3403            ids21.append(pid21)
3404        deltav = ps1[ids21] - ps2
3405        db = np.mean(np.linalg.norm(deltav, axis=1))
3406        return (da + db) / 2
3407
3408    def remove_outliers(self, radius, neighbors=5):
3409        """
3410        Remove outliers from a cloud of points within the specified `radius` search.
3411
3412        Arguments:
3413            radius : (float)
3414                Specify the local search radius.
3415            neighbors : (int)
3416                Specify the number of neighbors that a point must have,
3417                within the specified radius, for the point to not be considered isolated.
3418
3419        Examples:
3420            - [clustering.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/clustering.py)
3421
3422                ![](https://vedo.embl.es/images/basic/clustering.png)
3423        """
3424        removal = vtk.vtkRadiusOutlierRemoval()
3425        removal.SetInputData(self.polydata())
3426        removal.SetRadius(radius)
3427        removal.SetNumberOfNeighbors(neighbors)
3428        removal.GenerateOutliersOff()
3429        removal.Update()
3430        inputobj = removal.GetOutput()
3431        if inputobj.GetNumberOfCells() == 0:
3432            carr = vtk.vtkCellArray()
3433            for i in range(inputobj.GetNumberOfPoints()):
3434                carr.InsertNextCell(1)
3435                carr.InsertCellPoint(i)
3436            inputobj.SetVerts(carr)
3437        self._update(inputobj)
3438        self.mapper().ScalarVisibilityOff()
3439        self.pipeline = utils.OperationNode("remove_outliers", parents=[self])
3440        return self
3441
3442    def smooth_mls_1d(self, f=0.2, radius=None):
3443        """
3444        Smooth mesh or points with a `Moving Least Squares` variant.
3445        The point data array "Variances" will contain the residue calculated for each point.
3446        Input mesh's polydata is modified.
3447
3448        Arguments:
3449            f : (float)
3450                smoothing factor - typical range is [0,2].
3451            radius : (float)
3452                radius search in absolute units. If set then `f` is ignored.
3453
3454        Examples:
3455            - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py)
3456            - [skeletonize.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/skeletonize.py)
3457
3458            ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png)
3459        """
3460        coords = self.points()
3461        ncoords = len(coords)
3462
3463        if radius:
3464            Ncp = 0
3465        else:
3466            Ncp = int(ncoords * f / 10)
3467            if Ncp < 5:
3468                vedo.logger.warning(f"Please choose a fraction higher than {f}")
3469                Ncp = 5
3470
3471        variances, newline = [], []
3472        for p in coords:
3473            points = self.closest_point(p, n=Ncp, radius=radius)
3474            if len(points) < 4:
3475                continue
3476
3477            points = np.array(points)
3478            pointsmean = points.mean(axis=0)  # plane center
3479            _, dd, vv = np.linalg.svd(points - pointsmean)
3480            newp = np.dot(p - pointsmean, vv[0]) * vv[0] + pointsmean
3481            variances.append(dd[1] + dd[2])
3482            newline.append(newp)
3483
3484        vdata = utils.numpy2vtk(np.array(variances))
3485        vdata.SetName("Variances")
3486        self.inputdata().GetPointData().AddArray(vdata)
3487        self.inputdata().GetPointData().Modified()
3488        self.points(newline)
3489        self.pipeline = utils.OperationNode("smooth_mls_1d", parents=[self])
3490        return self
3491
3492    def smooth_mls_2d(self, f=0.2, radius=None):
3493        """
3494        Smooth mesh or points with a `Moving Least Squares` algorithm variant.
3495        The list `mesh.info['variances']` contains the residue calculated for each point.
3496        When a radius is specified points that are isolated will not be moved and will get
3497        a False entry in array `mesh.info['is_valid']`.
3498
3499        Arguments:
3500            f : (float)
3501                smoothing factor - typical range is [0,2].
3502            radius : (float)
3503                radius search in absolute units. If set then `f` is ignored.
3504
3505        Examples:
3506            - [moving_least_squares2D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares2D.py)
3507            - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py)
3508
3509                ![](https://vedo.embl.es/images/advanced/recosurface.png)
3510        """
3511        coords = self.points()
3512        ncoords = len(coords)
3513
3514        if radius:
3515            Ncp = 1
3516        else:
3517            Ncp = int(ncoords * f / 100)
3518            if Ncp < 4:
3519                vedo.logger.error(f"MLS2D: Please choose a fraction higher than {f}")
3520                Ncp = 4
3521
3522        variances, newpts, valid = [], [], []
3523        pb = None
3524        if ncoords > 10000:
3525            pb = utils.ProgressBar(0, ncoords)
3526        for p in coords:
3527            if pb:
3528                pb.print("smoothMLS2D working ...")
3529            pts = self.closest_point(p, n=Ncp, radius=radius)
3530            if len(pts) > 3:
3531                ptsmean = pts.mean(axis=0)  # plane center
3532                _, dd, vv = np.linalg.svd(pts - ptsmean)
3533                cv = np.cross(vv[0], vv[1])
3534                t = (np.dot(cv, ptsmean) - np.dot(cv, p)) / np.dot(cv, cv)
3535                newp = p + cv * t
3536                newpts.append(newp)
3537                variances.append(dd[2])
3538                if radius:
3539                    valid.append(True)
3540            else:
3541                newpts.append(p)
3542                variances.append(0)
3543                if radius:
3544                    valid.append(False)
3545
3546        self.info["variances"] = np.array(variances)
3547        self.info["is_valid"] = np.array(valid)
3548        self.points(newpts)
3549
3550        self.pipeline = utils.OperationNode("smooth_mls_2d", parents=[self])
3551        return self
3552
3553    def smooth_lloyd_2d(self, iterations=2, bounds=None, options="Qbb Qc Qx"):
3554        """Lloyd relaxation of a 2D pointcloud."""
3555        # Credits: https://hatarilabs.com/ih-en/
3556        # tutorial-to-create-a-geospatial-voronoi-sh-mesh-with-python-scipy-and-geopandas
3557        from scipy.spatial import Voronoi as scipy_voronoi
3558
3559        def _constrain_points(points):
3560            # Update any points that have drifted beyond the boundaries of this space
3561            if bounds is not None:
3562                for point in points:
3563                    if point[0] < bounds[0]: point[0] = bounds[0]
3564                    if point[0] > bounds[1]: point[0] = bounds[1]
3565                    if point[1] < bounds[2]: point[1] = bounds[2]
3566                    if point[1] > bounds[3]: point[1] = bounds[3]
3567            return points
3568
3569        def _find_centroid(vertices):
3570            # The equation for the method used here to find the centroid of a
3571            # 2D polygon is given here: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
3572            area = 0
3573            centroid_x = 0
3574            centroid_y = 0
3575            for i in range(len(vertices) - 1):
3576                step = (vertices[i, 0] * vertices[i + 1, 1]) - (vertices[i + 1, 0] * vertices[i, 1])
3577                centroid_x += (vertices[i, 0] + vertices[i + 1, 0]) * step
3578                centroid_y += (vertices[i, 1] + vertices[i + 1, 1]) * step
3579                area += step
3580            if area:
3581                centroid_x = (1.0 / (3.0 * area)) * centroid_x
3582                centroid_y = (1.0 / (3.0 * area)) * centroid_y
3583            # prevent centroids from escaping bounding box
3584            return _constrain_points([[centroid_x, centroid_y]])[0]
3585
3586        def _relax(voron):
3587            # Moves each point to the centroid of its cell in the voronoi
3588            # map to "relax" the points (i.e. jitter the points so as
3589            # to spread them out within the space).
3590            centroids = []
3591            for idx in voron.point_region:
3592                # the region is a series of indices into voronoi.vertices
3593                # remove point at infinity, designated by index -1
3594                region = [i for i in voron.regions[idx] if i != -1]
3595                # enclose the polygon
3596                region = region + [region[0]]
3597                verts = voron.vertices[region]
3598                # find the centroid of those vertices
3599                centroids.append(_find_centroid(verts))
3600            return _constrain_points(centroids)
3601
3602        if bounds is None:
3603            bounds = self.bounds()
3604
3605        pts = self.points()[:, (0, 1)]
3606        for i in range(iterations):
3607            vor = scipy_voronoi(pts, qhull_options=options)
3608            _constrain_points(vor.vertices)
3609            pts = _relax(vor)
3610        # m = vedo.Mesh([pts, self.faces()]) # not yet working properly
3611        out = Points(pts, c="k")
3612        out.pipeline = utils.OperationNode("smooth_lloyd", parents=[self])
3613        return out
3614
3615    def project_on_plane(self, plane="z", point=None, direction=None):
3616        """
3617        Project the mesh on one of the Cartesian planes.
3618
3619        Arguments:
3620            plane : (str, Plane)
3621                if plane is `str`, plane can be one of ['x', 'y', 'z'],
3622                represents x-plane, y-plane and z-plane, respectively.
3623                Otherwise, plane should be an instance of `vedo.shapes.Plane`.
3624            point : (float, array)
3625                if plane is `str`, point should be a float represents the intercept.
3626                Otherwise, point is the camera point of perspective projection
3627            direction : (array)
3628                direction of oblique projection
3629
3630        Note:
3631            Parameters `point` and `direction` are only used if the given plane
3632            is an instance of `vedo.shapes.Plane`. And one of these two params
3633            should be left as `None` to specify the projection type.
3634
3635        Example:
3636            ```python
3637            s.project_on_plane(plane='z') # project to z-plane
3638            plane = Plane(pos=(4, 8, -4), normal=(-1, 0, 1), s=(5,5))
3639            s.project_on_plane(plane=plane)                       # orthogonal projection
3640            s.project_on_plane(plane=plane, point=(6, 6, 6))      # perspective projection
3641            s.project_on_plane(plane=plane, direction=(1, 2, -1)) # oblique projection
3642            ```
3643
3644        Examples:
3645            - [silhouette2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette2.py)
3646
3647                ![](https://vedo.embl.es/images/basic/silhouette2.png)
3648        """
3649        coords = self.points()
3650
3651        if plane == "x":
3652            coords[:, 0] = self.GetOrigin()[0]
3653            intercept = self.xbounds()[0] if point is None else point
3654            self.x(intercept)
3655        elif plane == "y":
3656            coords[:, 1] = self.GetOrigin()[1]
3657            intercept = self.ybounds()[0] if point is None else point
3658            self.y(intercept)
3659        elif plane == "z":
3660            coords[:, 2] = self.GetOrigin()[2]
3661            intercept = self.zbounds()[0] if point is None else point
3662            self.z(intercept)
3663
3664        elif isinstance(plane, vedo.shapes.Plane):
3665            normal = plane.normal / np.linalg.norm(plane.normal)
3666            pl = np.hstack((normal, -np.dot(plane.pos(), normal))).reshape(4, 1)
3667            if direction is None and point is None:
3668                # orthogonal projection
3669                pt = np.hstack((normal, [0])).reshape(4, 1)
3670                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T # python3 only
3671                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3672
3673            elif direction is None:
3674                # perspective projection
3675                pt = np.hstack((np.array(point), [1])).reshape(4, 1)
3676                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T
3677                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3678
3679            elif point is None:
3680                # oblique projection
3681                pt = np.hstack((np.array(direction), [0])).reshape(4, 1)
3682                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T
3683                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3684
3685            coords = np.concatenate([coords, np.ones((coords.shape[:-1] + (1,)))], axis=-1)
3686            # coords = coords @ proj_mat.T
3687            coords = np.matmul(coords, proj_mat.T)
3688            coords = coords[:, :3] / coords[:, 3:]
3689
3690        else:
3691            vedo.logger.error(f"unknown plane {plane}")
3692            raise RuntimeError()
3693
3694        self.alpha(0.1)
3695        self.points(coords)
3696        return self
3697
3698    def warp(self, source, target, sigma=1.0, mode="3d"):
3699        """
3700        `Thin Plate Spline` transformations describe a nonlinear warp transform defined by a set
3701        of source and target landmarks. Any point on the mesh close to a source landmark will
3702        be moved to a place close to the corresponding target landmark.
3703        The points in between are interpolated smoothly using
3704        Bookstein's Thin Plate Spline algorithm.
3705
3706        Transformation object can be accessed with `mesh.transform`.
3707
3708        Arguments:
3709            sigma : (float)
3710                specify the 'stiffness' of the spline.
3711            mode : (str)
3712                set the basis function to either abs(R) (for 3d) or R2LogR (for 2d meshes)
3713
3714        Examples:
3715            - [interpolate_field.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_field.py)
3716            - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py)
3717            - [warp2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp2.py)
3718
3719                ![](https://vedo.embl.es/images/advanced/warp2.png)
3720        """
3721        parents = [self]
3722        if isinstance(source, Points):
3723            parents.append(source)
3724            source = source.points()
3725        else:
3726            source = utils.make3d(source)
3727
3728        if isinstance(target, Points):
3729            parents.append(target)
3730            target = target.points()
3731        else:
3732            target = utils.make3d(target)
3733
3734        ns = len(source)
3735        ptsou = vtk.vtkPoints()
3736        ptsou.SetNumberOfPoints(ns)
3737        for i in range(ns):
3738            ptsou.SetPoint(i, source[i])
3739
3740        nt = len(target)
3741        if ns != nt:
3742            vedo.logger.error(f"#source {ns} != {nt} #target points")
3743            raise RuntimeError()
3744
3745        pttar = vtk.vtkPoints()
3746        pttar.SetNumberOfPoints(nt)
3747        for i in range(ns):
3748            pttar.SetPoint(i, target[i])
3749
3750        T = vtk.vtkThinPlateSplineTransform()
3751        if mode.lower() == "3d":
3752            T.SetBasisToR()
3753        elif mode.lower() == "2d":
3754            T.SetBasisToR2LogR()
3755        else:
3756            vedo.logger.error(f"unknown mode {mode}")
3757            raise RuntimeError()
3758
3759        T.SetSigma(sigma)
3760        T.SetSourceLandmarks(ptsou)
3761        T.SetTargetLandmarks(pttar)
3762        self.transform = T
3763        self.apply_transform(T, reset=True)
3764
3765        self.pipeline = utils.OperationNode("warp", parents=parents)
3766        return self
3767
3768    def cut_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0), invert=False):
3769        """
3770        Cut the mesh with the plane defined by a point and a normal.
3771
3772        Arguments:
3773            origin : (array)
3774                the cutting plane goes through this point
3775            normal : (array)
3776                normal of the cutting plane
3777
3778        Example:
3779            ```python
3780            from vedo import Cube
3781            cube = Cube().cut_with_plane(normal=(1,1,1))
3782            cube.back_color('pink').show()
3783            ```
3784            ![](https://vedo.embl.es/images/feats/cut_with_plane_cube.png)
3785
3786        Examples:
3787            - [trail.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/trail.py)
3788
3789                ![](https://vedo.embl.es/images/simulations/trail.gif)
3790
3791        Check out also:
3792            `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`.
3793        """
3794        s = str(normal)
3795        if "x" in s:
3796            normal = (1, 0, 0)
3797            if "-" in s:
3798                normal = -np.array(normal)
3799        elif "y" in s:
3800            normal = (0, 1, 0)
3801            if "-" in s:
3802                normal = -np.array(normal)
3803        elif "z" in s:
3804            normal = (0, 0, 1)
3805            if "-" in s:
3806                normal = -np.array(normal)
3807        plane = vtk.vtkPlane()
3808        plane.SetOrigin(origin)
3809        plane.SetNormal(normal)
3810
3811        clipper = vtk.vtkClipPolyData()
3812        clipper.SetInputData(self.polydata(True))  # must be True
3813        clipper.SetClipFunction(plane)
3814        clipper.GenerateClippedOutputOff()
3815        clipper.GenerateClipScalarsOff()
3816        clipper.SetInsideOut(invert)
3817        clipper.SetValue(0)
3818        clipper.Update()
3819
3820        cpoly = clipper.GetOutput()
3821
3822        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3823            self._update(cpoly)
3824        else:
3825            # bring the underlying polydata to where _data is
3826            M = vtk.vtkMatrix4x4()
3827            M.DeepCopy(self.GetMatrix())
3828            M.Invert()
3829            tr = vtk.vtkTransform()
3830            tr.SetMatrix(M)
3831            tf = vtk.vtkTransformPolyDataFilter()
3832            tf.SetTransform(tr)
3833            tf.SetInputData(cpoly)
3834            tf.Update()
3835            self._update(tf.GetOutput())
3836
3837        self.pipeline = utils.OperationNode("cut_with_plane", parents=[self])
3838        return self
3839
3840    def cut_with_planes(self, origins, normals, invert=False):
3841        """
3842        Cut the mesh with a convex set of planes defined by points and normals.
3843
3844        Arguments:
3845            origins : (array)
3846                each cutting plane goes through this point
3847            normals : (array)
3848                normal of each of the cutting planes
3849
3850        Check out also:
3851            `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`
3852        """
3853
3854        vpoints = vtk.vtkPoints()
3855        for p in utils.make3d(origins):
3856            vpoints.InsertNextPoint(p)
3857        normals = utils.make3d(normals)
3858
3859        planes = vtk.vtkPlanes()
3860        planes.SetPoints(vpoints)
3861        planes.SetNormals(utils.numpy2vtk(normals, dtype=float))
3862
3863        clipper = vtk.vtkClipPolyData()
3864        clipper.SetInputData(self.polydata(True))  # must be True
3865        clipper.SetInsideOut(invert)
3866        clipper.SetClipFunction(planes)
3867        clipper.GenerateClippedOutputOff()
3868        clipper.GenerateClipScalarsOff()
3869        clipper.SetValue(0)
3870        clipper.Update()
3871
3872        cpoly = clipper.GetOutput()
3873
3874        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3875            self._update(cpoly)
3876        else:
3877            # bring the underlying polydata to where _data is
3878            M = vtk.vtkMatrix4x4()
3879            M.DeepCopy(self.GetMatrix())
3880            M.Invert()
3881            tr = vtk.vtkTransform()
3882            tr.SetMatrix(M)
3883            tf = vtk.vtkTransformPolyDataFilter()
3884            tf.SetTransform(tr)
3885            tf.SetInputData(cpoly)
3886            tf.Update()
3887            self._update(tf.GetOutput())
3888
3889        self.pipeline = utils.OperationNode("cut_with_planes", parents=[self])
3890        return self
3891
3892    def cut_with_box(self, bounds, invert=False):
3893        """
3894        Cut the current mesh with a box or a set of boxes.
3895        This is much faster than `cut_with_mesh()`.
3896
3897        Input `bounds` can be either:
3898        - a Mesh or Points object
3899        - a list of 6 number representing a bounding box `[xmin,xmax, ymin,ymax, zmin,zmax]`
3900        - a list of bounding boxes like the above: `[[xmin1,...], [xmin2,...], ...]`
3901
3902        Example:
3903            ```python
3904            from vedo import Sphere, Cube, show
3905            mesh = Sphere(r=1, res=50)
3906            box  = Cube(side=1.5).wireframe()
3907            mesh.cut_with_box(box)
3908            show(mesh, box, axes=1)
3909            ```
3910            ![](https://vedo.embl.es/images/feats/cut_with_box_cube.png)
3911
3912        Check out also:
3913            `cut_with_line()`, `cut_with_plane()`, `cut_with_cylinder()`
3914        """
3915        if isinstance(bounds, Points):
3916            bounds = bounds.bounds()
3917
3918        box = vtk.vtkBox()
3919        if utils.is_sequence(bounds[0]):
3920            for bs in bounds:
3921                box.AddBounds(bs)
3922        else:
3923            box.SetBounds(bounds)
3924
3925        clipper = vtk.vtkClipPolyData()
3926        clipper.SetInputData(self.polydata(True))  # must be True
3927        clipper.SetClipFunction(box)
3928        clipper.SetInsideOut(not invert)
3929        clipper.GenerateClippedOutputOff()
3930        clipper.GenerateClipScalarsOff()
3931        clipper.SetValue(0)
3932        clipper.Update()
3933        cpoly = clipper.GetOutput()
3934
3935        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3936            self._update(cpoly)
3937        else:
3938            # bring the underlying polydata to where _data is
3939            M = vtk.vtkMatrix4x4()
3940            M.DeepCopy(self.GetMatrix())
3941            M.Invert()
3942            tr = vtk.vtkTransform()
3943            tr.SetMatrix(M)
3944            tf = vtk.vtkTransformPolyDataFilter()
3945            tf.SetTransform(tr)
3946            tf.SetInputData(cpoly)
3947            tf.Update()
3948            self._update(tf.GetOutput())
3949
3950        self.pipeline = utils.OperationNode("cut_with_box", parents=[self])
3951        return self
3952
3953    def cut_with_line(self, points, invert=False, closed=True):
3954        """
3955        Cut the current mesh with a line vertically in the z-axis direction like a cookie cutter.
3956        The polyline is defined by a set of points (z-coordinates are ignored).
3957        This is much faster than `cut_with_mesh()`.
3958
3959        Check out also:
3960            `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()`
3961        """
3962        pplane = vtk.vtkPolyPlane()
3963        if isinstance(points, Points):
3964            points = points.points().tolist()
3965
3966        if closed:
3967            if isinstance(points, np.ndarray):
3968                points = points.tolist()
3969            points.append(points[0])
3970
3971        vpoints = vtk.vtkPoints()
3972        for p in points:
3973            if len(p) == 2:
3974                p = [p[0], p[1], 0.0]
3975            vpoints.InsertNextPoint(p)
3976
3977        n = len(points)
3978        polyline = vtk.vtkPolyLine()
3979        polyline.Initialize(n, vpoints)
3980        polyline.GetPointIds().SetNumberOfIds(n)
3981        for i in range(n):
3982            polyline.GetPointIds().SetId(i, i)
3983        pplane.SetPolyLine(polyline)
3984
3985        clipper = vtk.vtkClipPolyData()
3986        clipper.SetInputData(self.polydata(True))  # must be True
3987        clipper.SetClipFunction(pplane)
3988        clipper.SetInsideOut(invert)
3989        clipper.GenerateClippedOutputOff()
3990        clipper.GenerateClipScalarsOff()
3991        clipper.SetValue(0)
3992        clipper.Update()
3993        cpoly = clipper.GetOutput()
3994
3995        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3996            self._update(cpoly)
3997        else:
3998            # bring the underlying polydata to where _data is
3999            M = vtk.vtkMatrix4x4()
4000            M.DeepCopy(self.GetMatrix())
4001            M.Invert()
4002            tr = vtk.vtkTransform()
4003            tr.SetMatrix(M)
4004            tf = vtk.vtkTransformPolyDataFilter()
4005            tf.SetTransform(tr)
4006            tf.SetInputData(cpoly)
4007            tf.Update()
4008            self._update(tf.GetOutput())
4009
4010        self.pipeline = utils.OperationNode("cut_with_line", parents=[self])
4011        return self
4012
4013    def cut_with_cookiecutter(self, lines):
4014        """
4015        Cut the current mesh with a single line or a set of lines.
4016
4017        Input `lines` can be either:
4018        - a `Mesh` or `Points` object
4019        - a list of 3D points: `[(x1,y1,z1), (x2,y2,z2), ...]`
4020        - a list of 2D points: `[(x1,y1), (x2,y2), ...]`
4021
4022        Example:
4023            ```python
4024            from vedo import *
4025            grid = Mesh(dataurl + "dolfin_fine.vtk")
4026            grid.compute_quality().cmap("Greens")
4027            pols = merge(
4028                Polygon(nsides=10, r=0.3).pos(0.7, 0.3),
4029                Polygon(nsides=10, r=0.2).pos(0.3, 0.7),
4030            )
4031            lines = pols.boundaries()
4032            grid.cut_with_cookiecutter(lines)
4033            show(grid, lines, axes=8, bg='blackboard').close()
4034            ```
4035            ![](https://vedo.embl.es/images/feats/cookiecutter.png)
4036
4037        Check out also:
4038            `cut_with_line()` and `cut_with_point_loop()`
4039
4040        Note:
4041            In case of a warning message like:
4042                "Mesh and trim loop point data attributes are different"
4043            consider interpolating the mesh point data to the loop points,
4044            Eg. (in the above example):
4045            ```python
4046            lines = pols.boundaries().interpolate_data_from(grid, n=2)
4047            ```
4048
4049        Note:
4050            trying to invert the selection by reversing the loop order
4051            will have no effect in this method, hence it does not have
4052            the `invert` option.
4053        """
4054        if utils.is_sequence(lines):
4055            lines = utils.make3d(lines)
4056            iline = list(range(len(lines))) + [0]
4057            poly = utils.buildPolyData(lines, lines=[iline])
4058        else:
4059            poly = lines.polydata()
4060
4061        # if invert: # not working
4062        #     rev = vtk.vtkReverseSense()
4063        #     rev.ReverseCellsOn()
4064        #     rev.SetInputData(poly)
4065        #     rev.Update()
4066        #     poly = rev.GetOutput()
4067
4068        # Build loops from the polyline
4069        build_loops = vtk.vtkContourLoopExtraction()
4070        build_loops.SetInputData(poly)
4071        build_loops.Update()
4072        boundaryPoly = build_loops.GetOutput()
4073
4074        ccut = vtk.vtkCookieCutter()
4075        ccut.SetInputData(self.polydata())
4076        ccut.SetLoopsData(boundaryPoly)
4077        ccut.SetPointInterpolationToMeshEdges()
4078        # ccut.SetPointInterpolationToLoopEdges()
4079        ccut.PassCellDataOn()
4080        # ccut.PassPointDataOn()
4081        ccut.Update()
4082        cpoly = ccut.GetOutput()
4083
4084        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4085            self._update(cpoly)
4086        else:
4087            # bring the underlying polydata to where _data is
4088            M = vtk.vtkMatrix4x4()
4089            M.DeepCopy(self.GetMatrix())
4090            M.Invert()
4091            tr = vtk.vtkTransform()
4092            tr.SetMatrix(M)
4093            tf = vtk.vtkTransformPolyDataFilter()
4094            tf.SetTransform(tr)
4095            tf.SetInputData(cpoly)
4096            tf.Update()
4097            self._update(tf.GetOutput())
4098
4099        self.pipeline = utils.OperationNode("cut_with_cookiecutter", parents=[self])
4100        return self
4101
4102    def cut_with_cylinder(self, center=(0, 0, 0), axis=(0, 0, 1), r=1, invert=False):
4103        """
4104        Cut the current mesh with an infinite cylinder.
4105        This is much faster than `cut_with_mesh()`.
4106
4107        Arguments:
4108            center : (array)
4109                the center of the cylinder
4110            normal : (array)
4111                direction of the cylinder axis
4112            r : (float)
4113                radius of the cylinder
4114
4115        Example:
4116            ```python
4117            from vedo import Disc, show
4118            disc = Disc(r1=1, r2=1.2)
4119            mesh = disc.extrude(3, res=50).linewidth(1)
4120            mesh.cut_with_cylinder([0,0,2], r=0.4, axis='y', invert=True)
4121            show(mesh, axes=1)
4122            ```
4123            ![](https://vedo.embl.es/images/feats/cut_with_cylinder.png)
4124
4125        Examples:
4126            - [optics_main1.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/optics_main1.py)
4127
4128        Check out also:
4129            `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()`
4130        """
4131        s = str(axis)
4132        if "x" in s:
4133            axis = (1, 0, 0)
4134        elif "y" in s:
4135            axis = (0, 1, 0)
4136        elif "z" in s:
4137            axis = (0, 0, 1)
4138        cyl = vtk.vtkCylinder()
4139        cyl.SetCenter(center)
4140        cyl.SetAxis(axis[0], axis[1], axis[2])
4141        cyl.SetRadius(r)
4142
4143        clipper = vtk.vtkClipPolyData()
4144        clipper.SetInputData(self.polydata(True))  # must be True
4145        clipper.SetClipFunction(cyl)
4146        clipper.SetInsideOut(not invert)
4147        clipper.GenerateClippedOutputOff()
4148        clipper.GenerateClipScalarsOff()
4149        clipper.SetValue(0)
4150        clipper.Update()
4151        cpoly = clipper.GetOutput()
4152
4153        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4154            self._update(cpoly)
4155        else:
4156            # bring the underlying polydata to where _data is
4157            M = vtk.vtkMatrix4x4()
4158            M.DeepCopy(self.GetMatrix())
4159            M.Invert()
4160            tr = vtk.vtkTransform()
4161            tr.SetMatrix(M)
4162            tf = vtk.vtkTransformPolyDataFilter()
4163            tf.SetTransform(tr)
4164            tf.SetInputData(cpoly)
4165            tf.Update()
4166            self._update(tf.GetOutput())
4167
4168        self.pipeline = utils.OperationNode("cut_with_cylinder", parents=[self])
4169        return self
4170
4171    def cut_with_sphere(self, center=(0, 0, 0), r=1.0, invert=False):
4172        """
4173        Cut the current mesh with an sphere.
4174        This is much faster than `cut_with_mesh()`.
4175
4176        Arguments:
4177            center : (array)
4178                the center of the sphere
4179            r : (float)
4180                radius of the sphere
4181
4182        Example:
4183            ```python
4184            from vedo import Disc, show
4185            disc = Disc(r1=1, r2=1.2)
4186            mesh = disc.extrude(3, res=50).linewidth(1)
4187            mesh.cut_with_sphere([1,-0.7,2], r=1.5, invert=True)
4188            show(mesh, axes=1)
4189            ```
4190            ![](https://vedo.embl.es/images/feats/cut_with_sphere.png)
4191
4192        Check out also:
4193            `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()`
4194        """
4195        sph = vtk.vtkSphere()
4196        sph.SetCenter(center)
4197        sph.SetRadius(r)
4198
4199        clipper = vtk.vtkClipPolyData()
4200        clipper.SetInputData(self.polydata(True))  # must be True
4201        clipper.SetClipFunction(sph)
4202        clipper.SetInsideOut(not invert)
4203        clipper.GenerateClippedOutputOff()
4204        clipper.GenerateClipScalarsOff()
4205        clipper.SetValue(0)
4206        clipper.Update()
4207        cpoly = clipper.GetOutput()
4208
4209        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4210            self._update(cpoly)
4211        else:
4212            # bring the underlying polydata to where _data is
4213            M = vtk.vtkMatrix4x4()
4214            M.DeepCopy(self.GetMatrix())
4215            M.Invert()
4216            tr = vtk.vtkTransform()
4217            tr.SetMatrix(M)
4218            tf = vtk.vtkTransformPolyDataFilter()
4219            tf.SetTransform(tr)
4220            tf.SetInputData(cpoly)
4221            tf.Update()
4222            self._update(tf.GetOutput())
4223
4224        self.pipeline = utils.OperationNode("cut_with_sphere", parents=[self])
4225        return self
4226
4227    def cut_with_mesh(self, mesh, invert=False, keep=False):
4228        """
4229        Cut an `Mesh` mesh with another `Mesh`.
4230
4231        Use `invert` to invert the selection.
4232
4233        Use `keep` to keep the cutoff part, in this case an `Assembly` is returned:
4234        the "cut" object and the "discarded" part of the original object.
4235        You can access both via `assembly.unpack()` method.
4236
4237        Example:
4238        ```python
4239        from vedo import *
4240        arr = np.random.randn(100000, 3)/2
4241        pts = Points(arr).c('red3').pos(5,0,0)
4242        cube = Cube().pos(4,0.5,0)
4243        assem = pts.cut_with_mesh(cube, keep=True)
4244        show(assem.unpack(), axes=1).close()
4245        ```
4246        ![](https://vedo.embl.es/images/feats/cut_with_mesh.png)
4247
4248       Check out also:
4249            `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()`
4250       """
4251        polymesh = mesh.polydata()
4252        poly = self.polydata()
4253
4254        # Create an array to hold distance information
4255        signed_distances = vtk.vtkFloatArray()
4256        signed_distances.SetNumberOfComponents(1)
4257        signed_distances.SetName("SignedDistances")
4258
4259        # implicit function that will be used to slice the mesh
4260        ippd = vtk.vtkImplicitPolyDataDistance()
4261        ippd.SetInput(polymesh)
4262
4263        # Evaluate the signed distance function at all of the grid points
4264        for pointId in range(poly.GetNumberOfPoints()):
4265            p = poly.GetPoint(pointId)
4266            signed_distance = ippd.EvaluateFunction(p)
4267            signed_distances.InsertNextValue(signed_distance)
4268
4269        currentscals = poly.GetPointData().GetScalars()
4270        if currentscals:
4271            currentscals = currentscals.GetName()
4272
4273        poly.GetPointData().AddArray(signed_distances)
4274        poly.GetPointData().SetActiveScalars("SignedDistances")
4275
4276        clipper = vtk.vtkClipPolyData()
4277        clipper.SetInputData(poly)
4278        clipper.SetInsideOut(not invert)
4279        clipper.SetGenerateClippedOutput(keep)
4280        clipper.SetValue(0.0)
4281        clipper.Update()
4282        cpoly = clipper.GetOutput()
4283        if keep:
4284            kpoly = clipper.GetOutput(1)
4285
4286        vis = False
4287        if currentscals:
4288            cpoly.GetPointData().SetActiveScalars(currentscals)
4289            vis = self.mapper().GetScalarVisibility()
4290
4291        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4292            self._update(cpoly)
4293        else:
4294            # bring the underlying polydata to where _data is
4295            M = vtk.vtkMatrix4x4()
4296            M.DeepCopy(self.GetMatrix())
4297            M.Invert()
4298            tr = vtk.vtkTransform()
4299            tr.SetMatrix(M)
4300            tf = vtk.vtkTransformPolyDataFilter()
4301            tf.SetTransform(tr)
4302            tf.SetInputData(cpoly)
4303            tf.Update()
4304            self._update(tf.GetOutput())
4305
4306        self.pointdata.remove("SignedDistances")
4307        self.mapper().SetScalarVisibility(vis)
4308        if keep:
4309            if isinstance(self, vedo.Mesh):
4310                cutoff = vedo.Mesh(kpoly)
4311            else:
4312                cutoff = vedo.Points(kpoly)
4313            cutoff.property = vtk.vtkProperty()
4314            cutoff.property.DeepCopy(self.property)
4315            cutoff.SetProperty(cutoff.property)
4316            cutoff.c("k5").alpha(0.2)
4317            return vedo.Assembly([self, cutoff])
4318
4319        self.pipeline = utils.OperationNode("cut_with_mesh", parents=[self, mesh])
4320        return self
4321
4322    def cut_with_point_loop(self, points, invert=False, on="points", include_boundary=False):
4323        """
4324        Cut an `Mesh` object with a set of points forming a closed loop.
4325
4326        Arguments:
4327            invert : (bool)
4328                invert selection (inside-out)
4329            on : (str)
4330                if 'cells' will extract the whole cells lying inside (or outside) the point loop
4331            include_boundary : (bool)
4332                include cells lying exactly on the boundary line. Only relevant on 'cells' mode
4333
4334        Examples:
4335            - [cut_with_points1.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points1.py)
4336
4337                ![](https://vedo.embl.es/images/advanced/cutWithPoints1.png)
4338
4339            - [cut_with_points2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points2.py)
4340
4341                ![](https://vedo.embl.es/images/advanced/cutWithPoints2.png)
4342        """
4343        if isinstance(points, Points):
4344            parents = [points]
4345            vpts = points.polydata().GetPoints()
4346            points = points.points()
4347        else:
4348            parents = [self]
4349            vpts = vtk.vtkPoints()
4350            points = utils.make3d(points)
4351            for p in points:
4352                vpts.InsertNextPoint(p)
4353
4354        if "cell" in on:
4355            ippd = vtk.vtkImplicitSelectionLoop()
4356            ippd.SetLoop(vpts)
4357            ippd.AutomaticNormalGenerationOn()
4358            clipper = vtk.vtkExtractPolyDataGeometry()
4359            clipper.SetInputData(self.polydata())
4360            clipper.SetImplicitFunction(ippd)
4361            clipper.SetExtractInside(not invert)
4362            clipper.SetExtractBoundaryCells(include_boundary)
4363        else:
4364            spol = vtk.vtkSelectPolyData()
4365            spol.SetLoop(vpts)
4366            spol.GenerateSelectionScalarsOn()
4367            spol.GenerateUnselectedOutputOff()
4368            spol.SetInputData(self.polydata())
4369            spol.Update()
4370            clipper = vtk.vtkClipPolyData()
4371            clipper.SetInputData(spol.GetOutput())
4372            clipper.SetInsideOut(not invert)
4373            clipper.SetValue(0.0)
4374        clipper.Update()
4375        cpoly = clipper.GetOutput()
4376
4377        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4378            self._update(cpoly)
4379        else:
4380            # bring the underlying polydata to where _data is
4381            M = vtk.vtkMatrix4x4()
4382            M.DeepCopy(self.GetMatrix())
4383            M.Invert()
4384            tr = vtk.vtkTransform()
4385            tr.SetMatrix(M)
4386            tf = vtk.vtkTransformPolyDataFilter()
4387            tf.SetTransform(tr)
4388            tf.SetInputData(clipper.GetOutput())
4389            tf.Update()
4390            self._update(tf.GetOutput())
4391
4392        self.pipeline = utils.OperationNode("cut_with_pointloop", parents=parents)
4393        return self
4394
4395    def cut_with_scalar(self, value, name="", invert=False):
4396        """
4397        Cut a mesh or point cloud with some input scalar point-data.
4398
4399        Arguments:
4400            value : (float)
4401                cutting value
4402            name : (str)
4403                array name of the scalars to be used
4404            invert : (bool)
4405                flip selection
4406
4407        Example:
4408            ```python
4409            from vedo import *
4410            s = Sphere().lw(1)
4411            pts = s.points()
4412            scalars = np.sin(3*pts[:,2]) + pts[:,0]
4413            s.pointdata["somevalues"] = scalars
4414            s.cut_with_scalar(0.3)
4415            s.cmap("Spectral", "somevalues").add_scalarbar()
4416            s.show(axes=1).close()
4417            ```
4418            ![](https://vedo.embl.es/images/feats/cut_with_scalars.png)
4419        """
4420        if name:
4421            self.pointdata.select(name)
4422        clipper = vtk.vtkClipPolyData()
4423        clipper.SetInputData(self._data)
4424        clipper.SetValue(value)
4425        clipper.GenerateClippedOutputOff()
4426        clipper.SetInsideOut(not invert)
4427        clipper.Update()
4428        self._update(clipper.GetOutput())
4429
4430        self.pipeline = utils.OperationNode("cut_with_scalars", parents=[self])
4431        return self
4432
4433    def implicit_modeller(self, distance=0.05, res=(50, 50, 50), bounds=(), maxdist=None):
4434        """Find the surface which sits at the specified distance from the input one."""
4435        if not bounds:
4436            bounds = self.bounds()
4437
4438        if not maxdist:
4439            maxdist = self.diagonal_size() / 2
4440
4441        imp = vtk.vtkImplicitModeller()
4442        imp.SetInputData(self.polydata())
4443        imp.SetSampleDimensions(res)
4444        imp.SetMaximumDistance(maxdist)
4445        imp.SetModelBounds(bounds)
4446        contour = vtk.vtkContourFilter()
4447        contour.SetInputConnection(imp.GetOutputPort())
4448        contour.SetValue(0, distance)
4449        contour.Update()
4450        poly = contour.GetOutput()
4451        out = vedo.Mesh(poly, c="lb")
4452
4453        out.pipeline = utils.OperationNode("implicit_modeller", parents=[self])
4454        return out
4455
4456
4457    def generate_mesh(
4458        self,
4459        line_resolution=None,
4460        mesh_resolution=None,
4461        smooth=0.0,
4462        jitter=0.001,
4463        grid=None,
4464        quads=False,
4465        invert=False,
4466    ):
4467        """
4468        Generate a polygonal Mesh from a closed contour line.
4469        If line is not closed it will be closed with a straight segment.
4470
4471        Arguments:
4472            line_resolution : (int)
4473                resolution of the contour line. The default is None, in this case
4474                the contour is not resampled.
4475            mesh_resolution : (int)
4476                resolution of the internal triangles not touching the boundary.
4477            smooth : (float)
4478                smoothing of the contour before meshing.
4479            jitter : (float)
4480                add a small noise to the internal points.
4481            grid : (Grid)
4482                manually pass a Grid object. The default is True.
4483            quads : (bool)
4484                generate a mesh of quads instead of triangles.
4485            invert : (bool)
4486                flip the line orientation. The default is False.
4487
4488        Examples:
4489            - [line2mesh_tri.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_tri.py)
4490
4491                ![](https://vedo.embl.es/images/advanced/line2mesh_tri.jpg)
4492
4493            - [line2mesh_quads.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_quads.py)
4494
4495                ![](https://vedo.embl.es/images/advanced/line2mesh_quads.png)
4496        """
4497        if line_resolution is None:
4498            contour = vedo.shapes.Line(self.points())
4499        else:
4500            contour = vedo.shapes.Spline(self.points(), smooth=smooth, res=line_resolution)
4501        contour.clean()
4502
4503        length = contour.length()
4504        density = length / contour.npoints
4505        vedo.logger.debug(f"tomesh():\n\tline length = {length}")
4506        vedo.logger.debug(f"\tdensity = {density} length/pt_separation")
4507
4508        x0, x1 = contour.xbounds()
4509        y0, y1 = contour.ybounds()
4510
4511        if grid is None:
4512            if mesh_resolution is None:
4513                resx = int((x1 - x0) / density + 0.5)
4514                resy = int((y1 - y0) / density + 0.5)
4515                vedo.logger.debug(f"tmesh_resolution = {[resx, resy]}")
4516            else:
4517                if utils.is_sequence(mesh_resolution):
4518                    resx, resy = mesh_resolution
4519                else:
4520                    resx, resy = mesh_resolution, mesh_resolution
4521            grid = vedo.shapes.Grid(
4522                [(x0 + x1) / 2, (y0 + y1) / 2, 0],
4523                s=((x1 - x0) * 1.025, (y1 - y0) * 1.025),
4524                res=(resx, resy),
4525            )
4526        else:
4527            grid = grid.clone()
4528
4529        cpts = contour.points()
4530
4531        # make sure it's closed
4532        p0, p1 = cpts[0], cpts[-1]
4533        nj = max(2, int(utils.mag(p1 - p0) / density + 0.5))
4534        joinline = vedo.shapes.Line(p1, p0, res=nj)
4535        contour = vedo.merge(contour, joinline).subsample(0.0001)
4536
4537        ####################################### quads
4538        if quads:
4539            cmesh = grid.clone().cut_with_point_loop(contour, on="cells", invert=invert)
4540            cmesh.wireframe(False).lw(0.5)
4541            cmesh.pipeline = utils.OperationNode(
4542                "generate_mesh",
4543                parents=[self, contour],
4544                comment=f"#quads {cmesh.inputdata().GetNumberOfCells()}",
4545            )
4546            return cmesh
4547        #############################################
4548
4549        grid_tmp = grid.points()
4550
4551        if jitter:
4552            np.random.seed(0)
4553            sigma = 1.0 / np.sqrt(grid.npoints) * grid.diagonal_size() * jitter
4554            vedo.logger.debug(f"\tsigma jittering = {sigma}")
4555            grid_tmp += np.random.rand(grid.npoints, 3) * sigma
4556            grid_tmp[:, 2] = 0.0
4557
4558        todel = []
4559        density /= np.sqrt(3)
4560        vgrid_tmp = Points(grid_tmp)
4561
4562        for p in contour.points():
4563            out = vgrid_tmp.closest_point(p, radius=density, return_point_id=True)
4564            todel += out.tolist()
4565        # cpoints = contour.points()
4566        # for i, p in enumerate(cpoints):
4567        #     if i:
4568        #         den = utils.mag(p-cpoints[i-1])/1.732
4569        #     else:
4570        #         den = density
4571        #     todel += vgrid_tmp.closest_point(p, radius=den, return_point_id=True)
4572
4573        grid_tmp = grid_tmp.tolist()
4574        for index in sorted(list(set(todel)), reverse=True):
4575            del grid_tmp[index]
4576
4577        points = contour.points().tolist() + grid_tmp
4578        if invert:
4579            boundary = reversed(range(contour.npoints))
4580        else:
4581            boundary = range(contour.npoints)
4582
4583        dln = delaunay2d(points, mode="xy", boundaries=[boundary])
4584        dln.compute_normals(points=False)  # fixes reversd faces
4585        dln.lw(0.5)
4586
4587        dln.pipeline = utils.OperationNode(
4588            "generate_mesh",
4589            parents=[self, contour],
4590            comment=f"#cells {dln.inputdata().GetNumberOfCells()}",
4591        )
4592        return dln
4593
4594    def reconstruct_surface(
4595        self,
4596        dims=(100, 100, 100),
4597        radius=None,
4598        sample_size=None,
4599        hole_filling=True,
4600        bounds=(),
4601        padding=0.05,
4602    ):
4603        """
4604        Surface reconstruction from a scattered cloud of points.
4605
4606        Arguments:
4607            dims : (int)
4608                number of voxels in x, y and z to control precision.
4609            radius : (float)
4610                radius of influence of each point.
4611                Smaller values generally improve performance markedly.
4612                Note that after the signed distance function is computed,
4613                any voxel taking on the value >= radius
4614                is presumed to be "unseen" or uninitialized.
4615            sample_size : (int)
4616                if normals are not present
4617                they will be calculated using this sample size per point.
4618            hole_filling : (bool)
4619                enables hole filling, this generates
4620                separating surfaces between the empty and unseen portions of the volume.
4621            bounds : (list)
4622                region in space in which to perform the sampling
4623                in format (xmin,xmax, ymin,ymax, zim, zmax)
4624            padding : (float)
4625                increase by this fraction the bounding box
4626
4627        Examples:
4628            - [recosurface.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/recosurface.py)
4629
4630                ![](https://vedo.embl.es/images/advanced/recosurface.png)
4631        """
4632        if not utils.is_sequence(dims):
4633            dims = (dims, dims, dims)
4634
4635        sdf = vtk.vtkSignedDistance()
4636
4637        if len(bounds) == 6:
4638            sdf.SetBounds(bounds)
4639        else:
4640            x0, x1, y0, y1, z0, z1 = self.bounds()
4641            sdf.SetBounds(
4642                x0 - (x1 - x0) * padding,
4643                x1 + (x1 - x0) * padding,
4644                y0 - (y1 - y0) * padding,
4645                y1 + (y1 - y0) * padding,
4646                z0 - (z1 - z0) * padding,
4647                z1 + (z1 - z0) * padding,
4648            )
4649
4650        pd = self.polydata()
4651
4652        if pd.GetPointData().GetNormals():
4653            sdf.SetInputData(pd)
4654        else:
4655            normals = vtk.vtkPCANormalEstimation()
4656            normals.SetInputData(pd)
4657            if not sample_size:
4658                sample_size = int(pd.GetNumberOfPoints() / 50)
4659            normals.SetSampleSize(sample_size)
4660            normals.SetNormalOrientationToGraphTraversal()
4661            sdf.SetInputConnection(normals.GetOutputPort())
4662            # print("Recalculating normals with sample size =", sample_size)
4663
4664        if radius is None:
4665            radius = self.diagonal_size() / (sum(dims) / 3) * 5
4666            # print("Calculating mesh from points with radius =", radius)
4667
4668        sdf.SetRadius(radius)
4669        sdf.SetDimensions(dims)
4670        sdf.Update()
4671
4672        surface = vtk.vtkExtractSurface()
4673        surface.SetRadius(radius * 0.99)
4674        surface.SetHoleFilling(hole_filling)
4675        surface.ComputeNormalsOff()
4676        surface.ComputeGradientsOff()
4677        surface.SetInputConnection(sdf.GetOutputPort())
4678        surface.Update()
4679        m = vedo.mesh.Mesh(surface.GetOutput(), c=self.color())
4680
4681        m.pipeline = utils.OperationNode(
4682            "reconstruct_surface", parents=[self],
4683            comment=f"#pts {m.inputdata().GetNumberOfPoints()}"
4684        )
4685        return m
4686
4687    def compute_clustering(self, radius):
4688        """
4689        Cluster points in space. The `radius` is the radius of local search.
4690        An array named "ClusterId" is added to the vertex points.
4691
4692        Examples:
4693            - [clustering.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/clustering.py)
4694
4695                ![](https://vedo.embl.es/images/basic/clustering.png)
4696        """
4697        cluster = vtk.vtkEuclideanClusterExtraction()
4698        cluster.SetInputData(self.inputdata())
4699        cluster.SetExtractionModeToAllClusters()
4700        cluster.SetRadius(radius)
4701        cluster.ColorClustersOn()
4702        cluster.Update()
4703        idsarr = cluster.GetOutput().GetPointData().GetArray("ClusterId")
4704        self.inputdata().GetPointData().AddArray(idsarr)
4705
4706        self.pipeline = utils.OperationNode(
4707            "compute_clustering", parents=[self], comment=f"radius = {radius}"
4708        )
4709        return self
4710
4711    def compute_connections(self, radius, mode=0, regions=(), vrange=(0, 1), seeds=(), angle=0):
4712        """
4713        Extracts and/or segments points from a point cloud based on geometric distance measures
4714        (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range.
4715        The default operation is to segment the points into "connected" regions where the connection
4716        is determined by an appropriate distance measure. Each region is given a region id.
4717
4718        Optionally, the filter can output the largest connected region of points; a particular region
4719        (via id specification); those regions that are seeded using a list of input point ids;
4720        or the region of points closest to a specified position.
4721
4722        The key parameter of this filter is the radius defining a sphere around each point which defines
4723        a local neighborhood: any other points in the local neighborhood are assumed connected to the point.
4724        Note that the radius is defined in absolute terms.
4725
4726        Other parameters are used to further qualify what it means to be a neighboring point.
4727        For example, scalar range and/or point normals can be used to further constrain the neighborhood.
4728        Also the extraction mode defines how the filter operates.
4729        By default, all regions are extracted but it is possible to extract particular regions;
4730        the region closest to a seed point; seeded regions; or the largest region found while processing.
4731        By default, all regions are extracted.
4732
4733        On output, all points are labeled with a region number.
4734        However note that the number of input and output points may not be the same:
4735        if not extracting all regions then the output size may be less than the input size.
4736
4737        Arguments:
4738            radius : (float)
4739                variable specifying a local sphere used to define local point neighborhood
4740            mode : (int)
4741                - 0,  Extract all regions
4742                - 1,  Extract point seeded regions
4743                - 2,  Extract largest region
4744                - 3,  Test specified regions
4745                - 4,  Extract all regions with scalar connectivity
4746                - 5,  Extract point seeded regions
4747            regions : (list)
4748                a list of non-negative regions id to extract
4749            vrange : (list)
4750                scalar range to use to extract points based on scalar connectivity
4751            seeds : (list)
4752                a list of non-negative point seed ids
4753            angle : (list)
4754                points are connected if the angle between their normals is
4755                within this angle threshold (expressed in degrees).
4756        """
4757        # https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html
4758        cpf = vtk.vtkConnectedPointsFilter()
4759        cpf.SetInputData(self.polydata())
4760        cpf.SetRadius(radius)
4761        if mode == 0:  # Extract all regions
4762            pass
4763
4764        elif mode == 1:  # Extract point seeded regions
4765            cpf.SetExtractionModeToPointSeededRegions()
4766            for s in seeds:
4767                cpf.AddSeed(s)
4768
4769        elif mode == 2:  # Test largest region
4770            cpf.SetExtractionModeToLargestRegion()
4771
4772        elif mode == 3:  # Test specified regions
4773            cpf.SetExtractionModeToSpecifiedRegions()
4774            for r in regions:
4775                cpf.AddSpecifiedRegion(r)
4776
4777        elif mode == 4:  # Extract all regions with scalar connectivity
4778            cpf.SetExtractionModeToLargestRegion()
4779            cpf.ScalarConnectivityOn()
4780            cpf.SetScalarRange(vrange[0], vrange[1])
4781
4782        elif mode == 5:  # Extract point seeded regions
4783            cpf.SetExtractionModeToLargestRegion()
4784            cpf.ScalarConnectivityOn()
4785            cpf.SetScalarRange(vrange[0], vrange[1])
4786            cpf.AlignedNormalsOn()
4787            cpf.SetNormalAngle(angle)
4788
4789        cpf.Update()
4790        return self._update(cpf.GetOutput())
4791
4792    def compute_camera_distance(self):
4793        """
4794        Calculate the distance from points to the camera.
4795        A pointdata array is created with name 'DistanceToCamera'.
4796        """
4797        if vedo.plotter_instance.renderer:
4798            poly = self.polydata()
4799            dc = vtk.vtkDistanceToCamera()
4800            dc.SetInputData(poly)
4801            dc.SetRenderer(vedo.plotter_instance.renderer)
4802            dc.Update()
4803            return self._update(dc.GetOutput())
4804        return self
4805
4806    def density(
4807        self, dims=(40, 40, 40), bounds=None, radius=None, compute_gradient=False, locator=None
4808    ):
4809        """
4810        Generate a density field from a point cloud. Input can also be a set of 3D coordinates.
4811        Output is a `Volume`.
4812
4813        The local neighborhood is specified as the `radius` around each sample position (each voxel).
4814        The density is expressed as the number of counts in the radius search.
4815
4816        Arguments:
4817            dims : (int,list)
4818                number of voxels in x, y and z of the output Volume.
4819            compute_gradient : (bool)
4820                Turn on/off the generation of the gradient vector,
4821                gradient magnitude scalar, and function classification scalar.
4822                By default this is off. Note that this will increase execution time
4823                and the size of the output. (The names of these point data arrays are:
4824                "Gradient", "Gradient Magnitude", and "Classification")
4825            locator : (vtkPointLocator)
4826                can be assigned from a previous call for speed (access it via `object.point_locator`).
4827
4828        Examples:
4829            - [plot_density3d.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/plot_density3d.py)
4830
4831                ![](https://vedo.embl.es/images/pyplot/plot_density3d.png)
4832        """
4833        pdf = vtk.vtkPointDensityFilter()
4834        pdf.SetInputData(self.polydata())
4835
4836        if not utils.is_sequence(dims):
4837            dims = [dims, dims, dims]
4838
4839        if bounds is None:
4840            bounds = list(self.bounds())
4841        elif len(bounds) == 4:
4842            bounds = [*bounds, 0, 0]
4843
4844        if bounds[5] - bounds[4] == 0 or len(dims) == 2:  # its 2D
4845            dims = list(dims)
4846            dims = [dims[0], dims[1], 2]
4847            diag = self.diagonal_size()
4848            bounds[5] = bounds[4] + diag / 1000
4849        pdf.SetModelBounds(bounds)
4850
4851        pdf.SetSampleDimensions(dims)
4852
4853        if locator:
4854            pdf.SetLocator(locator)
4855
4856        pdf.SetDensityEstimateToFixedRadius()
4857        if radius is None:
4858            radius = self.diagonal_size() / 20
4859        pdf.SetRadius(radius)
4860
4861        pdf.SetComputeGradient(compute_gradient)
4862        pdf.Update()
4863        img = pdf.GetOutput()
4864        vol = vedo.volume.Volume(img).mode(1)
4865        vol.name = "PointDensity"
4866        vol.info["radius"] = radius
4867        vol.locator = pdf.GetLocator()
4868
4869        vol.pipeline = utils.OperationNode(
4870            "density", parents=[self], comment=f"dims = {tuple(vol.dimensions())}"
4871        )
4872        return vol
4873
4874    def densify(self, target_distance=0.1, nclosest=6, radius=None, niter=1, nmax=None):
4875        """
4876        Return a copy of the cloud with new added points.
4877        The new points are created in such a way that all points in any local neighborhood are
4878        within a target distance of one another.
4879
4880        For each input point, the distance to all points in its neighborhood is computed.
4881        If any of its neighbors is further than the target distance,
4882        the edge connecting the point and its neighbor is bisected and
4883        a new point is inserted at the bisection point.
4884        A single pass is completed once all the input points are visited.
4885        Then the process repeats to the number of iterations.
4886
4887        Examples:
4888            - [densifycloud.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/densifycloud.py)
4889
4890                ![](https://vedo.embl.es/images/volumetric/densifycloud.png)
4891
4892        .. note::
4893            Points will be created in an iterative fashion until all points in their
4894            local neighborhood are the target distance apart or less.
4895            Note that the process may terminate early due to the
4896            number of iterations. By default the target distance is set to 0.5.
4897            Note that the target_distance should be less than the radius
4898            or nothing will change on output.
4899
4900        .. warning::
4901            This class can generate a lot of points very quickly.
4902            The maximum number of iterations is by default set to =1.0 for this reason.
4903            Increase the number of iterations very carefully.
4904            Also, `nmax` can be set to limit the explosion of points.
4905            It is also recommended that a N closest neighborhood is used.
4906
4907        """
4908        src = vtk.vtkProgrammableSource()
4909        opts = self.points()
4910
4911        def _readPoints():
4912            output = src.GetPolyDataOutput()
4913            points = vtk.vtkPoints()
4914            for p in opts:
4915                points.InsertNextPoint(p)
4916            output.SetPoints(points)
4917
4918        src.SetExecuteMethod(_readPoints)
4919
4920        dens = vtk.vtkDensifyPointCloudFilter()
4921        dens.SetInputConnection(src.GetOutputPort())
4922        dens.InterpolateAttributeDataOn()
4923        dens.SetTargetDistance(target_distance)
4924        dens.SetMaximumNumberOfIterations(niter)
4925        if nmax:
4926            dens.SetMaximumNumberOfPoints(nmax)
4927
4928        if radius:
4929            dens.SetNeighborhoodTypeToRadius()
4930            dens.SetRadius(radius)
4931        elif nclosest:
4932            dens.SetNeighborhoodTypeToNClosest()
4933            dens.SetNumberOfClosestPoints(nclosest)
4934        else:
4935            vedo.logger.error("set either radius or nclosest")
4936            raise RuntimeError()
4937        dens.Update()
4938        pts = utils.vtk2numpy(dens.GetOutput().GetPoints().GetData())
4939        cld = Points(pts, c=None).point_size(self.GetProperty().GetPointSize())
4940        cld.interpolate_data_from(self, n=nclosest, radius=radius)
4941        cld.name = "densifiedCloud"
4942
4943        cld.pipeline = utils.OperationNode(
4944            "densify", parents=[self], c="#e9c46a:",
4945            comment=f"#pts {cld.inputdata().GetNumberOfPoints()}"
4946        )
4947        return cld
4948
4949
4950    ###############################################################################
4951    ## stuff returning Volume
4952
4953    def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None):
4954        """
4955        Compute the `Volume` object whose voxels contains the signed distance from
4956        the point cloud. The point cloud must have Normals.
4957
4958        Arguments:
4959            bounds : (list, actor)
4960                bounding box sizes
4961            dims : (list)
4962                dimensions (nr. of voxels) of the output volume.
4963            invert : (bool)
4964                flip the sign
4965            maxradius : (float)
4966                specify how far out to propagate distance calculation
4967
4968        Examples:
4969            - [distance2mesh.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/distance2mesh.py)
4970
4971                ![](https://vedo.embl.es/images/basic/distance2mesh.png)
4972        """
4973        if bounds is None:
4974            bounds = self.bounds()
4975        if maxradius is None:
4976            maxradius = self.diagonal_size() / 2
4977        dist = vtk.vtkSignedDistance()
4978        dist.SetInputData(self.polydata())
4979        dist.SetRadius(maxradius)
4980        dist.SetBounds(bounds)
4981        dist.SetDimensions(dims)
4982        dist.Update()
4983        img = dist.GetOutput()
4984        if invert:
4985            mat = vtk.vtkImageMathematics()
4986            mat.SetInput1Data(img)
4987            mat.SetOperationToMultiplyByK()
4988            mat.SetConstantK(-1)
4989            mat.Update()
4990            img = mat.GetOutput()
4991
4992        vol = vedo.Volume(img)
4993        vol.name = "SignedDistanceVolume"
4994
4995        vol.pipeline = utils.OperationNode(
4996            "signed_distance",
4997            parents=[self],
4998            comment=f"dim = {tuple(vol.dimensions())}",
4999            c="#e9c46a:#0096c7",
5000        )
5001        return vol
5002
5003    def tovolume(
5004        self, kernel="shepard", radius=None, n=None, bounds=None, null_value=None, dims=(25, 25, 25)
5005    ):
5006        """
5007        Generate a `Volume` by interpolating a scalar
5008        or vector field which is only known on a scattered set of points or mesh.
5009        Available interpolation kernels are: shepard, gaussian, or linear.
5010
5011        Arguments:
5012            kernel : (str)
5013                interpolation kernel type [shepard]
5014            radius : (float)
5015                radius of the local search
5016            n : (int)
5017                number of point to use for interpolation
5018            bounds : (list)
5019                bounding box of the output Volume object
5020            dims : (list)
5021                dimensions of the output Volume object
5022            null_value : (float)
5023                value to be assigned to invalid points
5024
5025        Examples:
5026            - [interpolate_volume.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/interpolate_volume.py)
5027
5028                ![](https://vedo.embl.es/images/volumetric/59095175-1ec5a300-8918-11e9-8bc0-fd35c8981e2b.jpg)
5029        """
5030        if radius is None and not n:
5031            vedo.logger.error("please set either radius or n")
5032            raise RuntimeError
5033
5034        poly = self.polydata()
5035
5036        # Create a probe volume
5037        probe = vtk.vtkImageData()
5038        probe.SetDimensions(dims)
5039        if bounds is None:
5040            bounds = self.bounds()
5041        probe.SetOrigin(bounds[0], bounds[2], bounds[4])
5042        probe.SetSpacing(
5043            (bounds[1] - bounds[0]) / dims[0],
5044            (bounds[3] - bounds[2]) / dims[1],
5045            (bounds[5] - bounds[4]) / dims[2],
5046        )
5047
5048        if not self.point_locator:
5049            self.point_locator = vtk.vtkPointLocator()
5050            self.point_locator.SetDataSet(poly)
5051            self.point_locator.BuildLocator()
5052
5053        if kernel == "shepard":
5054            kern = vtk.vtkShepardKernel()
5055            kern.SetPowerParameter(2)
5056        elif kernel == "gaussian":
5057            kern = vtk.vtkGaussianKernel()
5058        elif kernel == "linear":
5059            kern = vtk.vtkLinearKernel()
5060        else:
5061            vedo.logger.error("Error in tovolume(), available kernels are:")
5062            vedo.logger.error(" [shepard, gaussian, linear]")
5063            raise RuntimeError()
5064
5065        if radius:
5066            kern.SetRadius(radius)
5067
5068        interpolator = vtk.vtkPointInterpolator()
5069        interpolator.SetInputData(probe)
5070        interpolator.SetSourceData(poly)
5071        interpolator.SetKernel(kern)
5072        interpolator.SetLocator(self.point_locator)
5073
5074        if n:
5075            kern.SetNumberOfPoints(n)
5076            kern.SetKernelFootprintToNClosest()
5077        else:
5078            kern.SetRadius(radius)
5079
5080        if null_value is not None:
5081            interpolator.SetNullValue(null_value)
5082        else:
5083            interpolator.SetNullPointsStrategyToClosestPoint()
5084        interpolator.Update()
5085
5086        vol = vedo.Volume(interpolator.GetOutput())
5087
5088        vol.pipeline = utils.OperationNode(
5089            "signed_distance",
5090            parents=[self],
5091            comment=f"dim = {tuple(vol.dimensions())}",
5092            c="#e9c46a:#0096c7",
5093        )
5094        return vol
5095
5096    def generate_random_data(self):
5097        """Fill a dataset with random attributes"""
5098        gen = vtk.vtkRandomAttributeGenerator()
5099        gen.SetInputData(self._data)
5100        gen.GenerateAllDataOn()
5101        gen.SetDataTypeToFloat()
5102        gen.GeneratePointNormalsOff()
5103        gen.GeneratePointTensorsOn()
5104        gen.GenerateCellScalarsOn()
5105        gen.Update()
5106
5107        m = self._update(gen.GetOutput())
5108
5109        m.pipeline = utils.OperationNode("generate\nrandom data", parents=[self])
5110        return m
class Points(vedo.base.BaseActor, vtkmodules.vtkRenderingCore.vtkActor):
 716class Points(BaseActor, vtk.vtkActor):
 717    """Work with pointclouds."""
 718
 719    def __init__(self, inputobj=None, r=4, c=(0.2, 0.2, 0.2), alpha=1, blur=False, emissive=True):
 720        """
 721        Build an object made of only vertex points for a list of 2D/3D points.
 722        Both shapes (N, 3) or (3, N) are accepted as input, if N>3.
 723        For very large point clouds a list of colors and alpha can be assigned to each
 724        point in the form c=[(R,G,B,A), ... ] where 0<=R<256, ... 0<=A<256.
 725
 726        Arguments:
 727            inputobj : (list, tuple)
 728            r : (int)
 729                Point radius in units of pixels.
 730            c : (str, list)
 731                Color name or rgb tuple.
 732            alpha : (float)
 733                Transparency in range [0,1].
 734            blur : (bool)
 735                Apply a gaussian convolution filter to the points.
 736                In this case the radius `r` is in absolute units of the mesh coordinates.
 737            emissive : (bool)
 738                Halo of point becomes emissive.
 739
 740        Example:
 741            ```python
 742            from vedo import *
 743
 744            def fibonacci_sphere(n):
 745                s = np.linspace(0, n, num=n, endpoint=False)
 746                theta = s * 2.399963229728653
 747                y = 1 - s * (2/(n-1))
 748                r = np.sqrt(1 - y * y)
 749                x = np.cos(theta) * r
 750                z = np.sin(theta) * r
 751                return [x,y,z]
 752
 753            Points(fibonacci_sphere(1000)).show(axes=1).close()
 754            ```
 755            ![](https://vedo.embl.es/images/feats/fibonacci.png)
 756        """
 757
 758        vtk.vtkActor.__init__(self)
 759        BaseActor.__init__(self)
 760
 761        self._data = None
 762
 763        if blur:
 764            self._mapper = vtk.vtkPointGaussianMapper()
 765            if emissive:
 766                self._mapper.SetEmissive(bool(emissive))
 767            self._mapper.SetScaleFactor(r * 1.4142)
 768
 769            # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
 770            if alpha < 1:
 771                self._mapper.SetSplatShaderCode(
 772                    "//VTK::Color::Impl\n"
 773                    "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
 774                    "if (dist > 1.0) {\n"
 775                    "   discard;\n"
 776                    "} else {\n"
 777                    f"  float scale = ({alpha} - dist);\n"
 778                    "   ambientColor *= scale;\n"
 779                    "   diffuseColor *= scale;\n"
 780                    "}\n"
 781                )
 782                alpha = 1
 783
 784        else:
 785            self._mapper = vtk.vtkPolyDataMapper()
 786        self.SetMapper(self._mapper)
 787
 788        self._bfprop = None  # backface property holder
 789
 790        self._scals_idx = 0  # index of the active scalar changed from CLI
 791        self._ligthingnr = 0  # index of the lighting mode changed from CLI
 792        self._cmap_name = ""  # remember the name for self._keypress
 793        # self.name = "Points" # better not to give it a name here
 794
 795        self.property = self.GetProperty()
 796
 797        try:
 798            if not blur:
 799                self.property.RenderPointsAsSpheresOn()
 800        except AttributeError:
 801            pass
 802
 803        if inputobj is None:  ####################
 804            self._data = vtk.vtkPolyData()
 805            return
 806        ########################################
 807
 808        self.property.SetRepresentationToPoints()
 809        self.property.SetPointSize(r)
 810        self.property.LightingOff()
 811
 812        if isinstance(inputobj, vedo.BaseActor):
 813            inputobj = inputobj.points()  # numpy
 814
 815        ######
 816        if isinstance(inputobj, vtk.vtkActor):
 817            poly_copy = vtk.vtkPolyData()
 818            pr = vtk.vtkProperty()
 819            pr.DeepCopy(inputobj.GetProperty())
 820            poly_copy.DeepCopy(inputobj.GetMapper().GetInput())
 821            pr.SetRepresentationToPoints()
 822            pr.SetPointSize(r)
 823            self._data = poly_copy
 824            self._mapper.SetInputData(poly_copy)
 825            self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility())
 826            self.SetProperty(pr)
 827            self.property = pr
 828
 829        elif isinstance(inputobj, vtk.vtkPolyData):
 830            if inputobj.GetNumberOfCells() == 0:
 831                carr = vtk.vtkCellArray()
 832                for i in range(inputobj.GetNumberOfPoints()):
 833                    carr.InsertNextCell(1)
 834                    carr.InsertCellPoint(i)
 835                inputobj.SetVerts(carr)
 836            self._data = inputobj  # cache vtkPolyData and mapper for speed
 837
 838        elif utils.is_sequence(inputobj):  # passing point coords
 839            plist = inputobj
 840            n = len(plist)
 841
 842            if n == 3:  # assume plist is in the format [all_x, all_y, all_z]
 843                if utils.is_sequence(plist[0]) and len(plist[0]) > 3:
 844                    plist = np.stack((plist[0], plist[1], plist[2]), axis=1)
 845            elif n == 2:  # assume plist is in the format [all_x, all_y, 0]
 846                if utils.is_sequence(plist[0]) and len(plist[0]) > 3:
 847                    plist = np.stack((plist[0], plist[1], np.zeros(len(plist[0]))), axis=1)
 848
 849            # if n and len(plist[0]) == 2:  # make it 3d
 850            #     plist = np.c_[np.array(plist), np.zeros(len(plist))]
 851            plist = utils.make3d(plist)
 852
 853            if (
 854                utils.is_sequence(c)
 855                and (len(c) > 3 or (utils.is_sequence(c[0]) and len(c[0]) == 4))
 856            ) or utils.is_sequence(alpha):
 857
 858                cols = c
 859
 860                n = len(plist)
 861                if n != len(cols):
 862                    vedo.logger.error(f"mismatch in Points() colors array lengths {n} and {len(cols)}")
 863                    raise RuntimeError()
 864
 865                src = vtk.vtkPointSource()
 866                src.SetNumberOfPoints(n)
 867                src.Update()
 868
 869                vgf = vtk.vtkVertexGlyphFilter()
 870                vgf.SetInputData(src.GetOutput())
 871                vgf.Update()
 872                pd = vgf.GetOutput()
 873
 874                pd.GetPoints().SetData(utils.numpy2vtk(plist, dtype=np.float32))
 875
 876                ucols = vtk.vtkUnsignedCharArray()
 877                ucols.SetNumberOfComponents(4)
 878                ucols.SetName("Points_RGBA")
 879                if utils.is_sequence(alpha):
 880                    if len(alpha) != n:
 881                        vedo.logger.error(f"mismatch in Points() alpha array lengths {n} and {len(cols)}")
 882                        raise RuntimeError()
 883                    alphas = alpha
 884                    alpha = 1
 885                else:
 886                    alphas = (alpha,) * n
 887
 888                if utils.is_sequence(cols):
 889                    c = None
 890                    if len(cols[0]) == 4:
 891                        for i in range(n):  # FAST
 892                            rc, gc, bc, ac = cols[i]
 893                            ucols.InsertNextTuple4(rc, gc, bc, ac)
 894                    else:
 895                        for i in range(n):  # SLOW
 896                            rc, gc, bc = colors.get_color(cols[i])
 897                            ucols.InsertNextTuple4(rc * 255, gc * 255, bc * 255, alphas[i] * 255)
 898                else:
 899                    c = cols
 900
 901                pd.GetPointData().AddArray(ucols)
 902                pd.GetPointData().SetActiveScalars("Points_RGBA")
 903                self._mapper.SetInputData(pd)
 904                self._mapper.ScalarVisibilityOn()
 905                self._data = pd
 906
 907            else:
 908
 909                pd = utils.buildPolyData(plist)
 910                self._mapper.SetInputData(pd)
 911                c = colors.get_color(c)
 912                self.property.SetColor(c)
 913                self.property.SetOpacity(alpha)
 914                self._data = pd
 915
 916            ##########
 917            self.pipeline = utils.OperationNode(
 918                self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}"
 919            )
 920            return
 921            ##########
 922
 923        elif isinstance(inputobj, str):
 924            verts = vedo.file_io.load(inputobj)
 925            self.filename = inputobj
 926            self._data = verts.polydata()
 927
 928        else:
 929
 930            # try to extract the points from the VTK input data object
 931            try:
 932                vvpts = inputobj.GetPoints()
 933                pd = vtk.vtkPolyData()
 934                pd.SetPoints(vvpts)
 935                for i in range(inputobj.GetPointData().GetNumberOfArrays()):
 936                    arr = inputobj.GetPointData().GetArray(i)
 937                    pd.GetPointData().AddArray(arr)
 938
 939                self._mapper.SetInputData(pd)
 940                c = colors.get_color(c)
 941                self.property.SetColor(c)
 942                self.property.SetOpacity(alpha)
 943                self._data = pd
 944            except:
 945                vedo.logger.error(f"cannot build Points from type {type(inputobj)}")
 946                raise RuntimeError()
 947
 948        c = colors.get_color(c)
 949        self.property.SetColor(c)
 950        self.property.SetOpacity(alpha)
 951
 952        self._mapper.SetInputData(self._data)
 953
 954        self.pipeline = utils.OperationNode(
 955            self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}"
 956        )
 957        return
 958
 959    def _repr_html_(self):
 960        """
 961        HTML representation of the Point cloud object for Jupyter Notebooks.
 962
 963        Returns:
 964            HTML text with the image and some properties.
 965        """
 966        import io
 967        import base64
 968        from PIL import Image
 969
 970        library_name = "vedo.pointcloud.Points"
 971        help_url = "https://vedo.embl.es/docs/vedo/pointcloud.html"
 972
 973        arr = self.thumbnail()
 974        im = Image.fromarray(arr)
 975        buffered = io.BytesIO()
 976        im.save(buffered, format="PNG", quality=100)
 977        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
 978        url = "data:image/png;base64," + encoded
 979        image = f"<img src='{url}'></img>"
 980
 981        bounds = "<br/>".join(
 982            [
 983                utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4)
 984                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
 985            ]
 986        )
 987        average_size = "{size:.3f}".format(size=self.average_size())
 988
 989        help_text = ""
 990        if self.name:
 991            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
 992        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
 993        if self.filename:
 994            dots = ""
 995            if len(self.filename) > 30:
 996                dots = "..."
 997            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
 998
 999        pdata = ""
1000        if self._data.GetPointData().GetScalars():
1001            if self._data.GetPointData().GetScalars().GetName():
1002                name = self._data.GetPointData().GetScalars().GetName()
1003                pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>"
1004
1005        cdata = ""
1006        if self._data.GetCellData().GetScalars():
1007            if self._data.GetCellData().GetScalars().GetName():
1008                name = self._data.GetCellData().GetScalars().GetName()
1009                cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>"
1010
1011        allt = [
1012            "<table>",
1013            "<tr>",
1014            "<td>",
1015            image,
1016            "</td>",
1017            "<td style='text-align: center; vertical-align: center;'><br/>",
1018            help_text,
1019            "<table>",
1020            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
1021            "<tr><td><b> center of mass </b></td><td>"
1022            + utils.precision(self.center_of_mass(), 3)
1023            + "</td></tr>",
1024            "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>",
1025            "<tr><td><b> nr. points </b></td><td>" + str(self.npoints) + "</td></tr>",
1026            pdata,
1027            cdata,
1028            "</table>",
1029            "</table>",
1030        ]
1031        return "\n".join(allt)
1032
1033
1034    ##################################################################################
1035    def _update(self, polydata):
1036        # Overwrite the polygonal mesh with a new vtkPolyData
1037        self._data = polydata
1038        self.mapper().SetInputData(polydata)
1039        self.mapper().Modified()
1040        return self
1041
1042    def __add__(self, meshs):
1043        if isinstance(meshs, list):
1044            alist = [self]
1045            for l in meshs:
1046                if isinstance(l, vedo.Assembly):
1047                    alist += l.unpack()
1048                else:
1049                    alist += l
1050            return vedo.assembly.Assembly(alist)
1051
1052        if isinstance(meshs, vedo.Assembly):
1053            return meshs + self  # use Assembly.__add__
1054
1055        return vedo.assembly.Assembly([self, meshs])
1056
1057    def polydata(self, transformed=True):
1058        """
1059        Returns the `vtkPolyData` object associated to a `Mesh`.
1060
1061        .. note::
1062            If `transformed=True` return a copy of polydata that corresponds
1063            to the current mesh position in space.
1064        """
1065        if not self._data:
1066            self._data = self.mapper().GetInput()
1067            return self._data
1068
1069        if transformed:
1070            # if self.GetIsIdentity() or self._data.GetNumberOfPoints()==0: # commmentd out on 15th feb 2020
1071            if self._data.GetNumberOfPoints() == 0:
1072                # no need to do much
1073                return self._data
1074
1075            # otherwise make a copy that corresponds to
1076            # the actual position in space of the mesh
1077            M = self.GetMatrix()
1078            transform = vtk.vtkTransform()
1079            transform.SetMatrix(M)
1080            tp = vtk.vtkTransformPolyDataFilter()
1081            tp.SetTransform(transform)
1082            tp.SetInputData(self._data)
1083            tp.Update()
1084            return tp.GetOutput()
1085
1086        return self._data
1087
1088
1089    def clone(self, deep=True, transformed=False):
1090        """
1091        Clone a `PointCloud` or `Mesh` object to make an exact copy of it.
1092
1093        Arguments:
1094            deep : (bool)
1095                if False only build a shallow copy of the object (faster copy).
1096
1097            transformed : (bool)
1098                if True reset the current transformation of the copy to unit.
1099
1100        Examples:
1101            - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py)
1102
1103               ![](https://vedo.embl.es/images/basic/mirror.png)
1104        """
1105        poly = self.polydata(transformed)
1106        poly_copy = vtk.vtkPolyData()
1107        if deep:
1108            poly_copy.DeepCopy(poly)
1109        else:
1110            poly_copy.ShallowCopy(poly)
1111
1112        if isinstance(self, vedo.Mesh):
1113            cloned = vedo.Mesh(poly_copy)
1114        else:
1115            cloned = Points(poly_copy)
1116
1117        pr = vtk.vtkProperty()
1118        pr.DeepCopy(self.GetProperty())
1119        cloned.SetProperty(pr)
1120        cloned.property = pr
1121
1122        if self.GetBackfaceProperty():
1123            bfpr = vtk.vtkProperty()
1124            bfpr.DeepCopy(self.GetBackfaceProperty())
1125            cloned.SetBackfaceProperty(bfpr)
1126
1127        if not transformed:
1128            if self.transform:
1129                # already has a transform which can be non linear, so use that
1130                cloned.SetUserTransform(self.transform)
1131            else:
1132                # assign the same transformation to the copy
1133                cloned.SetOrigin(self.GetOrigin())
1134                cloned.SetScale(self.GetScale())
1135                cloned.SetOrientation(self.GetOrientation())
1136                cloned.SetPosition(self.GetPosition())
1137
1138        mp = cloned.mapper()
1139        sm = self.mapper()
1140        mp.SetScalarVisibility(sm.GetScalarVisibility())
1141        mp.SetScalarRange(sm.GetScalarRange())
1142        mp.SetColorMode(sm.GetColorMode())
1143        lsr = sm.GetUseLookupTableScalarRange()
1144        mp.SetUseLookupTableScalarRange(lsr)
1145        mp.SetScalarMode(sm.GetScalarMode())
1146        lut = sm.GetLookupTable()
1147        if lut:
1148            mp.SetLookupTable(lut)
1149
1150        if self.GetTexture():
1151            cloned.texture(self.GetTexture())
1152
1153        cloned.SetPickable(self.GetPickable())
1154
1155        cloned.base = np.array(self.base)
1156        cloned.top = np.array(self.top)
1157        cloned.name = str(self.name)
1158        cloned.filename = str(self.filename)
1159        cloned.info = dict(self.info)
1160
1161        # better not to share the same locators with original obj
1162        cloned.point_locator = None
1163        cloned.cell_locator = None
1164
1165        cloned.pipeline = utils.OperationNode("clone", parents=[self], shape="diamond", c="#edede9")
1166        return cloned
1167
1168    def clone2d(
1169        self,
1170        pos=(0, 0),
1171        coordsys=4,
1172        scale=None,
1173        c=None,
1174        alpha=None,
1175        ps=2,
1176        lw=1,
1177        sendback=False,
1178        layer=0,
1179    ):
1180        """
1181        Copy a 3D Mesh into a static 2D image. Returns a `vtkActor2D`.
1182
1183        Arguments:
1184            coordsys : (int)
1185                the coordinate system, options are
1186                - 0 = Displays
1187                - 1 = Normalized Display
1188                - 2 = Viewport (origin is the bottom-left corner of the window)
1189                - 3 = Normalized Viewport
1190                - 4 = View (origin is the center of the window)
1191                - 5 = World (anchor the 2d image to mesh)
1192
1193            ps : (int)
1194                point size in pixel units
1195
1196            lw : (int)
1197                line width in pixel units
1198
1199            sendback : (bool)
1200                put it behind any other 3D object
1201
1202        Examples:
1203            - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py)
1204
1205                ![](https://vedo.embl.es/images/other/clone2d.png)
1206        """
1207        if scale is None:
1208            msiz = self.diagonal_size()
1209            if vedo.plotter_instance and vedo.plotter_instance.window:
1210                sz = vedo.plotter_instance.window.GetSize()
1211                dsiz = utils.mag(sz)
1212                scale = dsiz / msiz / 10
1213            else:
1214                scale = 350 / msiz
1215
1216        cmsh = self.clone()
1217        poly = cmsh.pos(0, 0, 0).scale(scale).polydata()
1218
1219        mapper3d = self.mapper()
1220        cm = mapper3d.GetColorMode()
1221        lut = mapper3d.GetLookupTable()
1222        sv = mapper3d.GetScalarVisibility()
1223        use_lut = mapper3d.GetUseLookupTableScalarRange()
1224        vrange = mapper3d.GetScalarRange()
1225        sm = mapper3d.GetScalarMode()
1226
1227        mapper2d = vtk.vtkPolyDataMapper2D()
1228        mapper2d.ShallowCopy(mapper3d)
1229        mapper2d.SetInputData(poly)
1230        mapper2d.SetColorMode(cm)
1231        mapper2d.SetLookupTable(lut)
1232        mapper2d.SetScalarVisibility(sv)
1233        mapper2d.SetUseLookupTableScalarRange(use_lut)
1234        mapper2d.SetScalarRange(vrange)
1235        mapper2d.SetScalarMode(sm)
1236
1237        act2d = vtk.vtkActor2D()
1238        act2d.SetMapper(mapper2d)
1239        act2d.SetLayerNumber(layer)
1240        csys = act2d.GetPositionCoordinate()
1241        csys.SetCoordinateSystem(coordsys)
1242        act2d.SetPosition(pos)
1243        if c is not None:
1244            c = colors.get_color(c)
1245            act2d.GetProperty().SetColor(c)
1246            mapper2d.SetScalarVisibility(False)
1247        else:
1248            act2d.GetProperty().SetColor(cmsh.color())
1249        if alpha is not None:
1250            act2d.GetProperty().SetOpacity(alpha)
1251        else:
1252            act2d.GetProperty().SetOpacity(cmsh.alpha())
1253        act2d.GetProperty().SetPointSize(ps)
1254        act2d.GetProperty().SetLineWidth(lw)
1255        act2d.GetProperty().SetDisplayLocationToForeground()
1256        if sendback:
1257            act2d.GetProperty().SetDisplayLocationToBackground()
1258
1259        # print(csys.GetCoordinateSystemAsString())
1260        # print(act2d.GetHeight(), act2d.GetWidth(), act2d.GetLayerNumber())
1261        return act2d
1262
1263    def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2):
1264        """
1265        Add a trailing line to mesh.
1266        This new mesh is accessible through `mesh.trail`.
1267
1268        Arguments:
1269            offset : (float)
1270                set an offset vector from the object center.
1271            n : (int)
1272                number of segments
1273            lw : (float)
1274                line width of the trail
1275
1276        Examples:
1277            - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py)
1278
1279                ![](https://vedo.embl.es/images/simulations/trail.gif)
1280
1281            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1282            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1283        """
1284        if self.trail is None:
1285            pos = self.GetPosition()
1286            self.trail_offset = np.asarray(offset)
1287            self.trail_points = [pos] * n
1288
1289            if c is None:
1290                col = self.GetProperty().GetColor()
1291            else:
1292                col = colors.get_color(c)
1293
1294            tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw)
1295            self.trail = tline  # holds the Line
1296        return self
1297
1298    def update_trail(self):
1299        """
1300        Update the trailing line of a moving object.
1301        """
1302        if isinstance(self, vedo.shapes.Arrow):
1303            currentpos = self.tipPoint()  # the tip of Arrow
1304        else:
1305            currentpos = np.array(self.GetPosition())
1306
1307        self.trail_points.append(currentpos)  # cycle
1308        self.trail_points.pop(0)
1309
1310        data = np.array(self.trail_points) - currentpos + self.trail_offset
1311        tpoly = self.trail.polydata(False)
1312        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1313        self.trail.SetPosition(currentpos)
1314        return self
1315
1316
1317    def _compute_shadow(self, plane, point, direction):
1318        shad = self.clone()
1319        shad._data.GetPointData().SetTCoords(None) # remove any texture coords
1320        shad.name = "Shadow"
1321
1322        pts = shad.points()
1323        if plane == 'x':
1324            # shad = shad.project_on_plane('x')
1325            # instead do it manually so in case of alpha<1 
1326            # we dont see glitches due to coplanar points
1327            # we leave a small tolerance of 0.1% in thickness
1328            x0, x1 = self.xbounds()
1329            pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[0]
1330            shad.points(pts)
1331            shad.x(point)
1332        elif plane == 'y':
1333            x0, x1 = self.ybounds()
1334            pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[1]
1335            shad.points(pts)
1336            shad.y(point)
1337        elif plane == "z":
1338            x0, x1 = self.zbounds()
1339            pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[2]
1340            shad.points(pts)
1341            shad.z(point)
1342        else:
1343            shad = shad.project_on_plane(plane, point, direction)
1344        return shad
1345
1346    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0):
1347        """
1348        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1349        The output is a new `Mesh` representing the shadow.
1350        This new mesh is accessible through `mesh.shadow`.
1351        By default the shadow mesh is placed on the bottom wall of the bounding box.
1352
1353        See also `pointcloud.project_on_plane()`.
1354
1355        Arguments:
1356            plane : (str, Plane)
1357                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1358                represents x-plane, y-plane and z-plane, respectively.
1359                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1360            point : (float, array)
1361                if plane is `str`, point should be a float represents the intercept.
1362                Otherwise, point is the camera point of perspective projection
1363            direction : (list)
1364                direction of oblique projection
1365            culling : (int)
1366                choose between front [1] or backface [-1] culling or None.
1367
1368        Examples:
1369            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1370            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1371            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1372
1373            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1374        """
1375        shad = self._compute_shadow(plane, point, direction)
1376        shad.c(c).alpha(alpha)
1377
1378        try:
1379            # Points dont have these methods
1380            shad.flat()
1381            if culling in (1, True):
1382                shad.frontface_culling()
1383            elif culling == -1:
1384                shad.backface_culling()
1385        except AttributeError:
1386            pass
1387
1388        shad.GetProperty().LightingOff()
1389        shad.SetPickable(False)
1390        shad.SetUseBounds(True)
1391
1392        if shad not in self.shadows:
1393            self.shadows.append(shad)
1394            shad.info = dict(plane=plane, point=point, direction=direction)
1395        return self
1396
1397    def update_shadows(self):
1398        """
1399        Update the shadows of a moving object.
1400        """
1401        for sha in self.shadows:
1402            plane = sha.info['plane']
1403            point = sha.info['point']
1404            direction = sha.info['direction']
1405            new_sha = self._compute_shadow(plane, point, direction)
1406            sha._update(new_sha._data)
1407        return self
1408
1409
1410    def delete_cells_by_point_index(self, indices):
1411        """
1412        Delete a list of vertices identified by any of their vertex index.
1413
1414        See also `delete_cells()`.
1415
1416        Examples:
1417            - [elete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/elete_mesh_pts.py)
1418
1419                ![](https://vedo.embl.es/images/basic/deleteMeshPoints.png)
1420        """
1421        cell_ids = vtk.vtkIdList()
1422        data = self.inputdata()
1423        data.BuildLinks()
1424        n = 0
1425        for i in np.unique(indices):
1426            data.GetPointCells(i, cell_ids)
1427            for j in range(cell_ids.GetNumberOfIds()):
1428                data.DeleteCell(cell_ids.GetId(j))  # flag cell
1429                n += 1
1430
1431        data.RemoveDeletedCells()
1432        self.mapper().Modified()
1433        self.pipeline = utils.OperationNode(f"delete {n} cells\nby point index", parents=[self])
1434        return self
1435
1436    def compute_normals_with_pca(self, n=20, orientation_point=None, invert=False):
1437        """
1438        Generate point normals using PCA (principal component analysis).
1439        Basically this estimates a local tangent plane around each sample point p
1440        by considering a small neighborhood of points around p, and fitting a plane
1441        to the neighborhood (via PCA).
1442
1443        Arguments:
1444            n : (int)
1445                neighborhood size to calculate the normal
1446            orientation_point : (list)
1447                adjust the +/- sign of the normals so that
1448                the normals all point towards a specified point. If None, perform a traversal
1449                of the point cloud and flip neighboring normals so that they are mutually consistent.
1450            invert : (bool)
1451                flip all normals
1452        """
1453        poly = self.polydata()
1454        pcan = vtk.vtkPCANormalEstimation()
1455        pcan.SetInputData(poly)
1456        pcan.SetSampleSize(n)
1457
1458        if orientation_point is not None:
1459            pcan.SetNormalOrientationToPoint()
1460            pcan.SetOrientationPoint(orientation_point)
1461        else:
1462            pcan.SetNormalOrientationToGraphTraversal()
1463
1464        if invert:
1465            pcan.FlipNormalsOn()
1466        pcan.Update()
1467
1468        varr = pcan.GetOutput().GetPointData().GetNormals()
1469        varr.SetName("Normals")
1470        self.inputdata().GetPointData().SetNormals(varr)
1471        self.inputdata().GetPointData().Modified()
1472        return self
1473
1474    def compute_acoplanarity(self, n=25, radius=None, on="points"):
1475        """
1476        Compute acoplanarity which is a measure of how much a local region of the mesh
1477        differs from a plane.
1478        The information is stored in a `pointdata` or `celldata` array with name 'Acoplanarity'.
1479        Either `n` (number of neighbour points) or `radius` (radius of local search) can be specified.
1480        If a radius value is given and not enough points fall inside it, then a -1 is stored.
1481
1482        Example:
1483            ```python
1484            from vedo import *
1485            msh = ParametricShape('RandomHills')
1486            msh.compute_acoplanarity(radius=0.1, on='cells')
1487            msh.cmap("coolwarm", on='cells').add_scalarbar()
1488            msh.show(axes=1).close()
1489            ```
1490            ![](https://vedo.embl.es/images/feats/acoplanarity.jpg)
1491        """
1492        acoplanarities = []
1493        if "point" in on:
1494            pts = self.points()
1495        elif "cell" in on:
1496            pts = self.cell_centers()
1497        else:
1498            raise ValueError(f"In compute_acoplanarity() set on to either 'cells' or 'points', not {on}")
1499
1500        for p in utils.progressbar(pts, delay=5, width=15, title=f"{on} acoplanarity"):
1501            if n:
1502                data = self.closest_point(p, n=n)
1503                npts = n
1504            elif radius:
1505                data = self.closest_point(p, radius=radius)
1506                npts = len(data)
1507
1508            try:
1509                center = data.mean(axis=0)
1510                res = np.linalg.svd(data - center)
1511                acoplanarities.append(res[1][2] / npts)
1512            except:
1513                acoplanarities.append(-1.0)
1514
1515        if "point" in on:
1516            self.pointdata["Acoplanarity"] = np.array(acoplanarities, dtype=float)
1517        else:
1518            self.celldata["Acoplanarity"] = np.array(acoplanarities, dtype=float)
1519        return self
1520
1521    def distance_to(self, pcloud, signed=False, invert=False, name="Distance"):
1522        """
1523        Computes the distance from one point cloud or mesh to another point cloud or mesh.
1524        This new `pointdata` array is saved with default name "Distance".
1525
1526        Keywords `signed` and `invert` are used to compute signed distance,
1527        but the mesh in that case must have polygonal faces (not a simple point cloud),
1528        and normals must also be computed.
1529
1530        Examples:
1531            - [distance2mesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/distance2mesh.py)
1532
1533                ![](https://vedo.embl.es/images/basic/distance2mesh.png)
1534        """
1535        if pcloud.inputdata().GetNumberOfPolys():
1536
1537            poly1 = self.polydata()
1538            poly2 = pcloud.polydata()
1539            df = vtk.vtkDistancePolyDataFilter()
1540            df.ComputeSecondDistanceOff()
1541            df.SetInputData(0, poly1)
1542            df.SetInputData(1, poly2)
1543            df.SetSignedDistance(signed)
1544            df.SetNegateDistance(invert)
1545            df.Update()
1546            scals = df.GetOutput().GetPointData().GetScalars()
1547            dists = utils.vtk2numpy(scals)
1548
1549        else:  # has no polygons and vtkDistancePolyDataFilter wants them (dont know why)
1550
1551            if signed:
1552                vedo.logger.warning("distanceTo() called with signed=True but input object has no polygons")
1553
1554            if not pcloud.point_locator:
1555                pcloud.point_locator = vtk.vtkPointLocator()
1556                pcloud.point_locator.SetDataSet(pcloud.polydata())
1557                pcloud.point_locator.BuildLocator()
1558
1559            ids = []
1560            ps1 = self.points()
1561            ps2 = pcloud.points()
1562            for p in ps1:
1563                pid = pcloud.point_locator.FindClosestPoint(p)
1564                ids.append(pid)
1565
1566            deltas = ps2[ids] - ps1
1567            dists = np.linalg.norm(deltas, axis=1).astype(np.float32)
1568            scals = utils.numpy2vtk(dists)
1569
1570        scals.SetName(name)
1571        self.inputdata().GetPointData().AddArray(scals)  # must be self.inputdata() !
1572        self.inputdata().GetPointData().SetActiveScalars(scals.GetName())
1573        rng = scals.GetRange()
1574        self.mapper().SetScalarRange(rng[0], rng[1])
1575        self.mapper().ScalarVisibilityOn()
1576
1577        self.pipeline = utils.OperationNode(
1578            "distance_to",
1579            parents=[self, pcloud],
1580            shape="cylinder",
1581            comment=f"#pts {self._data.GetNumberOfPoints()}",
1582        )
1583        return dists
1584
1585    def alpha(self, opacity=None):
1586        """Set/get mesh's transparency. Same as `mesh.opacity()`."""
1587        if opacity is None:
1588            return self.GetProperty().GetOpacity()
1589
1590        self.GetProperty().SetOpacity(opacity)
1591        bfp = self.GetBackfaceProperty()
1592        if bfp:
1593            if opacity < 1:
1594                self._bfprop = bfp
1595                self.SetBackfaceProperty(None)
1596            else:
1597                self.SetBackfaceProperty(self._bfprop)
1598        return self
1599
1600    def opacity(self, alpha=None):
1601        """Set/get mesh's transparency. Same as `mesh.alpha()`."""
1602        return self.alpha(alpha)
1603
1604    def force_opaque(self, value=True):
1605        """ Force the Mesh, Line or point cloud to be treated as opaque"""
1606        ## force the opaque pass, fixes picking in vtk9
1607        # but causes other bad troubles with lines..
1608        self.SetForceOpaque(value)
1609        return self
1610
1611    def force_translucent(self, value=True):
1612        """ Force the Mesh, Line or point cloud to be treated as translucent"""
1613        self.SetForceTranslucent(value)
1614        return self
1615
1616    def point_size(self, value=None):
1617        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
1618        if value is None:
1619            return self.GetProperty().GetPointSize()
1620            #self.GetProperty().SetRepresentationToSurface()
1621        else:
1622            self.GetProperty().SetRepresentationToPoints()
1623            self.GetProperty().SetPointSize(value)
1624        return self
1625
1626    def ps(self, pointsize=None):
1627        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
1628        return self.point_size(pointsize)
1629
1630    def render_points_as_spheres(self, value=True):
1631        """Make points look spheric or make them look as squares."""
1632        self.GetProperty().SetRenderPointsAsSpheres(value)
1633        return self
1634
1635    def color(self, c=False, alpha=None):
1636        """
1637        Set/get mesh's color.
1638        If None is passed as input, will use colors from active scalars.
1639        Same as `mesh.c()`.
1640        """
1641        # overrides base.color()
1642        if c is False:
1643            return np.array(self.GetProperty().GetColor())
1644        if c is None:
1645            self.mapper().ScalarVisibilityOn()
1646            return self
1647        self.mapper().ScalarVisibilityOff()
1648        cc = colors.get_color(c)
1649        self.GetProperty().SetColor(cc)
1650        if self.trail:
1651            self.trail.GetProperty().SetColor(cc)
1652        if alpha is not None:
1653            self.alpha(alpha)
1654        return self
1655
1656    def clean(self):
1657        """
1658        Clean pointcloud or mesh by removing coincident points.
1659        """
1660        cpd = vtk.vtkCleanPolyData()
1661        cpd.PointMergingOn()
1662        cpd.ConvertLinesToPointsOn()
1663        cpd.ConvertPolysToLinesOn()
1664        cpd.ConvertStripsToPolysOn()
1665        cpd.SetInputData(self.inputdata())
1666        cpd.Update()
1667        out = self._update(cpd.GetOutput())
1668
1669        out.pipeline = utils.OperationNode(
1670            "clean", parents=[self],
1671            comment=f"#pts {out.inputdata().GetNumberOfPoints()}"
1672        )
1673        return out
1674
1675    def subsample(self, fraction, absolute=False):
1676        """
1677        Subsample a point cloud by requiring that the points
1678        or vertices are far apart at least by the specified fraction of the object size.
1679        If a Mesh is passed the polygonal faces are not removed
1680        but holes can appear as vertices are removed.
1681
1682        Examples:
1683            - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py)
1684
1685                ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png)
1686
1687            - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py)
1688
1689                ![](https://vedo.embl.es/images/advanced/recosurface.png)
1690        """
1691        if not absolute:
1692            if fraction > 1:
1693                vedo.logger.warning(
1694                    f"subsample(fraction=...), fraction must be < 1, but is {fraction}"
1695                )
1696            if fraction <= 0:
1697                return self
1698
1699        cpd = vtk.vtkCleanPolyData()
1700        cpd.PointMergingOn()
1701        cpd.ConvertLinesToPointsOn()
1702        cpd.ConvertPolysToLinesOn()
1703        cpd.ConvertStripsToPolysOn()
1704        cpd.SetInputData(self.inputdata())
1705        if absolute:
1706            cpd.SetTolerance(fraction / self.diagonal_size())
1707            # cpd.SetToleranceIsAbsolute(absolute)
1708        else:
1709            cpd.SetTolerance(fraction)
1710        cpd.Update()
1711
1712        ps = 2
1713        if self.GetProperty().GetRepresentation() == 0:
1714            ps = self.GetProperty().GetPointSize()
1715
1716        out = self._update(cpd.GetOutput()).ps(ps)
1717
1718        out.pipeline = utils.OperationNode(
1719            "subsample", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}"
1720        )
1721        return out
1722
1723    def threshold(self, scalars, above=None, below=None, on="points"):
1724        """
1725        Extracts cells where scalar value satisfies threshold criterion.
1726
1727        Arguments:
1728            scalars : (str)
1729                name of the scalars array.
1730            above : (float)
1731                minimum value of the scalar
1732            below : (float)
1733                maximum value of the scalar
1734            on : (str)
1735                if 'cells' assume array of scalars refers to cell data.
1736
1737        Examples:
1738            - [mesh_threshold.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_threshold.py)
1739        """
1740        thres = vtk.vtkThreshold()
1741        thres.SetInputData(self.inputdata())
1742
1743        if on.startswith("c"):
1744            asso = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS
1745        else:
1746            asso = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS
1747
1748        thres.SetInputArrayToProcess(0, 0, 0, asso, scalars)
1749
1750        if above is None and below is not None:
1751            thres.ThresholdByLower(below)
1752        elif below is None and above is not None:
1753            thres.ThresholdByUpper(above)
1754        else:
1755            thres.ThresholdBetween(above, below)
1756        thres.Update()
1757
1758        gf = vtk.vtkGeometryFilter()
1759        gf.SetInputData(thres.GetOutput())
1760        gf.Update()
1761        return self._update(gf.GetOutput())
1762
1763    def quantize(self, value):
1764        """
1765        The user should input a value and all {x,y,z} coordinates
1766        will be quantized to that absolute grain size.
1767        """
1768        poly = self.inputdata()
1769        qp = vtk.vtkQuantizePolyDataPoints()
1770        qp.SetInputData(poly)
1771        qp.SetQFactor(value)
1772        qp.Update()
1773        out = self._update(qp.GetOutput()).flat()
1774        out.pipeline = utils.OperationNode("quantize", parents=[self])
1775        return out
1776
1777    def average_size(self):
1778        """
1779        Calculate the average size of a mesh.
1780        This is the mean of the vertex distances from the center of mass.
1781        """
1782        coords = self.points()
1783        cm = np.mean(coords, axis=0)
1784        if coords.shape[0] == 0:
1785            return 0.0
1786        cc = coords - cm
1787        return np.mean(np.linalg.norm(cc, axis=1))
1788
1789    def center_of_mass(self):
1790        """Get the center of mass of mesh."""
1791        cmf = vtk.vtkCenterOfMass()
1792        cmf.SetInputData(self.polydata())
1793        cmf.Update()
1794        c = cmf.GetCenter()
1795        return np.array(c)
1796
1797    def normal_at(self, i):
1798        """Return the normal vector at vertex point `i`."""
1799        normals = self.polydata().GetPointData().GetNormals()
1800        return np.array(normals.GetTuple(i))
1801
1802    def normals(self, cells=False, recompute=True):
1803        """Retrieve vertex normals as a numpy array.
1804
1805        Arguments:
1806            cells : (bool)
1807                if `True` return cell normals.
1808
1809            recompute : (bool)
1810                if `True` normals are recalculated if not already present.
1811                Note that this might modify the number of mesh points.
1812        """
1813        if cells:
1814            vtknormals = self.polydata().GetCellData().GetNormals()
1815        else:
1816            vtknormals = self.polydata().GetPointData().GetNormals()
1817        if not vtknormals and recompute:
1818            try:
1819                self.compute_normals(cells=cells)
1820                if cells:
1821                    vtknormals = self.polydata().GetCellData().GetNormals()
1822                else:
1823                    vtknormals = self.polydata().GetPointData().GetNormals()
1824            except AttributeError:
1825                # can be that 'Points' object has no attribute 'compute_normals'
1826                pass
1827
1828        if not vtknormals:
1829            return np.array([])
1830        return utils.vtk2numpy(vtknormals)
1831
1832    def labels(
1833        self,
1834        content=None,
1835        on="points",
1836        scale=None,
1837        xrot=0.0,
1838        yrot=0.0,
1839        zrot=0.0,
1840        ratio=1,
1841        precision=None,
1842        italic=False,
1843        font="",
1844        justify="bottom-left",
1845        c="black",
1846        alpha=1.0,
1847        cells=None,
1848    ):
1849        """
1850        Generate value or ID labels for mesh cells or points.
1851        For large nr. of labels use `font="VTK"` which is much faster.
1852
1853        See also:
1854            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1855
1856        Arguments:
1857            content : (list,int,str)
1858                either 'id', 'cellid', array name or array number.
1859                A array can also be passed (must match the nr. of points or cells).
1860            on : (str)
1861                generate labels for "cells" instead of "points"
1862            scale : (float)
1863                absolute size of labels, if left as None it is automatic
1864            zrot : (float)
1865                local rotation angle of label in degrees
1866            ratio : (int)
1867                skipping ratio, to reduce nr of labels for large meshes
1868            precision : (int)
1869                numeric precision of labels
1870
1871        ```python
1872        from vedo import *
1873        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1874        point_ids = s.labels('id', on="points").c('green')
1875        cell_ids  = s.labels('id', on="cells" ).c('black')
1876        show(s, point_ids, cell_ids)
1877        ```
1878        ![](https://vedo.embl.es/images/feats/labels.png)
1879
1880        Examples:
1881            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1882
1883                ![](https://vedo.embl.es/images/basic/boundaries.png)
1884        """
1885        if cells is not None:  # deprecation message
1886            vedo.logger.warning("In labels(cells=...) please use labels(on='cells') instead")
1887
1888        if "cell" in on or "face" in on:
1889            cells = True
1890
1891        if isinstance(content, str):
1892            if content in ("cellid", "cellsid"):
1893                cells = True
1894                content = "id"
1895
1896        if cells:
1897            elems = self.cell_centers()
1898            norms = self.normals(cells=True, recompute=False)
1899            ns = np.sqrt(self.ncells)
1900        else:
1901            elems = self.points()
1902            norms = self.normals(cells=False, recompute=False)
1903            ns = np.sqrt(self.npoints)
1904
1905        hasnorms = False
1906        if len(norms) > 0:
1907            hasnorms = True
1908
1909        if scale is None:
1910            if not ns:
1911                ns = 100
1912            scale = self.diagonal_size() / ns / 10
1913
1914        arr = None
1915        mode = 0
1916        if content is None:
1917            mode = 0
1918            if cells:
1919                if self.inputdata().GetCellData().GetScalars():
1920                    name = self.inputdata().GetCellData().GetScalars().GetName()
1921                    arr = self.celldata[name]
1922            else:
1923                if self.inputdata().GetPointData().GetScalars():
1924                    name = self.inputdata().GetPointData().GetScalars().GetName()
1925                    arr = self.pointdata[name]
1926        elif isinstance(content, (str, int)):
1927            if content == "id":
1928                mode = 1
1929            elif cells:
1930                mode = 0
1931                arr = self.celldata[content]
1932            else:
1933                mode = 0
1934                arr = self.pointdata[content]
1935        elif utils.is_sequence(content):
1936            mode = 0
1937            arr = content
1938            # print('WEIRD labels() test', content)
1939            # exit()
1940
1941        if arr is None and mode == 0:
1942            vedo.logger.error("in labels(), array not found for points or cells")
1943            return None
1944
1945        tapp = vtk.vtkAppendPolyData()
1946        ninputs = 0
1947
1948        for i, e in enumerate(elems):
1949            if i % ratio:
1950                continue
1951
1952            if mode == 1:
1953                txt_lab = str(i)
1954            else:
1955                if precision:
1956                    txt_lab = utils.precision(arr[i], precision)
1957                else:
1958                    txt_lab = str(arr[i])
1959
1960            if not txt_lab:
1961                continue
1962
1963            if font == "VTK":
1964                tx = vtk.vtkVectorText()
1965                tx.SetText(txt_lab)
1966                tx.Update()
1967                tx_poly = tx.GetOutput()
1968            else:
1969                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify)
1970                tx_poly = tx_poly.inputdata()
1971
1972            if tx_poly.GetNumberOfPoints() == 0:
1973                continue  #######################
1974            ninputs += 1
1975
1976            T = vtk.vtkTransform()
1977            T.PostMultiply()
1978            if italic:
1979                T.Concatenate([1,0.2,0,0,
1980                               0,1,0,0,
1981                               0,0,1,0,
1982                               0,0,0,1])
1983            if hasnorms:
1984                ni = norms[i]
1985                if cells:  # center-justify
1986                    bb = tx_poly.GetBounds()
1987                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1988                    T.Translate(-dx, -dy, 0)
1989                if xrot:
1990                    T.RotateX(xrot)
1991                if yrot:
1992                    T.RotateY(yrot)
1993                if zrot:
1994                    T.RotateZ(zrot)
1995                crossvec = np.cross([0, 0, 1], ni)
1996                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1997                T.RotateWXYZ(angle, crossvec)
1998                if cells:  # small offset along normal only for cells
1999                    T.Translate(ni * scale / 2)
2000            else:
2001                if xrot:
2002                    T.RotateX(xrot)
2003                if yrot:
2004                    T.RotateY(yrot)
2005                if zrot:
2006                    T.RotateZ(zrot)
2007            T.Scale(scale, scale, scale)
2008            T.Translate(e)
2009            tf = vtk.vtkTransformPolyDataFilter()
2010            tf.SetInputData(tx_poly)
2011            tf.SetTransform(T)
2012            tf.Update()
2013            tapp.AddInputData(tf.GetOutput())
2014
2015        if ninputs:
2016            tapp.Update()
2017            lpoly = tapp.GetOutput()
2018        else:  # return an empty obj
2019            lpoly = vtk.vtkPolyData()
2020
2021        ids = vedo.mesh.Mesh(lpoly, c=c, alpha=alpha)
2022        ids.GetProperty().LightingOff()
2023        ids.PickableOff()
2024        ids.SetUseBounds(False)
2025        return ids
2026
2027    def labels2d(
2028        self,
2029        content="id",
2030        on="points",
2031        scale=1.0,
2032        precision=4,
2033        font="Calco",
2034        justify="bottom-left",
2035        angle=0.0,
2036        frame=False,
2037        c="black",
2038        bc=None,
2039        alpha=1.0,
2040    ):
2041        """
2042        Generate value or ID bi-dimensional labels for mesh cells or points.
2043
2044        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
2045
2046        Arguments:
2047            content : (str)
2048                either 'id', 'cellid', or array name
2049            on : (str)
2050                generate labels for "cells" instead of "points" (the default)
2051            scale : (float)
2052                size scaling of labels
2053            precision : (int)
2054                precision of numeric labels
2055            angle : (float)
2056                local rotation angle of label in degrees
2057            frame : (bool)
2058                draw a frame around the label
2059            bc : (str)
2060                background color of the label
2061
2062        ```python
2063        from vedo import Sphere, show
2064        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
2065        sph.celldata["zvals"] = sph.cell_centers()[:,2]
2066        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
2067        show(sph, l2d, axes=1).close()
2068        ```
2069        ![](https://vedo.embl.es/images/feats/labels2d.png)
2070        """
2071        cells = False
2072        if isinstance(content, str):
2073            if content in ("cellid", "cellsid"):
2074                cells = True
2075                content = "id"
2076
2077        if "cell" in on:
2078            cells = True
2079        elif "point" in on:
2080            cells = False
2081
2082        if cells:
2083            if content != "id" and content not in self.celldata.keys():
2084                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
2085                return None
2086            cellcloud = Points(self.cell_centers())
2087            arr = self.inputdata().GetCellData().GetScalars()
2088            poly = cellcloud.polydata(False)
2089            poly.GetPointData().SetScalars(arr)
2090        else:
2091            poly = self.polydata()
2092            if content != "id" and content not in self.pointdata.keys():
2093                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
2094                return None
2095            self.pointdata.select(content)
2096
2097        mp = vtk.vtkLabeledDataMapper()
2098
2099        if content == "id":
2100            mp.SetLabelModeToLabelIds()
2101        else:
2102            mp.SetLabelModeToLabelScalars()
2103            if precision is not None:
2104                mp.SetLabelFormat(f"%-#.{precision}g")
2105
2106        pr = mp.GetLabelTextProperty()
2107        c = colors.get_color(c)
2108        pr.SetColor(c)
2109        pr.SetOpacity(alpha)
2110        pr.SetFrame(frame)
2111        pr.SetFrameColor(c)
2112        pr.SetItalic(False)
2113        pr.BoldOff()
2114        pr.ShadowOff()
2115        pr.UseTightBoundingBoxOn()
2116        pr.SetOrientation(angle)
2117        pr.SetFontFamily(vtk.VTK_FONT_FILE)
2118        fl = utils.get_font_path(font)
2119        pr.SetFontFile(fl)
2120        pr.SetFontSize(int(20 * scale))
2121
2122        if "cent" in justify or "mid" in justify:
2123            pr.SetJustificationToCentered()
2124        elif "rig" in justify:
2125            pr.SetJustificationToRight()
2126        elif "left" in justify:
2127            pr.SetJustificationToLeft()
2128        # ------
2129        if "top" in justify:
2130            pr.SetVerticalJustificationToTop()
2131        else:
2132            pr.SetVerticalJustificationToBottom()
2133
2134        if bc is not None:
2135            bc = colors.get_color(bc)
2136            pr.SetBackgroundColor(bc)
2137            pr.SetBackgroundOpacity(alpha)
2138
2139        mp.SetInputData(poly)
2140        a2d = vtk.vtkActor2D()
2141        a2d.PickableOff()
2142        a2d.SetMapper(mp)
2143        return a2d
2144
2145    def legend(self, txt):
2146        """Book a legend text."""
2147        self.info["legend"] = txt
2148        return self
2149
2150    def flagpole(
2151        self,
2152        txt=None,
2153        point=None,
2154        offset=None,
2155        s=None,
2156        font="",
2157        rounded=True,
2158        c=None,
2159        alpha=1.0,
2160        lw=2,
2161        italic=0.0,
2162        padding=0.1,
2163    ):
2164        """
2165        Generate a flag pole style element to describe an object.
2166        Returns a `Mesh` object.
2167
2168        Use flagpole.follow_camera() to make it face the camera in the scene.
2169
2170        See also `flagpost()`.
2171
2172        Arguments:
2173            txt : (str)
2174                Text to display. The default is the filename or the object name.
2175            point : (list)
2176                position of the flagpole pointer. 
2177            offset : (list)
2178                text offset wrt the application point. 
2179            s : (float)
2180                size of the flagpole.
2181            font : (str)
2182                font face. Check [available fonts here](https://vedo.embl.es/fonts).
2183            rounded : (bool)
2184                draw a rounded or squared box around the text.
2185            c : (list)
2186                text and box color.
2187            alpha : (float)
2188                opacity of text and box.
2189            lw : (float)
2190                line with of box frame.
2191            italic : (float)
2192                italicness of text.
2193
2194        Examples:
2195            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2196
2197                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2198
2199            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2200            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2201            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2202        """
2203        acts = []
2204
2205        if txt is None:
2206            if self.filename:
2207                txt = self.filename.split("/")[-1]
2208            elif self.name:
2209                txt = self.name
2210            else:
2211                return None
2212
2213        x0, x1, y0, y1, z0, z1 = self.bounds()
2214        d = self.diagonal_size()
2215        if point is None:
2216            if d:
2217                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2218            else:  # it's a Point
2219                point = self.GetPosition()
2220
2221        pt = utils.make3d(point)
2222
2223        if offset is None:
2224            offset = [(x1 - x0) / 2, (y1 - y0) / 6, 0]
2225        offset = utils.make3d(offset)
2226
2227        if s is None:
2228            s = d / 20
2229
2230        sph = None
2231        if d and (z1 - z0) / d > 0.1:
2232            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2233
2234        if c is None:
2235            c = np.array(self.color()) / 1.4
2236
2237        lb = vedo.shapes.Text3D(
2238            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center-left"
2239        )
2240        acts.append(lb)
2241
2242        if d and not sph:
2243            sph = vedo.shapes.Circle(pt, r=s / 3, res=15)
2244        acts.append(sph)
2245
2246        x0, x1, y0, y1, z0, z1 = lb.GetBounds()
2247        if rounded:
2248            box = vedo.shapes.KSpline(
2249                [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)], closed=True
2250            )
2251        else:
2252            box = vedo.shapes.Line(
2253                [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z0)]
2254            )
2255
2256        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2257
2258        box.SetOrigin(cnt)
2259        box.scale([1 + padding, 1 + 2 * padding, 1])
2260        acts.append(box)
2261
2262        # pts = box.points()
2263        # bfaces = []
2264        # for i, pt in enumerate(pts):
2265        #     if i:
2266        #         face = [i-1, i, 0]
2267        #         bfaces.append(face)
2268        # bpts = [cnt] + pts.tolist()
2269        # box2 = vedo.Mesh([bpts, bfaces]).z(-cnt[0]/10)#.c('w').alpha(0.1)
2270        # #should be made assembly otherwise later merge() nullifies it
2271        # box2.SetOrigin(cnt)
2272        # acts.append(box2)
2273
2274        x0, x1, y0, y1, z0, z1 = box.bounds()
2275        if x0 < pt[0] < x1:
2276            c0 = box.closest_point(pt)
2277            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2278        elif (pt[0] - x0) < (x1 - pt[0]):
2279            c0 = [x0, (y0 + y1) / 2, pt[2]]
2280            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2281        else:
2282            c0 = [x1, (y0 + y1) / 2, pt[2]]
2283            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2284
2285        con = vedo.shapes.Line([c0, c1, pt])
2286        acts.append(con)
2287
2288        macts = vedo.merge(acts).c(c).alpha(alpha)
2289        macts.SetOrigin(pt)
2290        macts.bc("tomato").pickable(False)
2291        macts.GetProperty().LightingOff()
2292        macts.GetProperty().SetLineWidth(lw)
2293        macts.UseBoundsOff()
2294        macts.name = "FlagPole"
2295        return macts
2296
2297    def flagpost(
2298        self,
2299        txt=None,
2300        point=None,
2301        offset=None,
2302        s=1.0,
2303        c="k9",
2304        bc="k1",
2305        alpha=1,
2306        lw=0,
2307        font="Calco",
2308        justify="center-left",
2309        vspacing=1.0,
2310    ):
2311        """
2312        Generate a flag post style element to describe an object.
2313
2314        Arguments:
2315            txt : (str)
2316                Text to display. The default is the filename or the object name.
2317            point : (list)
2318                position of the flag anchor point. The default is None.
2319            offset : (list)
2320                a 3D displacement or offset. The default is None.
2321            s : (float)
2322                size of the text to be shown
2323            c : (list)
2324                color of text and line
2325            bc : (list)
2326                color of the flag background
2327            alpha : (float)
2328                opacity of text and box.
2329            lw : (int)
2330                line with of box frame. The default is 0.
2331            font : (str)
2332                font name. Use a monospace font for better rendering. The default is "Calco".
2333                Type `vedo -r fonts` for a font demo.
2334                Check [available fonts here](https://vedo.embl.es/fonts).
2335            justify : (str)
2336                internal text justification. The default is "center-left".
2337            vspacing : (float)
2338                vertical spacing between lines.
2339
2340        Examples:
2341            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
2342
2343            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2344        """
2345        if txt is None:
2346            if self.filename:
2347                txt = self.filename.split("/")[-1]
2348            elif self.name:
2349                txt = self.name
2350            else:
2351                return None
2352
2353        x0, x1, y0, y1, z0, z1 = self.bounds()
2354        d = self.diagonal_size()
2355        if point is None:
2356            if d:
2357                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2358            else:  # it's a Point
2359                point = self.GetPosition()
2360
2361        point = utils.make3d(point)
2362
2363        if offset is None:
2364            offset = [0, 0, (z1 - z0) / 2]
2365        offset = utils.make3d(offset)
2366
2367        fpost = vedo.addons.Flagpost(
2368            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2369        )
2370        self._caption = fpost
2371        return fpost
2372
2373    def caption(
2374        self,
2375        txt=None,
2376        point=None,
2377        size=(0.30, 0.15),
2378        padding=5,
2379        font="Calco",
2380        justify="center-right",
2381        vspacing=1.0,
2382        c=None,
2383        alpha=1.0,
2384        lw=1,
2385        ontop=True,
2386    ):
2387        """
2388        Add a 2D caption to an object which follows the camera movements.
2389        Latex is not supported. Returns the same input object for concatenation.
2390
2391        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2392        with similar functionality.
2393
2394        Arguments:
2395            txt : (str)
2396                text to be rendered. The default is the file name.
2397            point : (list)
2398                anchoring point. The default is None.
2399            size : (list)
2400                (width, height) of the caption box. The default is (0.30, 0.15).
2401            padding : (float)
2402                padding space of the caption box in pixels. The default is 5.
2403            font : (str)
2404                font name. Use a monospace font for better rendering. The default is "VictorMono".
2405                Type `vedo -r fonts` for a font demo.
2406                Check [available fonts here](https://vedo.embl.es/fonts).
2407            justify : (str)
2408                internal text justification. The default is "center-right".
2409            vspacing : (float)
2410                vertical spacing between lines. The default is 1.
2411            c : (str)
2412                text and box color. The default is 'lb'.
2413            alpha : (float)
2414                text and box transparency. The default is 1.
2415            lw : (int)
2416                line width in pixels. The default is 1.
2417            ontop : (bool)
2418                keep the 2d caption always on top. The default is True.
2419
2420        Examples:
2421            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2422
2423                ![](https://vedo.embl.es/images/pyplot/caption.png)
2424
2425            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2426            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2427        """
2428        if txt is None:
2429            if self.filename:
2430                txt = self.filename.split("/")[-1]
2431            elif self.name:
2432                txt = self.name
2433
2434        if not txt:  # disable it
2435            self._caption = None
2436            return self
2437
2438        for r in vedo.shapes._reps:
2439            txt = txt.replace(r[0], r[1])
2440
2441        if c is None:
2442            c = np.array(self.GetProperty().GetColor()) / 2
2443        else:
2444            c = colors.get_color(c)
2445
2446        if point is None:
2447            x0, x1, y0, y1, _, z1 = self.GetBounds()
2448            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2449            point = self.closest_point(pt)
2450
2451        capt = vtk.vtkCaptionActor2D()
2452        capt.SetAttachmentPoint(point)
2453        capt.SetBorder(True)
2454        capt.SetLeader(True)
2455        sph = vtk.vtkSphereSource()
2456        sph.Update()
2457        capt.SetLeaderGlyphData(sph.GetOutput())
2458        capt.SetMaximumLeaderGlyphSize(5)
2459        capt.SetPadding(int(padding))
2460        capt.SetCaption(txt)
2461        capt.SetWidth(size[0])
2462        capt.SetHeight(size[1])
2463        capt.SetThreeDimensionalLeader(not ontop)
2464
2465        pra = capt.GetProperty()
2466        pra.SetColor(c)
2467        pra.SetOpacity(alpha)
2468        pra.SetLineWidth(lw)
2469
2470        pr = capt.GetCaptionTextProperty()
2471        pr.SetFontFamily(vtk.VTK_FONT_FILE)
2472        fl = utils.get_font_path(font)
2473        pr.SetFontFile(fl)
2474        pr.ShadowOff()
2475        pr.BoldOff()
2476        pr.FrameOff()
2477        pr.SetColor(c)
2478        pr.SetOpacity(alpha)
2479        pr.SetJustificationToLeft()
2480        if "top" in justify:
2481            pr.SetVerticalJustificationToTop()
2482        if "bottom" in justify:
2483            pr.SetVerticalJustificationToBottom()
2484        if "cent" in justify:
2485            pr.SetVerticalJustificationToCentered()
2486            pr.SetJustificationToCentered()
2487        if "left" in justify:
2488            pr.SetJustificationToLeft()
2489        if "right" in justify:
2490            pr.SetJustificationToRight()
2491        pr.SetLineSpacing(vspacing)
2492        self._caption = capt
2493        return self
2494
2495
2496    def align_to(self, target, iters=100, rigid=False, invert=False, use_centroids=False):
2497        """
2498        Aligned to target mesh through the `Iterative Closest Point` algorithm.
2499
2500        The core of the algorithm is to match each vertex in one surface with
2501        the closest surface point on the other, then apply the transformation
2502        that modify one surface to best match the other (in the least-square sense).
2503
2504        Arguments:
2505            rigid : (bool)
2506                if True do not allow scaling
2507            invert : (bool)
2508                if True start by aligning the target to the source but
2509                invert the transformation finally. Useful when the target is smaller
2510                than the source.
2511            use_centroids : (bool)
2512                start by matching the centroids of the two objects.
2513
2514        Examples:
2515            - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py)
2516
2517                ![](https://vedo.embl.es/images/basic/align1.png)
2518
2519            - [align2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align2.py)
2520
2521                ![](https://vedo.embl.es/images/basic/align2.png)
2522        """
2523        icp = vtk.vtkIterativeClosestPointTransform()
2524        icp.SetSource(self.polydata())
2525        icp.SetTarget(target.polydata())
2526        if invert:
2527            icp.Inverse()
2528        icp.SetMaximumNumberOfIterations(iters)
2529        if rigid:
2530            icp.GetLandmarkTransform().SetModeToRigidBody()
2531        icp.SetStartByMatchingCentroids(use_centroids)
2532        icp.Update()
2533
2534        M = icp.GetMatrix()
2535        if invert:
2536            M.Invert()  # icp.GetInverse() doesnt work!
2537        # self.apply_transform(M)
2538        self.SetUserMatrix(M)
2539
2540        self.transform = self.GetUserTransform()
2541        self.point_locator = None
2542        self.cell_locator = None
2543
2544        self.pipeline = utils.OperationNode(
2545            "align_to", parents=[self, target], comment=f"rigid = {rigid}"
2546        )
2547        return self
2548
2549    def transform_with_landmarks(
2550        self, source_landmarks, target_landmarks, rigid=False, affine=False, least_squares=False
2551    ):
2552        """
2553        Transform mesh orientation and position based on a set of landmarks points.
2554        The algorithm finds the best matching of source points to target points
2555        in the mean least square sense, in one single step.
2556
2557        If affine is True the x, y and z axes can scale independently but stay collinear.
2558        With least_squares they can vary orientation.
2559
2560        Examples:
2561            - [align5.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align5.py)
2562
2563                ![](https://vedo.embl.es/images/basic/align5.png)
2564        """
2565
2566        if utils.is_sequence(source_landmarks):
2567            ss = vtk.vtkPoints()
2568            for p in source_landmarks:
2569                ss.InsertNextPoint(p)
2570        else:
2571            ss = source_landmarks.polydata().GetPoints()
2572            if least_squares:
2573                source_landmarks = source_landmarks.points()
2574
2575        if utils.is_sequence(target_landmarks):
2576            st = vtk.vtkPoints()
2577            for p in target_landmarks:
2578                st.InsertNextPoint(p)
2579        else:
2580            st = target_landmarks.polydata().GetPoints()
2581            if least_squares:
2582                target_landmarks = target_landmarks.points()
2583
2584        if ss.GetNumberOfPoints() != st.GetNumberOfPoints():
2585            n1 = ss.GetNumberOfPoints()
2586            n2 = st.GetNumberOfPoints()
2587            vedo.logger.error(f"source and target have different nr of points {n1} vs {n2}")
2588            raise RuntimeError()
2589
2590        lmt = vtk.vtkLandmarkTransform()
2591        lmt.SetSourceLandmarks(ss)
2592        lmt.SetTargetLandmarks(st)
2593        lmt.SetModeToSimilarity()
2594        if rigid:
2595            lmt.SetModeToRigidBody()
2596            lmt.Update()
2597            self.SetUserTransform(lmt)
2598
2599        elif affine:
2600            lmt.SetModeToAffine()
2601            lmt.Update()
2602            self.SetUserTransform(lmt)
2603
2604        elif least_squares:
2605            cms = source_landmarks.mean(axis=0)
2606            cmt = target_landmarks.mean(axis=0)
2607            m = np.linalg.lstsq(source_landmarks - cms, target_landmarks - cmt, rcond=None)[0]
2608            M = vtk.vtkMatrix4x4()
2609            for i in range(3):
2610                for j in range(3):
2611                    M.SetElement(j, i, m[i][j])
2612            lmt = vtk.vtkTransform()
2613            lmt.Translate(cmt)
2614            lmt.Concatenate(M)
2615            lmt.Translate(-cms)
2616            self.apply_transform(lmt, concatenate=True)
2617        else:
2618            self.SetUserTransform(lmt)
2619
2620        self.transform = lmt
2621        self.point_locator = None
2622        self.cell_locator = None
2623        self.pipeline = utils.OperationNode("transform_with_landmarks", parents=[self])
2624        return self
2625
2626
2627    def apply_transform(self, T, reset=False, concatenate=False):
2628        """
2629        Apply a linear or non-linear transformation to the mesh polygonal data.
2630
2631        Arguments:
2632            T : (matrix)
2633                `vtkTransform`, `vtkMatrix4x4` or a 4x4 or 3x3 python or numpy matrix.
2634            reset : (bool)
2635                if True reset the current transformation matrix
2636                to identity after having moved the object, otherwise the internal
2637                matrix will stay the same (to only affect visualization).
2638                It the input transformation has no internal defined matrix (ie. non linear)
2639                then reset will be assumed as True.
2640            concatenate : (bool)
2641                concatenate the transformation with the current existing one
2642
2643        Example:
2644            ```python
2645            from vedo import Cube, show
2646            c1 = Cube().rotate_z(5).x(2).y(1)
2647            print("cube1 position", c1.pos())
2648            T = c1.get_transform()  # rotate by 5 degrees, sum 2 to x and 1 to y
2649            c2 = Cube().c('r4')
2650            c2.apply_transform(T)   # ignore previous movements
2651            c2.apply_transform(T, concatenate=True)
2652            c2.apply_transform(T, concatenate=True)
2653            print("cube2 position", c2.pos())
2654            show(c1, c2, axes=1).close()
2655            ```
2656            ![](https://vedo.embl.es/images/feats/apply_transform.png)
2657        """
2658        self.point_locator = None
2659        self.cell_locator = None
2660
2661        if isinstance(T, vtk.vtkMatrix4x4):
2662            tr = vtk.vtkTransform()
2663            tr.SetMatrix(T)
2664            T = tr
2665
2666        elif utils.is_sequence(T):
2667            M = vtk.vtkMatrix4x4()
2668            n = len(T[0])
2669            for i in range(n):
2670                for j in range(n):
2671                    M.SetElement(i, j, T[i][j])
2672            tr = vtk.vtkTransform()
2673            tr.SetMatrix(M)
2674            T = tr
2675
2676        if reset or not hasattr(T, "GetScale"):  # might be non-linear
2677
2678            tf = vtk.vtkTransformPolyDataFilter()
2679            tf.SetTransform(T)
2680            tf.SetInputData(self.polydata())
2681            tf.Update()
2682
2683            I = vtk.vtkMatrix4x4()
2684            self.PokeMatrix(I)  # reset to identity
2685            self.SetUserTransform(None)
2686
2687            self._update(tf.GetOutput())  ### UPDATE
2688            self.transform = T
2689
2690        else:
2691
2692            if concatenate:
2693
2694                M = vtk.vtkTransform()
2695                M.PostMultiply()
2696                M.SetMatrix(self.GetMatrix())
2697
2698                M.Concatenate(T)
2699
2700                self.SetScale(M.GetScale())
2701                self.SetOrientation(M.GetOrientation())
2702                self.SetPosition(M.GetPosition())
2703                self.transform = M
2704                self.SetUserTransform(None)
2705
2706            else:
2707
2708                self.SetScale(T.GetScale())
2709                self.SetOrientation(T.GetOrientation())
2710                self.SetPosition(T.GetPosition())
2711                self.SetUserTransform(None)
2712
2713                self.transform = T
2714
2715        return self
2716
2717    def normalize(self):
2718        """Scale Mesh average size to unit."""
2719        coords = self.points()
2720        if not coords.shape[0]:
2721            return self
2722        cm = np.mean(coords, axis=0)
2723        pts = coords - cm
2724        xyz2 = np.sum(pts * pts, axis=0)
2725        scale = 1 / np.sqrt(np.sum(xyz2) / len(pts))
2726        t = vtk.vtkTransform()
2727        t.PostMultiply()
2728        # t.Translate(-cm)
2729        t.Scale(scale, scale, scale)
2730        # t.Translate(cm)
2731        tf = vtk.vtkTransformPolyDataFilter()
2732        tf.SetInputData(self.inputdata())
2733        tf.SetTransform(t)
2734        tf.Update()
2735        self.point_locator = None
2736        self.cell_locator = None
2737        return self._update(tf.GetOutput())
2738
2739    def mirror(self, axis="x", origin=(0, 0, 0), reset=False):
2740        """
2741        Mirror the mesh  along one of the cartesian axes
2742
2743        Arguments:
2744            axis : (str)
2745                axis to use for mirroring, must be set to x, y, z or n.
2746                Or any combination of those. Adding 'n' reverses mesh faces (hence normals).
2747            origin : (list)
2748                use this point as the origin of the mirroring transformation.
2749            reset : (bool)
2750                if True keep into account the current position of the object,
2751                and then reset its internal transformation matrix to Identity.
2752
2753        Examples:
2754            - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py)
2755
2756                ![](https://vedo.embl.es/images/basic/mirror.png)
2757        """
2758        sx, sy, sz = 1, 1, 1
2759        if "x" in axis.lower(): sx = -1
2760        if "y" in axis.lower(): sy = -1
2761        if "z" in axis.lower(): sz = -1
2762        origin = np.array(origin)
2763        tr = vtk.vtkTransform()
2764        tr.PostMultiply()
2765        tr.Translate(-origin)
2766        tr.Scale(sx, sy, sz)
2767        tr.Translate(origin)
2768        tf = vtk.vtkTransformPolyDataFilter()
2769        tf.SetInputData(self.polydata(reset))
2770        tf.SetTransform(tr)
2771        tf.Update()
2772        outpoly = tf.GetOutput()
2773        if reset:
2774            self.PokeMatrix(vtk.vtkMatrix4x4())  # reset to identity
2775        if sx * sy * sz < 0 or "n" in axis:
2776            rs = vtk.vtkReverseSense()
2777            rs.SetInputData(outpoly)
2778            rs.ReverseNormalsOff()
2779            rs.Update()
2780            outpoly = rs.GetOutput()
2781
2782        self.point_locator = None
2783        self.cell_locator = None
2784
2785        out = self._update(outpoly)
2786
2787        out.pipeline = utils.OperationNode(f"mirror\naxis = {axis}", parents=[self])
2788        return out
2789
2790    def shear(self, x=0, y=0, z=0):
2791        """Apply a shear deformation along one of the main axes"""
2792        t = vtk.vtkTransform()
2793        sx, sy, sz = self.GetScale()
2794        t.SetMatrix([sx, x, 0, 0,
2795                      y,sy, z, 0,
2796                      0, 0,sz, 0,
2797                      0, 0, 0, 1])
2798        self.apply_transform(t, reset=True)
2799        return self
2800
2801    def flip_normals(self):
2802        """Flip all mesh normals. Same as `mesh.mirror('n')`."""
2803        rs = vtk.vtkReverseSense()
2804        rs.SetInputData(self.inputdata())
2805        rs.ReverseCellsOff()
2806        rs.ReverseNormalsOn()
2807        rs.Update()
2808        out = self._update(rs.GetOutput())
2809        self.pipeline = utils.OperationNode("flip_normals", parents=[self])
2810        return out
2811
2812    #####################################################################################
2813    def cmap(
2814        self,
2815        input_cmap,
2816        input_array=None,
2817        on="points",
2818        name="Scalars",
2819        vmin=None,
2820        vmax=None,
2821        n_colors=256,
2822        alpha=1.0,
2823        logscale=False,
2824    ):
2825        """
2826        Set individual point/cell colors by providing a list of scalar values and a color map.
2827
2828        Arguments:
2829            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
2830                color map scheme to transform a real number into a color.
2831            input_array : (str, list, vtkArray)
2832                can be the string name of an existing array, a numpy array or a `vtkArray`.
2833            on : (str)
2834                either 'points' or 'cells'.
2835                Apply the color map to data which is defined on either points or cells.
2836            name : (str)
2837                give a name to the provided numpy array (if input_array is a numpy array)
2838            vmin : (float)
2839                clip scalars to this minimum value
2840            vmax : (float)
2841                clip scalars to this maximum value
2842            n_colors : (int)
2843                number of distinct colors to be used in colormap table.
2844            alpha : (float, list)
2845                Mesh transparency. Can be a `list` of values one for each vertex.
2846            logscale : (bool)
2847                Use logscale
2848
2849        Examples:
2850            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
2851            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
2852            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
2853            (and many others)
2854
2855                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
2856        """
2857        self._cmap_name = input_cmap
2858        poly = self.inputdata()
2859
2860        if input_array is None:
2861            if not self.pointdata.keys() and self.celldata.keys():
2862                on = "cells"
2863                if not poly.GetCellData().GetScalars():
2864                    input_array = 0  # pick the first at hand
2865
2866        if on.startswith("point"):
2867            data = poly.GetPointData()
2868            n = poly.GetNumberOfPoints()
2869        elif on.startswith("cell"):
2870            data = poly.GetCellData()
2871            n = poly.GetNumberOfCells()
2872        else:
2873            vedo.logger.error("Must specify in cmap(on=...) to either 'cells' or 'points'")
2874            raise RuntimeError()
2875
2876        if input_array is None:  # if None try to fetch the active scalars
2877            arr = data.GetScalars()
2878            if not arr:
2879                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
2880                return self
2881
2882            if not arr.GetName():  # sometimes arrays dont have a name..
2883                arr.SetName(name)
2884
2885        elif isinstance(input_array, str):  # if a string is passed
2886            arr = data.GetArray(input_array)
2887            if not arr:
2888                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
2889                return self
2890
2891        elif isinstance(input_array, int):  # if an int is passed
2892            if input_array < data.GetNumberOfArrays():
2893                arr = data.GetArray(input_array)
2894            else:
2895                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
2896                return self
2897
2898        elif utils.is_sequence(input_array):  # if a numpy array is passed
2899            npts = len(input_array)
2900            if npts != n:
2901                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
2902                return self
2903            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
2904            data.AddArray(arr)
2905            data.Modified()
2906
2907        elif isinstance(input_array, vtk.vtkArray):  # if a vtkArray is passed
2908            arr = input_array
2909            data.AddArray(arr)
2910            data.Modified()
2911
2912        else:
2913            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
2914            raise RuntimeError()
2915
2916        # Now we have array "arr"
2917        array_name = arr.GetName()
2918
2919        if arr.GetNumberOfComponents() == 1:
2920            if vmin is None:
2921                vmin = arr.GetRange()[0]
2922            if vmax is None:
2923                vmax = arr.GetRange()[1]
2924        else:
2925            if vmin is None or vmax is None:
2926                vn = utils.mag(utils.vtk2numpy(arr))
2927            if vmin is None:
2928                vmin = vn.min()
2929            if vmax is None:
2930                vmax = vn.max()
2931
2932        # interpolate alphas if they are not constant
2933        if not utils.is_sequence(alpha):
2934            alpha = [alpha] * n_colors
2935        else:
2936            v = np.linspace(0, 1, n_colors, endpoint=True)
2937            xp = np.linspace(0, 1, len(alpha), endpoint=True)
2938            alpha = np.interp(v, xp, alpha)
2939
2940        ########################### build the look-up table
2941        if isinstance(input_cmap, vtk.vtkLookupTable):  # vtkLookupTable
2942            lut = input_cmap
2943
2944        elif utils.is_sequence(input_cmap):  # manual sequence of colors
2945            lut = vtk.vtkLookupTable()
2946            if logscale:
2947                lut.SetScaleToLog10()
2948            lut.SetRange(vmin, vmax)
2949            ncols = len(input_cmap)
2950            lut.SetNumberOfTableValues(ncols)
2951
2952            for i, c in enumerate(input_cmap):
2953                r, g, b = colors.get_color(c)
2954                lut.SetTableValue(i, r, g, b, alpha[i])
2955            lut.Build()
2956
2957        else:  # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
2958            lut = vtk.vtkLookupTable()
2959            if logscale:
2960                lut.SetScaleToLog10()
2961            lut.SetVectorModeToMagnitude()
2962            lut.SetRange(vmin, vmax)
2963            lut.SetNumberOfTableValues(n_colors)
2964            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
2965            for i, c in enumerate(mycols):
2966                r, g, b = c
2967                lut.SetTableValue(i, r, g, b, alpha[i])
2968            lut.Build()
2969
2970        arr.SetLookupTable(lut)
2971
2972        data.SetActiveScalars(array_name)
2973        # data.SetScalars(arr)  # wrong! it deletes array in position 0, never use SetScalars
2974        # data.SetActiveAttribute(array_name, 0) # boh!
2975
2976        if data.GetScalars():
2977            data.GetScalars().SetLookupTable(lut)
2978            data.GetScalars().Modified()
2979
2980        self._mapper.SetLookupTable(lut)
2981        self._mapper.SetColorModeToMapScalars()  # so we dont need to convert uint8 scalars
2982
2983        self._mapper.ScalarVisibilityOn()
2984        self._mapper.SetScalarRange(lut.GetRange())
2985        if on.startswith("point"):
2986            self._mapper.SetScalarModeToUsePointData()
2987        else:
2988            self._mapper.SetScalarModeToUseCellData()
2989        if hasattr(self._mapper, "SetArrayName"):
2990            self._mapper.SetArrayName(array_name)
2991
2992        return self
2993
2994    def cell_individual_colors(self, colorlist):
2995        # DEPRECATED
2996        self.cellcolors = colorlist
2997        print("Please use property mesh.cellcolors=... instead of mesh.cell_individual_colors()")
2998        return self
2999
3000    @property
3001    def cellcolors(self):
3002        """
3003        Colorize each cell (face) of a mesh by passing
3004        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
3005        Colors levels and opacities must be in the range [0,255].
3006
3007        A single constant color can also be passed as string or RGBA.
3008
3009        A cell array named "CellsRGBA" is automatically created.
3010
3011        Examples:
3012            - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py)
3013            - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py)
3014
3015            ![](https://vedo.embl.es/images/basic/colorMeshCells.png)
3016        """
3017        if "CellsRGBA" not in self.celldata.keys():
3018            lut = self.mapper().GetLookupTable()
3019            vscalars = self._data.GetCellData().GetScalars()
3020            if vscalars is None or lut is None:
3021                arr = np.zeros([self.ncells, 4], dtype=np.uint8)
3022                col = np.array(self.property.GetColor())
3023                col = np.round(col * 255).astype(np.uint8)
3024                alf = self.property.GetOpacity()
3025                alf = np.round(alf * 255).astype(np.uint8)
3026                arr[:, (0, 1, 2)] = col
3027                arr[:, 3] = alf
3028            else:
3029                cols = lut.MapScalars(vscalars, 0, 0)
3030                arr = utils.vtk2numpy(cols)
3031            self.celldata["CellsRGBA"] = arr
3032        self.celldata.select("CellsRGBA")
3033        return self.celldata["CellsRGBA"]
3034
3035    @cellcolors.setter
3036    def cellcolors(self, value):
3037        if isinstance(value, str):
3038            c = colors.get_color(value)
3039            value = np.array([*c, 1]) * 255
3040            value = np.round(value)
3041
3042        value = np.asarray(value)
3043        n = self.ncells
3044
3045        if value.ndim == 1:
3046            value = np.repeat([value], n, axis=0)
3047
3048        if value.shape[1] == 3:
3049            z = np.zeros((n, 1), dtype=np.uint8)
3050            value = np.append(value, z + 255, axis=1)
3051
3052        assert n == value.shape[0]
3053
3054        self.celldata["CellsRGBA"] = value.astype(np.uint8)
3055        self.celldata.select("CellsRGBA")
3056
3057
3058    @property
3059    def pointcolors(self):
3060        """
3061        Colorize each point (or vertex of a mesh) by passing
3062        a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A].
3063        Colors levels and opacities must be in the range [0,255].
3064
3065        A single constant color can also be passed as string or RGBA.
3066
3067        A point array named "PointsRGBA" is automatically created.
3068        """
3069        if "PointsRGBA" not in self.pointdata.keys():
3070            lut = self.mapper().GetLookupTable()
3071            vscalars = self._data.GetPointData().GetScalars()
3072            if vscalars is None or lut is None:
3073                arr = np.zeros([self.npoints, 4], dtype=np.uint8)
3074                col = np.array(self.property.GetColor())
3075                col = np.round(col * 255).astype(np.uint8)
3076                alf = self.property.GetOpacity()
3077                alf = np.round(alf * 255).astype(np.uint8)
3078                arr[:, (0, 1, 2)] = col
3079                arr[:, 3] = alf
3080            else:
3081                cols = lut.MapScalars(vscalars, 0, 0)
3082                arr = utils.vtk2numpy(cols)
3083            self.pointdata["PointsRGBA"] = arr
3084        self.pointdata.select("PointsRGBA")
3085        return self.pointdata["PointsRGBA"]
3086
3087    @pointcolors.setter
3088    def pointcolors(self, value):
3089        if isinstance(value, str):
3090            c = colors.get_color(value)
3091            value = np.array([*c, 1]) * 255
3092            value = np.round(value)
3093
3094        value = np.asarray(value)
3095        n = self.npoints
3096
3097        if value.ndim == 1:
3098            value = np.repeat([value], n, axis=0)
3099
3100        if value.shape[1] == 3:
3101            z = np.zeros((n, 1), dtype=np.uint8)
3102            value = np.append(value, z + 255, axis=1)
3103
3104        assert n == value.shape[0]
3105
3106        self.pointdata["PointsRGBA"] = value.astype(np.uint8)
3107        self.pointdata.select("PointsRGBA")
3108
3109
3110    def interpolate_data_from(
3111        self,
3112        source,
3113        radius=None,
3114        n=None,
3115        kernel="shepard",
3116        exclude=("Normals",),
3117        on="points",
3118        null_strategy=1,
3119        null_value=0,
3120    ):
3121        """
3122        Interpolate over source to port its data onto the current object using various kernels.
3123
3124        If n (number of closest points to use) is set then radius value is ignored.
3125
3126        Arguments:
3127            kernel : (str)
3128                available kernels are [shepard, gaussian, linear]
3129            null_strategy : (int)
3130                specify a strategy to use when encountering a "null" point
3131                during the interpolation process. Null points occur when the local neighborhood
3132                (of nearby points to interpolate from) is empty.
3133
3134                - Case 0: an output array is created that marks points
3135                  as being valid (=1) or null (invalid =0), and the null_value is set as well
3136                - Case 1: the output data value(s) are set to the provided null_value
3137                - Case 2: simply use the closest point to perform the interpolation.
3138            null_value : (float)
3139                see above.
3140
3141        Examples:
3142            - [interpolateMeshArray.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolateMeshArray.py)
3143
3144                ![](https://vedo.embl.es/images/advanced/interpolateMeshArray.png)
3145        """
3146        if radius is None and not n:
3147            vedo.logger.error("in interpolate_data_from(): please set either radius or n")
3148            raise RuntimeError
3149
3150        if on == "points":
3151            points = source.polydata()
3152        elif on == "cells":
3153            poly2 = vtk.vtkPolyData()
3154            poly2.ShallowCopy(source.polydata())
3155            c2p = vtk.vtkCellDataToPointData()
3156            c2p.SetInputData(poly2)
3157            c2p.Update()
3158            points = c2p.GetOutput()
3159        else:
3160            vedo.logger.error("in interpolate_data_from(), on must be on points or cells")
3161            raise RuntimeError()
3162
3163        locator = vtk.vtkPointLocator()
3164        locator.SetDataSet(points)
3165        locator.BuildLocator()
3166
3167        if kernel.lower() == "shepard":
3168            kern = vtk.vtkShepardKernel()
3169            kern.SetPowerParameter(2)
3170        elif kernel.lower() == "gaussian":
3171            kern = vtk.vtkGaussianKernel()
3172            kern.SetSharpness(2)
3173        elif kernel.lower() == "linear":
3174            kern = vtk.vtkLinearKernel()
3175        else:
3176            vedo.logger.error("available kernels are: [shepard, gaussian, linear]")
3177            raise RuntimeError()
3178
3179        if n:
3180            kern.SetNumberOfPoints(n)
3181            kern.SetKernelFootprintToNClosest()
3182        else:
3183            kern.SetRadius(radius)
3184
3185        interpolator = vtk.vtkPointInterpolator()
3186        interpolator.SetInputData(self.polydata())
3187        interpolator.SetSourceData(points)
3188        interpolator.SetKernel(kern)
3189        interpolator.SetLocator(locator)
3190        interpolator.PassFieldArraysOff()
3191        interpolator.SetNullPointsStrategy(null_strategy)
3192        interpolator.SetNullValue(null_value)
3193        interpolator.SetValidPointsMaskArrayName("ValidPointMask")
3194        for ex in exclude:
3195            interpolator.AddExcludedArray(ex)
3196        interpolator.Update()
3197
3198        if on == "cells":
3199            p2c = vtk.vtkPointDataToCellData()
3200            p2c.SetInputData(interpolator.GetOutput())
3201            p2c.Update()
3202            cpoly = p2c.GetOutput()
3203        else:
3204            cpoly = interpolator.GetOutput()
3205
3206        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3207            self._update(cpoly)
3208        else:
3209            # bring the underlying polydata to where _data is
3210            M = vtk.vtkMatrix4x4()
3211            M.DeepCopy(self.GetMatrix())
3212            M.Invert()
3213            tr = vtk.vtkTransform()
3214            tr.SetMatrix(M)
3215            tf = vtk.vtkTransformPolyDataFilter()
3216            tf.SetTransform(tr)
3217            tf.SetInputData(cpoly)
3218            tf.Update()
3219            self._update(tf.GetOutput())
3220
3221        self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source])
3222        return self
3223
3224    def add_gaussian_noise(self, sigma=1.0):
3225        """
3226        Add gaussian noise to point positions.
3227        An extra array is added named "GaussianNoise" with the shifts.
3228
3229        Arguments:
3230            sigma : (float)
3231                nr. of standard deviations, expressed in percent of the diagonal size of mesh.
3232                Can also be a list [sigma_x, sigma_y, sigma_z].
3233
3234        Examples:
3235            ```python
3236            from vedo import Sphere
3237            Sphere().add_gaussian_noise(1.0).point_size(8).show().close()
3238            ```
3239        """
3240        sz = self.diagonal_size()
3241        pts = self.points()
3242        n = len(pts)
3243        ns = (np.random.randn(n, 3) * sigma) * (sz / 100)
3244        vpts = vtk.vtkPoints()
3245        vpts.SetNumberOfPoints(n)
3246        vpts.SetData(utils.numpy2vtk(pts + ns, dtype=np.float32))
3247        self.inputdata().SetPoints(vpts)
3248        self.inputdata().GetPoints().Modified()
3249        self.pointdata["GaussianNoise"] = -ns
3250        self.pipeline = utils.OperationNode(
3251            "gaussian_noise", parents=[self], shape="egg", comment=f"sigma = {sigma}"
3252        )
3253        return self
3254
3255
3256    def closest_point(self, pt, n=1, radius=None, return_point_id=False, return_cell_id=False):
3257        """
3258        Find the closest point(s) on a mesh given from the input point `pt`.
3259
3260        Arguments:
3261            n : (int)
3262                if greater than 1, return a list of n ordered closest points
3263            radius : (float)
3264                if given, get all points within that radius. Then n is ignored.
3265            return_point_id : (bool)
3266                return point ID instead of coordinates
3267            return_cell_id : (bool)
3268                return cell ID in which the closest point sits
3269
3270        Examples:
3271            - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py)
3272            - [fitplanes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitplanes.py)
3273            - [quadratic_morphing.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/quadratic_morphing.py)
3274
3275        .. note::
3276            The appropriate tree search locator is built on the fly and cached for speed.
3277
3278            If you want to reset it use `mymesh.point_locator=None`
3279        """
3280        # NB: every time the mesh moves or is warped the locators are set to None
3281        if (n > 1 or radius) or (n == 1 and return_point_id):
3282
3283            poly = None
3284            if not self.point_locator:
3285                poly = self.polydata()
3286                self.point_locator = vtk.vtkStaticPointLocator()
3287                self.point_locator.SetDataSet(poly)
3288                self.point_locator.BuildLocator()
3289
3290            ##########
3291            if radius:
3292                vtklist = vtk.vtkIdList()
3293                self.point_locator.FindPointsWithinRadius(radius, pt, vtklist)
3294            elif n > 1:
3295                vtklist = vtk.vtkIdList()
3296                self.point_locator.FindClosestNPoints(n, pt, vtklist)
3297            else:  # n==1 hence return_point_id==True
3298                ########
3299                return self.point_locator.FindClosestPoint(pt)
3300                ########
3301
3302            if return_point_id:
3303                ########
3304                return utils.vtk2numpy(vtklist)
3305                ########
3306
3307            if not poly:
3308                poly = self.polydata()
3309            trgp = []
3310            for i in range(vtklist.GetNumberOfIds()):
3311                trgp_ = [0, 0, 0]
3312                vi = vtklist.GetId(i)
3313                poly.GetPoints().GetPoint(vi, trgp_)
3314                trgp.append(trgp_)
3315            ########
3316            return np.array(trgp)
3317            ########
3318
3319        else:
3320
3321            if not self.cell_locator:
3322                poly = self.polydata()
3323
3324                # As per Miquel example with limbs the vtkStaticCellLocator doesnt work !!
3325                # https://discourse.vtk.org/t/vtkstaticcelllocator-problem-vtk9-0-3/7854/4
3326                if vedo.vtk_version[0] >= 9 and vedo.vtk_version[0] > 0:
3327                    self.cell_locator = vtk.vtkStaticCellLocator()
3328                else:
3329                    self.cell_locator = vtk.vtkCellLocator()
3330
3331                self.cell_locator.SetDataSet(poly)
3332                self.cell_locator.BuildLocator()
3333
3334            trgp = [0, 0, 0]
3335            cid = vtk.mutable(0)
3336            dist2 = vtk.mutable(0)
3337            subid = vtk.mutable(0)
3338            self.cell_locator.FindClosestPoint(pt, trgp, cid, subid, dist2)
3339
3340            if return_cell_id:
3341                return int(cid)
3342
3343            return np.array(trgp)
3344
3345
3346    def hausdorff_distance(self, points):
3347        """
3348        Compute the Hausdorff distance to the input point set.
3349        Returns a single `float`.
3350
3351        Example:
3352            ```python
3353            from vedo import *
3354            t = np.linspace(0, 2*np.pi, 100)
3355            x = 4/3 * sin(t)**3
3356            y = cos(t) - cos(2*t)/3 - cos(3*t)/6 - cos(4*t)/12
3357            pol1 = Line(np.c_[x,y], closed=True).triangulate()
3358            pol2 = Polygon(nsides=5).pos(2,2)
3359            d12 = pol1.distance_to(pol2)
3360            d21 = pol2.distance_to(pol1)
3361            pol1.lw(0).cmap("viridis")
3362            pol2.lw(0).cmap("viridis")
3363            print("distance d12, d21 :", min(d12), min(d21))
3364            print("hausdorff distance:", pol1.hausdorff_distance(pol2))
3365            print("chamfer distance  :", pol1.chamfer_distance(pol2))
3366            show(pol1, pol2, axes=1)
3367            ```
3368            ![](https://vedo.embl.es/images/feats/heart.png)
3369        """
3370        hp = vtk.vtkHausdorffDistancePointSetFilter()
3371        hp.SetInputData(0, self.polydata())
3372        hp.SetInputData(1, points.polydata())
3373        hp.SetTargetDistanceMethodToPointToCell()
3374        hp.Update()
3375        return hp.GetHausdorffDistance()
3376
3377    def chamfer_distance(self, pcloud):
3378        """
3379        Compute the Chamfer distance to the input point set.
3380        Returns a single `float`.
3381        """
3382        if not pcloud.point_locator:
3383            pcloud.point_locator = vtk.vtkPointLocator()
3384            pcloud.point_locator.SetDataSet(pcloud.polydata())
3385            pcloud.point_locator.BuildLocator()
3386        if not self.point_locator:
3387            self.point_locator = vtk.vtkPointLocator()
3388            self.point_locator.SetDataSet(self.polydata())
3389            self.point_locator.BuildLocator()
3390
3391        ps1 = self.points()
3392        ps2 = pcloud.points()
3393
3394        ids12 = []
3395        for p in ps1:
3396            pid12 = pcloud.point_locator.FindClosestPoint(p)
3397            ids12.append(pid12)
3398        deltav = ps2[ids12] - ps1
3399        da = np.mean(np.linalg.norm(deltav, axis=1))
3400
3401        ids21 = []
3402        for p in ps2:
3403            pid21 = self.point_locator.FindClosestPoint(p)
3404            ids21.append(pid21)
3405        deltav = ps1[ids21] - ps2
3406        db = np.mean(np.linalg.norm(deltav, axis=1))
3407        return (da + db) / 2
3408
3409    def remove_outliers(self, radius, neighbors=5):
3410        """
3411        Remove outliers from a cloud of points within the specified `radius` search.
3412
3413        Arguments:
3414            radius : (float)
3415                Specify the local search radius.
3416            neighbors : (int)
3417                Specify the number of neighbors that a point must have,
3418                within the specified radius, for the point to not be considered isolated.
3419
3420        Examples:
3421            - [clustering.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/clustering.py)
3422
3423                ![](https://vedo.embl.es/images/basic/clustering.png)
3424        """
3425        removal = vtk.vtkRadiusOutlierRemoval()
3426        removal.SetInputData(self.polydata())
3427        removal.SetRadius(radius)
3428        removal.SetNumberOfNeighbors(neighbors)
3429        removal.GenerateOutliersOff()
3430        removal.Update()
3431        inputobj = removal.GetOutput()
3432        if inputobj.GetNumberOfCells() == 0:
3433            carr = vtk.vtkCellArray()
3434            for i in range(inputobj.GetNumberOfPoints()):
3435                carr.InsertNextCell(1)
3436                carr.InsertCellPoint(i)
3437            inputobj.SetVerts(carr)
3438        self._update(inputobj)
3439        self.mapper().ScalarVisibilityOff()
3440        self.pipeline = utils.OperationNode("remove_outliers", parents=[self])
3441        return self
3442
3443    def smooth_mls_1d(self, f=0.2, radius=None):
3444        """
3445        Smooth mesh or points with a `Moving Least Squares` variant.
3446        The point data array "Variances" will contain the residue calculated for each point.
3447        Input mesh's polydata is modified.
3448
3449        Arguments:
3450            f : (float)
3451                smoothing factor - typical range is [0,2].
3452            radius : (float)
3453                radius search in absolute units. If set then `f` is ignored.
3454
3455        Examples:
3456            - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py)
3457            - [skeletonize.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/skeletonize.py)
3458
3459            ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png)
3460        """
3461        coords = self.points()
3462        ncoords = len(coords)
3463
3464        if radius:
3465            Ncp = 0
3466        else:
3467            Ncp = int(ncoords * f / 10)
3468            if Ncp < 5:
3469                vedo.logger.warning(f"Please choose a fraction higher than {f}")
3470                Ncp = 5
3471
3472        variances, newline = [], []
3473        for p in coords:
3474            points = self.closest_point(p, n=Ncp, radius=radius)
3475            if len(points) < 4:
3476                continue
3477
3478            points = np.array(points)
3479            pointsmean = points.mean(axis=0)  # plane center
3480            _, dd, vv = np.linalg.svd(points - pointsmean)
3481            newp = np.dot(p - pointsmean, vv[0]) * vv[0] + pointsmean
3482            variances.append(dd[1] + dd[2])
3483            newline.append(newp)
3484
3485        vdata = utils.numpy2vtk(np.array(variances))
3486        vdata.SetName("Variances")
3487        self.inputdata().GetPointData().AddArray(vdata)
3488        self.inputdata().GetPointData().Modified()
3489        self.points(newline)
3490        self.pipeline = utils.OperationNode("smooth_mls_1d", parents=[self])
3491        return self
3492
3493    def smooth_mls_2d(self, f=0.2, radius=None):
3494        """
3495        Smooth mesh or points with a `Moving Least Squares` algorithm variant.
3496        The list `mesh.info['variances']` contains the residue calculated for each point.
3497        When a radius is specified points that are isolated will not be moved and will get
3498        a False entry in array `mesh.info['is_valid']`.
3499
3500        Arguments:
3501            f : (float)
3502                smoothing factor - typical range is [0,2].
3503            radius : (float)
3504                radius search in absolute units. If set then `f` is ignored.
3505
3506        Examples:
3507            - [moving_least_squares2D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares2D.py)
3508            - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py)
3509
3510                ![](https://vedo.embl.es/images/advanced/recosurface.png)
3511        """
3512        coords = self.points()
3513        ncoords = len(coords)
3514
3515        if radius:
3516            Ncp = 1
3517        else:
3518            Ncp = int(ncoords * f / 100)
3519            if Ncp < 4:
3520                vedo.logger.error(f"MLS2D: Please choose a fraction higher than {f}")
3521                Ncp = 4
3522
3523        variances, newpts, valid = [], [], []
3524        pb = None
3525        if ncoords > 10000:
3526            pb = utils.ProgressBar(0, ncoords)
3527        for p in coords:
3528            if pb:
3529                pb.print("smoothMLS2D working ...")
3530            pts = self.closest_point(p, n=Ncp, radius=radius)
3531            if len(pts) > 3:
3532                ptsmean = pts.mean(axis=0)  # plane center
3533                _, dd, vv = np.linalg.svd(pts - ptsmean)
3534                cv = np.cross(vv[0], vv[1])
3535                t = (np.dot(cv, ptsmean) - np.dot(cv, p)) / np.dot(cv, cv)
3536                newp = p + cv * t
3537                newpts.append(newp)
3538                variances.append(dd[2])
3539                if radius:
3540                    valid.append(True)
3541            else:
3542                newpts.append(p)
3543                variances.append(0)
3544                if radius:
3545                    valid.append(False)
3546
3547        self.info["variances"] = np.array(variances)
3548        self.info["is_valid"] = np.array(valid)
3549        self.points(newpts)
3550
3551        self.pipeline = utils.OperationNode("smooth_mls_2d", parents=[self])
3552        return self
3553
3554    def smooth_lloyd_2d(self, iterations=2, bounds=None, options="Qbb Qc Qx"):
3555        """Lloyd relaxation of a 2D pointcloud."""
3556        # Credits: https://hatarilabs.com/ih-en/
3557        # tutorial-to-create-a-geospatial-voronoi-sh-mesh-with-python-scipy-and-geopandas
3558        from scipy.spatial import Voronoi as scipy_voronoi
3559
3560        def _constrain_points(points):
3561            # Update any points that have drifted beyond the boundaries of this space
3562            if bounds is not None:
3563                for point in points:
3564                    if point[0] < bounds[0]: point[0] = bounds[0]
3565                    if point[0] > bounds[1]: point[0] = bounds[1]
3566                    if point[1] < bounds[2]: point[1] = bounds[2]
3567                    if point[1] > bounds[3]: point[1] = bounds[3]
3568            return points
3569
3570        def _find_centroid(vertices):
3571            # The equation for the method used here to find the centroid of a
3572            # 2D polygon is given here: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
3573            area = 0
3574            centroid_x = 0
3575            centroid_y = 0
3576            for i in range(len(vertices) - 1):
3577                step = (vertices[i, 0] * vertices[i + 1, 1]) - (vertices[i + 1, 0] * vertices[i, 1])
3578                centroid_x += (vertices[i, 0] + vertices[i + 1, 0]) * step
3579                centroid_y += (vertices[i, 1] + vertices[i + 1, 1]) * step
3580                area += step
3581            if area:
3582                centroid_x = (1.0 / (3.0 * area)) * centroid_x
3583                centroid_y = (1.0 / (3.0 * area)) * centroid_y
3584            # prevent centroids from escaping bounding box
3585            return _constrain_points([[centroid_x, centroid_y]])[0]
3586
3587        def _relax(voron):
3588            # Moves each point to the centroid of its cell in the voronoi
3589            # map to "relax" the points (i.e. jitter the points so as
3590            # to spread them out within the space).
3591            centroids = []
3592            for idx in voron.point_region:
3593                # the region is a series of indices into voronoi.vertices
3594                # remove point at infinity, designated by index -1
3595                region = [i for i in voron.regions[idx] if i != -1]
3596                # enclose the polygon
3597                region = region + [region[0]]
3598                verts = voron.vertices[region]
3599                # find the centroid of those vertices
3600                centroids.append(_find_centroid(verts))
3601            return _constrain_points(centroids)
3602
3603        if bounds is None:
3604            bounds = self.bounds()
3605
3606        pts = self.points()[:, (0, 1)]
3607        for i in range(iterations):
3608            vor = scipy_voronoi(pts, qhull_options=options)
3609            _constrain_points(vor.vertices)
3610            pts = _relax(vor)
3611        # m = vedo.Mesh([pts, self.faces()]) # not yet working properly
3612        out = Points(pts, c="k")
3613        out.pipeline = utils.OperationNode("smooth_lloyd", parents=[self])
3614        return out
3615
3616    def project_on_plane(self, plane="z", point=None, direction=None):
3617        """
3618        Project the mesh on one of the Cartesian planes.
3619
3620        Arguments:
3621            plane : (str, Plane)
3622                if plane is `str`, plane can be one of ['x', 'y', 'z'],
3623                represents x-plane, y-plane and z-plane, respectively.
3624                Otherwise, plane should be an instance of `vedo.shapes.Plane`.
3625            point : (float, array)
3626                if plane is `str`, point should be a float represents the intercept.
3627                Otherwise, point is the camera point of perspective projection
3628            direction : (array)
3629                direction of oblique projection
3630
3631        Note:
3632            Parameters `point` and `direction` are only used if the given plane
3633            is an instance of `vedo.shapes.Plane`. And one of these two params
3634            should be left as `None` to specify the projection type.
3635
3636        Example:
3637            ```python
3638            s.project_on_plane(plane='z') # project to z-plane
3639            plane = Plane(pos=(4, 8, -4), normal=(-1, 0, 1), s=(5,5))
3640            s.project_on_plane(plane=plane)                       # orthogonal projection
3641            s.project_on_plane(plane=plane, point=(6, 6, 6))      # perspective projection
3642            s.project_on_plane(plane=plane, direction=(1, 2, -1)) # oblique projection
3643            ```
3644
3645        Examples:
3646            - [silhouette2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette2.py)
3647
3648                ![](https://vedo.embl.es/images/basic/silhouette2.png)
3649        """
3650        coords = self.points()
3651
3652        if plane == "x":
3653            coords[:, 0] = self.GetOrigin()[0]
3654            intercept = self.xbounds()[0] if point is None else point
3655            self.x(intercept)
3656        elif plane == "y":
3657            coords[:, 1] = self.GetOrigin()[1]
3658            intercept = self.ybounds()[0] if point is None else point
3659            self.y(intercept)
3660        elif plane == "z":
3661            coords[:, 2] = self.GetOrigin()[2]
3662            intercept = self.zbounds()[0] if point is None else point
3663            self.z(intercept)
3664
3665        elif isinstance(plane, vedo.shapes.Plane):
3666            normal = plane.normal / np.linalg.norm(plane.normal)
3667            pl = np.hstack((normal, -np.dot(plane.pos(), normal))).reshape(4, 1)
3668            if direction is None and point is None:
3669                # orthogonal projection
3670                pt = np.hstack((normal, [0])).reshape(4, 1)
3671                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T # python3 only
3672                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3673
3674            elif direction is None:
3675                # perspective projection
3676                pt = np.hstack((np.array(point), [1])).reshape(4, 1)
3677                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T
3678                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3679
3680            elif point is None:
3681                # oblique projection
3682                pt = np.hstack((np.array(direction), [0])).reshape(4, 1)
3683                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T
3684                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3685
3686            coords = np.concatenate([coords, np.ones((coords.shape[:-1] + (1,)))], axis=-1)
3687            # coords = coords @ proj_mat.T
3688            coords = np.matmul(coords, proj_mat.T)
3689            coords = coords[:, :3] / coords[:, 3:]
3690
3691        else:
3692            vedo.logger.error(f"unknown plane {plane}")
3693            raise RuntimeError()
3694
3695        self.alpha(0.1)
3696        self.points(coords)
3697        return self
3698
3699    def warp(self, source, target, sigma=1.0, mode="3d"):
3700        """
3701        `Thin Plate Spline` transformations describe a nonlinear warp transform defined by a set
3702        of source and target landmarks. Any point on the mesh close to a source landmark will
3703        be moved to a place close to the corresponding target landmark.
3704        The points in between are interpolated smoothly using
3705        Bookstein's Thin Plate Spline algorithm.
3706
3707        Transformation object can be accessed with `mesh.transform`.
3708
3709        Arguments:
3710            sigma : (float)
3711                specify the 'stiffness' of the spline.
3712            mode : (str)
3713                set the basis function to either abs(R) (for 3d) or R2LogR (for 2d meshes)
3714
3715        Examples:
3716            - [interpolate_field.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_field.py)
3717            - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py)
3718            - [warp2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp2.py)
3719
3720                ![](https://vedo.embl.es/images/advanced/warp2.png)
3721        """
3722        parents = [self]
3723        if isinstance(source, Points):
3724            parents.append(source)
3725            source = source.points()
3726        else:
3727            source = utils.make3d(source)
3728
3729        if isinstance(target, Points):
3730            parents.append(target)
3731            target = target.points()
3732        else:
3733            target = utils.make3d(target)
3734
3735        ns = len(source)
3736        ptsou = vtk.vtkPoints()
3737        ptsou.SetNumberOfPoints(ns)
3738        for i in range(ns):
3739            ptsou.SetPoint(i, source[i])
3740
3741        nt = len(target)
3742        if ns != nt:
3743            vedo.logger.error(f"#source {ns} != {nt} #target points")
3744            raise RuntimeError()
3745
3746        pttar = vtk.vtkPoints()
3747        pttar.SetNumberOfPoints(nt)
3748        for i in range(ns):
3749            pttar.SetPoint(i, target[i])
3750
3751        T = vtk.vtkThinPlateSplineTransform()
3752        if mode.lower() == "3d":
3753            T.SetBasisToR()
3754        elif mode.lower() == "2d":
3755            T.SetBasisToR2LogR()
3756        else:
3757            vedo.logger.error(f"unknown mode {mode}")
3758            raise RuntimeError()
3759
3760        T.SetSigma(sigma)
3761        T.SetSourceLandmarks(ptsou)
3762        T.SetTargetLandmarks(pttar)
3763        self.transform = T
3764        self.apply_transform(T, reset=True)
3765
3766        self.pipeline = utils.OperationNode("warp", parents=parents)
3767        return self
3768
3769    def cut_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0), invert=False):
3770        """
3771        Cut the mesh with the plane defined by a point and a normal.
3772
3773        Arguments:
3774            origin : (array)
3775                the cutting plane goes through this point
3776            normal : (array)
3777                normal of the cutting plane
3778
3779        Example:
3780            ```python
3781            from vedo import Cube
3782            cube = Cube().cut_with_plane(normal=(1,1,1))
3783            cube.back_color('pink').show()
3784            ```
3785            ![](https://vedo.embl.es/images/feats/cut_with_plane_cube.png)
3786
3787        Examples:
3788            - [trail.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/trail.py)
3789
3790                ![](https://vedo.embl.es/images/simulations/trail.gif)
3791
3792        Check out also:
3793            `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`.
3794        """
3795        s = str(normal)
3796        if "x" in s:
3797            normal = (1, 0, 0)
3798            if "-" in s:
3799                normal = -np.array(normal)
3800        elif "y" in s:
3801            normal = (0, 1, 0)
3802            if "-" in s:
3803                normal = -np.array(normal)
3804        elif "z" in s:
3805            normal = (0, 0, 1)
3806            if "-" in s:
3807                normal = -np.array(normal)
3808        plane = vtk.vtkPlane()
3809        plane.SetOrigin(origin)
3810        plane.SetNormal(normal)
3811
3812        clipper = vtk.vtkClipPolyData()
3813        clipper.SetInputData(self.polydata(True))  # must be True
3814        clipper.SetClipFunction(plane)
3815        clipper.GenerateClippedOutputOff()
3816        clipper.GenerateClipScalarsOff()
3817        clipper.SetInsideOut(invert)
3818        clipper.SetValue(0)
3819        clipper.Update()
3820
3821        cpoly = clipper.GetOutput()
3822
3823        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3824            self._update(cpoly)
3825        else:
3826            # bring the underlying polydata to where _data is
3827            M = vtk.vtkMatrix4x4()
3828            M.DeepCopy(self.GetMatrix())
3829            M.Invert()
3830            tr = vtk.vtkTransform()
3831            tr.SetMatrix(M)
3832            tf = vtk.vtkTransformPolyDataFilter()
3833            tf.SetTransform(tr)
3834            tf.SetInputData(cpoly)
3835            tf.Update()
3836            self._update(tf.GetOutput())
3837
3838        self.pipeline = utils.OperationNode("cut_with_plane", parents=[self])
3839        return self
3840
3841    def cut_with_planes(self, origins, normals, invert=False):
3842        """
3843        Cut the mesh with a convex set of planes defined by points and normals.
3844
3845        Arguments:
3846            origins : (array)
3847                each cutting plane goes through this point
3848            normals : (array)
3849                normal of each of the cutting planes
3850
3851        Check out also:
3852            `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`
3853        """
3854
3855        vpoints = vtk.vtkPoints()
3856        for p in utils.make3d(origins):
3857            vpoints.InsertNextPoint(p)
3858        normals = utils.make3d(normals)
3859
3860        planes = vtk.vtkPlanes()
3861        planes.SetPoints(vpoints)
3862        planes.SetNormals(utils.numpy2vtk(normals, dtype=float))
3863
3864        clipper = vtk.vtkClipPolyData()
3865        clipper.SetInputData(self.polydata(True))  # must be True
3866        clipper.SetInsideOut(invert)
3867        clipper.SetClipFunction(planes)
3868        clipper.GenerateClippedOutputOff()
3869        clipper.GenerateClipScalarsOff()
3870        clipper.SetValue(0)
3871        clipper.Update()
3872
3873        cpoly = clipper.GetOutput()
3874
3875        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3876            self._update(cpoly)
3877        else:
3878            # bring the underlying polydata to where _data is
3879            M = vtk.vtkMatrix4x4()
3880            M.DeepCopy(self.GetMatrix())
3881            M.Invert()
3882            tr = vtk.vtkTransform()
3883            tr.SetMatrix(M)
3884            tf = vtk.vtkTransformPolyDataFilter()
3885            tf.SetTransform(tr)
3886            tf.SetInputData(cpoly)
3887            tf.Update()
3888            self._update(tf.GetOutput())
3889
3890        self.pipeline = utils.OperationNode("cut_with_planes", parents=[self])
3891        return self
3892
3893    def cut_with_box(self, bounds, invert=False):
3894        """
3895        Cut the current mesh with a box or a set of boxes.
3896        This is much faster than `cut_with_mesh()`.
3897
3898        Input `bounds` can be either:
3899        - a Mesh or Points object
3900        - a list of 6 number representing a bounding box `[xmin,xmax, ymin,ymax, zmin,zmax]`
3901        - a list of bounding boxes like the above: `[[xmin1,...], [xmin2,...], ...]`
3902
3903        Example:
3904            ```python
3905            from vedo import Sphere, Cube, show
3906            mesh = Sphere(r=1, res=50)
3907            box  = Cube(side=1.5).wireframe()
3908            mesh.cut_with_box(box)
3909            show(mesh, box, axes=1)
3910            ```
3911            ![](https://vedo.embl.es/images/feats/cut_with_box_cube.png)
3912
3913        Check out also:
3914            `cut_with_line()`, `cut_with_plane()`, `cut_with_cylinder()`
3915        """
3916        if isinstance(bounds, Points):
3917            bounds = bounds.bounds()
3918
3919        box = vtk.vtkBox()
3920        if utils.is_sequence(bounds[0]):
3921            for bs in bounds:
3922                box.AddBounds(bs)
3923        else:
3924            box.SetBounds(bounds)
3925
3926        clipper = vtk.vtkClipPolyData()
3927        clipper.SetInputData(self.polydata(True))  # must be True
3928        clipper.SetClipFunction(box)
3929        clipper.SetInsideOut(not invert)
3930        clipper.GenerateClippedOutputOff()
3931        clipper.GenerateClipScalarsOff()
3932        clipper.SetValue(0)
3933        clipper.Update()
3934        cpoly = clipper.GetOutput()
3935
3936        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3937            self._update(cpoly)
3938        else:
3939            # bring the underlying polydata to where _data is
3940            M = vtk.vtkMatrix4x4()
3941            M.DeepCopy(self.GetMatrix())
3942            M.Invert()
3943            tr = vtk.vtkTransform()
3944            tr.SetMatrix(M)
3945            tf = vtk.vtkTransformPolyDataFilter()
3946            tf.SetTransform(tr)
3947            tf.SetInputData(cpoly)
3948            tf.Update()
3949            self._update(tf.GetOutput())
3950
3951        self.pipeline = utils.OperationNode("cut_with_box", parents=[self])
3952        return self
3953
3954    def cut_with_line(self, points, invert=False, closed=True):
3955        """
3956        Cut the current mesh with a line vertically in the z-axis direction like a cookie cutter.
3957        The polyline is defined by a set of points (z-coordinates are ignored).
3958        This is much faster than `cut_with_mesh()`.
3959
3960        Check out also:
3961            `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()`
3962        """
3963        pplane = vtk.vtkPolyPlane()
3964        if isinstance(points, Points):
3965            points = points.points().tolist()
3966
3967        if closed:
3968            if isinstance(points, np.ndarray):
3969                points = points.tolist()
3970            points.append(points[0])
3971
3972        vpoints = vtk.vtkPoints()
3973        for p in points:
3974            if len(p) == 2:
3975                p = [p[0], p[1], 0.0]
3976            vpoints.InsertNextPoint(p)
3977
3978        n = len(points)
3979        polyline = vtk.vtkPolyLine()
3980        polyline.Initialize(n, vpoints)
3981        polyline.GetPointIds().SetNumberOfIds(n)
3982        for i in range(n):
3983            polyline.GetPointIds().SetId(i, i)
3984        pplane.SetPolyLine(polyline)
3985
3986        clipper = vtk.vtkClipPolyData()
3987        clipper.SetInputData(self.polydata(True))  # must be True
3988        clipper.SetClipFunction(pplane)
3989        clipper.SetInsideOut(invert)
3990        clipper.GenerateClippedOutputOff()
3991        clipper.GenerateClipScalarsOff()
3992        clipper.SetValue(0)
3993        clipper.Update()
3994        cpoly = clipper.GetOutput()
3995
3996        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3997            self._update(cpoly)
3998        else:
3999            # bring the underlying polydata to where _data is
4000            M = vtk.vtkMatrix4x4()
4001            M.DeepCopy(self.GetMatrix())
4002            M.Invert()
4003            tr = vtk.vtkTransform()
4004            tr.SetMatrix(M)
4005            tf = vtk.vtkTransformPolyDataFilter()
4006            tf.SetTransform(tr)
4007            tf.SetInputData(cpoly)
4008            tf.Update()
4009            self._update(tf.GetOutput())
4010
4011        self.pipeline = utils.OperationNode("cut_with_line", parents=[self])
4012        return self
4013
4014    def cut_with_cookiecutter(self, lines):
4015        """
4016        Cut the current mesh with a single line or a set of lines.
4017
4018        Input `lines` can be either:
4019        - a `Mesh` or `Points` object
4020        - a list of 3D points: `[(x1,y1,z1), (x2,y2,z2), ...]`
4021        - a list of 2D points: `[(x1,y1), (x2,y2), ...]`
4022
4023        Example:
4024            ```python
4025            from vedo import *
4026            grid = Mesh(dataurl + "dolfin_fine.vtk")
4027            grid.compute_quality().cmap("Greens")
4028            pols = merge(
4029                Polygon(nsides=10, r=0.3).pos(0.7, 0.3),
4030                Polygon(nsides=10, r=0.2).pos(0.3, 0.7),
4031            )
4032            lines = pols.boundaries()
4033            grid.cut_with_cookiecutter(lines)
4034            show(grid, lines, axes=8, bg='blackboard').close()
4035            ```
4036            ![](https://vedo.embl.es/images/feats/cookiecutter.png)
4037
4038        Check out also:
4039            `cut_with_line()` and `cut_with_point_loop()`
4040
4041        Note:
4042            In case of a warning message like:
4043                "Mesh and trim loop point data attributes are different"
4044            consider interpolating the mesh point data to the loop points,
4045            Eg. (in the above example):
4046            ```python
4047            lines = pols.boundaries().interpolate_data_from(grid, n=2)
4048            ```
4049
4050        Note:
4051            trying to invert the selection by reversing the loop order
4052            will have no effect in this method, hence it does not have
4053            the `invert` option.
4054        """
4055        if utils.is_sequence(lines):
4056            lines = utils.make3d(lines)
4057            iline = list(range(len(lines))) + [0]
4058            poly = utils.buildPolyData(lines, lines=[iline])
4059        else:
4060            poly = lines.polydata()
4061
4062        # if invert: # not working
4063        #     rev = vtk.vtkReverseSense()
4064        #     rev.ReverseCellsOn()
4065        #     rev.SetInputData(poly)
4066        #     rev.Update()
4067        #     poly = rev.GetOutput()
4068
4069        # Build loops from the polyline
4070        build_loops = vtk.vtkContourLoopExtraction()
4071        build_loops.SetInputData(poly)
4072        build_loops.Update()
4073        boundaryPoly = build_loops.GetOutput()
4074
4075        ccut = vtk.vtkCookieCutter()
4076        ccut.SetInputData(self.polydata())
4077        ccut.SetLoopsData(boundaryPoly)
4078        ccut.SetPointInterpolationToMeshEdges()
4079        # ccut.SetPointInterpolationToLoopEdges()
4080        ccut.PassCellDataOn()
4081        # ccut.PassPointDataOn()
4082        ccut.Update()
4083        cpoly = ccut.GetOutput()
4084
4085        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4086            self._update(cpoly)
4087        else:
4088            # bring the underlying polydata to where _data is
4089            M = vtk.vtkMatrix4x4()
4090            M.DeepCopy(self.GetMatrix())
4091            M.Invert()
4092            tr = vtk.vtkTransform()
4093            tr.SetMatrix(M)
4094            tf = vtk.vtkTransformPolyDataFilter()
4095            tf.SetTransform(tr)
4096            tf.SetInputData(cpoly)
4097            tf.Update()
4098            self._update(tf.GetOutput())
4099
4100        self.pipeline = utils.OperationNode("cut_with_cookiecutter", parents=[self])
4101        return self
4102
4103    def cut_with_cylinder(self, center=(0, 0, 0), axis=(0, 0, 1), r=1, invert=False):
4104        """
4105        Cut the current mesh with an infinite cylinder.
4106        This is much faster than `cut_with_mesh()`.
4107
4108        Arguments:
4109            center : (array)
4110                the center of the cylinder
4111            normal : (array)
4112                direction of the cylinder axis
4113            r : (float)
4114                radius of the cylinder
4115
4116        Example:
4117            ```python
4118            from vedo import Disc, show
4119            disc = Disc(r1=1, r2=1.2)
4120            mesh = disc.extrude(3, res=50).linewidth(1)
4121            mesh.cut_with_cylinder([0,0,2], r=0.4, axis='y', invert=True)
4122            show(mesh, axes=1)
4123            ```
4124            ![](https://vedo.embl.es/images/feats/cut_with_cylinder.png)
4125
4126        Examples:
4127            - [optics_main1.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/optics_main1.py)
4128
4129        Check out also:
4130            `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()`
4131        """
4132        s = str(axis)
4133        if "x" in s:
4134            axis = (1, 0, 0)
4135        elif "y" in s:
4136            axis = (0, 1, 0)
4137        elif "z" in s:
4138            axis = (0, 0, 1)
4139        cyl = vtk.vtkCylinder()
4140        cyl.SetCenter(center)
4141        cyl.SetAxis(axis[0], axis[1], axis[2])
4142        cyl.SetRadius(r)
4143
4144        clipper = vtk.vtkClipPolyData()
4145        clipper.SetInputData(self.polydata(True))  # must be True
4146        clipper.SetClipFunction(cyl)
4147        clipper.SetInsideOut(not invert)
4148        clipper.GenerateClippedOutputOff()
4149        clipper.GenerateClipScalarsOff()
4150        clipper.SetValue(0)
4151        clipper.Update()
4152        cpoly = clipper.GetOutput()
4153
4154        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4155            self._update(cpoly)
4156        else:
4157            # bring the underlying polydata to where _data is
4158            M = vtk.vtkMatrix4x4()
4159            M.DeepCopy(self.GetMatrix())
4160            M.Invert()
4161            tr = vtk.vtkTransform()
4162            tr.SetMatrix(M)
4163            tf = vtk.vtkTransformPolyDataFilter()
4164            tf.SetTransform(tr)
4165            tf.SetInputData(cpoly)
4166            tf.Update()
4167            self._update(tf.GetOutput())
4168
4169        self.pipeline = utils.OperationNode("cut_with_cylinder", parents=[self])
4170        return self
4171
4172    def cut_with_sphere(self, center=(0, 0, 0), r=1.0, invert=False):
4173        """
4174        Cut the current mesh with an sphere.
4175        This is much faster than `cut_with_mesh()`.
4176
4177        Arguments:
4178            center : (array)
4179                the center of the sphere
4180            r : (float)
4181                radius of the sphere
4182
4183        Example:
4184            ```python
4185            from vedo import Disc, show
4186            disc = Disc(r1=1, r2=1.2)
4187            mesh = disc.extrude(3, res=50).linewidth(1)
4188            mesh.cut_with_sphere([1,-0.7,2], r=1.5, invert=True)
4189            show(mesh, axes=1)
4190            ```
4191            ![](https://vedo.embl.es/images/feats/cut_with_sphere.png)
4192
4193        Check out also:
4194            `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()`
4195        """
4196        sph = vtk.vtkSphere()
4197        sph.SetCenter(center)
4198        sph.SetRadius(r)
4199
4200        clipper = vtk.vtkClipPolyData()
4201        clipper.SetInputData(self.polydata(True))  # must be True
4202        clipper.SetClipFunction(sph)
4203        clipper.SetInsideOut(not invert)
4204        clipper.GenerateClippedOutputOff()
4205        clipper.GenerateClipScalarsOff()
4206        clipper.SetValue(0)
4207        clipper.Update()
4208        cpoly = clipper.GetOutput()
4209
4210        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4211            self._update(cpoly)
4212        else:
4213            # bring the underlying polydata to where _data is
4214            M = vtk.vtkMatrix4x4()
4215            M.DeepCopy(self.GetMatrix())
4216            M.Invert()
4217            tr = vtk.vtkTransform()
4218            tr.SetMatrix(M)
4219            tf = vtk.vtkTransformPolyDataFilter()
4220            tf.SetTransform(tr)
4221            tf.SetInputData(cpoly)
4222            tf.Update()
4223            self._update(tf.GetOutput())
4224
4225        self.pipeline = utils.OperationNode("cut_with_sphere", parents=[self])
4226        return self
4227
4228    def cut_with_mesh(self, mesh, invert=False, keep=False):
4229        """
4230        Cut an `Mesh` mesh with another `Mesh`.
4231
4232        Use `invert` to invert the selection.
4233
4234        Use `keep` to keep the cutoff part, in this case an `Assembly` is returned:
4235        the "cut" object and the "discarded" part of the original object.
4236        You can access both via `assembly.unpack()` method.
4237
4238        Example:
4239        ```python
4240        from vedo import *
4241        arr = np.random.randn(100000, 3)/2
4242        pts = Points(arr).c('red3').pos(5,0,0)
4243        cube = Cube().pos(4,0.5,0)
4244        assem = pts.cut_with_mesh(cube, keep=True)
4245        show(assem.unpack(), axes=1).close()
4246        ```
4247        ![](https://vedo.embl.es/images/feats/cut_with_mesh.png)
4248
4249       Check out also:
4250            `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()`
4251       """
4252        polymesh = mesh.polydata()
4253        poly = self.polydata()
4254
4255        # Create an array to hold distance information
4256        signed_distances = vtk.vtkFloatArray()
4257        signed_distances.SetNumberOfComponents(1)
4258        signed_distances.SetName("SignedDistances")
4259
4260        # implicit function that will be used to slice the mesh
4261        ippd = vtk.vtkImplicitPolyDataDistance()
4262        ippd.SetInput(polymesh)
4263
4264        # Evaluate the signed distance function at all of the grid points
4265        for pointId in range(poly.GetNumberOfPoints()):
4266            p = poly.GetPoint(pointId)
4267            signed_distance = ippd.EvaluateFunction(p)
4268            signed_distances.InsertNextValue(signed_distance)
4269
4270        currentscals = poly.GetPointData().GetScalars()
4271        if currentscals:
4272            currentscals = currentscals.GetName()
4273
4274        poly.GetPointData().AddArray(signed_distances)
4275        poly.GetPointData().SetActiveScalars("SignedDistances")
4276
4277        clipper = vtk.vtkClipPolyData()
4278        clipper.SetInputData(poly)
4279        clipper.SetInsideOut(not invert)
4280        clipper.SetGenerateClippedOutput(keep)
4281        clipper.SetValue(0.0)
4282        clipper.Update()
4283        cpoly = clipper.GetOutput()
4284        if keep:
4285            kpoly = clipper.GetOutput(1)
4286
4287        vis = False
4288        if currentscals:
4289            cpoly.GetPointData().SetActiveScalars(currentscals)
4290            vis = self.mapper().GetScalarVisibility()
4291
4292        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4293            self._update(cpoly)
4294        else:
4295            # bring the underlying polydata to where _data is
4296            M = vtk.vtkMatrix4x4()
4297            M.DeepCopy(self.GetMatrix())
4298            M.Invert()
4299            tr = vtk.vtkTransform()
4300            tr.SetMatrix(M)
4301            tf = vtk.vtkTransformPolyDataFilter()
4302            tf.SetTransform(tr)
4303            tf.SetInputData(cpoly)
4304            tf.Update()
4305            self._update(tf.GetOutput())
4306
4307        self.pointdata.remove("SignedDistances")
4308        self.mapper().SetScalarVisibility(vis)
4309        if keep:
4310            if isinstance(self, vedo.Mesh):
4311                cutoff = vedo.Mesh(kpoly)
4312            else:
4313                cutoff = vedo.Points(kpoly)
4314            cutoff.property = vtk.vtkProperty()
4315            cutoff.property.DeepCopy(self.property)
4316            cutoff.SetProperty(cutoff.property)
4317            cutoff.c("k5").alpha(0.2)
4318            return vedo.Assembly([self, cutoff])
4319
4320        self.pipeline = utils.OperationNode("cut_with_mesh", parents=[self, mesh])
4321        return self
4322
4323    def cut_with_point_loop(self, points, invert=False, on="points", include_boundary=False):
4324        """
4325        Cut an `Mesh` object with a set of points forming a closed loop.
4326
4327        Arguments:
4328            invert : (bool)
4329                invert selection (inside-out)
4330            on : (str)
4331                if 'cells' will extract the whole cells lying inside (or outside) the point loop
4332            include_boundary : (bool)
4333                include cells lying exactly on the boundary line. Only relevant on 'cells' mode
4334
4335        Examples:
4336            - [cut_with_points1.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points1.py)
4337
4338                ![](https://vedo.embl.es/images/advanced/cutWithPoints1.png)
4339
4340            - [cut_with_points2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points2.py)
4341
4342                ![](https://vedo.embl.es/images/advanced/cutWithPoints2.png)
4343        """
4344        if isinstance(points, Points):
4345            parents = [points]
4346            vpts = points.polydata().GetPoints()
4347            points = points.points()
4348        else:
4349            parents = [self]
4350            vpts = vtk.vtkPoints()
4351            points = utils.make3d(points)
4352            for p in points:
4353                vpts.InsertNextPoint(p)
4354
4355        if "cell" in on:
4356            ippd = vtk.vtkImplicitSelectionLoop()
4357            ippd.SetLoop(vpts)
4358            ippd.AutomaticNormalGenerationOn()
4359            clipper = vtk.vtkExtractPolyDataGeometry()
4360            clipper.SetInputData(self.polydata())
4361            clipper.SetImplicitFunction(ippd)
4362            clipper.SetExtractInside(not invert)
4363            clipper.SetExtractBoundaryCells(include_boundary)
4364        else:
4365            spol = vtk.vtkSelectPolyData()
4366            spol.SetLoop(vpts)
4367            spol.GenerateSelectionScalarsOn()
4368            spol.GenerateUnselectedOutputOff()
4369            spol.SetInputData(self.polydata())
4370            spol.Update()
4371            clipper = vtk.vtkClipPolyData()
4372            clipper.SetInputData(spol.GetOutput())
4373            clipper.SetInsideOut(not invert)
4374            clipper.SetValue(0.0)
4375        clipper.Update()
4376        cpoly = clipper.GetOutput()
4377
4378        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4379            self._update(cpoly)
4380        else:
4381            # bring the underlying polydata to where _data is
4382            M = vtk.vtkMatrix4x4()
4383            M.DeepCopy(self.GetMatrix())
4384            M.Invert()
4385            tr = vtk.vtkTransform()
4386            tr.SetMatrix(M)
4387            tf = vtk.vtkTransformPolyDataFilter()
4388            tf.SetTransform(tr)
4389            tf.SetInputData(clipper.GetOutput())
4390            tf.Update()
4391            self._update(tf.GetOutput())
4392
4393        self.pipeline = utils.OperationNode("cut_with_pointloop", parents=parents)
4394        return self
4395
4396    def cut_with_scalar(self, value, name="", invert=False):
4397        """
4398        Cut a mesh or point cloud with some input scalar point-data.
4399
4400        Arguments:
4401            value : (float)
4402                cutting value
4403            name : (str)
4404                array name of the scalars to be used
4405            invert : (bool)
4406                flip selection
4407
4408        Example:
4409            ```python
4410            from vedo import *
4411            s = Sphere().lw(1)
4412            pts = s.points()
4413            scalars = np.sin(3*pts[:,2]) + pts[:,0]
4414            s.pointdata["somevalues"] = scalars
4415            s.cut_with_scalar(0.3)
4416            s.cmap("Spectral", "somevalues").add_scalarbar()
4417            s.show(axes=1).close()
4418            ```
4419            ![](https://vedo.embl.es/images/feats/cut_with_scalars.png)
4420        """
4421        if name:
4422            self.pointdata.select(name)
4423        clipper = vtk.vtkClipPolyData()
4424        clipper.SetInputData(self._data)
4425        clipper.SetValue(value)
4426        clipper.GenerateClippedOutputOff()
4427        clipper.SetInsideOut(not invert)
4428        clipper.Update()
4429        self._update(clipper.GetOutput())
4430
4431        self.pipeline = utils.OperationNode("cut_with_scalars", parents=[self])
4432        return self
4433
4434    def implicit_modeller(self, distance=0.05, res=(50, 50, 50), bounds=(), maxdist=None):
4435        """Find the surface which sits at the specified distance from the input one."""
4436        if not bounds:
4437            bounds = self.bounds()
4438
4439        if not maxdist:
4440            maxdist = self.diagonal_size() / 2
4441
4442        imp = vtk.vtkImplicitModeller()
4443        imp.SetInputData(self.polydata())
4444        imp.SetSampleDimensions(res)
4445        imp.SetMaximumDistance(maxdist)
4446        imp.SetModelBounds(bounds)
4447        contour = vtk.vtkContourFilter()
4448        contour.SetInputConnection(imp.GetOutputPort())
4449        contour.SetValue(0, distance)
4450        contour.Update()
4451        poly = contour.GetOutput()
4452        out = vedo.Mesh(poly, c="lb")
4453
4454        out.pipeline = utils.OperationNode("implicit_modeller", parents=[self])
4455        return out
4456
4457
4458    def generate_mesh(
4459        self,
4460        line_resolution=None,
4461        mesh_resolution=None,
4462        smooth=0.0,
4463        jitter=0.001,
4464        grid=None,
4465        quads=False,
4466        invert=False,
4467    ):
4468        """
4469        Generate a polygonal Mesh from a closed contour line.
4470        If line is not closed it will be closed with a straight segment.
4471
4472        Arguments:
4473            line_resolution : (int)
4474                resolution of the contour line. The default is None, in this case
4475                the contour is not resampled.
4476            mesh_resolution : (int)
4477                resolution of the internal triangles not touching the boundary.
4478            smooth : (float)
4479                smoothing of the contour before meshing.
4480            jitter : (float)
4481                add a small noise to the internal points.
4482            grid : (Grid)
4483                manually pass a Grid object. The default is True.
4484            quads : (bool)
4485                generate a mesh of quads instead of triangles.
4486            invert : (bool)
4487                flip the line orientation. The default is False.
4488
4489        Examples:
4490            - [line2mesh_tri.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_tri.py)
4491
4492                ![](https://vedo.embl.es/images/advanced/line2mesh_tri.jpg)
4493
4494            - [line2mesh_quads.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_quads.py)
4495
4496                ![](https://vedo.embl.es/images/advanced/line2mesh_quads.png)
4497        """
4498        if line_resolution is None:
4499            contour = vedo.shapes.Line(self.points())
4500        else:
4501            contour = vedo.shapes.Spline(self.points(), smooth=smooth, res=line_resolution)
4502        contour.clean()
4503
4504        length = contour.length()
4505        density = length / contour.npoints
4506        vedo.logger.debug(f"tomesh():\n\tline length = {length}")
4507        vedo.logger.debug(f"\tdensity = {density} length/pt_separation")
4508
4509        x0, x1 = contour.xbounds()
4510        y0, y1 = contour.ybounds()
4511
4512        if grid is None:
4513            if mesh_resolution is None:
4514                resx = int((x1 - x0) / density + 0.5)
4515                resy = int((y1 - y0) / density + 0.5)
4516                vedo.logger.debug(f"tmesh_resolution = {[resx, resy]}")
4517            else:
4518                if utils.is_sequence(mesh_resolution):
4519                    resx, resy = mesh_resolution
4520                else:
4521                    resx, resy = mesh_resolution, mesh_resolution
4522            grid = vedo.shapes.Grid(
4523                [(x0 + x1) / 2, (y0 + y1) / 2, 0],
4524                s=((x1 - x0) * 1.025, (y1 - y0) * 1.025),
4525                res=(resx, resy),
4526            )
4527        else:
4528            grid = grid.clone()
4529
4530        cpts = contour.points()
4531
4532        # make sure it's closed
4533        p0, p1 = cpts[0], cpts[-1]
4534        nj = max(2, int(utils.mag(p1 - p0) / density + 0.5))
4535        joinline = vedo.shapes.Line(p1, p0, res=nj)
4536        contour = vedo.merge(contour, joinline).subsample(0.0001)
4537
4538        ####################################### quads
4539        if quads:
4540            cmesh = grid.clone().cut_with_point_loop(contour, on="cells", invert=invert)
4541            cmesh.wireframe(False).lw(0.5)
4542            cmesh.pipeline = utils.OperationNode(
4543                "generate_mesh",
4544                parents=[self, contour],
4545                comment=f"#quads {cmesh.inputdata().GetNumberOfCells()}",
4546            )
4547            return cmesh
4548        #############################################
4549
4550        grid_tmp = grid.points()
4551
4552        if jitter:
4553            np.random.seed(0)
4554            sigma = 1.0 / np.sqrt(grid.npoints) * grid.diagonal_size() * jitter
4555            vedo.logger.debug(f"\tsigma jittering = {sigma}")
4556            grid_tmp += np.random.rand(grid.npoints, 3) * sigma
4557            grid_tmp[:, 2] = 0.0
4558
4559        todel = []
4560        density /= np.sqrt(3)
4561        vgrid_tmp = Points(grid_tmp)
4562
4563        for p in contour.points():
4564            out = vgrid_tmp.closest_point(p, radius=density, return_point_id=True)
4565            todel += out.tolist()
4566        # cpoints = contour.points()
4567        # for i, p in enumerate(cpoints):
4568        #     if i:
4569        #         den = utils.mag(p-cpoints[i-1])/1.732
4570        #     else:
4571        #         den = density
4572        #     todel += vgrid_tmp.closest_point(p, radius=den, return_point_id=True)
4573
4574        grid_tmp = grid_tmp.tolist()
4575        for index in sorted(list(set(todel)), reverse=True):
4576            del grid_tmp[index]
4577
4578        points = contour.points().tolist() + grid_tmp
4579        if invert:
4580            boundary = reversed(range(contour.npoints))
4581        else:
4582            boundary = range(contour.npoints)
4583
4584        dln = delaunay2d(points, mode="xy", boundaries=[boundary])
4585        dln.compute_normals(points=False)  # fixes reversd faces
4586        dln.lw(0.5)
4587
4588        dln.pipeline = utils.OperationNode(
4589            "generate_mesh",
4590            parents=[self, contour],
4591            comment=f"#cells {dln.inputdata().GetNumberOfCells()}",
4592        )
4593        return dln
4594
4595    def reconstruct_surface(
4596        self,
4597        dims=(100, 100, 100),
4598        radius=None,
4599        sample_size=None,
4600        hole_filling=True,
4601        bounds=(),
4602        padding=0.05,
4603    ):
4604        """
4605        Surface reconstruction from a scattered cloud of points.
4606
4607        Arguments:
4608            dims : (int)
4609                number of voxels in x, y and z to control precision.
4610            radius : (float)
4611                radius of influence of each point.
4612                Smaller values generally improve performance markedly.
4613                Note that after the signed distance function is computed,
4614                any voxel taking on the value >= radius
4615                is presumed to be "unseen" or uninitialized.
4616            sample_size : (int)
4617                if normals are not present
4618                they will be calculated using this sample size per point.
4619            hole_filling : (bool)
4620                enables hole filling, this generates
4621                separating surfaces between the empty and unseen portions of the volume.
4622            bounds : (list)
4623                region in space in which to perform the sampling
4624                in format (xmin,xmax, ymin,ymax, zim, zmax)
4625            padding : (float)
4626                increase by this fraction the bounding box
4627
4628        Examples:
4629            - [recosurface.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/recosurface.py)
4630
4631                ![](https://vedo.embl.es/images/advanced/recosurface.png)
4632        """
4633        if not utils.is_sequence(dims):
4634            dims = (dims, dims, dims)
4635
4636        sdf = vtk.vtkSignedDistance()
4637
4638        if len(bounds) == 6:
4639            sdf.SetBounds(bounds)
4640        else:
4641            x0, x1, y0, y1, z0, z1 = self.bounds()
4642            sdf.SetBounds(
4643                x0 - (x1 - x0) * padding,
4644                x1 + (x1 - x0) * padding,
4645                y0 - (y1 - y0) * padding,
4646                y1 + (y1 - y0) * padding,
4647                z0 - (z1 - z0) * padding,
4648                z1 + (z1 - z0) * padding,
4649            )
4650
4651        pd = self.polydata()
4652
4653        if pd.GetPointData().GetNormals():
4654            sdf.SetInputData(pd)
4655        else:
4656            normals = vtk.vtkPCANormalEstimation()
4657            normals.SetInputData(pd)
4658            if not sample_size:
4659                sample_size = int(pd.GetNumberOfPoints() / 50)
4660            normals.SetSampleSize(sample_size)
4661            normals.SetNormalOrientationToGraphTraversal()
4662            sdf.SetInputConnection(normals.GetOutputPort())
4663            # print("Recalculating normals with sample size =", sample_size)
4664
4665        if radius is None:
4666            radius = self.diagonal_size() / (sum(dims) / 3) * 5
4667            # print("Calculating mesh from points with radius =", radius)
4668
4669        sdf.SetRadius(radius)
4670        sdf.SetDimensions(dims)
4671        sdf.Update()
4672
4673        surface = vtk.vtkExtractSurface()
4674        surface.SetRadius(radius * 0.99)
4675        surface.SetHoleFilling(hole_filling)
4676        surface.ComputeNormalsOff()
4677        surface.ComputeGradientsOff()
4678        surface.SetInputConnection(sdf.GetOutputPort())
4679        surface.Update()
4680        m = vedo.mesh.Mesh(surface.GetOutput(), c=self.color())
4681
4682        m.pipeline = utils.OperationNode(
4683            "reconstruct_surface", parents=[self],
4684            comment=f"#pts {m.inputdata().GetNumberOfPoints()}"
4685        )
4686        return m
4687
4688    def compute_clustering(self, radius):
4689        """
4690        Cluster points in space. The `radius` is the radius of local search.
4691        An array named "ClusterId" is added to the vertex points.
4692
4693        Examples:
4694            - [clustering.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/clustering.py)
4695
4696                ![](https://vedo.embl.es/images/basic/clustering.png)
4697        """
4698        cluster = vtk.vtkEuclideanClusterExtraction()
4699        cluster.SetInputData(self.inputdata())
4700        cluster.SetExtractionModeToAllClusters()
4701        cluster.SetRadius(radius)
4702        cluster.ColorClustersOn()
4703        cluster.Update()
4704        idsarr = cluster.GetOutput().GetPointData().GetArray("ClusterId")
4705        self.inputdata().GetPointData().AddArray(idsarr)
4706
4707        self.pipeline = utils.OperationNode(
4708            "compute_clustering", parents=[self], comment=f"radius = {radius}"
4709        )
4710        return self
4711
4712    def compute_connections(self, radius, mode=0, regions=(), vrange=(0, 1), seeds=(), angle=0):
4713        """
4714        Extracts and/or segments points from a point cloud based on geometric distance measures
4715        (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range.
4716        The default operation is to segment the points into "connected" regions where the connection
4717        is determined by an appropriate distance measure. Each region is given a region id.
4718
4719        Optionally, the filter can output the largest connected region of points; a particular region
4720        (via id specification); those regions that are seeded using a list of input point ids;
4721        or the region of points closest to a specified position.
4722
4723        The key parameter of this filter is the radius defining a sphere around each point which defines
4724        a local neighborhood: any other points in the local neighborhood are assumed connected to the point.
4725        Note that the radius is defined in absolute terms.
4726
4727        Other parameters are used to further qualify what it means to be a neighboring point.
4728        For example, scalar range and/or point normals can be used to further constrain the neighborhood.
4729        Also the extraction mode defines how the filter operates.
4730        By default, all regions are extracted but it is possible to extract particular regions;
4731        the region closest to a seed point; seeded regions; or the largest region found while processing.
4732        By default, all regions are extracted.
4733
4734        On output, all points are labeled with a region number.
4735        However note that the number of input and output points may not be the same:
4736        if not extracting all regions then the output size may be less than the input size.
4737
4738        Arguments:
4739            radius : (float)
4740                variable specifying a local sphere used to define local point neighborhood
4741            mode : (int)
4742                - 0,  Extract all regions
4743                - 1,  Extract point seeded regions
4744                - 2,  Extract largest region
4745                - 3,  Test specified regions
4746                - 4,  Extract all regions with scalar connectivity
4747                - 5,  Extract point seeded regions
4748            regions : (list)
4749                a list of non-negative regions id to extract
4750            vrange : (list)
4751                scalar range to use to extract points based on scalar connectivity
4752            seeds : (list)
4753                a list of non-negative point seed ids
4754            angle : (list)
4755                points are connected if the angle between their normals is
4756                within this angle threshold (expressed in degrees).
4757        """
4758        # https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html
4759        cpf = vtk.vtkConnectedPointsFilter()
4760        cpf.SetInputData(self.polydata())
4761        cpf.SetRadius(radius)
4762        if mode == 0:  # Extract all regions
4763            pass
4764
4765        elif mode == 1:  # Extract point seeded regions
4766            cpf.SetExtractionModeToPointSeededRegions()
4767            for s in seeds:
4768                cpf.AddSeed(s)
4769
4770        elif mode == 2:  # Test largest region
4771            cpf.SetExtractionModeToLargestRegion()
4772
4773        elif mode == 3:  # Test specified regions
4774            cpf.SetExtractionModeToSpecifiedRegions()
4775            for r in regions:
4776                cpf.AddSpecifiedRegion(r)
4777
4778        elif mode == 4:  # Extract all regions with scalar connectivity
4779            cpf.SetExtractionModeToLargestRegion()
4780            cpf.ScalarConnectivityOn()
4781            cpf.SetScalarRange(vrange[0], vrange[1])
4782
4783        elif mode == 5:  # Extract point seeded regions
4784            cpf.SetExtractionModeToLargestRegion()
4785            cpf.ScalarConnectivityOn()
4786            cpf.SetScalarRange(vrange[0], vrange[1])
4787            cpf.AlignedNormalsOn()
4788            cpf.SetNormalAngle(angle)
4789
4790        cpf.Update()
4791        return self._update(cpf.GetOutput())
4792
4793    def compute_camera_distance(self):
4794        """
4795        Calculate the distance from points to the camera.
4796        A pointdata array is created with name 'DistanceToCamera'.
4797        """
4798        if vedo.plotter_instance.renderer:
4799            poly = self.polydata()
4800            dc = vtk.vtkDistanceToCamera()
4801            dc.SetInputData(poly)
4802            dc.SetRenderer(vedo.plotter_instance.renderer)
4803            dc.Update()
4804            return self._update(dc.GetOutput())
4805        return self
4806
4807    def density(
4808        self, dims=(40, 40, 40), bounds=None, radius=None, compute_gradient=False, locator=None
4809    ):
4810        """
4811        Generate a density field from a point cloud. Input can also be a set of 3D coordinates.
4812        Output is a `Volume`.
4813
4814        The local neighborhood is specified as the `radius` around each sample position (each voxel).
4815        The density is expressed as the number of counts in the radius search.
4816
4817        Arguments:
4818            dims : (int,list)
4819                number of voxels in x, y and z of the output Volume.
4820            compute_gradient : (bool)
4821                Turn on/off the generation of the gradient vector,
4822                gradient magnitude scalar, and function classification scalar.
4823                By default this is off. Note that this will increase execution time
4824                and the size of the output. (The names of these point data arrays are:
4825                "Gradient", "Gradient Magnitude", and "Classification")
4826            locator : (vtkPointLocator)
4827                can be assigned from a previous call for speed (access it via `object.point_locator`).
4828
4829        Examples:
4830            - [plot_density3d.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/plot_density3d.py)
4831
4832                ![](https://vedo.embl.es/images/pyplot/plot_density3d.png)
4833        """
4834        pdf = vtk.vtkPointDensityFilter()
4835        pdf.SetInputData(self.polydata())
4836
4837        if not utils.is_sequence(dims):
4838            dims = [dims, dims, dims]
4839
4840        if bounds is None:
4841            bounds = list(self.bounds())
4842        elif len(bounds) == 4:
4843            bounds = [*bounds, 0, 0]
4844
4845        if bounds[5] - bounds[4] == 0 or len(dims) == 2:  # its 2D
4846            dims = list(dims)
4847            dims = [dims[0], dims[1], 2]
4848            diag = self.diagonal_size()
4849            bounds[5] = bounds[4] + diag / 1000
4850        pdf.SetModelBounds(bounds)
4851
4852        pdf.SetSampleDimensions(dims)
4853
4854        if locator:
4855            pdf.SetLocator(locator)
4856
4857        pdf.SetDensityEstimateToFixedRadius()
4858        if radius is None:
4859            radius = self.diagonal_size() / 20
4860        pdf.SetRadius(radius)
4861
4862        pdf.SetComputeGradient(compute_gradient)
4863        pdf.Update()
4864        img = pdf.GetOutput()
4865        vol = vedo.volume.Volume(img).mode(1)
4866        vol.name = "PointDensity"
4867        vol.info["radius"] = radius
4868        vol.locator = pdf.GetLocator()
4869
4870        vol.pipeline = utils.OperationNode(
4871            "density", parents=[self], comment=f"dims = {tuple(vol.dimensions())}"
4872        )
4873        return vol
4874
4875    def densify(self, target_distance=0.1, nclosest=6, radius=None, niter=1, nmax=None):
4876        """
4877        Return a copy of the cloud with new added points.
4878        The new points are created in such a way that all points in any local neighborhood are
4879        within a target distance of one another.
4880
4881        For each input point, the distance to all points in its neighborhood is computed.
4882        If any of its neighbors is further than the target distance,
4883        the edge connecting the point and its neighbor is bisected and
4884        a new point is inserted at the bisection point.
4885        A single pass is completed once all the input points are visited.
4886        Then the process repeats to the number of iterations.
4887
4888        Examples:
4889            - [densifycloud.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/densifycloud.py)
4890
4891                ![](https://vedo.embl.es/images/volumetric/densifycloud.png)
4892
4893        .. note::
4894            Points will be created in an iterative fashion until all points in their
4895            local neighborhood are the target distance apart or less.
4896            Note that the process may terminate early due to the
4897            number of iterations. By default the target distance is set to 0.5.
4898            Note that the target_distance should be less than the radius
4899            or nothing will change on output.
4900
4901        .. warning::
4902            This class can generate a lot of points very quickly.
4903            The maximum number of iterations is by default set to =1.0 for this reason.
4904            Increase the number of iterations very carefully.
4905            Also, `nmax` can be set to limit the explosion of points.
4906            It is also recommended that a N closest neighborhood is used.
4907
4908        """
4909        src = vtk.vtkProgrammableSource()
4910        opts = self.points()
4911
4912        def _readPoints():
4913            output = src.GetPolyDataOutput()
4914            points = vtk.vtkPoints()
4915            for p in opts:
4916                points.InsertNextPoint(p)
4917            output.SetPoints(points)
4918
4919        src.SetExecuteMethod(_readPoints)
4920
4921        dens = vtk.vtkDensifyPointCloudFilter()
4922        dens.SetInputConnection(src.GetOutputPort())
4923        dens.InterpolateAttributeDataOn()
4924        dens.SetTargetDistance(target_distance)
4925        dens.SetMaximumNumberOfIterations(niter)
4926        if nmax:
4927            dens.SetMaximumNumberOfPoints(nmax)
4928
4929        if radius:
4930            dens.SetNeighborhoodTypeToRadius()
4931            dens.SetRadius(radius)
4932        elif nclosest:
4933            dens.SetNeighborhoodTypeToNClosest()
4934            dens.SetNumberOfClosestPoints(nclosest)
4935        else:
4936            vedo.logger.error("set either radius or nclosest")
4937            raise RuntimeError()
4938        dens.Update()
4939        pts = utils.vtk2numpy(dens.GetOutput().GetPoints().GetData())
4940        cld = Points(pts, c=None).point_size(self.GetProperty().GetPointSize())
4941        cld.interpolate_data_from(self, n=nclosest, radius=radius)
4942        cld.name = "densifiedCloud"
4943
4944        cld.pipeline = utils.OperationNode(
4945            "densify", parents=[self], c="#e9c46a:",
4946            comment=f"#pts {cld.inputdata().GetNumberOfPoints()}"
4947        )
4948        return cld
4949
4950
4951    ###############################################################################
4952    ## stuff returning Volume
4953
4954    def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None):
4955        """
4956        Compute the `Volume` object whose voxels contains the signed distance from
4957        the point cloud. The point cloud must have Normals.
4958
4959        Arguments:
4960            bounds : (list, actor)
4961                bounding box sizes
4962            dims : (list)
4963                dimensions (nr. of voxels) of the output volume.
4964            invert : (bool)
4965                flip the sign
4966            maxradius : (float)
4967                specify how far out to propagate distance calculation
4968
4969        Examples:
4970            - [distance2mesh.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/distance2mesh.py)
4971
4972                ![](https://vedo.embl.es/images/basic/distance2mesh.png)
4973        """
4974        if bounds is None:
4975            bounds = self.bounds()
4976        if maxradius is None:
4977            maxradius = self.diagonal_size() / 2
4978        dist = vtk.vtkSignedDistance()
4979        dist.SetInputData(self.polydata())
4980        dist.SetRadius(maxradius)
4981        dist.SetBounds(bounds)
4982        dist.SetDimensions(dims)
4983        dist.Update()
4984        img = dist.GetOutput()
4985        if invert:
4986            mat = vtk.vtkImageMathematics()
4987            mat.SetInput1Data(img)
4988            mat.SetOperationToMultiplyByK()
4989            mat.SetConstantK(-1)
4990            mat.Update()
4991            img = mat.GetOutput()
4992
4993        vol = vedo.Volume(img)
4994        vol.name = "SignedDistanceVolume"
4995
4996        vol.pipeline = utils.OperationNode(
4997            "signed_distance",
4998            parents=[self],
4999            comment=f"dim = {tuple(vol.dimensions())}",
5000            c="#e9c46a:#0096c7",
5001        )
5002        return vol
5003
5004    def tovolume(
5005        self, kernel="shepard", radius=None, n=None, bounds=None, null_value=None, dims=(25, 25, 25)
5006    ):
5007        """
5008        Generate a `Volume` by interpolating a scalar
5009        or vector field which is only known on a scattered set of points or mesh.
5010        Available interpolation kernels are: shepard, gaussian, or linear.
5011
5012        Arguments:
5013            kernel : (str)
5014                interpolation kernel type [shepard]
5015            radius : (float)
5016                radius of the local search
5017            n : (int)
5018                number of point to use for interpolation
5019            bounds : (list)
5020                bounding box of the output Volume object
5021            dims : (list)
5022                dimensions of the output Volume object
5023            null_value : (float)
5024                value to be assigned to invalid points
5025
5026        Examples:
5027            - [interpolate_volume.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/interpolate_volume.py)
5028
5029                ![](https://vedo.embl.es/images/volumetric/59095175-1ec5a300-8918-11e9-8bc0-fd35c8981e2b.jpg)
5030        """
5031        if radius is None and not n:
5032            vedo.logger.error("please set either radius or n")
5033            raise RuntimeError
5034
5035        poly = self.polydata()
5036
5037        # Create a probe volume
5038        probe = vtk.vtkImageData()
5039        probe.SetDimensions(dims)
5040        if bounds is None:
5041            bounds = self.bounds()
5042        probe.SetOrigin(bounds[0], bounds[2], bounds[4])
5043        probe.SetSpacing(
5044            (bounds[1] - bounds[0]) / dims[0],
5045            (bounds[3] - bounds[2]) / dims[1],
5046            (bounds[5] - bounds[4]) / dims[2],
5047        )
5048
5049        if not self.point_locator:
5050            self.point_locator = vtk.vtkPointLocator()
5051            self.point_locator.SetDataSet(poly)
5052            self.point_locator.BuildLocator()
5053
5054        if kernel == "shepard":
5055            kern = vtk.vtkShepardKernel()
5056            kern.SetPowerParameter(2)
5057        elif kernel == "gaussian":
5058            kern = vtk.vtkGaussianKernel()
5059        elif kernel == "linear":
5060            kern = vtk.vtkLinearKernel()
5061        else:
5062            vedo.logger.error("Error in tovolume(), available kernels are:")
5063            vedo.logger.error(" [shepard, gaussian, linear]")
5064            raise RuntimeError()
5065
5066        if radius:
5067            kern.SetRadius(radius)
5068
5069        interpolator = vtk.vtkPointInterpolator()
5070        interpolator.SetInputData(probe)
5071        interpolator.SetSourceData(poly)
5072        interpolator.SetKernel(kern)
5073        interpolator.SetLocator(self.point_locator)
5074
5075        if n:
5076            kern.SetNumberOfPoints(n)
5077            kern.SetKernelFootprintToNClosest()
5078        else:
5079            kern.SetRadius(radius)
5080
5081        if null_value is not None:
5082            interpolator.SetNullValue(null_value)
5083        else:
5084            interpolator.SetNullPointsStrategyToClosestPoint()
5085        interpolator.Update()
5086
5087        vol = vedo.Volume(interpolator.GetOutput())
5088
5089        vol.pipeline = utils.OperationNode(
5090            "signed_distance",
5091            parents=[self],
5092            comment=f"dim = {tuple(vol.dimensions())}",
5093            c="#e9c46a:#0096c7",
5094        )
5095        return vol
5096
5097    def generate_random_data(self):
5098        """Fill a dataset with random attributes"""
5099        gen = vtk.vtkRandomAttributeGenerator()
5100        gen.SetInputData(self._data)
5101        gen.GenerateAllDataOn()
5102        gen.SetDataTypeToFloat()
5103        gen.GeneratePointNormalsOff()
5104        gen.GeneratePointTensorsOn()
5105        gen.GenerateCellScalarsOn()
5106        gen.Update()
5107
5108        m = self._update(gen.GetOutput())
5109
5110        m.pipeline = utils.OperationNode("generate\nrandom data", parents=[self])
5111        return m

Work with pointclouds.

Points( inputobj=None, r=4, c=(0.2, 0.2, 0.2), alpha=1, blur=False, emissive=True)
719    def __init__(self, inputobj=None, r=4, c=(0.2, 0.2, 0.2), alpha=1, blur=False, emissive=True):
720        """
721        Build an object made of only vertex points for a list of 2D/3D points.
722        Both shapes (N, 3) or (3, N) are accepted as input, if N>3.
723        For very large point clouds a list of colors and alpha can be assigned to each
724        point in the form c=[(R,G,B,A), ... ] where 0<=R<256, ... 0<=A<256.
725
726        Arguments:
727            inputobj : (list, tuple)
728            r : (int)
729                Point radius in units of pixels.
730            c : (str, list)
731                Color name or rgb tuple.
732            alpha : (float)
733                Transparency in range [0,1].
734            blur : (bool)
735                Apply a gaussian convolution filter to the points.
736                In this case the radius `r` is in absolute units of the mesh coordinates.
737            emissive : (bool)
738                Halo of point becomes emissive.
739
740        Example:
741            ```python
742            from vedo import *
743
744            def fibonacci_sphere(n):
745                s = np.linspace(0, n, num=n, endpoint=False)
746                theta = s * 2.399963229728653
747                y = 1 - s * (2/(n-1))
748                r = np.sqrt(1 - y * y)
749                x = np.cos(theta) * r
750                z = np.sin(theta) * r
751                return [x,y,z]
752
753            Points(fibonacci_sphere(1000)).show(axes=1).close()
754            ```
755            ![](https://vedo.embl.es/images/feats/fibonacci.png)
756        """
757
758        vtk.vtkActor.__init__(self)
759        BaseActor.__init__(self)
760
761        self._data = None
762
763        if blur:
764            self._mapper = vtk.vtkPointGaussianMapper()
765            if emissive:
766                self._mapper.SetEmissive(bool(emissive))
767            self._mapper.SetScaleFactor(r * 1.4142)
768
769            # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/
770            if alpha < 1:
771                self._mapper.SetSplatShaderCode(
772                    "//VTK::Color::Impl\n"
773                    "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n"
774                    "if (dist > 1.0) {\n"
775                    "   discard;\n"
776                    "} else {\n"
777                    f"  float scale = ({alpha} - dist);\n"
778                    "   ambientColor *= scale;\n"
779                    "   diffuseColor *= scale;\n"
780                    "}\n"
781                )
782                alpha = 1
783
784        else:
785            self._mapper = vtk.vtkPolyDataMapper()
786        self.SetMapper(self._mapper)
787
788        self._bfprop = None  # backface property holder
789
790        self._scals_idx = 0  # index of the active scalar changed from CLI
791        self._ligthingnr = 0  # index of the lighting mode changed from CLI
792        self._cmap_name = ""  # remember the name for self._keypress
793        # self.name = "Points" # better not to give it a name here
794
795        self.property = self.GetProperty()
796
797        try:
798            if not blur:
799                self.property.RenderPointsAsSpheresOn()
800        except AttributeError:
801            pass
802
803        if inputobj is None:  ####################
804            self._data = vtk.vtkPolyData()
805            return
806        ########################################
807
808        self.property.SetRepresentationToPoints()
809        self.property.SetPointSize(r)
810        self.property.LightingOff()
811
812        if isinstance(inputobj, vedo.BaseActor):
813            inputobj = inputobj.points()  # numpy
814
815        ######
816        if isinstance(inputobj, vtk.vtkActor):
817            poly_copy = vtk.vtkPolyData()
818            pr = vtk.vtkProperty()
819            pr.DeepCopy(inputobj.GetProperty())
820            poly_copy.DeepCopy(inputobj.GetMapper().GetInput())
821            pr.SetRepresentationToPoints()
822            pr.SetPointSize(r)
823            self._data = poly_copy
824            self._mapper.SetInputData(poly_copy)
825            self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility())
826            self.SetProperty(pr)
827            self.property = pr
828
829        elif isinstance(inputobj, vtk.vtkPolyData):
830            if inputobj.GetNumberOfCells() == 0:
831                carr = vtk.vtkCellArray()
832                for i in range(inputobj.GetNumberOfPoints()):
833                    carr.InsertNextCell(1)
834                    carr.InsertCellPoint(i)
835                inputobj.SetVerts(carr)
836            self._data = inputobj  # cache vtkPolyData and mapper for speed
837
838        elif utils.is_sequence(inputobj):  # passing point coords
839            plist = inputobj
840            n = len(plist)
841
842            if n == 3:  # assume plist is in the format [all_x, all_y, all_z]
843                if utils.is_sequence(plist[0]) and len(plist[0]) > 3:
844                    plist = np.stack((plist[0], plist[1], plist[2]), axis=1)
845            elif n == 2:  # assume plist is in the format [all_x, all_y, 0]
846                if utils.is_sequence(plist[0]) and len(plist[0]) > 3:
847                    plist = np.stack((plist[0], plist[1], np.zeros(len(plist[0]))), axis=1)
848
849            # if n and len(plist[0]) == 2:  # make it 3d
850            #     plist = np.c_[np.array(plist), np.zeros(len(plist))]
851            plist = utils.make3d(plist)
852
853            if (
854                utils.is_sequence(c)
855                and (len(c) > 3 or (utils.is_sequence(c[0]) and len(c[0]) == 4))
856            ) or utils.is_sequence(alpha):
857
858                cols = c
859
860                n = len(plist)
861                if n != len(cols):
862                    vedo.logger.error(f"mismatch in Points() colors array lengths {n} and {len(cols)}")
863                    raise RuntimeError()
864
865                src = vtk.vtkPointSource()
866                src.SetNumberOfPoints(n)
867                src.Update()
868
869                vgf = vtk.vtkVertexGlyphFilter()
870                vgf.SetInputData(src.GetOutput())
871                vgf.Update()
872                pd = vgf.GetOutput()
873
874                pd.GetPoints().SetData(utils.numpy2vtk(plist, dtype=np.float32))
875
876                ucols = vtk.vtkUnsignedCharArray()
877                ucols.SetNumberOfComponents(4)
878                ucols.SetName("Points_RGBA")
879                if utils.is_sequence(alpha):
880                    if len(alpha) != n:
881                        vedo.logger.error(f"mismatch in Points() alpha array lengths {n} and {len(cols)}")
882                        raise RuntimeError()
883                    alphas = alpha
884                    alpha = 1
885                else:
886                    alphas = (alpha,) * n
887
888                if utils.is_sequence(cols):
889                    c = None
890                    if len(cols[0]) == 4:
891                        for i in range(n):  # FAST
892                            rc, gc, bc, ac = cols[i]
893                            ucols.InsertNextTuple4(rc, gc, bc, ac)
894                    else:
895                        for i in range(n):  # SLOW
896                            rc, gc, bc = colors.get_color(cols[i])
897                            ucols.InsertNextTuple4(rc * 255, gc * 255, bc * 255, alphas[i] * 255)
898                else:
899                    c = cols
900
901                pd.GetPointData().AddArray(ucols)
902                pd.GetPointData().SetActiveScalars("Points_RGBA")
903                self._mapper.SetInputData(pd)
904                self._mapper.ScalarVisibilityOn()
905                self._data = pd
906
907            else:
908
909                pd = utils.buildPolyData(plist)
910                self._mapper.SetInputData(pd)
911                c = colors.get_color(c)
912                self.property.SetColor(c)
913                self.property.SetOpacity(alpha)
914                self._data = pd
915
916            ##########
917            self.pipeline = utils.OperationNode(
918                self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}"
919            )
920            return
921            ##########
922
923        elif isinstance(inputobj, str):
924            verts = vedo.file_io.load(inputobj)
925            self.filename = inputobj
926            self._data = verts.polydata()
927
928        else:
929
930            # try to extract the points from the VTK input data object
931            try:
932                vvpts = inputobj.GetPoints()
933                pd = vtk.vtkPolyData()
934                pd.SetPoints(vvpts)
935                for i in range(inputobj.GetPointData().GetNumberOfArrays()):
936                    arr = inputobj.GetPointData().GetArray(i)
937                    pd.GetPointData().AddArray(arr)
938
939                self._mapper.SetInputData(pd)
940                c = colors.get_color(c)
941                self.property.SetColor(c)
942                self.property.SetOpacity(alpha)
943                self._data = pd
944            except:
945                vedo.logger.error(f"cannot build Points from type {type(inputobj)}")
946                raise RuntimeError()
947
948        c = colors.get_color(c)
949        self.property.SetColor(c)
950        self.property.SetOpacity(alpha)
951
952        self._mapper.SetInputData(self._data)
953
954        self.pipeline = utils.OperationNode(
955            self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}"
956        )
957        return

Build an object made of only vertex points for a list of 2D/3D points. Both shapes (N, 3) or (3, N) are accepted as input, if N>3. For very large point clouds a list of colors and alpha can be assigned to each point in the form c=[(R,G,B,A), ... ] where 0<=R<256, ... 0<=A<256.

Arguments:
  • inputobj : (list, tuple)
  • r : (int) Point radius in units of pixels.
  • c : (str, list) Color name or rgb tuple.
  • alpha : (float) Transparency in range [0,1].
  • blur : (bool) Apply a gaussian convolution filter to the points. In this case the radius r is in absolute units of the mesh coordinates.
  • emissive : (bool) Halo of point becomes emissive.
Example:
from vedo import *

def fibonacci_sphere(n):
    s = np.linspace(0, n, num=n, endpoint=False)
    theta = s * 2.399963229728653
    y = 1 - s * (2/(n-1))
    r = np.sqrt(1 - y * y)
    x = np.cos(theta) * r
    z = np.sin(theta) * r
    return [x,y,z]

Points(fibonacci_sphere(1000)).show(axes=1).close()

def polydata(self, transformed=True):
1057    def polydata(self, transformed=True):
1058        """
1059        Returns the `vtkPolyData` object associated to a `Mesh`.
1060
1061        .. note::
1062            If `transformed=True` return a copy of polydata that corresponds
1063            to the current mesh position in space.
1064        """
1065        if not self._data:
1066            self._data = self.mapper().GetInput()
1067            return self._data
1068
1069        if transformed:
1070            # if self.GetIsIdentity() or self._data.GetNumberOfPoints()==0: # commmentd out on 15th feb 2020
1071            if self._data.GetNumberOfPoints() == 0:
1072                # no need to do much
1073                return self._data
1074
1075            # otherwise make a copy that corresponds to
1076            # the actual position in space of the mesh
1077            M = self.GetMatrix()
1078            transform = vtk.vtkTransform()
1079            transform.SetMatrix(M)
1080            tp = vtk.vtkTransformPolyDataFilter()
1081            tp.SetTransform(transform)
1082            tp.SetInputData(self._data)
1083            tp.Update()
1084            return tp.GetOutput()
1085
1086        return self._data

Returns the vtkPolyData object associated to a Mesh.

If transformed=True return a copy of polydata that corresponds to the current mesh position in space.

def clone(self, deep=True, transformed=False):
1089    def clone(self, deep=True, transformed=False):
1090        """
1091        Clone a `PointCloud` or `Mesh` object to make an exact copy of it.
1092
1093        Arguments:
1094            deep : (bool)
1095                if False only build a shallow copy of the object (faster copy).
1096
1097            transformed : (bool)
1098                if True reset the current transformation of the copy to unit.
1099
1100        Examples:
1101            - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py)
1102
1103               ![](https://vedo.embl.es/images/basic/mirror.png)
1104        """
1105        poly = self.polydata(transformed)
1106        poly_copy = vtk.vtkPolyData()
1107        if deep:
1108            poly_copy.DeepCopy(poly)
1109        else:
1110            poly_copy.ShallowCopy(poly)
1111
1112        if isinstance(self, vedo.Mesh):
1113            cloned = vedo.Mesh(poly_copy)
1114        else:
1115            cloned = Points(poly_copy)
1116
1117        pr = vtk.vtkProperty()
1118        pr.DeepCopy(self.GetProperty())
1119        cloned.SetProperty(pr)
1120        cloned.property = pr
1121
1122        if self.GetBackfaceProperty():
1123            bfpr = vtk.vtkProperty()
1124            bfpr.DeepCopy(self.GetBackfaceProperty())
1125            cloned.SetBackfaceProperty(bfpr)
1126
1127        if not transformed:
1128            if self.transform:
1129                # already has a transform which can be non linear, so use that
1130                cloned.SetUserTransform(self.transform)
1131            else:
1132                # assign the same transformation to the copy
1133                cloned.SetOrigin(self.GetOrigin())
1134                cloned.SetScale(self.GetScale())
1135                cloned.SetOrientation(self.GetOrientation())
1136                cloned.SetPosition(self.GetPosition())
1137
1138        mp = cloned.mapper()
1139        sm = self.mapper()
1140        mp.SetScalarVisibility(sm.GetScalarVisibility())
1141        mp.SetScalarRange(sm.GetScalarRange())
1142        mp.SetColorMode(sm.GetColorMode())
1143        lsr = sm.GetUseLookupTableScalarRange()
1144        mp.SetUseLookupTableScalarRange(lsr)
1145        mp.SetScalarMode(sm.GetScalarMode())
1146        lut = sm.GetLookupTable()
1147        if lut:
1148            mp.SetLookupTable(lut)
1149
1150        if self.GetTexture():
1151            cloned.texture(self.GetTexture())
1152
1153        cloned.SetPickable(self.GetPickable())
1154
1155        cloned.base = np.array(self.base)
1156        cloned.top = np.array(self.top)
1157        cloned.name = str(self.name)
1158        cloned.filename = str(self.filename)
1159        cloned.info = dict(self.info)
1160
1161        # better not to share the same locators with original obj
1162        cloned.point_locator = None
1163        cloned.cell_locator = None
1164
1165        cloned.pipeline = utils.OperationNode("clone", parents=[self], shape="diamond", c="#edede9")
1166        return cloned

Clone a PointCloud or Mesh object to make an exact copy of it.

Arguments:
  • deep : (bool) if False only build a shallow copy of the object (faster copy).
  • transformed : (bool) if True reset the current transformation of the copy to unit.
Examples:
def clone2d( self, pos=(0, 0), coordsys=4, scale=None, c=None, alpha=None, ps=2, lw=1, sendback=False, layer=0):
1168    def clone2d(
1169        self,
1170        pos=(0, 0),
1171        coordsys=4,
1172        scale=None,
1173        c=None,
1174        alpha=None,
1175        ps=2,
1176        lw=1,
1177        sendback=False,
1178        layer=0,
1179    ):
1180        """
1181        Copy a 3D Mesh into a static 2D image. Returns a `vtkActor2D`.
1182
1183        Arguments:
1184            coordsys : (int)
1185                the coordinate system, options are
1186                - 0 = Displays
1187                - 1 = Normalized Display
1188                - 2 = Viewport (origin is the bottom-left corner of the window)
1189                - 3 = Normalized Viewport
1190                - 4 = View (origin is the center of the window)
1191                - 5 = World (anchor the 2d image to mesh)
1192
1193            ps : (int)
1194                point size in pixel units
1195
1196            lw : (int)
1197                line width in pixel units
1198
1199            sendback : (bool)
1200                put it behind any other 3D object
1201
1202        Examples:
1203            - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py)
1204
1205                ![](https://vedo.embl.es/images/other/clone2d.png)
1206        """
1207        if scale is None:
1208            msiz = self.diagonal_size()
1209            if vedo.plotter_instance and vedo.plotter_instance.window:
1210                sz = vedo.plotter_instance.window.GetSize()
1211                dsiz = utils.mag(sz)
1212                scale = dsiz / msiz / 10
1213            else:
1214                scale = 350 / msiz
1215
1216        cmsh = self.clone()
1217        poly = cmsh.pos(0, 0, 0).scale(scale).polydata()
1218
1219        mapper3d = self.mapper()
1220        cm = mapper3d.GetColorMode()
1221        lut = mapper3d.GetLookupTable()
1222        sv = mapper3d.GetScalarVisibility()
1223        use_lut = mapper3d.GetUseLookupTableScalarRange()
1224        vrange = mapper3d.GetScalarRange()
1225        sm = mapper3d.GetScalarMode()
1226
1227        mapper2d = vtk.vtkPolyDataMapper2D()
1228        mapper2d.ShallowCopy(mapper3d)
1229        mapper2d.SetInputData(poly)
1230        mapper2d.SetColorMode(cm)
1231        mapper2d.SetLookupTable(lut)
1232        mapper2d.SetScalarVisibility(sv)
1233        mapper2d.SetUseLookupTableScalarRange(use_lut)
1234        mapper2d.SetScalarRange(vrange)
1235        mapper2d.SetScalarMode(sm)
1236
1237        act2d = vtk.vtkActor2D()
1238        act2d.SetMapper(mapper2d)
1239        act2d.SetLayerNumber(layer)
1240        csys = act2d.GetPositionCoordinate()
1241        csys.SetCoordinateSystem(coordsys)
1242        act2d.SetPosition(pos)
1243        if c is not None:
1244            c = colors.get_color(c)
1245            act2d.GetProperty().SetColor(c)
1246            mapper2d.SetScalarVisibility(False)
1247        else:
1248            act2d.GetProperty().SetColor(cmsh.color())
1249        if alpha is not None:
1250            act2d.GetProperty().SetOpacity(alpha)
1251        else:
1252            act2d.GetProperty().SetOpacity(cmsh.alpha())
1253        act2d.GetProperty().SetPointSize(ps)
1254        act2d.GetProperty().SetLineWidth(lw)
1255        act2d.GetProperty().SetDisplayLocationToForeground()
1256        if sendback:
1257            act2d.GetProperty().SetDisplayLocationToBackground()
1258
1259        # print(csys.GetCoordinateSystemAsString())
1260        # print(act2d.GetHeight(), act2d.GetWidth(), act2d.GetLayerNumber())
1261        return act2d

Copy a 3D Mesh into a static 2D image. Returns a vtkActor2D.

Arguments:
  • coordsys : (int) the coordinate system, options are
    • 0 = Displays
    • 1 = Normalized Display
    • 2 = Viewport (origin is the bottom-left corner of the window)
    • 3 = Normalized Viewport
    • 4 = View (origin is the center of the window)
    • 5 = World (anchor the 2d image to mesh)
  • ps : (int) point size in pixel units
  • lw : (int) line width in pixel units
  • sendback : (bool) put it behind any other 3D object
Examples:
def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2):
1263    def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2):
1264        """
1265        Add a trailing line to mesh.
1266        This new mesh is accessible through `mesh.trail`.
1267
1268        Arguments:
1269            offset : (float)
1270                set an offset vector from the object center.
1271            n : (int)
1272                number of segments
1273            lw : (float)
1274                line width of the trail
1275
1276        Examples:
1277            - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py)
1278
1279                ![](https://vedo.embl.es/images/simulations/trail.gif)
1280
1281            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1282            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1283        """
1284        if self.trail is None:
1285            pos = self.GetPosition()
1286            self.trail_offset = np.asarray(offset)
1287            self.trail_points = [pos] * n
1288
1289            if c is None:
1290                col = self.GetProperty().GetColor()
1291            else:
1292                col = colors.get_color(c)
1293
1294            tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw)
1295            self.trail = tline  # holds the Line
1296        return self

Add a trailing line to mesh. This new mesh is accessible through mesh.trail.

Arguments:
  • offset : (float) set an offset vector from the object center.
  • n : (int) number of segments
  • lw : (float) line width of the trail
Examples:
def update_trail(self):
1298    def update_trail(self):
1299        """
1300        Update the trailing line of a moving object.
1301        """
1302        if isinstance(self, vedo.shapes.Arrow):
1303            currentpos = self.tipPoint()  # the tip of Arrow
1304        else:
1305            currentpos = np.array(self.GetPosition())
1306
1307        self.trail_points.append(currentpos)  # cycle
1308        self.trail_points.pop(0)
1309
1310        data = np.array(self.trail_points) - currentpos + self.trail_offset
1311        tpoly = self.trail.polydata(False)
1312        tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32))
1313        self.trail.SetPosition(currentpos)
1314        return self

Update the trailing line of a moving object.

def add_shadow( self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0):
1346    def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0):
1347        """
1348        Generate a shadow out of an `Mesh` on one of the three Cartesian planes.
1349        The output is a new `Mesh` representing the shadow.
1350        This new mesh is accessible through `mesh.shadow`.
1351        By default the shadow mesh is placed on the bottom wall of the bounding box.
1352
1353        See also `pointcloud.project_on_plane()`.
1354
1355        Arguments:
1356            plane : (str, Plane)
1357                if plane is `str`, plane can be one of `['x', 'y', 'z']`,
1358                represents x-plane, y-plane and z-plane, respectively.
1359                Otherwise, plane should be an instance of `vedo.shapes.Plane`
1360            point : (float, array)
1361                if plane is `str`, point should be a float represents the intercept.
1362                Otherwise, point is the camera point of perspective projection
1363            direction : (list)
1364                direction of oblique projection
1365            culling : (int)
1366                choose between front [1] or backface [-1] culling or None.
1367
1368        Examples:
1369            - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py)
1370            - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py)
1371            - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py)
1372
1373            ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)
1374        """
1375        shad = self._compute_shadow(plane, point, direction)
1376        shad.c(c).alpha(alpha)
1377
1378        try:
1379            # Points dont have these methods
1380            shad.flat()
1381            if culling in (1, True):
1382                shad.frontface_culling()
1383            elif culling == -1:
1384                shad.backface_culling()
1385        except AttributeError:
1386            pass
1387
1388        shad.GetProperty().LightingOff()
1389        shad.SetPickable(False)
1390        shad.SetUseBounds(True)
1391
1392        if shad not in self.shadows:
1393            self.shadows.append(shad)
1394            shad.info = dict(plane=plane, point=point, direction=direction)
1395        return self

Generate a shadow out of an Mesh on one of the three Cartesian planes. The output is a new Mesh representing the shadow. This new mesh is accessible through mesh.shadow. By default the shadow mesh is placed on the bottom wall of the bounding box.

See also pointcloud.project_on_plane().

Arguments:
  • plane : (str, Plane) if plane is str, plane can be one of ['x', 'y', 'z'], represents x-plane, y-plane and z-plane, respectively. Otherwise, plane should be an instance of vedo.shapes.Plane
  • point : (float, array) if plane is str, point should be a float represents the intercept. Otherwise, point is the camera point of perspective projection
  • direction : (list) direction of oblique projection
  • culling : (int) choose between front [1] or backface [-1] culling or None.
Examples:

def update_shadows(self):
1397    def update_shadows(self):
1398        """
1399        Update the shadows of a moving object.
1400        """
1401        for sha in self.shadows:
1402            plane = sha.info['plane']
1403            point = sha.info['point']
1404            direction = sha.info['direction']
1405            new_sha = self._compute_shadow(plane, point, direction)
1406            sha._update(new_sha._data)
1407        return self

Update the shadows of a moving object.

def delete_cells_by_point_index(self, indices):
1410    def delete_cells_by_point_index(self, indices):
1411        """
1412        Delete a list of vertices identified by any of their vertex index.
1413
1414        See also `delete_cells()`.
1415
1416        Examples:
1417            - [elete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/elete_mesh_pts.py)
1418
1419                ![](https://vedo.embl.es/images/basic/deleteMeshPoints.png)
1420        """
1421        cell_ids = vtk.vtkIdList()
1422        data = self.inputdata()
1423        data.BuildLinks()
1424        n = 0
1425        for i in np.unique(indices):
1426            data.GetPointCells(i, cell_ids)
1427            for j in range(cell_ids.GetNumberOfIds()):
1428                data.DeleteCell(cell_ids.GetId(j))  # flag cell
1429                n += 1
1430
1431        data.RemoveDeletedCells()
1432        self.mapper().Modified()
1433        self.pipeline = utils.OperationNode(f"delete {n} cells\nby point index", parents=[self])
1434        return self

Delete a list of vertices identified by any of their vertex index.

See also delete_cells().

Examples:
def compute_normals_with_pca(self, n=20, orientation_point=None, invert=False):
1436    def compute_normals_with_pca(self, n=20, orientation_point=None, invert=False):
1437        """
1438        Generate point normals using PCA (principal component analysis).
1439        Basically this estimates a local tangent plane around each sample point p
1440        by considering a small neighborhood of points around p, and fitting a plane
1441        to the neighborhood (via PCA).
1442
1443        Arguments:
1444            n : (int)
1445                neighborhood size to calculate the normal
1446            orientation_point : (list)
1447                adjust the +/- sign of the normals so that
1448                the normals all point towards a specified point. If None, perform a traversal
1449                of the point cloud and flip neighboring normals so that they are mutually consistent.
1450            invert : (bool)
1451                flip all normals
1452        """
1453        poly = self.polydata()
1454        pcan = vtk.vtkPCANormalEstimation()
1455        pcan.SetInputData(poly)
1456        pcan.SetSampleSize(n)
1457
1458        if orientation_point is not None:
1459            pcan.SetNormalOrientationToPoint()
1460            pcan.SetOrientationPoint(orientation_point)
1461        else:
1462            pcan.SetNormalOrientationToGraphTraversal()
1463
1464        if invert:
1465            pcan.FlipNormalsOn()
1466        pcan.Update()
1467
1468        varr = pcan.GetOutput().GetPointData().GetNormals()
1469        varr.SetName("Normals")
1470        self.inputdata().GetPointData().SetNormals(varr)
1471        self.inputdata().GetPointData().Modified()
1472        return self

Generate point normals using PCA (principal component analysis). Basically this estimates a local tangent plane around each sample point p by considering a small neighborhood of points around p, and fitting a plane to the neighborhood (via PCA).

Arguments:
  • n : (int) neighborhood size to calculate the normal
  • orientation_point : (list) adjust the +/- sign of the normals so that the normals all point towards a specified point. If None, perform a traversal of the point cloud and flip neighboring normals so that they are mutually consistent.
  • invert : (bool) flip all normals
def compute_acoplanarity(self, n=25, radius=None, on='points'):
1474    def compute_acoplanarity(self, n=25, radius=None, on="points"):
1475        """
1476        Compute acoplanarity which is a measure of how much a local region of the mesh
1477        differs from a plane.
1478        The information is stored in a `pointdata` or `celldata` array with name 'Acoplanarity'.
1479        Either `n` (number of neighbour points) or `radius` (radius of local search) can be specified.
1480        If a radius value is given and not enough points fall inside it, then a -1 is stored.
1481
1482        Example:
1483            ```python
1484            from vedo import *
1485            msh = ParametricShape('RandomHills')
1486            msh.compute_acoplanarity(radius=0.1, on='cells')
1487            msh.cmap("coolwarm", on='cells').add_scalarbar()
1488            msh.show(axes=1).close()
1489            ```
1490            ![](https://vedo.embl.es/images/feats/acoplanarity.jpg)
1491        """
1492        acoplanarities = []
1493        if "point" in on:
1494            pts = self.points()
1495        elif "cell" in on:
1496            pts = self.cell_centers()
1497        else:
1498            raise ValueError(f"In compute_acoplanarity() set on to either 'cells' or 'points', not {on}")
1499
1500        for p in utils.progressbar(pts, delay=5, width=15, title=f"{on} acoplanarity"):
1501            if n:
1502                data = self.closest_point(p, n=n)
1503                npts = n
1504            elif radius:
1505                data = self.closest_point(p, radius=radius)
1506                npts = len(data)
1507
1508            try:
1509                center = data.mean(axis=0)
1510                res = np.linalg.svd(data - center)
1511                acoplanarities.append(res[1][2] / npts)
1512            except:
1513                acoplanarities.append(-1.0)
1514
1515        if "point" in on:
1516            self.pointdata["Acoplanarity"] = np.array(acoplanarities, dtype=float)
1517        else:
1518            self.celldata["Acoplanarity"] = np.array(acoplanarities, dtype=float)
1519        return self

Compute acoplanarity which is a measure of how much a local region of the mesh differs from a plane. The information is stored in a pointdata or celldata array with name 'Acoplanarity'. Either n (number of neighbour points) or radius (radius of local search) can be specified. If a radius value is given and not enough points fall inside it, then a -1 is stored.

Example:
from vedo import *
msh = ParametricShape('RandomHills')
msh.compute_acoplanarity(radius=0.1, on='cells')
msh.cmap("coolwarm", on='cells').add_scalarbar()
msh.show(axes=1).close()

def distance_to(self, pcloud, signed=False, invert=False, name='Distance'):
1521    def distance_to(self, pcloud, signed=False, invert=False, name="Distance"):
1522        """
1523        Computes the distance from one point cloud or mesh to another point cloud or mesh.
1524        This new `pointdata` array is saved with default name "Distance".
1525
1526        Keywords `signed` and `invert` are used to compute signed distance,
1527        but the mesh in that case must have polygonal faces (not a simple point cloud),
1528        and normals must also be computed.
1529
1530        Examples:
1531            - [distance2mesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/distance2mesh.py)
1532
1533                ![](https://vedo.embl.es/images/basic/distance2mesh.png)
1534        """
1535        if pcloud.inputdata().GetNumberOfPolys():
1536
1537            poly1 = self.polydata()
1538            poly2 = pcloud.polydata()
1539            df = vtk.vtkDistancePolyDataFilter()
1540            df.ComputeSecondDistanceOff()
1541            df.SetInputData(0, poly1)
1542            df.SetInputData(1, poly2)
1543            df.SetSignedDistance(signed)
1544            df.SetNegateDistance(invert)
1545            df.Update()
1546            scals = df.GetOutput().GetPointData().GetScalars()
1547            dists = utils.vtk2numpy(scals)
1548
1549        else:  # has no polygons and vtkDistancePolyDataFilter wants them (dont know why)
1550
1551            if signed:
1552                vedo.logger.warning("distanceTo() called with signed=True but input object has no polygons")
1553
1554            if not pcloud.point_locator:
1555                pcloud.point_locator = vtk.vtkPointLocator()
1556                pcloud.point_locator.SetDataSet(pcloud.polydata())
1557                pcloud.point_locator.BuildLocator()
1558
1559            ids = []
1560            ps1 = self.points()
1561            ps2 = pcloud.points()
1562            for p in ps1:
1563                pid = pcloud.point_locator.FindClosestPoint(p)
1564                ids.append(pid)
1565
1566            deltas = ps2[ids] - ps1
1567            dists = np.linalg.norm(deltas, axis=1).astype(np.float32)
1568            scals = utils.numpy2vtk(dists)
1569
1570        scals.SetName(name)
1571        self.inputdata().GetPointData().AddArray(scals)  # must be self.inputdata() !
1572        self.inputdata().GetPointData().SetActiveScalars(scals.GetName())
1573        rng = scals.GetRange()
1574        self.mapper().SetScalarRange(rng[0], rng[1])
1575        self.mapper().ScalarVisibilityOn()
1576
1577        self.pipeline = utils.OperationNode(
1578            "distance_to",
1579            parents=[self, pcloud],
1580            shape="cylinder",
1581            comment=f"#pts {self._data.GetNumberOfPoints()}",
1582        )
1583        return dists

Computes the distance from one point cloud or mesh to another point cloud or mesh. This new pointdata array is saved with default name "Distance".

Keywords signed and invert are used to compute signed distance, but the mesh in that case must have polygonal faces (not a simple point cloud), and normals must also be computed.

Examples:
def alpha(self, opacity=None):
1585    def alpha(self, opacity=None):
1586        """Set/get mesh's transparency. Same as `mesh.opacity()`."""
1587        if opacity is None:
1588            return self.GetProperty().GetOpacity()
1589
1590        self.GetProperty().SetOpacity(opacity)
1591        bfp = self.GetBackfaceProperty()
1592        if bfp:
1593            if opacity < 1:
1594                self._bfprop = bfp
1595                self.SetBackfaceProperty(None)
1596            else:
1597                self.SetBackfaceProperty(self._bfprop)
1598        return self

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

def opacity(self, alpha=None):
1600    def opacity(self, alpha=None):
1601        """Set/get mesh's transparency. Same as `mesh.alpha()`."""
1602        return self.alpha(alpha)

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

def force_opaque(self, value=True):
1604    def force_opaque(self, value=True):
1605        """ Force the Mesh, Line or point cloud to be treated as opaque"""
1606        ## force the opaque pass, fixes picking in vtk9
1607        # but causes other bad troubles with lines..
1608        self.SetForceOpaque(value)
1609        return self

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

def force_translucent(self, value=True):
1611    def force_translucent(self, value=True):
1612        """ Force the Mesh, Line or point cloud to be treated as translucent"""
1613        self.SetForceTranslucent(value)
1614        return self

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

def point_size(self, value=None):
1616    def point_size(self, value=None):
1617        """Set/get mesh's point size of vertices. Same as `mesh.ps()`"""
1618        if value is None:
1619            return self.GetProperty().GetPointSize()
1620            #self.GetProperty().SetRepresentationToSurface()
1621        else:
1622            self.GetProperty().SetRepresentationToPoints()
1623            self.GetProperty().SetPointSize(value)
1624        return self

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

def ps(self, pointsize=None):
1626    def ps(self, pointsize=None):
1627        """Set/get mesh's point size of vertices. Same as `mesh.point_size()`"""
1628        return self.point_size(pointsize)

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

def render_points_as_spheres(self, value=True):
1630    def render_points_as_spheres(self, value=True):
1631        """Make points look spheric or make them look as squares."""
1632        self.GetProperty().SetRenderPointsAsSpheres(value)
1633        return self

Make points look spheric or make them look as squares.

def color(self, c=False, alpha=None):
1635    def color(self, c=False, alpha=None):
1636        """
1637        Set/get mesh's color.
1638        If None is passed as input, will use colors from active scalars.
1639        Same as `mesh.c()`.
1640        """
1641        # overrides base.color()
1642        if c is False:
1643            return np.array(self.GetProperty().GetColor())
1644        if c is None:
1645            self.mapper().ScalarVisibilityOn()
1646            return self
1647        self.mapper().ScalarVisibilityOff()
1648        cc = colors.get_color(c)
1649        self.GetProperty().SetColor(cc)
1650        if self.trail:
1651            self.trail.GetProperty().SetColor(cc)
1652        if alpha is not None:
1653            self.alpha(alpha)
1654        return self

Set/get mesh's color. If None is passed as input, will use colors from active scalars. Same as mesh.c().

def clean(self):
1656    def clean(self):
1657        """
1658        Clean pointcloud or mesh by removing coincident points.
1659        """
1660        cpd = vtk.vtkCleanPolyData()
1661        cpd.PointMergingOn()
1662        cpd.ConvertLinesToPointsOn()
1663        cpd.ConvertPolysToLinesOn()
1664        cpd.ConvertStripsToPolysOn()
1665        cpd.SetInputData(self.inputdata())
1666        cpd.Update()
1667        out = self._update(cpd.GetOutput())
1668
1669        out.pipeline = utils.OperationNode(
1670            "clean", parents=[self],
1671            comment=f"#pts {out.inputdata().GetNumberOfPoints()}"
1672        )
1673        return out

Clean pointcloud or mesh by removing coincident points.

def subsample(self, fraction, absolute=False):
1675    def subsample(self, fraction, absolute=False):
1676        """
1677        Subsample a point cloud by requiring that the points
1678        or vertices are far apart at least by the specified fraction of the object size.
1679        If a Mesh is passed the polygonal faces are not removed
1680        but holes can appear as vertices are removed.
1681
1682        Examples:
1683            - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py)
1684
1685                ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png)
1686
1687            - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py)
1688
1689                ![](https://vedo.embl.es/images/advanced/recosurface.png)
1690        """
1691        if not absolute:
1692            if fraction > 1:
1693                vedo.logger.warning(
1694                    f"subsample(fraction=...), fraction must be < 1, but is {fraction}"
1695                )
1696            if fraction <= 0:
1697                return self
1698
1699        cpd = vtk.vtkCleanPolyData()
1700        cpd.PointMergingOn()
1701        cpd.ConvertLinesToPointsOn()
1702        cpd.ConvertPolysToLinesOn()
1703        cpd.ConvertStripsToPolysOn()
1704        cpd.SetInputData(self.inputdata())
1705        if absolute:
1706            cpd.SetTolerance(fraction / self.diagonal_size())
1707            # cpd.SetToleranceIsAbsolute(absolute)
1708        else:
1709            cpd.SetTolerance(fraction)
1710        cpd.Update()
1711
1712        ps = 2
1713        if self.GetProperty().GetRepresentation() == 0:
1714            ps = self.GetProperty().GetPointSize()
1715
1716        out = self._update(cpd.GetOutput()).ps(ps)
1717
1718        out.pipeline = utils.OperationNode(
1719            "subsample", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}"
1720        )
1721        return out

Subsample a point cloud by requiring that the points or vertices are far apart at least by the specified fraction of the object size. If a Mesh is passed the polygonal faces are not removed but holes can appear as vertices are removed.

Examples:
def threshold(self, scalars, above=None, below=None, on='points'):
1723    def threshold(self, scalars, above=None, below=None, on="points"):
1724        """
1725        Extracts cells where scalar value satisfies threshold criterion.
1726
1727        Arguments:
1728            scalars : (str)
1729                name of the scalars array.
1730            above : (float)
1731                minimum value of the scalar
1732            below : (float)
1733                maximum value of the scalar
1734            on : (str)
1735                if 'cells' assume array of scalars refers to cell data.
1736
1737        Examples:
1738            - [mesh_threshold.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_threshold.py)
1739        """
1740        thres = vtk.vtkThreshold()
1741        thres.SetInputData(self.inputdata())
1742
1743        if on.startswith("c"):
1744            asso = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS
1745        else:
1746            asso = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS
1747
1748        thres.SetInputArrayToProcess(0, 0, 0, asso, scalars)
1749
1750        if above is None and below is not None:
1751            thres.ThresholdByLower(below)
1752        elif below is None and above is not None:
1753            thres.ThresholdByUpper(above)
1754        else:
1755            thres.ThresholdBetween(above, below)
1756        thres.Update()
1757
1758        gf = vtk.vtkGeometryFilter()
1759        gf.SetInputData(thres.GetOutput())
1760        gf.Update()
1761        return self._update(gf.GetOutput())

Extracts cells where scalar value satisfies threshold criterion.

Arguments:
  • scalars : (str) name of the scalars array.
  • above : (float) minimum value of the scalar
  • below : (float) maximum value of the scalar
  • on : (str) if 'cells' assume array of scalars refers to cell data.
Examples:
def quantize(self, value):
1763    def quantize(self, value):
1764        """
1765        The user should input a value and all {x,y,z} coordinates
1766        will be quantized to that absolute grain size.
1767        """
1768        poly = self.inputdata()
1769        qp = vtk.vtkQuantizePolyDataPoints()
1770        qp.SetInputData(poly)
1771        qp.SetQFactor(value)
1772        qp.Update()
1773        out = self._update(qp.GetOutput()).flat()
1774        out.pipeline = utils.OperationNode("quantize", parents=[self])
1775        return out

The user should input a value and all {x,y,z} coordinates will be quantized to that absolute grain size.

def average_size(self):
1777    def average_size(self):
1778        """
1779        Calculate the average size of a mesh.
1780        This is the mean of the vertex distances from the center of mass.
1781        """
1782        coords = self.points()
1783        cm = np.mean(coords, axis=0)
1784        if coords.shape[0] == 0:
1785            return 0.0
1786        cc = coords - cm
1787        return np.mean(np.linalg.norm(cc, axis=1))

Calculate the average size of a mesh. This is the mean of the vertex distances from the center of mass.

def center_of_mass(self):
1789    def center_of_mass(self):
1790        """Get the center of mass of mesh."""
1791        cmf = vtk.vtkCenterOfMass()
1792        cmf.SetInputData(self.polydata())
1793        cmf.Update()
1794        c = cmf.GetCenter()
1795        return np.array(c)

Get the center of mass of mesh.

def normal_at(self, i):
1797    def normal_at(self, i):
1798        """Return the normal vector at vertex point `i`."""
1799        normals = self.polydata().GetPointData().GetNormals()
1800        return np.array(normals.GetTuple(i))

Return the normal vector at vertex point i.

def normals(self, cells=False, recompute=True):
1802    def normals(self, cells=False, recompute=True):
1803        """Retrieve vertex normals as a numpy array.
1804
1805        Arguments:
1806            cells : (bool)
1807                if `True` return cell normals.
1808
1809            recompute : (bool)
1810                if `True` normals are recalculated if not already present.
1811                Note that this might modify the number of mesh points.
1812        """
1813        if cells:
1814            vtknormals = self.polydata().GetCellData().GetNormals()
1815        else:
1816            vtknormals = self.polydata().GetPointData().GetNormals()
1817        if not vtknormals and recompute:
1818            try:
1819                self.compute_normals(cells=cells)
1820                if cells:
1821                    vtknormals = self.polydata().GetCellData().GetNormals()
1822                else:
1823                    vtknormals = self.polydata().GetPointData().GetNormals()
1824            except AttributeError:
1825                # can be that 'Points' object has no attribute 'compute_normals'
1826                pass
1827
1828        if not vtknormals:
1829            return np.array([])
1830        return utils.vtk2numpy(vtknormals)

Retrieve vertex normals as a numpy array.

Arguments:
  • cells : (bool) if True return cell normals.
  • recompute : (bool) if True normals are recalculated if not already present. Note that this might modify the number of mesh points.
def labels( self, content=None, on='points', scale=None, xrot=0.0, yrot=0.0, zrot=0.0, ratio=1, precision=None, italic=False, font='', justify='bottom-left', c='black', alpha=1.0, cells=None):
1832    def labels(
1833        self,
1834        content=None,
1835        on="points",
1836        scale=None,
1837        xrot=0.0,
1838        yrot=0.0,
1839        zrot=0.0,
1840        ratio=1,
1841        precision=None,
1842        italic=False,
1843        font="",
1844        justify="bottom-left",
1845        c="black",
1846        alpha=1.0,
1847        cells=None,
1848    ):
1849        """
1850        Generate value or ID labels for mesh cells or points.
1851        For large nr. of labels use `font="VTK"` which is much faster.
1852
1853        See also:
1854            `labels2d()`, `flagpole()`, `caption()` and `legend()`.
1855
1856        Arguments:
1857            content : (list,int,str)
1858                either 'id', 'cellid', array name or array number.
1859                A array can also be passed (must match the nr. of points or cells).
1860            on : (str)
1861                generate labels for "cells" instead of "points"
1862            scale : (float)
1863                absolute size of labels, if left as None it is automatic
1864            zrot : (float)
1865                local rotation angle of label in degrees
1866            ratio : (int)
1867                skipping ratio, to reduce nr of labels for large meshes
1868            precision : (int)
1869                numeric precision of labels
1870
1871        ```python
1872        from vedo import *
1873        s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
1874        point_ids = s.labels('id', on="points").c('green')
1875        cell_ids  = s.labels('id', on="cells" ).c('black')
1876        show(s, point_ids, cell_ids)
1877        ```
1878        ![](https://vedo.embl.es/images/feats/labels.png)
1879
1880        Examples:
1881            - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py)
1882
1883                ![](https://vedo.embl.es/images/basic/boundaries.png)
1884        """
1885        if cells is not None:  # deprecation message
1886            vedo.logger.warning("In labels(cells=...) please use labels(on='cells') instead")
1887
1888        if "cell" in on or "face" in on:
1889            cells = True
1890
1891        if isinstance(content, str):
1892            if content in ("cellid", "cellsid"):
1893                cells = True
1894                content = "id"
1895
1896        if cells:
1897            elems = self.cell_centers()
1898            norms = self.normals(cells=True, recompute=False)
1899            ns = np.sqrt(self.ncells)
1900        else:
1901            elems = self.points()
1902            norms = self.normals(cells=False, recompute=False)
1903            ns = np.sqrt(self.npoints)
1904
1905        hasnorms = False
1906        if len(norms) > 0:
1907            hasnorms = True
1908
1909        if scale is None:
1910            if not ns:
1911                ns = 100
1912            scale = self.diagonal_size() / ns / 10
1913
1914        arr = None
1915        mode = 0
1916        if content is None:
1917            mode = 0
1918            if cells:
1919                if self.inputdata().GetCellData().GetScalars():
1920                    name = self.inputdata().GetCellData().GetScalars().GetName()
1921                    arr = self.celldata[name]
1922            else:
1923                if self.inputdata().GetPointData().GetScalars():
1924                    name = self.inputdata().GetPointData().GetScalars().GetName()
1925                    arr = self.pointdata[name]
1926        elif isinstance(content, (str, int)):
1927            if content == "id":
1928                mode = 1
1929            elif cells:
1930                mode = 0
1931                arr = self.celldata[content]
1932            else:
1933                mode = 0
1934                arr = self.pointdata[content]
1935        elif utils.is_sequence(content):
1936            mode = 0
1937            arr = content
1938            # print('WEIRD labels() test', content)
1939            # exit()
1940
1941        if arr is None and mode == 0:
1942            vedo.logger.error("in labels(), array not found for points or cells")
1943            return None
1944
1945        tapp = vtk.vtkAppendPolyData()
1946        ninputs = 0
1947
1948        for i, e in enumerate(elems):
1949            if i % ratio:
1950                continue
1951
1952            if mode == 1:
1953                txt_lab = str(i)
1954            else:
1955                if precision:
1956                    txt_lab = utils.precision(arr[i], precision)
1957                else:
1958                    txt_lab = str(arr[i])
1959
1960            if not txt_lab:
1961                continue
1962
1963            if font == "VTK":
1964                tx = vtk.vtkVectorText()
1965                tx.SetText(txt_lab)
1966                tx.Update()
1967                tx_poly = tx.GetOutput()
1968            else:
1969                tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify)
1970                tx_poly = tx_poly.inputdata()
1971
1972            if tx_poly.GetNumberOfPoints() == 0:
1973                continue  #######################
1974            ninputs += 1
1975
1976            T = vtk.vtkTransform()
1977            T.PostMultiply()
1978            if italic:
1979                T.Concatenate([1,0.2,0,0,
1980                               0,1,0,0,
1981                               0,0,1,0,
1982                               0,0,0,1])
1983            if hasnorms:
1984                ni = norms[i]
1985                if cells:  # center-justify
1986                    bb = tx_poly.GetBounds()
1987                    dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2
1988                    T.Translate(-dx, -dy, 0)
1989                if xrot:
1990                    T.RotateX(xrot)
1991                if yrot:
1992                    T.RotateY(yrot)
1993                if zrot:
1994                    T.RotateZ(zrot)
1995                crossvec = np.cross([0, 0, 1], ni)
1996                angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3
1997                T.RotateWXYZ(angle, crossvec)
1998                if cells:  # small offset along normal only for cells
1999                    T.Translate(ni * scale / 2)
2000            else:
2001                if xrot:
2002                    T.RotateX(xrot)
2003                if yrot:
2004                    T.RotateY(yrot)
2005                if zrot:
2006                    T.RotateZ(zrot)
2007            T.Scale(scale, scale, scale)
2008            T.Translate(e)
2009            tf = vtk.vtkTransformPolyDataFilter()
2010            tf.SetInputData(tx_poly)
2011            tf.SetTransform(T)
2012            tf.Update()
2013            tapp.AddInputData(tf.GetOutput())
2014
2015        if ninputs:
2016            tapp.Update()
2017            lpoly = tapp.GetOutput()
2018        else:  # return an empty obj
2019            lpoly = vtk.vtkPolyData()
2020
2021        ids = vedo.mesh.Mesh(lpoly, c=c, alpha=alpha)
2022        ids.GetProperty().LightingOff()
2023        ids.PickableOff()
2024        ids.SetUseBounds(False)
2025        return ids

Generate value or ID labels for mesh cells or points. For large nr. of labels use font="VTK" which is much faster.

See also:

labels2d(), flagpole(), caption() and legend().

Arguments:
  • content : (list,int,str) either 'id', 'cellid', array name or array number. A array can also be passed (must match the nr. of points or cells).
  • on : (str) generate labels for "cells" instead of "points"
  • scale : (float) absolute size of labels, if left as None it is automatic
  • zrot : (float) local rotation angle of label in degrees
  • ratio : (int) skipping ratio, to reduce nr of labels for large meshes
  • precision : (int) numeric precision of labels
from vedo import *
s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
point_ids = s.labels('id', on="points").c('green')
cell_ids  = s.labels('id', on="cells" ).c('black')
show(s, point_ids, cell_ids)

Examples:
def labels2d( self, content='id', on='points', scale=1.0, precision=4, font='Calco', justify='bottom-left', angle=0.0, frame=False, c='black', bc=None, alpha=1.0):
2027    def labels2d(
2028        self,
2029        content="id",
2030        on="points",
2031        scale=1.0,
2032        precision=4,
2033        font="Calco",
2034        justify="bottom-left",
2035        angle=0.0,
2036        frame=False,
2037        c="black",
2038        bc=None,
2039        alpha=1.0,
2040    ):
2041        """
2042        Generate value or ID bi-dimensional labels for mesh cells or points.
2043
2044        See also: `labels()`, `flagpole()`, `caption()` and `legend()`.
2045
2046        Arguments:
2047            content : (str)
2048                either 'id', 'cellid', or array name
2049            on : (str)
2050                generate labels for "cells" instead of "points" (the default)
2051            scale : (float)
2052                size scaling of labels
2053            precision : (int)
2054                precision of numeric labels
2055            angle : (float)
2056                local rotation angle of label in degrees
2057            frame : (bool)
2058                draw a frame around the label
2059            bc : (str)
2060                background color of the label
2061
2062        ```python
2063        from vedo import Sphere, show
2064        sph = Sphere(quads=True, res=4).compute_normals().wireframe()
2065        sph.celldata["zvals"] = sph.cell_centers()[:,2]
2066        l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
2067        show(sph, l2d, axes=1).close()
2068        ```
2069        ![](https://vedo.embl.es/images/feats/labels2d.png)
2070        """
2071        cells = False
2072        if isinstance(content, str):
2073            if content in ("cellid", "cellsid"):
2074                cells = True
2075                content = "id"
2076
2077        if "cell" in on:
2078            cells = True
2079        elif "point" in on:
2080            cells = False
2081
2082        if cells:
2083            if content != "id" and content not in self.celldata.keys():
2084                vedo.logger.error(f"In labels2d: cell array {content} does not exist.")
2085                return None
2086            cellcloud = Points(self.cell_centers())
2087            arr = self.inputdata().GetCellData().GetScalars()
2088            poly = cellcloud.polydata(False)
2089            poly.GetPointData().SetScalars(arr)
2090        else:
2091            poly = self.polydata()
2092            if content != "id" and content not in self.pointdata.keys():
2093                vedo.logger.error(f"In labels2d: point array {content} does not exist.")
2094                return None
2095            self.pointdata.select(content)
2096
2097        mp = vtk.vtkLabeledDataMapper()
2098
2099        if content == "id":
2100            mp.SetLabelModeToLabelIds()
2101        else:
2102            mp.SetLabelModeToLabelScalars()
2103            if precision is not None:
2104                mp.SetLabelFormat(f"%-#.{precision}g")
2105
2106        pr = mp.GetLabelTextProperty()
2107        c = colors.get_color(c)
2108        pr.SetColor(c)
2109        pr.SetOpacity(alpha)
2110        pr.SetFrame(frame)
2111        pr.SetFrameColor(c)
2112        pr.SetItalic(False)
2113        pr.BoldOff()
2114        pr.ShadowOff()
2115        pr.UseTightBoundingBoxOn()
2116        pr.SetOrientation(angle)
2117        pr.SetFontFamily(vtk.VTK_FONT_FILE)
2118        fl = utils.get_font_path(font)
2119        pr.SetFontFile(fl)
2120        pr.SetFontSize(int(20 * scale))
2121
2122        if "cent" in justify or "mid" in justify:
2123            pr.SetJustificationToCentered()
2124        elif "rig" in justify:
2125            pr.SetJustificationToRight()
2126        elif "left" in justify:
2127            pr.SetJustificationToLeft()
2128        # ------
2129        if "top" in justify:
2130            pr.SetVerticalJustificationToTop()
2131        else:
2132            pr.SetVerticalJustificationToBottom()
2133
2134        if bc is not None:
2135            bc = colors.get_color(bc)
2136            pr.SetBackgroundColor(bc)
2137            pr.SetBackgroundOpacity(alpha)
2138
2139        mp.SetInputData(poly)
2140        a2d = vtk.vtkActor2D()
2141        a2d.PickableOff()
2142        a2d.SetMapper(mp)
2143        return a2d

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

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

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

def legend(self, txt):
2145    def legend(self, txt):
2146        """Book a legend text."""
2147        self.info["legend"] = txt
2148        return self

Book a legend text.

def flagpole( self, txt=None, point=None, offset=None, s=None, font='', rounded=True, c=None, alpha=1.0, lw=2, italic=0.0, padding=0.1):
2150    def flagpole(
2151        self,
2152        txt=None,
2153        point=None,
2154        offset=None,
2155        s=None,
2156        font="",
2157        rounded=True,
2158        c=None,
2159        alpha=1.0,
2160        lw=2,
2161        italic=0.0,
2162        padding=0.1,
2163    ):
2164        """
2165        Generate a flag pole style element to describe an object.
2166        Returns a `Mesh` object.
2167
2168        Use flagpole.follow_camera() to make it face the camera in the scene.
2169
2170        See also `flagpost()`.
2171
2172        Arguments:
2173            txt : (str)
2174                Text to display. The default is the filename or the object name.
2175            point : (list)
2176                position of the flagpole pointer. 
2177            offset : (list)
2178                text offset wrt the application point. 
2179            s : (float)
2180                size of the flagpole.
2181            font : (str)
2182                font face. Check [available fonts here](https://vedo.embl.es/fonts).
2183            rounded : (bool)
2184                draw a rounded or squared box around the text.
2185            c : (list)
2186                text and box color.
2187            alpha : (float)
2188                opacity of text and box.
2189            lw : (float)
2190                line with of box frame.
2191            italic : (float)
2192                italicness of text.
2193
2194        Examples:
2195            - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py)
2196
2197                ![](https://vedo.embl.es/images/pyplot/intersect2d.png)
2198
2199            - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2200            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2201            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2202        """
2203        acts = []
2204
2205        if txt is None:
2206            if self.filename:
2207                txt = self.filename.split("/")[-1]
2208            elif self.name:
2209                txt = self.name
2210            else:
2211                return None
2212
2213        x0, x1, y0, y1, z0, z1 = self.bounds()
2214        d = self.diagonal_size()
2215        if point is None:
2216            if d:
2217                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2218            else:  # it's a Point
2219                point = self.GetPosition()
2220
2221        pt = utils.make3d(point)
2222
2223        if offset is None:
2224            offset = [(x1 - x0) / 2, (y1 - y0) / 6, 0]
2225        offset = utils.make3d(offset)
2226
2227        if s is None:
2228            s = d / 20
2229
2230        sph = None
2231        if d and (z1 - z0) / d > 0.1:
2232            sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6)
2233
2234        if c is None:
2235            c = np.array(self.color()) / 1.4
2236
2237        lb = vedo.shapes.Text3D(
2238            txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center-left"
2239        )
2240        acts.append(lb)
2241
2242        if d and not sph:
2243            sph = vedo.shapes.Circle(pt, r=s / 3, res=15)
2244        acts.append(sph)
2245
2246        x0, x1, y0, y1, z0, z1 = lb.GetBounds()
2247        if rounded:
2248            box = vedo.shapes.KSpline(
2249                [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)], closed=True
2250            )
2251        else:
2252            box = vedo.shapes.Line(
2253                [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z0)]
2254            )
2255
2256        cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
2257
2258        box.SetOrigin(cnt)
2259        box.scale([1 + padding, 1 + 2 * padding, 1])
2260        acts.append(box)
2261
2262        # pts = box.points()
2263        # bfaces = []
2264        # for i, pt in enumerate(pts):
2265        #     if i:
2266        #         face = [i-1, i, 0]
2267        #         bfaces.append(face)
2268        # bpts = [cnt] + pts.tolist()
2269        # box2 = vedo.Mesh([bpts, bfaces]).z(-cnt[0]/10)#.c('w').alpha(0.1)
2270        # #should be made assembly otherwise later merge() nullifies it
2271        # box2.SetOrigin(cnt)
2272        # acts.append(box2)
2273
2274        x0, x1, y0, y1, z0, z1 = box.bounds()
2275        if x0 < pt[0] < x1:
2276            c0 = box.closest_point(pt)
2277            c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]]
2278        elif (pt[0] - x0) < (x1 - pt[0]):
2279            c0 = [x0, (y0 + y1) / 2, pt[2]]
2280            c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]]
2281        else:
2282            c0 = [x1, (y0 + y1) / 2, pt[2]]
2283            c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]]
2284
2285        con = vedo.shapes.Line([c0, c1, pt])
2286        acts.append(con)
2287
2288        macts = vedo.merge(acts).c(c).alpha(alpha)
2289        macts.SetOrigin(pt)
2290        macts.bc("tomato").pickable(False)
2291        macts.GetProperty().LightingOff()
2292        macts.GetProperty().SetLineWidth(lw)
2293        macts.UseBoundsOff()
2294        macts.name = "FlagPole"
2295        return macts

Generate a flag pole style element to describe an object. Returns a Mesh object.

Use flagpole.follow_camera() to make it face the camera in the scene.

See also flagpost().

Arguments:
  • txt : (str) Text to display. The default is the filename or the object name.
  • point : (list) position of the flagpole pointer.
  • offset : (list) text offset wrt the application point.
  • s : (float) size of the flagpole.
  • font : (str) font face. Check available fonts here.
  • rounded : (bool) draw a rounded or squared box around the text.
  • c : (list) text and box color.
  • alpha : (float) opacity of text and box.
  • lw : (float) line with of box frame.
  • italic : (float) italicness of text.
Examples:
def flagpost( self, txt=None, point=None, offset=None, s=1.0, c='k9', bc='k1', alpha=1, lw=0, font='Calco', justify='center-left', vspacing=1.0):
2297    def flagpost(
2298        self,
2299        txt=None,
2300        point=None,
2301        offset=None,
2302        s=1.0,
2303        c="k9",
2304        bc="k1",
2305        alpha=1,
2306        lw=0,
2307        font="Calco",
2308        justify="center-left",
2309        vspacing=1.0,
2310    ):
2311        """
2312        Generate a flag post style element to describe an object.
2313
2314        Arguments:
2315            txt : (str)
2316                Text to display. The default is the filename or the object name.
2317            point : (list)
2318                position of the flag anchor point. The default is None.
2319            offset : (list)
2320                a 3D displacement or offset. The default is None.
2321            s : (float)
2322                size of the text to be shown
2323            c : (list)
2324                color of text and line
2325            bc : (list)
2326                color of the flag background
2327            alpha : (float)
2328                opacity of text and box.
2329            lw : (int)
2330                line with of box frame. The default is 0.
2331            font : (str)
2332                font name. Use a monospace font for better rendering. The default is "Calco".
2333                Type `vedo -r fonts` for a font demo.
2334                Check [available fonts here](https://vedo.embl.es/fonts).
2335            justify : (str)
2336                internal text justification. The default is "center-left".
2337            vspacing : (float)
2338                vertical spacing between lines.
2339
2340        Examples:
2341            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
2342
2343            ![](https://vedo.embl.es/images/other/flag_labels2.png)
2344        """
2345        if txt is None:
2346            if self.filename:
2347                txt = self.filename.split("/")[-1]
2348            elif self.name:
2349                txt = self.name
2350            else:
2351                return None
2352
2353        x0, x1, y0, y1, z0, z1 = self.bounds()
2354        d = self.diagonal_size()
2355        if point is None:
2356            if d:
2357                point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1])
2358            else:  # it's a Point
2359                point = self.GetPosition()
2360
2361        point = utils.make3d(point)
2362
2363        if offset is None:
2364            offset = [0, 0, (z1 - z0) / 2]
2365        offset = utils.make3d(offset)
2366
2367        fpost = vedo.addons.Flagpost(
2368            txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing
2369        )
2370        self._caption = fpost
2371        return fpost

Generate a flag post style element to describe an object.

Arguments:
  • txt : (str) Text to display. The default is the filename or the object name.
  • point : (list) position of the flag anchor point. The default is None.
  • offset : (list) a 3D displacement or offset. The default is None.
  • s : (float) size of the text to be shown
  • c : (list) color of text and line
  • bc : (list) color of the flag background
  • alpha : (float) opacity of text and box.
  • lw : (int) line with of box frame. The default is 0.
  • font : (str) font name. Use a monospace font for better rendering. The default is "Calco". Type vedo -r fonts for a font demo. Check available fonts here.
  • justify : (str) internal text justification. The default is "center-left".
  • vspacing : (float) vertical spacing between lines.
Examples:

def caption( self, txt=None, point=None, size=(0.3, 0.15), padding=5, font='Calco', justify='center-right', vspacing=1.0, c=None, alpha=1.0, lw=1, ontop=True):
2373    def caption(
2374        self,
2375        txt=None,
2376        point=None,
2377        size=(0.30, 0.15),
2378        padding=5,
2379        font="Calco",
2380        justify="center-right",
2381        vspacing=1.0,
2382        c=None,
2383        alpha=1.0,
2384        lw=1,
2385        ontop=True,
2386    ):
2387        """
2388        Add a 2D caption to an object which follows the camera movements.
2389        Latex is not supported. Returns the same input object for concatenation.
2390
2391        See also `flagpole()`, `flagpost()`, `labels()` and `legend()`
2392        with similar functionality.
2393
2394        Arguments:
2395            txt : (str)
2396                text to be rendered. The default is the file name.
2397            point : (list)
2398                anchoring point. The default is None.
2399            size : (list)
2400                (width, height) of the caption box. The default is (0.30, 0.15).
2401            padding : (float)
2402                padding space of the caption box in pixels. The default is 5.
2403            font : (str)
2404                font name. Use a monospace font for better rendering. The default is "VictorMono".
2405                Type `vedo -r fonts` for a font demo.
2406                Check [available fonts here](https://vedo.embl.es/fonts).
2407            justify : (str)
2408                internal text justification. The default is "center-right".
2409            vspacing : (float)
2410                vertical spacing between lines. The default is 1.
2411            c : (str)
2412                text and box color. The default is 'lb'.
2413            alpha : (float)
2414                text and box transparency. The default is 1.
2415            lw : (int)
2416                line width in pixels. The default is 1.
2417            ontop : (bool)
2418                keep the 2d caption always on top. The default is True.
2419
2420        Examples:
2421            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
2422
2423                ![](https://vedo.embl.es/images/pyplot/caption.png)
2424
2425            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
2426            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
2427        """
2428        if txt is None:
2429            if self.filename:
2430                txt = self.filename.split("/")[-1]
2431            elif self.name:
2432                txt = self.name
2433
2434        if not txt:  # disable it
2435            self._caption = None
2436            return self
2437
2438        for r in vedo.shapes._reps:
2439            txt = txt.replace(r[0], r[1])
2440
2441        if c is None:
2442            c = np.array(self.GetProperty().GetColor()) / 2
2443        else:
2444            c = colors.get_color(c)
2445
2446        if point is None:
2447            x0, x1, y0, y1, _, z1 = self.GetBounds()
2448            pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1]
2449            point = self.closest_point(pt)
2450
2451        capt = vtk.vtkCaptionActor2D()
2452        capt.SetAttachmentPoint(point)
2453        capt.SetBorder(True)
2454        capt.SetLeader(True)
2455        sph = vtk.vtkSphereSource()
2456        sph.Update()
2457        capt.SetLeaderGlyphData(sph.GetOutput())
2458        capt.SetMaximumLeaderGlyphSize(5)
2459        capt.SetPadding(int(padding))
2460        capt.SetCaption(txt)
2461        capt.SetWidth(size[0])
2462        capt.SetHeight(size[1])
2463        capt.SetThreeDimensionalLeader(not ontop)
2464
2465        pra = capt.GetProperty()
2466        pra.SetColor(c)
2467        pra.SetOpacity(alpha)
2468        pra.SetLineWidth(lw)
2469
2470        pr = capt.GetCaptionTextProperty()
2471        pr.SetFontFamily(vtk.VTK_FONT_FILE)
2472        fl = utils.get_font_path(font)
2473        pr.SetFontFile(fl)
2474        pr.ShadowOff()
2475        pr.BoldOff()
2476        pr.FrameOff()
2477        pr.SetColor(c)
2478        pr.SetOpacity(alpha)
2479        pr.SetJustificationToLeft()
2480        if "top" in justify:
2481            pr.SetVerticalJustificationToTop()
2482        if "bottom" in justify:
2483            pr.SetVerticalJustificationToBottom()
2484        if "cent" in justify:
2485            pr.SetVerticalJustificationToCentered()
2486            pr.SetJustificationToCentered()
2487        if "left" in justify:
2488            pr.SetJustificationToLeft()
2489        if "right" in justify:
2490            pr.SetJustificationToRight()
2491        pr.SetLineSpacing(vspacing)
2492        self._caption = capt
2493        return self

Add a 2D caption to an object which follows the camera movements. Latex is not supported. Returns the same input object for concatenation.

See also flagpole(), flagpost(), labels() and legend() with similar functionality.

Arguments:
  • txt : (str) text to be rendered. The default is the file name.
  • point : (list) anchoring point. The default is None.
  • size : (list) (width, height) of the caption box. The default is (0.30, 0.15).
  • padding : (float) padding space of the caption box in pixels. The default is 5.
  • font : (str) font name. Use a monospace font for better rendering. The default is "VictorMono". Type vedo -r fonts for a font demo. Check available fonts here.
  • justify : (str) internal text justification. The default is "center-right".
  • vspacing : (float) vertical spacing between lines. The default is 1.
  • c : (str) text and box color. The default is 'lb'.
  • alpha : (float) text and box transparency. The default is 1.
  • lw : (int) line width in pixels. The default is 1.
  • ontop : (bool) keep the 2d caption always on top. The default is True.
Examples:
def align_to( self, target, iters=100, rigid=False, invert=False, use_centroids=False):
2496    def align_to(self, target, iters=100, rigid=False, invert=False, use_centroids=False):
2497        """
2498        Aligned to target mesh through the `Iterative Closest Point` algorithm.
2499
2500        The core of the algorithm is to match each vertex in one surface with
2501        the closest surface point on the other, then apply the transformation
2502        that modify one surface to best match the other (in the least-square sense).
2503
2504        Arguments:
2505            rigid : (bool)
2506                if True do not allow scaling
2507            invert : (bool)
2508                if True start by aligning the target to the source but
2509                invert the transformation finally. Useful when the target is smaller
2510                than the source.
2511            use_centroids : (bool)
2512                start by matching the centroids of the two objects.
2513
2514        Examples:
2515            - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py)
2516
2517                ![](https://vedo.embl.es/images/basic/align1.png)
2518
2519            - [align2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align2.py)
2520
2521                ![](https://vedo.embl.es/images/basic/align2.png)
2522        """
2523        icp = vtk.vtkIterativeClosestPointTransform()
2524        icp.SetSource(self.polydata())
2525        icp.SetTarget(target.polydata())
2526        if invert:
2527            icp.Inverse()
2528        icp.SetMaximumNumberOfIterations(iters)
2529        if rigid:
2530            icp.GetLandmarkTransform().SetModeToRigidBody()
2531        icp.SetStartByMatchingCentroids(use_centroids)
2532        icp.Update()
2533
2534        M = icp.GetMatrix()
2535        if invert:
2536            M.Invert()  # icp.GetInverse() doesnt work!
2537        # self.apply_transform(M)
2538        self.SetUserMatrix(M)
2539
2540        self.transform = self.GetUserTransform()
2541        self.point_locator = None
2542        self.cell_locator = None
2543
2544        self.pipeline = utils.OperationNode(
2545            "align_to", parents=[self, target], comment=f"rigid = {rigid}"
2546        )
2547        return self

Aligned to target mesh through the Iterative Closest Point algorithm.

The core of the algorithm is to match each vertex in one surface with the closest surface point on the other, then apply the transformation that modify one surface to best match the other (in the least-square sense).

Arguments:
  • rigid : (bool) if True do not allow scaling
  • invert : (bool) if True start by aligning the target to the source but invert the transformation finally. Useful when the target is smaller than the source.
  • use_centroids : (bool) start by matching the centroids of the two objects.
Examples:
def transform_with_landmarks( self, source_landmarks, target_landmarks, rigid=False, affine=False, least_squares=False):
2549    def transform_with_landmarks(
2550        self, source_landmarks, target_landmarks, rigid=False, affine=False, least_squares=False
2551    ):
2552        """
2553        Transform mesh orientation and position based on a set of landmarks points.
2554        The algorithm finds the best matching of source points to target points
2555        in the mean least square sense, in one single step.
2556
2557        If affine is True the x, y and z axes can scale independently but stay collinear.
2558        With least_squares they can vary orientation.
2559
2560        Examples:
2561            - [align5.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align5.py)
2562
2563                ![](https://vedo.embl.es/images/basic/align5.png)
2564        """
2565
2566        if utils.is_sequence(source_landmarks):
2567            ss = vtk.vtkPoints()
2568            for p in source_landmarks:
2569                ss.InsertNextPoint(p)
2570        else:
2571            ss = source_landmarks.polydata().GetPoints()
2572            if least_squares:
2573                source_landmarks = source_landmarks.points()
2574
2575        if utils.is_sequence(target_landmarks):
2576            st = vtk.vtkPoints()
2577            for p in target_landmarks:
2578                st.InsertNextPoint(p)
2579        else:
2580            st = target_landmarks.polydata().GetPoints()
2581            if least_squares:
2582                target_landmarks = target_landmarks.points()
2583
2584        if ss.GetNumberOfPoints() != st.GetNumberOfPoints():
2585            n1 = ss.GetNumberOfPoints()
2586            n2 = st.GetNumberOfPoints()
2587            vedo.logger.error(f"source and target have different nr of points {n1} vs {n2}")
2588            raise RuntimeError()
2589
2590        lmt = vtk.vtkLandmarkTransform()
2591        lmt.SetSourceLandmarks(ss)
2592        lmt.SetTargetLandmarks(st)
2593        lmt.SetModeToSimilarity()
2594        if rigid:
2595            lmt.SetModeToRigidBody()
2596            lmt.Update()
2597            self.SetUserTransform(lmt)
2598
2599        elif affine:
2600            lmt.SetModeToAffine()
2601            lmt.Update()
2602            self.SetUserTransform(lmt)
2603
2604        elif least_squares:
2605            cms = source_landmarks.mean(axis=0)
2606            cmt = target_landmarks.mean(axis=0)
2607            m = np.linalg.lstsq(source_landmarks - cms, target_landmarks - cmt, rcond=None)[0]
2608            M = vtk.vtkMatrix4x4()
2609            for i in range(3):
2610                for j in range(3):
2611                    M.SetElement(j, i, m[i][j])
2612            lmt = vtk.vtkTransform()
2613            lmt.Translate(cmt)
2614            lmt.Concatenate(M)
2615            lmt.Translate(-cms)
2616            self.apply_transform(lmt, concatenate=True)
2617        else:
2618            self.SetUserTransform(lmt)
2619
2620        self.transform = lmt
2621        self.point_locator = None
2622        self.cell_locator = None
2623        self.pipeline = utils.OperationNode("transform_with_landmarks", parents=[self])
2624        return self

Transform mesh orientation and position based on a set of landmarks points. The algorithm finds the best matching of source points to target points in the mean least square sense, in one single step.

If affine is True the x, y and z axes can scale independently but stay collinear. With least_squares they can vary orientation.

Examples:
def apply_transform(self, T, reset=False, concatenate=False):
2627    def apply_transform(self, T, reset=False, concatenate=False):
2628        """
2629        Apply a linear or non-linear transformation to the mesh polygonal data.
2630
2631        Arguments:
2632            T : (matrix)
2633                `vtkTransform`, `vtkMatrix4x4` or a 4x4 or 3x3 python or numpy matrix.
2634            reset : (bool)
2635                if True reset the current transformation matrix
2636                to identity after having moved the object, otherwise the internal
2637                matrix will stay the same (to only affect visualization).
2638                It the input transformation has no internal defined matrix (ie. non linear)
2639                then reset will be assumed as True.
2640            concatenate : (bool)
2641                concatenate the transformation with the current existing one
2642
2643        Example:
2644            ```python
2645            from vedo import Cube, show
2646            c1 = Cube().rotate_z(5).x(2).y(1)
2647            print("cube1 position", c1.pos())
2648            T = c1.get_transform()  # rotate by 5 degrees, sum 2 to x and 1 to y
2649            c2 = Cube().c('r4')
2650            c2.apply_transform(T)   # ignore previous movements
2651            c2.apply_transform(T, concatenate=True)
2652            c2.apply_transform(T, concatenate=True)
2653            print("cube2 position", c2.pos())
2654            show(c1, c2, axes=1).close()
2655            ```
2656            ![](https://vedo.embl.es/images/feats/apply_transform.png)
2657        """
2658        self.point_locator = None
2659        self.cell_locator = None
2660
2661        if isinstance(T, vtk.vtkMatrix4x4):
2662            tr = vtk.vtkTransform()
2663            tr.SetMatrix(T)
2664            T = tr
2665
2666        elif utils.is_sequence(T):
2667            M = vtk.vtkMatrix4x4()
2668            n = len(T[0])
2669            for i in range(n):
2670                for j in range(n):
2671                    M.SetElement(i, j, T[i][j])
2672            tr = vtk.vtkTransform()
2673            tr.SetMatrix(M)
2674            T = tr
2675
2676        if reset or not hasattr(T, "GetScale"):  # might be non-linear
2677
2678            tf = vtk.vtkTransformPolyDataFilter()
2679            tf.SetTransform(T)
2680            tf.SetInputData(self.polydata())
2681            tf.Update()
2682
2683            I = vtk.vtkMatrix4x4()
2684            self.PokeMatrix(I)  # reset to identity
2685            self.SetUserTransform(None)
2686
2687            self._update(tf.GetOutput())  ### UPDATE
2688            self.transform = T
2689
2690        else:
2691
2692            if concatenate:
2693
2694                M = vtk.vtkTransform()
2695                M.PostMultiply()
2696                M.SetMatrix(self.GetMatrix())
2697
2698                M.Concatenate(T)
2699
2700                self.SetScale(M.GetScale())
2701                self.SetOrientation(M.GetOrientation())
2702                self.SetPosition(M.GetPosition())
2703                self.transform = M
2704                self.SetUserTransform(None)
2705
2706            else:
2707
2708                self.SetScale(T.GetScale())
2709                self.SetOrientation(T.GetOrientation())
2710                self.SetPosition(T.GetPosition())
2711                self.SetUserTransform(None)
2712
2713                self.transform = T
2714
2715        return self

Apply a linear or non-linear transformation to the mesh polygonal data.

Arguments:
  • T : (matrix) vtkTransform, vtkMatrix4x4 or a 4x4 or 3x3 python or numpy matrix.
  • reset : (bool) if True reset the current transformation matrix to identity after having moved the object, otherwise the internal matrix will stay the same (to only affect visualization). It the input transformation has no internal defined matrix (ie. non linear) then reset will be assumed as True.
  • concatenate : (bool) concatenate the transformation with the current existing one
Example:
from vedo import Cube, show
c1 = Cube().rotate_z(5).x(2).y(1)
print("cube1 position", c1.pos())
T = c1.get_transform()  # rotate by 5 degrees, sum 2 to x and 1 to y
c2 = Cube().c('r4')
c2.apply_transform(T)   # ignore previous movements
c2.apply_transform(T, concatenate=True)
c2.apply_transform(T, concatenate=True)
print("cube2 position", c2.pos())
show(c1, c2, axes=1).close()

def normalize(self):
2717    def normalize(self):
2718        """Scale Mesh average size to unit."""
2719        coords = self.points()
2720        if not coords.shape[0]:
2721            return self
2722        cm = np.mean(coords, axis=0)
2723        pts = coords - cm
2724        xyz2 = np.sum(pts * pts, axis=0)
2725        scale = 1 / np.sqrt(np.sum(xyz2) / len(pts))
2726        t = vtk.vtkTransform()
2727        t.PostMultiply()
2728        # t.Translate(-cm)
2729        t.Scale(scale, scale, scale)
2730        # t.Translate(cm)
2731        tf = vtk.vtkTransformPolyDataFilter()
2732        tf.SetInputData(self.inputdata())
2733        tf.SetTransform(t)
2734        tf.Update()
2735        self.point_locator = None
2736        self.cell_locator = None
2737        return self._update(tf.GetOutput())

Scale Mesh average size to unit.

def mirror(self, axis='x', origin=(0, 0, 0), reset=False):
2739    def mirror(self, axis="x", origin=(0, 0, 0), reset=False):
2740        """
2741        Mirror the mesh  along one of the cartesian axes
2742
2743        Arguments:
2744            axis : (str)
2745                axis to use for mirroring, must be set to x, y, z or n.
2746                Or any combination of those. Adding 'n' reverses mesh faces (hence normals).
2747            origin : (list)
2748                use this point as the origin of the mirroring transformation.
2749            reset : (bool)
2750                if True keep into account the current position of the object,
2751                and then reset its internal transformation matrix to Identity.
2752
2753        Examples:
2754            - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py)
2755
2756                ![](https://vedo.embl.es/images/basic/mirror.png)
2757        """
2758        sx, sy, sz = 1, 1, 1
2759        if "x" in axis.lower(): sx = -1
2760        if "y" in axis.lower(): sy = -1
2761        if "z" in axis.lower(): sz = -1
2762        origin = np.array(origin)
2763        tr = vtk.vtkTransform()
2764        tr.PostMultiply()
2765        tr.Translate(-origin)
2766        tr.Scale(sx, sy, sz)
2767        tr.Translate(origin)
2768        tf = vtk.vtkTransformPolyDataFilter()
2769        tf.SetInputData(self.polydata(reset))
2770        tf.SetTransform(tr)
2771        tf.Update()
2772        outpoly = tf.GetOutput()
2773        if reset:
2774            self.PokeMatrix(vtk.vtkMatrix4x4())  # reset to identity
2775        if sx * sy * sz < 0 or "n" in axis:
2776            rs = vtk.vtkReverseSense()
2777            rs.SetInputData(outpoly)
2778            rs.ReverseNormalsOff()
2779            rs.Update()
2780            outpoly = rs.GetOutput()
2781
2782        self.point_locator = None
2783        self.cell_locator = None
2784
2785        out = self._update(outpoly)
2786
2787        out.pipeline = utils.OperationNode(f"mirror\naxis = {axis}", parents=[self])
2788        return out

Mirror the mesh along one of the cartesian axes

Arguments:
  • axis : (str) axis to use for mirroring, must be set to x, y, z or n. Or any combination of those. Adding 'n' reverses mesh faces (hence normals).
  • origin : (list) use this point as the origin of the mirroring transformation.
  • reset : (bool) if True keep into account the current position of the object, and then reset its internal transformation matrix to Identity.
Examples:
def shear(self, x=0, y=0, z=0):
2790    def shear(self, x=0, y=0, z=0):
2791        """Apply a shear deformation along one of the main axes"""
2792        t = vtk.vtkTransform()
2793        sx, sy, sz = self.GetScale()
2794        t.SetMatrix([sx, x, 0, 0,
2795                      y,sy, z, 0,
2796                      0, 0,sz, 0,
2797                      0, 0, 0, 1])
2798        self.apply_transform(t, reset=True)
2799        return self

Apply a shear deformation along one of the main axes

def flip_normals(self):
2801    def flip_normals(self):
2802        """Flip all mesh normals. Same as `mesh.mirror('n')`."""
2803        rs = vtk.vtkReverseSense()
2804        rs.SetInputData(self.inputdata())
2805        rs.ReverseCellsOff()
2806        rs.ReverseNormalsOn()
2807        rs.Update()
2808        out = self._update(rs.GetOutput())
2809        self.pipeline = utils.OperationNode("flip_normals", parents=[self])
2810        return out

Flip all mesh normals. Same as mesh.mirror('n').

def cmap( self, input_cmap, input_array=None, on='points', name='Scalars', vmin=None, vmax=None, n_colors=256, alpha=1.0, logscale=False):
2813    def cmap(
2814        self,
2815        input_cmap,
2816        input_array=None,
2817        on="points",
2818        name="Scalars",
2819        vmin=None,
2820        vmax=None,
2821        n_colors=256,
2822        alpha=1.0,
2823        logscale=False,
2824    ):
2825        """
2826        Set individual point/cell colors by providing a list of scalar values and a color map.
2827
2828        Arguments:
2829            input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap)
2830                color map scheme to transform a real number into a color.
2831            input_array : (str, list, vtkArray)
2832                can be the string name of an existing array, a numpy array or a `vtkArray`.
2833            on : (str)
2834                either 'points' or 'cells'.
2835                Apply the color map to data which is defined on either points or cells.
2836            name : (str)
2837                give a name to the provided numpy array (if input_array is a numpy array)
2838            vmin : (float)
2839                clip scalars to this minimum value
2840            vmax : (float)
2841                clip scalars to this maximum value
2842            n_colors : (int)
2843                number of distinct colors to be used in colormap table.
2844            alpha : (float, list)
2845                Mesh transparency. Can be a `list` of values one for each vertex.
2846            logscale : (bool)
2847                Use logscale
2848
2849        Examples:
2850            - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py)
2851            - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py)
2852            - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py)
2853            (and many others)
2854
2855                ![](https://vedo.embl.es/images/basic/mesh_custom.png)
2856        """
2857        self._cmap_name = input_cmap
2858        poly = self.inputdata()
2859
2860        if input_array is None:
2861            if not self.pointdata.keys() and self.celldata.keys():
2862                on = "cells"
2863                if not poly.GetCellData().GetScalars():
2864                    input_array = 0  # pick the first at hand
2865
2866        if on.startswith("point"):
2867            data = poly.GetPointData()
2868            n = poly.GetNumberOfPoints()
2869        elif on.startswith("cell"):
2870            data = poly.GetCellData()
2871            n = poly.GetNumberOfCells()
2872        else:
2873            vedo.logger.error("Must specify in cmap(on=...) to either 'cells' or 'points'")
2874            raise RuntimeError()
2875
2876        if input_array is None:  # if None try to fetch the active scalars
2877            arr = data.GetScalars()
2878            if not arr:
2879                vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.")
2880                return self
2881
2882            if not arr.GetName():  # sometimes arrays dont have a name..
2883                arr.SetName(name)
2884
2885        elif isinstance(input_array, str):  # if a string is passed
2886            arr = data.GetArray(input_array)
2887            if not arr:
2888                vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.")
2889                return self
2890
2891        elif isinstance(input_array, int):  # if an int is passed
2892            if input_array < data.GetNumberOfArrays():
2893                arr = data.GetArray(input_array)
2894            else:
2895                vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.")
2896                return self
2897
2898        elif utils.is_sequence(input_array):  # if a numpy array is passed
2899            npts = len(input_array)
2900            if npts != n:
2901                vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.")
2902                return self
2903            arr = utils.numpy2vtk(input_array, name=name, dtype=float)
2904            data.AddArray(arr)
2905            data.Modified()
2906
2907        elif isinstance(input_array, vtk.vtkArray):  # if a vtkArray is passed
2908            arr = input_array
2909            data.AddArray(arr)
2910            data.Modified()
2911
2912        else:
2913            vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}")
2914            raise RuntimeError()
2915
2916        # Now we have array "arr"
2917        array_name = arr.GetName()
2918
2919        if arr.GetNumberOfComponents() == 1:
2920            if vmin is None:
2921                vmin = arr.GetRange()[0]
2922            if vmax is None:
2923                vmax = arr.GetRange()[1]
2924        else:
2925            if vmin is None or vmax is None:
2926                vn = utils.mag(utils.vtk2numpy(arr))
2927            if vmin is None:
2928                vmin = vn.min()
2929            if vmax is None:
2930                vmax = vn.max()
2931
2932        # interpolate alphas if they are not constant
2933        if not utils.is_sequence(alpha):
2934            alpha = [alpha] * n_colors
2935        else:
2936            v = np.linspace(0, 1, n_colors, endpoint=True)
2937            xp = np.linspace(0, 1, len(alpha), endpoint=True)
2938            alpha = np.interp(v, xp, alpha)
2939
2940        ########################### build the look-up table
2941        if isinstance(input_cmap, vtk.vtkLookupTable):  # vtkLookupTable
2942            lut = input_cmap
2943
2944        elif utils.is_sequence(input_cmap):  # manual sequence of colors
2945            lut = vtk.vtkLookupTable()
2946            if logscale:
2947                lut.SetScaleToLog10()
2948            lut.SetRange(vmin, vmax)
2949            ncols = len(input_cmap)
2950            lut.SetNumberOfTableValues(ncols)
2951
2952            for i, c in enumerate(input_cmap):
2953                r, g, b = colors.get_color(c)
2954                lut.SetTableValue(i, r, g, b, alpha[i])
2955            lut.Build()
2956
2957        else:  # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap
2958            lut = vtk.vtkLookupTable()
2959            if logscale:
2960                lut.SetScaleToLog10()
2961            lut.SetVectorModeToMagnitude()
2962            lut.SetRange(vmin, vmax)
2963            lut.SetNumberOfTableValues(n_colors)
2964            mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors)
2965            for i, c in enumerate(mycols):
2966                r, g, b = c
2967                lut.SetTableValue(i, r, g, b, alpha[i])
2968            lut.Build()
2969
2970        arr.SetLookupTable(lut)
2971
2972        data.SetActiveScalars(array_name)
2973        # data.SetScalars(arr)  # wrong! it deletes array in position 0, never use SetScalars
2974        # data.SetActiveAttribute(array_name, 0) # boh!
2975
2976        if data.GetScalars():
2977            data.GetScalars().SetLookupTable(lut)
2978            data.GetScalars().Modified()
2979
2980        self._mapper.SetLookupTable(lut)
2981        self._mapper.SetColorModeToMapScalars()  # so we dont need to convert uint8 scalars
2982
2983        self._mapper.ScalarVisibilityOn()
2984        self._mapper.SetScalarRange(lut.GetRange())
2985        if on.startswith("point"):
2986            self._mapper.SetScalarModeToUsePointData()
2987        else:
2988            self._mapper.SetScalarModeToUseCellData()
2989        if hasattr(self._mapper, "SetArrayName"):
2990            self._mapper.SetArrayName(array_name)
2991
2992        return self

Set individual point/cell colors by providing a list of scalar values and a color map.

Arguments:
  • input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) color map scheme to transform a real number into a color.
  • input_array : (str, list, vtkArray) can be the string name of an existing array, a numpy array or a vtkArray.
  • on : (str) either 'points' or 'cells'. Apply the color map to data which is defined on either points or cells.
  • name : (str) give a name to the provided numpy array (if input_array is a numpy array)
  • vmin : (float) clip scalars to this minimum value
  • vmax : (float) clip scalars to this maximum value
  • n_colors : (int) number of distinct colors to be used in colormap table.
  • alpha : (float, list) Mesh transparency. Can be a list of values one for each vertex.
  • logscale : (bool) Use logscale
Examples:
def cell_individual_colors(self, colorlist):
2994    def cell_individual_colors(self, colorlist):
2995        # DEPRECATED
2996        self.cellcolors = colorlist
2997        print("Please use property mesh.cellcolors=... instead of mesh.cell_individual_colors()")
2998        return self
cellcolors

Colorize each cell (face) of a mesh by passing a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. Colors levels and opacities must be in the range [0,255].

A single constant color can also be passed as string or RGBA.

A cell array named "CellsRGBA" is automatically created.

Examples:

pointcolors

Colorize each point (or vertex of a mesh) by passing a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. Colors levels and opacities must be in the range [0,255].

A single constant color can also be passed as string or RGBA.

A point array named "PointsRGBA" is automatically created.

def interpolate_data_from( self, source, radius=None, n=None, kernel='shepard', exclude=('Normals',), on='points', null_strategy=1, null_value=0):
3110    def interpolate_data_from(
3111        self,
3112        source,
3113        radius=None,
3114        n=None,
3115        kernel="shepard",
3116        exclude=("Normals",),
3117        on="points",
3118        null_strategy=1,
3119        null_value=0,
3120    ):
3121        """
3122        Interpolate over source to port its data onto the current object using various kernels.
3123
3124        If n (number of closest points to use) is set then radius value is ignored.
3125
3126        Arguments:
3127            kernel : (str)
3128                available kernels are [shepard, gaussian, linear]
3129            null_strategy : (int)
3130                specify a strategy to use when encountering a "null" point
3131                during the interpolation process. Null points occur when the local neighborhood
3132                (of nearby points to interpolate from) is empty.
3133
3134                - Case 0: an output array is created that marks points
3135                  as being valid (=1) or null (invalid =0), and the null_value is set as well
3136                - Case 1: the output data value(s) are set to the provided null_value
3137                - Case 2: simply use the closest point to perform the interpolation.
3138            null_value : (float)
3139                see above.
3140
3141        Examples:
3142            - [interpolateMeshArray.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolateMeshArray.py)
3143
3144                ![](https://vedo.embl.es/images/advanced/interpolateMeshArray.png)
3145        """
3146        if radius is None and not n:
3147            vedo.logger.error("in interpolate_data_from(): please set either radius or n")
3148            raise RuntimeError
3149
3150        if on == "points":
3151            points = source.polydata()
3152        elif on == "cells":
3153            poly2 = vtk.vtkPolyData()
3154            poly2.ShallowCopy(source.polydata())
3155            c2p = vtk.vtkCellDataToPointData()
3156            c2p.SetInputData(poly2)
3157            c2p.Update()
3158            points = c2p.GetOutput()
3159        else:
3160            vedo.logger.error("in interpolate_data_from(), on must be on points or cells")
3161            raise RuntimeError()
3162
3163        locator = vtk.vtkPointLocator()
3164        locator.SetDataSet(points)
3165        locator.BuildLocator()
3166
3167        if kernel.lower() == "shepard":
3168            kern = vtk.vtkShepardKernel()
3169            kern.SetPowerParameter(2)
3170        elif kernel.lower() == "gaussian":
3171            kern = vtk.vtkGaussianKernel()
3172            kern.SetSharpness(2)
3173        elif kernel.lower() == "linear":
3174            kern = vtk.vtkLinearKernel()
3175        else:
3176            vedo.logger.error("available kernels are: [shepard, gaussian, linear]")
3177            raise RuntimeError()
3178
3179        if n:
3180            kern.SetNumberOfPoints(n)
3181            kern.SetKernelFootprintToNClosest()
3182        else:
3183            kern.SetRadius(radius)
3184
3185        interpolator = vtk.vtkPointInterpolator()
3186        interpolator.SetInputData(self.polydata())
3187        interpolator.SetSourceData(points)
3188        interpolator.SetKernel(kern)
3189        interpolator.SetLocator(locator)
3190        interpolator.PassFieldArraysOff()
3191        interpolator.SetNullPointsStrategy(null_strategy)
3192        interpolator.SetNullValue(null_value)
3193        interpolator.SetValidPointsMaskArrayName("ValidPointMask")
3194        for ex in exclude:
3195            interpolator.AddExcludedArray(ex)
3196        interpolator.Update()
3197
3198        if on == "cells":
3199            p2c = vtk.vtkPointDataToCellData()
3200            p2c.SetInputData(interpolator.GetOutput())
3201            p2c.Update()
3202            cpoly = p2c.GetOutput()
3203        else:
3204            cpoly = interpolator.GetOutput()
3205
3206        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3207            self._update(cpoly)
3208        else:
3209            # bring the underlying polydata to where _data is
3210            M = vtk.vtkMatrix4x4()
3211            M.DeepCopy(self.GetMatrix())
3212            M.Invert()
3213            tr = vtk.vtkTransform()
3214            tr.SetMatrix(M)
3215            tf = vtk.vtkTransformPolyDataFilter()
3216            tf.SetTransform(tr)
3217            tf.SetInputData(cpoly)
3218            tf.Update()
3219            self._update(tf.GetOutput())
3220
3221        self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source])
3222        return self

Interpolate over source to port its data onto the current object using various kernels.

If n (number of closest points to use) is set then radius value is ignored.

Arguments:
  • kernel : (str) available kernels are [shepard, gaussian, linear]
  • null_strategy : (int) specify a strategy to use when encountering a "null" point during the interpolation process. Null points occur when the local neighborhood (of nearby points to interpolate from) is empty.

    • Case 0: an output array is created that marks points as being valid (=1) or null (invalid =0), and the null_value is set as well
    • Case 1: the output data value(s) are set to the provided null_value
    • Case 2: simply use the closest point to perform the interpolation.
  • null_value : (float) see above.
Examples:
def add_gaussian_noise(self, sigma=1.0):
3224    def add_gaussian_noise(self, sigma=1.0):
3225        """
3226        Add gaussian noise to point positions.
3227        An extra array is added named "GaussianNoise" with the shifts.
3228
3229        Arguments:
3230            sigma : (float)
3231                nr. of standard deviations, expressed in percent of the diagonal size of mesh.
3232                Can also be a list [sigma_x, sigma_y, sigma_z].
3233
3234        Examples:
3235            ```python
3236            from vedo import Sphere
3237            Sphere().add_gaussian_noise(1.0).point_size(8).show().close()
3238            ```
3239        """
3240        sz = self.diagonal_size()
3241        pts = self.points()
3242        n = len(pts)
3243        ns = (np.random.randn(n, 3) * sigma) * (sz / 100)
3244        vpts = vtk.vtkPoints()
3245        vpts.SetNumberOfPoints(n)
3246        vpts.SetData(utils.numpy2vtk(pts + ns, dtype=np.float32))
3247        self.inputdata().SetPoints(vpts)
3248        self.inputdata().GetPoints().Modified()
3249        self.pointdata["GaussianNoise"] = -ns
3250        self.pipeline = utils.OperationNode(
3251            "gaussian_noise", parents=[self], shape="egg", comment=f"sigma = {sigma}"
3252        )
3253        return self

Add gaussian noise to point positions. An extra array is added named "GaussianNoise" with the shifts.

Arguments:
  • sigma : (float) nr. of standard deviations, expressed in percent of the diagonal size of mesh. Can also be a list [sigma_x, sigma_y, sigma_z].
Examples:
from vedo import Sphere
Sphere().add_gaussian_noise(1.0).point_size(8).show().close()
def closest_point( self, pt, n=1, radius=None, return_point_id=False, return_cell_id=False):
3256    def closest_point(self, pt, n=1, radius=None, return_point_id=False, return_cell_id=False):
3257        """
3258        Find the closest point(s) on a mesh given from the input point `pt`.
3259
3260        Arguments:
3261            n : (int)
3262                if greater than 1, return a list of n ordered closest points
3263            radius : (float)
3264                if given, get all points within that radius. Then n is ignored.
3265            return_point_id : (bool)
3266                return point ID instead of coordinates
3267            return_cell_id : (bool)
3268                return cell ID in which the closest point sits
3269
3270        Examples:
3271            - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py)
3272            - [fitplanes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitplanes.py)
3273            - [quadratic_morphing.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/quadratic_morphing.py)
3274
3275        .. note::
3276            The appropriate tree search locator is built on the fly and cached for speed.
3277
3278            If you want to reset it use `mymesh.point_locator=None`
3279        """
3280        # NB: every time the mesh moves or is warped the locators are set to None
3281        if (n > 1 or radius) or (n == 1 and return_point_id):
3282
3283            poly = None
3284            if not self.point_locator:
3285                poly = self.polydata()
3286                self.point_locator = vtk.vtkStaticPointLocator()
3287                self.point_locator.SetDataSet(poly)
3288                self.point_locator.BuildLocator()
3289
3290            ##########
3291            if radius:
3292                vtklist = vtk.vtkIdList()
3293                self.point_locator.FindPointsWithinRadius(radius, pt, vtklist)
3294            elif n > 1:
3295                vtklist = vtk.vtkIdList()
3296                self.point_locator.FindClosestNPoints(n, pt, vtklist)
3297            else:  # n==1 hence return_point_id==True
3298                ########
3299                return self.point_locator.FindClosestPoint(pt)
3300                ########
3301
3302            if return_point_id:
3303                ########
3304                return utils.vtk2numpy(vtklist)
3305                ########
3306
3307            if not poly:
3308                poly = self.polydata()
3309            trgp = []
3310            for i in range(vtklist.GetNumberOfIds()):
3311                trgp_ = [0, 0, 0]
3312                vi = vtklist.GetId(i)
3313                poly.GetPoints().GetPoint(vi, trgp_)
3314                trgp.append(trgp_)
3315            ########
3316            return np.array(trgp)
3317            ########
3318
3319        else:
3320
3321            if not self.cell_locator:
3322                poly = self.polydata()
3323
3324                # As per Miquel example with limbs the vtkStaticCellLocator doesnt work !!
3325                # https://discourse.vtk.org/t/vtkstaticcelllocator-problem-vtk9-0-3/7854/4
3326                if vedo.vtk_version[0] >= 9 and vedo.vtk_version[0] > 0:
3327                    self.cell_locator = vtk.vtkStaticCellLocator()
3328                else:
3329                    self.cell_locator = vtk.vtkCellLocator()
3330
3331                self.cell_locator.SetDataSet(poly)
3332                self.cell_locator.BuildLocator()
3333
3334            trgp = [0, 0, 0]
3335            cid = vtk.mutable(0)
3336            dist2 = vtk.mutable(0)
3337            subid = vtk.mutable(0)
3338            self.cell_locator.FindClosestPoint(pt, trgp, cid, subid, dist2)
3339
3340            if return_cell_id:
3341                return int(cid)
3342
3343            return np.array(trgp)

Find the closest point(s) on a mesh given from the input point pt.

Arguments:
  • n : (int) if greater than 1, return a list of n ordered closest points
  • radius : (float) if given, get all points within that radius. Then n is ignored.
  • return_point_id : (bool) return point ID instead of coordinates
  • return_cell_id : (bool) return cell ID in which the closest point sits
Examples:

The appropriate tree search locator is built on the fly and cached for speed.

If you want to reset it use mymesh.point_locator=None

def hausdorff_distance(self, points):
3346    def hausdorff_distance(self, points):
3347        """
3348        Compute the Hausdorff distance to the input point set.
3349        Returns a single `float`.
3350
3351        Example:
3352            ```python
3353            from vedo import *
3354            t = np.linspace(0, 2*np.pi, 100)
3355            x = 4/3 * sin(t)**3
3356            y = cos(t) - cos(2*t)/3 - cos(3*t)/6 - cos(4*t)/12
3357            pol1 = Line(np.c_[x,y], closed=True).triangulate()
3358            pol2 = Polygon(nsides=5).pos(2,2)
3359            d12 = pol1.distance_to(pol2)
3360            d21 = pol2.distance_to(pol1)
3361            pol1.lw(0).cmap("viridis")
3362            pol2.lw(0).cmap("viridis")
3363            print("distance d12, d21 :", min(d12), min(d21))
3364            print("hausdorff distance:", pol1.hausdorff_distance(pol2))
3365            print("chamfer distance  :", pol1.chamfer_distance(pol2))
3366            show(pol1, pol2, axes=1)
3367            ```
3368            ![](https://vedo.embl.es/images/feats/heart.png)
3369        """
3370        hp = vtk.vtkHausdorffDistancePointSetFilter()
3371        hp.SetInputData(0, self.polydata())
3372        hp.SetInputData(1, points.polydata())
3373        hp.SetTargetDistanceMethodToPointToCell()
3374        hp.Update()
3375        return hp.GetHausdorffDistance()

Compute the Hausdorff distance to the input point set. Returns a single float.

Example:
from vedo import *
t = np.linspace(0, 2*np.pi, 100)
x = 4/3 * sin(t)**3
y = cos(t) - cos(2*t)/3 - cos(3*t)/6 - cos(4*t)/12
pol1 = Line(np.c_[x,y], closed=True).triangulate()
pol2 = Polygon(nsides=5).pos(2,2)
d12 = pol1.distance_to(pol2)
d21 = pol2.distance_to(pol1)
pol1.lw(0).cmap("viridis")
pol2.lw(0).cmap("viridis")
print("distance d12, d21 :", min(d12), min(d21))
print("hausdorff distance:", pol1.hausdorff_distance(pol2))
print("chamfer distance  :", pol1.chamfer_distance(pol2))
show(pol1, pol2, axes=1)

def chamfer_distance(self, pcloud):
3377    def chamfer_distance(self, pcloud):
3378        """
3379        Compute the Chamfer distance to the input point set.
3380        Returns a single `float`.
3381        """
3382        if not pcloud.point_locator:
3383            pcloud.point_locator = vtk.vtkPointLocator()
3384            pcloud.point_locator.SetDataSet(pcloud.polydata())
3385            pcloud.point_locator.BuildLocator()
3386        if not self.point_locator:
3387            self.point_locator = vtk.vtkPointLocator()
3388            self.point_locator.SetDataSet(self.polydata())
3389            self.point_locator.BuildLocator()
3390
3391        ps1 = self.points()
3392        ps2 = pcloud.points()
3393
3394        ids12 = []
3395        for p in ps1:
3396            pid12 = pcloud.point_locator.FindClosestPoint(p)
3397            ids12.append(pid12)
3398        deltav = ps2[ids12] - ps1
3399        da = np.mean(np.linalg.norm(deltav, axis=1))
3400
3401        ids21 = []
3402        for p in ps2:
3403            pid21 = self.point_locator.FindClosestPoint(p)
3404            ids21.append(pid21)
3405        deltav = ps1[ids21] - ps2
3406        db = np.mean(np.linalg.norm(deltav, axis=1))
3407        return (da + db) / 2

Compute the Chamfer distance to the input point set. Returns a single float.

def remove_outliers(self, radius, neighbors=5):
3409    def remove_outliers(self, radius, neighbors=5):
3410        """
3411        Remove outliers from a cloud of points within the specified `radius` search.
3412
3413        Arguments:
3414            radius : (float)
3415                Specify the local search radius.
3416            neighbors : (int)
3417                Specify the number of neighbors that a point must have,
3418                within the specified radius, for the point to not be considered isolated.
3419
3420        Examples:
3421            - [clustering.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/clustering.py)
3422
3423                ![](https://vedo.embl.es/images/basic/clustering.png)
3424        """
3425        removal = vtk.vtkRadiusOutlierRemoval()
3426        removal.SetInputData(self.polydata())
3427        removal.SetRadius(radius)
3428        removal.SetNumberOfNeighbors(neighbors)
3429        removal.GenerateOutliersOff()
3430        removal.Update()
3431        inputobj = removal.GetOutput()
3432        if inputobj.GetNumberOfCells() == 0:
3433            carr = vtk.vtkCellArray()
3434            for i in range(inputobj.GetNumberOfPoints()):
3435                carr.InsertNextCell(1)
3436                carr.InsertCellPoint(i)
3437            inputobj.SetVerts(carr)
3438        self._update(inputobj)
3439        self.mapper().ScalarVisibilityOff()
3440        self.pipeline = utils.OperationNode("remove_outliers", parents=[self])
3441        return self

Remove outliers from a cloud of points within the specified radius search.

Arguments:
  • radius : (float) Specify the local search radius.
  • neighbors : (int) Specify the number of neighbors that a point must have, within the specified radius, for the point to not be considered isolated.
Examples:
def smooth_mls_1d(self, f=0.2, radius=None):
3443    def smooth_mls_1d(self, f=0.2, radius=None):
3444        """
3445        Smooth mesh or points with a `Moving Least Squares` variant.
3446        The point data array "Variances" will contain the residue calculated for each point.
3447        Input mesh's polydata is modified.
3448
3449        Arguments:
3450            f : (float)
3451                smoothing factor - typical range is [0,2].
3452            radius : (float)
3453                radius search in absolute units. If set then `f` is ignored.
3454
3455        Examples:
3456            - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py)
3457            - [skeletonize.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/skeletonize.py)
3458
3459            ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png)
3460        """
3461        coords = self.points()
3462        ncoords = len(coords)
3463
3464        if radius:
3465            Ncp = 0
3466        else:
3467            Ncp = int(ncoords * f / 10)
3468            if Ncp < 5:
3469                vedo.logger.warning(f"Please choose a fraction higher than {f}")
3470                Ncp = 5
3471
3472        variances, newline = [], []
3473        for p in coords:
3474            points = self.closest_point(p, n=Ncp, radius=radius)
3475            if len(points) < 4:
3476                continue
3477
3478            points = np.array(points)
3479            pointsmean = points.mean(axis=0)  # plane center
3480            _, dd, vv = np.linalg.svd(points - pointsmean)
3481            newp = np.dot(p - pointsmean, vv[0]) * vv[0] + pointsmean
3482            variances.append(dd[1] + dd[2])
3483            newline.append(newp)
3484
3485        vdata = utils.numpy2vtk(np.array(variances))
3486        vdata.SetName("Variances")
3487        self.inputdata().GetPointData().AddArray(vdata)
3488        self.inputdata().GetPointData().Modified()
3489        self.points(newline)
3490        self.pipeline = utils.OperationNode("smooth_mls_1d", parents=[self])
3491        return self

Smooth mesh or points with a Moving Least Squares variant. The point data array "Variances" will contain the residue calculated for each point. Input mesh's polydata is modified.

Arguments:
  • f : (float) smoothing factor - typical range is [0,2].
  • radius : (float) radius search in absolute units. If set then f is ignored.
Examples:

def smooth_mls_2d(self, f=0.2, radius=None):
3493    def smooth_mls_2d(self, f=0.2, radius=None):
3494        """
3495        Smooth mesh or points with a `Moving Least Squares` algorithm variant.
3496        The list `mesh.info['variances']` contains the residue calculated for each point.
3497        When a radius is specified points that are isolated will not be moved and will get
3498        a False entry in array `mesh.info['is_valid']`.
3499
3500        Arguments:
3501            f : (float)
3502                smoothing factor - typical range is [0,2].
3503            radius : (float)
3504                radius search in absolute units. If set then `f` is ignored.
3505
3506        Examples:
3507            - [moving_least_squares2D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares2D.py)
3508            - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py)
3509
3510                ![](https://vedo.embl.es/images/advanced/recosurface.png)
3511        """
3512        coords = self.points()
3513        ncoords = len(coords)
3514
3515        if radius:
3516            Ncp = 1
3517        else:
3518            Ncp = int(ncoords * f / 100)
3519            if Ncp < 4:
3520                vedo.logger.error(f"MLS2D: Please choose a fraction higher than {f}")
3521                Ncp = 4
3522
3523        variances, newpts, valid = [], [], []
3524        pb = None
3525        if ncoords > 10000:
3526            pb = utils.ProgressBar(0, ncoords)
3527        for p in coords:
3528            if pb:
3529                pb.print("smoothMLS2D working ...")
3530            pts = self.closest_point(p, n=Ncp, radius=radius)
3531            if len(pts) > 3:
3532                ptsmean = pts.mean(axis=0)  # plane center
3533                _, dd, vv = np.linalg.svd(pts - ptsmean)
3534                cv = np.cross(vv[0], vv[1])
3535                t = (np.dot(cv, ptsmean) - np.dot(cv, p)) / np.dot(cv, cv)
3536                newp = p + cv * t
3537                newpts.append(newp)
3538                variances.append(dd[2])
3539                if radius:
3540                    valid.append(True)
3541            else:
3542                newpts.append(p)
3543                variances.append(0)
3544                if radius:
3545                    valid.append(False)
3546
3547        self.info["variances"] = np.array(variances)
3548        self.info["is_valid"] = np.array(valid)
3549        self.points(newpts)
3550
3551        self.pipeline = utils.OperationNode("smooth_mls_2d", parents=[self])
3552        return self

Smooth mesh or points with a Moving Least Squares algorithm variant. The list mesh.info['variances'] contains the residue calculated for each point. When a radius is specified points that are isolated will not be moved and will get a False entry in array mesh.info['is_valid'].

Arguments:
  • f : (float) smoothing factor - typical range is [0,2].
  • radius : (float) radius search in absolute units. If set then f is ignored.
Examples:
def smooth_lloyd_2d(self, iterations=2, bounds=None, options='Qbb Qc Qx'):
3554    def smooth_lloyd_2d(self, iterations=2, bounds=None, options="Qbb Qc Qx"):
3555        """Lloyd relaxation of a 2D pointcloud."""
3556        # Credits: https://hatarilabs.com/ih-en/
3557        # tutorial-to-create-a-geospatial-voronoi-sh-mesh-with-python-scipy-and-geopandas
3558        from scipy.spatial import Voronoi as scipy_voronoi
3559
3560        def _constrain_points(points):
3561            # Update any points that have drifted beyond the boundaries of this space
3562            if bounds is not None:
3563                for point in points:
3564                    if point[0] < bounds[0]: point[0] = bounds[0]
3565                    if point[0] > bounds[1]: point[0] = bounds[1]
3566                    if point[1] < bounds[2]: point[1] = bounds[2]
3567                    if point[1] > bounds[3]: point[1] = bounds[3]
3568            return points
3569
3570        def _find_centroid(vertices):
3571            # The equation for the method used here to find the centroid of a
3572            # 2D polygon is given here: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
3573            area = 0
3574            centroid_x = 0
3575            centroid_y = 0
3576            for i in range(len(vertices) - 1):
3577                step = (vertices[i, 0] * vertices[i + 1, 1]) - (vertices[i + 1, 0] * vertices[i, 1])
3578                centroid_x += (vertices[i, 0] + vertices[i + 1, 0]) * step
3579                centroid_y += (vertices[i, 1] + vertices[i + 1, 1]) * step
3580                area += step
3581            if area:
3582                centroid_x = (1.0 / (3.0 * area)) * centroid_x
3583                centroid_y = (1.0 / (3.0 * area)) * centroid_y
3584            # prevent centroids from escaping bounding box
3585            return _constrain_points([[centroid_x, centroid_y]])[0]
3586
3587        def _relax(voron):
3588            # Moves each point to the centroid of its cell in the voronoi
3589            # map to "relax" the points (i.e. jitter the points so as
3590            # to spread them out within the space).
3591            centroids = []
3592            for idx in voron.point_region:
3593                # the region is a series of indices into voronoi.vertices
3594                # remove point at infinity, designated by index -1
3595                region = [i for i in voron.regions[idx] if i != -1]
3596                # enclose the polygon
3597                region = region + [region[0]]
3598                verts = voron.vertices[region]
3599                # find the centroid of those vertices
3600                centroids.append(_find_centroid(verts))
3601            return _constrain_points(centroids)
3602
3603        if bounds is None:
3604            bounds = self.bounds()
3605
3606        pts = self.points()[:, (0, 1)]
3607        for i in range(iterations):
3608            vor = scipy_voronoi(pts, qhull_options=options)
3609            _constrain_points(vor.vertices)
3610            pts = _relax(vor)
3611        # m = vedo.Mesh([pts, self.faces()]) # not yet working properly
3612        out = Points(pts, c="k")
3613        out.pipeline = utils.OperationNode("smooth_lloyd", parents=[self])
3614        return out

Lloyd relaxation of a 2D pointcloud.

def project_on_plane(self, plane='z', point=None, direction=None):
3616    def project_on_plane(self, plane="z", point=None, direction=None):
3617        """
3618        Project the mesh on one of the Cartesian planes.
3619
3620        Arguments:
3621            plane : (str, Plane)
3622                if plane is `str`, plane can be one of ['x', 'y', 'z'],
3623                represents x-plane, y-plane and z-plane, respectively.
3624                Otherwise, plane should be an instance of `vedo.shapes.Plane`.
3625            point : (float, array)
3626                if plane is `str`, point should be a float represents the intercept.
3627                Otherwise, point is the camera point of perspective projection
3628            direction : (array)
3629                direction of oblique projection
3630
3631        Note:
3632            Parameters `point` and `direction` are only used if the given plane
3633            is an instance of `vedo.shapes.Plane`. And one of these two params
3634            should be left as `None` to specify the projection type.
3635
3636        Example:
3637            ```python
3638            s.project_on_plane(plane='z') # project to z-plane
3639            plane = Plane(pos=(4, 8, -4), normal=(-1, 0, 1), s=(5,5))
3640            s.project_on_plane(plane=plane)                       # orthogonal projection
3641            s.project_on_plane(plane=plane, point=(6, 6, 6))      # perspective projection
3642            s.project_on_plane(plane=plane, direction=(1, 2, -1)) # oblique projection
3643            ```
3644
3645        Examples:
3646            - [silhouette2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette2.py)
3647
3648                ![](https://vedo.embl.es/images/basic/silhouette2.png)
3649        """
3650        coords = self.points()
3651
3652        if plane == "x":
3653            coords[:, 0] = self.GetOrigin()[0]
3654            intercept = self.xbounds()[0] if point is None else point
3655            self.x(intercept)
3656        elif plane == "y":
3657            coords[:, 1] = self.GetOrigin()[1]
3658            intercept = self.ybounds()[0] if point is None else point
3659            self.y(intercept)
3660        elif plane == "z":
3661            coords[:, 2] = self.GetOrigin()[2]
3662            intercept = self.zbounds()[0] if point is None else point
3663            self.z(intercept)
3664
3665        elif isinstance(plane, vedo.shapes.Plane):
3666            normal = plane.normal / np.linalg.norm(plane.normal)
3667            pl = np.hstack((normal, -np.dot(plane.pos(), normal))).reshape(4, 1)
3668            if direction is None and point is None:
3669                # orthogonal projection
3670                pt = np.hstack((normal, [0])).reshape(4, 1)
3671                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T # python3 only
3672                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3673
3674            elif direction is None:
3675                # perspective projection
3676                pt = np.hstack((np.array(point), [1])).reshape(4, 1)
3677                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T
3678                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3679
3680            elif point is None:
3681                # oblique projection
3682                pt = np.hstack((np.array(direction), [0])).reshape(4, 1)
3683                # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T
3684                proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T)
3685
3686            coords = np.concatenate([coords, np.ones((coords.shape[:-1] + (1,)))], axis=-1)
3687            # coords = coords @ proj_mat.T
3688            coords = np.matmul(coords, proj_mat.T)
3689            coords = coords[:, :3] / coords[:, 3:]
3690
3691        else:
3692            vedo.logger.error(f"unknown plane {plane}")
3693            raise RuntimeError()
3694
3695        self.alpha(0.1)
3696        self.points(coords)
3697        return self

Project the mesh on one of the Cartesian planes.

Arguments:
  • plane : (str, Plane) if plane is str, plane can be one of ['x', 'y', 'z'], represents x-plane, y-plane and z-plane, respectively. Otherwise, plane should be an instance of vedo.shapes.Plane.
  • point : (float, array) if plane is str, point should be a float represents the intercept. Otherwise, point is the camera point of perspective projection
  • direction : (array) direction of oblique projection
Note:

Parameters point and direction are only used if the given plane is an instance of vedo.shapes.Plane. And one of these two params should be left as None to specify the projection type.

Example:
s.project_on_plane(plane='z') # project to z-plane
plane = Plane(pos=(4, 8, -4), normal=(-1, 0, 1), s=(5,5))
s.project_on_plane(plane=plane)                       # orthogonal projection
s.project_on_plane(plane=plane, point=(6, 6, 6))      # perspective projection
s.project_on_plane(plane=plane, direction=(1, 2, -1)) # oblique projection
Examples:
def warp(self, source, target, sigma=1.0, mode='3d'):
3699    def warp(self, source, target, sigma=1.0, mode="3d"):
3700        """
3701        `Thin Plate Spline` transformations describe a nonlinear warp transform defined by a set
3702        of source and target landmarks. Any point on the mesh close to a source landmark will
3703        be moved to a place close to the corresponding target landmark.
3704        The points in between are interpolated smoothly using
3705        Bookstein's Thin Plate Spline algorithm.
3706
3707        Transformation object can be accessed with `mesh.transform`.
3708
3709        Arguments:
3710            sigma : (float)
3711                specify the 'stiffness' of the spline.
3712            mode : (str)
3713                set the basis function to either abs(R) (for 3d) or R2LogR (for 2d meshes)
3714
3715        Examples:
3716            - [interpolate_field.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_field.py)
3717            - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py)
3718            - [warp2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp2.py)
3719
3720                ![](https://vedo.embl.es/images/advanced/warp2.png)
3721        """
3722        parents = [self]
3723        if isinstance(source, Points):
3724            parents.append(source)
3725            source = source.points()
3726        else:
3727            source = utils.make3d(source)
3728
3729        if isinstance(target, Points):
3730            parents.append(target)
3731            target = target.points()
3732        else:
3733            target = utils.make3d(target)
3734
3735        ns = len(source)
3736        ptsou = vtk.vtkPoints()
3737        ptsou.SetNumberOfPoints(ns)
3738        for i in range(ns):
3739            ptsou.SetPoint(i, source[i])
3740
3741        nt = len(target)
3742        if ns != nt:
3743            vedo.logger.error(f"#source {ns} != {nt} #target points")
3744            raise RuntimeError()
3745
3746        pttar = vtk.vtkPoints()
3747        pttar.SetNumberOfPoints(nt)
3748        for i in range(ns):
3749            pttar.SetPoint(i, target[i])
3750
3751        T = vtk.vtkThinPlateSplineTransform()
3752        if mode.lower() == "3d":
3753            T.SetBasisToR()
3754        elif mode.lower() == "2d":
3755            T.SetBasisToR2LogR()
3756        else:
3757            vedo.logger.error(f"unknown mode {mode}")
3758            raise RuntimeError()
3759
3760        T.SetSigma(sigma)
3761        T.SetSourceLandmarks(ptsou)
3762        T.SetTargetLandmarks(pttar)
3763        self.transform = T
3764        self.apply_transform(T, reset=True)
3765
3766        self.pipeline = utils.OperationNode("warp", parents=parents)
3767        return self

Thin Plate Spline transformations describe a nonlinear warp transform defined by a set of source and target landmarks. Any point on the mesh close to a source landmark will be moved to a place close to the corresponding target landmark. The points in between are interpolated smoothly using Bookstein's Thin Plate Spline algorithm.

Transformation object can be accessed with mesh.transform.

Arguments:
  • sigma : (float) specify the 'stiffness' of the spline.
  • mode : (str) set the basis function to either abs(R) (for 3d) or R2LogR (for 2d meshes)
Examples:
def cut_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0), invert=False):
3769    def cut_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0), invert=False):
3770        """
3771        Cut the mesh with the plane defined by a point and a normal.
3772
3773        Arguments:
3774            origin : (array)
3775                the cutting plane goes through this point
3776            normal : (array)
3777                normal of the cutting plane
3778
3779        Example:
3780            ```python
3781            from vedo import Cube
3782            cube = Cube().cut_with_plane(normal=(1,1,1))
3783            cube.back_color('pink').show()
3784            ```
3785            ![](https://vedo.embl.es/images/feats/cut_with_plane_cube.png)
3786
3787        Examples:
3788            - [trail.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/trail.py)
3789
3790                ![](https://vedo.embl.es/images/simulations/trail.gif)
3791
3792        Check out also:
3793            `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`.
3794        """
3795        s = str(normal)
3796        if "x" in s:
3797            normal = (1, 0, 0)
3798            if "-" in s:
3799                normal = -np.array(normal)
3800        elif "y" in s:
3801            normal = (0, 1, 0)
3802            if "-" in s:
3803                normal = -np.array(normal)
3804        elif "z" in s:
3805            normal = (0, 0, 1)
3806            if "-" in s:
3807                normal = -np.array(normal)
3808        plane = vtk.vtkPlane()
3809        plane.SetOrigin(origin)
3810        plane.SetNormal(normal)
3811
3812        clipper = vtk.vtkClipPolyData()
3813        clipper.SetInputData(self.polydata(True))  # must be True
3814        clipper.SetClipFunction(plane)
3815        clipper.GenerateClippedOutputOff()
3816        clipper.GenerateClipScalarsOff()
3817        clipper.SetInsideOut(invert)
3818        clipper.SetValue(0)
3819        clipper.Update()
3820
3821        cpoly = clipper.GetOutput()
3822
3823        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3824            self._update(cpoly)
3825        else:
3826            # bring the underlying polydata to where _data is
3827            M = vtk.vtkMatrix4x4()
3828            M.DeepCopy(self.GetMatrix())
3829            M.Invert()
3830            tr = vtk.vtkTransform()
3831            tr.SetMatrix(M)
3832            tf = vtk.vtkTransformPolyDataFilter()
3833            tf.SetTransform(tr)
3834            tf.SetInputData(cpoly)
3835            tf.Update()
3836            self._update(tf.GetOutput())
3837
3838        self.pipeline = utils.OperationNode("cut_with_plane", parents=[self])
3839        return self

Cut the mesh with the plane defined by a point and a normal.

Arguments:
  • origin : (array) the cutting plane goes through this point
  • normal : (array) normal of the cutting plane
Example:
from vedo import Cube
cube = Cube().cut_with_plane(normal=(1,1,1))
cube.back_color('pink').show()

Examples:
Check out also:

cut_with_box(), cut_with_cylinder(), cut_with_sphere().

def cut_with_planes(self, origins, normals, invert=False):
3841    def cut_with_planes(self, origins, normals, invert=False):
3842        """
3843        Cut the mesh with a convex set of planes defined by points and normals.
3844
3845        Arguments:
3846            origins : (array)
3847                each cutting plane goes through this point
3848            normals : (array)
3849                normal of each of the cutting planes
3850
3851        Check out also:
3852            `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`
3853        """
3854
3855        vpoints = vtk.vtkPoints()
3856        for p in utils.make3d(origins):
3857            vpoints.InsertNextPoint(p)
3858        normals = utils.make3d(normals)
3859
3860        planes = vtk.vtkPlanes()
3861        planes.SetPoints(vpoints)
3862        planes.SetNormals(utils.numpy2vtk(normals, dtype=float))
3863
3864        clipper = vtk.vtkClipPolyData()
3865        clipper.SetInputData(self.polydata(True))  # must be True
3866        clipper.SetInsideOut(invert)
3867        clipper.SetClipFunction(planes)
3868        clipper.GenerateClippedOutputOff()
3869        clipper.GenerateClipScalarsOff()
3870        clipper.SetValue(0)
3871        clipper.Update()
3872
3873        cpoly = clipper.GetOutput()
3874
3875        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3876            self._update(cpoly)
3877        else:
3878            # bring the underlying polydata to where _data is
3879            M = vtk.vtkMatrix4x4()
3880            M.DeepCopy(self.GetMatrix())
3881            M.Invert()
3882            tr = vtk.vtkTransform()
3883            tr.SetMatrix(M)
3884            tf = vtk.vtkTransformPolyDataFilter()
3885            tf.SetTransform(tr)
3886            tf.SetInputData(cpoly)
3887            tf.Update()
3888            self._update(tf.GetOutput())
3889
3890        self.pipeline = utils.OperationNode("cut_with_planes", parents=[self])
3891        return self

Cut the mesh with a convex set of planes defined by points and normals.

Arguments:
  • origins : (array) each cutting plane goes through this point
  • normals : (array) normal of each of the cutting planes
Check out also:

cut_with_box(), cut_with_cylinder(), cut_with_sphere()

def cut_with_box(self, bounds, invert=False):
3893    def cut_with_box(self, bounds, invert=False):
3894        """
3895        Cut the current mesh with a box or a set of boxes.
3896        This is much faster than `cut_with_mesh()`.
3897
3898        Input `bounds` can be either:
3899        - a Mesh or Points object
3900        - a list of 6 number representing a bounding box `[xmin,xmax, ymin,ymax, zmin,zmax]`
3901        - a list of bounding boxes like the above: `[[xmin1,...], [xmin2,...], ...]`
3902
3903        Example:
3904            ```python
3905            from vedo import Sphere, Cube, show
3906            mesh = Sphere(r=1, res=50)
3907            box  = Cube(side=1.5).wireframe()
3908            mesh.cut_with_box(box)
3909            show(mesh, box, axes=1)
3910            ```
3911            ![](https://vedo.embl.es/images/feats/cut_with_box_cube.png)
3912
3913        Check out also:
3914            `cut_with_line()`, `cut_with_plane()`, `cut_with_cylinder()`
3915        """
3916        if isinstance(bounds, Points):
3917            bounds = bounds.bounds()
3918
3919        box = vtk.vtkBox()
3920        if utils.is_sequence(bounds[0]):
3921            for bs in bounds:
3922                box.AddBounds(bs)
3923        else:
3924            box.SetBounds(bounds)
3925
3926        clipper = vtk.vtkClipPolyData()
3927        clipper.SetInputData(self.polydata(True))  # must be True
3928        clipper.SetClipFunction(box)
3929        clipper.SetInsideOut(not invert)
3930        clipper.GenerateClippedOutputOff()
3931        clipper.GenerateClipScalarsOff()
3932        clipper.SetValue(0)
3933        clipper.Update()
3934        cpoly = clipper.GetOutput()
3935
3936        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3937            self._update(cpoly)
3938        else:
3939            # bring the underlying polydata to where _data is
3940            M = vtk.vtkMatrix4x4()
3941            M.DeepCopy(self.GetMatrix())
3942            M.Invert()
3943            tr = vtk.vtkTransform()
3944            tr.SetMatrix(M)
3945            tf = vtk.vtkTransformPolyDataFilter()
3946            tf.SetTransform(tr)
3947            tf.SetInputData(cpoly)
3948            tf.Update()
3949            self._update(tf.GetOutput())
3950
3951        self.pipeline = utils.OperationNode("cut_with_box", parents=[self])
3952        return self

Cut the current mesh with a box or a set of boxes. This is much faster than cut_with_mesh().

Input bounds can be either:

  • a Mesh or Points object
  • a list of 6 number representing a bounding box [xmin,xmax, ymin,ymax, zmin,zmax]
  • a list of bounding boxes like the above: [[xmin1,...], [xmin2,...], ...]
Example:
from vedo import Sphere, Cube, show
mesh = Sphere(r=1, res=50)
box  = Cube(side=1.5).wireframe()
mesh.cut_with_box(box)
show(mesh, box, axes=1)

Check out also:

cut_with_line(), cut_with_plane(), cut_with_cylinder()

def cut_with_line(self, points, invert=False, closed=True):
3954    def cut_with_line(self, points, invert=False, closed=True):
3955        """
3956        Cut the current mesh with a line vertically in the z-axis direction like a cookie cutter.
3957        The polyline is defined by a set of points (z-coordinates are ignored).
3958        This is much faster than `cut_with_mesh()`.
3959
3960        Check out also:
3961            `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()`
3962        """
3963        pplane = vtk.vtkPolyPlane()
3964        if isinstance(points, Points):
3965            points = points.points().tolist()
3966
3967        if closed:
3968            if isinstance(points, np.ndarray):
3969                points = points.tolist()
3970            points.append(points[0])
3971
3972        vpoints = vtk.vtkPoints()
3973        for p in points:
3974            if len(p) == 2:
3975                p = [p[0], p[1], 0.0]
3976            vpoints.InsertNextPoint(p)
3977
3978        n = len(points)
3979        polyline = vtk.vtkPolyLine()
3980        polyline.Initialize(n, vpoints)
3981        polyline.GetPointIds().SetNumberOfIds(n)
3982        for i in range(n):
3983            polyline.GetPointIds().SetId(i, i)
3984        pplane.SetPolyLine(polyline)
3985
3986        clipper = vtk.vtkClipPolyData()
3987        clipper.SetInputData(self.polydata(True))  # must be True
3988        clipper.SetClipFunction(pplane)
3989        clipper.SetInsideOut(invert)
3990        clipper.GenerateClippedOutputOff()
3991        clipper.GenerateClipScalarsOff()
3992        clipper.SetValue(0)
3993        clipper.Update()
3994        cpoly = clipper.GetOutput()
3995
3996        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
3997            self._update(cpoly)
3998        else:
3999            # bring the underlying polydata to where _data is
4000            M = vtk.vtkMatrix4x4()
4001            M.DeepCopy(self.GetMatrix())
4002            M.Invert()
4003            tr = vtk.vtkTransform()
4004            tr.SetMatrix(M)
4005            tf = vtk.vtkTransformPolyDataFilter()
4006            tf.SetTransform(tr)
4007            tf.SetInputData(cpoly)
4008            tf.Update()
4009            self._update(tf.GetOutput())
4010
4011        self.pipeline = utils.OperationNode("cut_with_line", parents=[self])
4012        return self

Cut the current mesh with a line vertically in the z-axis direction like a cookie cutter. The polyline is defined by a set of points (z-coordinates are ignored). This is much faster than cut_with_mesh().

Check out also:

cut_with_box(), cut_with_plane(), cut_with_sphere()

def cut_with_cookiecutter(self, lines):
4014    def cut_with_cookiecutter(self, lines):
4015        """
4016        Cut the current mesh with a single line or a set of lines.
4017
4018        Input `lines` can be either:
4019        - a `Mesh` or `Points` object
4020        - a list of 3D points: `[(x1,y1,z1), (x2,y2,z2), ...]`
4021        - a list of 2D points: `[(x1,y1), (x2,y2), ...]`
4022
4023        Example:
4024            ```python
4025            from vedo import *
4026            grid = Mesh(dataurl + "dolfin_fine.vtk")
4027            grid.compute_quality().cmap("Greens")
4028            pols = merge(
4029                Polygon(nsides=10, r=0.3).pos(0.7, 0.3),
4030                Polygon(nsides=10, r=0.2).pos(0.3, 0.7),
4031            )
4032            lines = pols.boundaries()
4033            grid.cut_with_cookiecutter(lines)
4034            show(grid, lines, axes=8, bg='blackboard').close()
4035            ```
4036            ![](https://vedo.embl.es/images/feats/cookiecutter.png)
4037
4038        Check out also:
4039            `cut_with_line()` and `cut_with_point_loop()`
4040
4041        Note:
4042            In case of a warning message like:
4043                "Mesh and trim loop point data attributes are different"
4044            consider interpolating the mesh point data to the loop points,
4045            Eg. (in the above example):
4046            ```python
4047            lines = pols.boundaries().interpolate_data_from(grid, n=2)
4048            ```
4049
4050        Note:
4051            trying to invert the selection by reversing the loop order
4052            will have no effect in this method, hence it does not have
4053            the `invert` option.
4054        """
4055        if utils.is_sequence(lines):
4056            lines = utils.make3d(lines)
4057            iline = list(range(len(lines))) + [0]
4058            poly = utils.buildPolyData(lines, lines=[iline])
4059        else:
4060            poly = lines.polydata()
4061
4062        # if invert: # not working
4063        #     rev = vtk.vtkReverseSense()
4064        #     rev.ReverseCellsOn()
4065        #     rev.SetInputData(poly)
4066        #     rev.Update()
4067        #     poly = rev.GetOutput()
4068
4069        # Build loops from the polyline
4070        build_loops = vtk.vtkContourLoopExtraction()
4071        build_loops.SetInputData(poly)
4072        build_loops.Update()
4073        boundaryPoly = build_loops.GetOutput()
4074
4075        ccut = vtk.vtkCookieCutter()
4076        ccut.SetInputData(self.polydata())
4077        ccut.SetLoopsData(boundaryPoly)
4078        ccut.SetPointInterpolationToMeshEdges()
4079        # ccut.SetPointInterpolationToLoopEdges()
4080        ccut.PassCellDataOn()
4081        # ccut.PassPointDataOn()
4082        ccut.Update()
4083        cpoly = ccut.GetOutput()
4084
4085        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4086            self._update(cpoly)
4087        else:
4088            # bring the underlying polydata to where _data is
4089            M = vtk.vtkMatrix4x4()
4090            M.DeepCopy(self.GetMatrix())
4091            M.Invert()
4092            tr = vtk.vtkTransform()
4093            tr.SetMatrix(M)
4094            tf = vtk.vtkTransformPolyDataFilter()
4095            tf.SetTransform(tr)
4096            tf.SetInputData(cpoly)
4097            tf.Update()
4098            self._update(tf.GetOutput())
4099
4100        self.pipeline = utils.OperationNode("cut_with_cookiecutter", parents=[self])
4101        return self

Cut the current mesh with a single line or a set of lines.

Input lines can be either:

  • a Mesh or Points object
  • a list of 3D points: [(x1,y1,z1), (x2,y2,z2), ...]
  • a list of 2D points: [(x1,y1), (x2,y2), ...]
Example:
from vedo import *
grid = Mesh(dataurl + "dolfin_fine.vtk")
grid.compute_quality().cmap("Greens")
pols = merge(
    Polygon(nsides=10, r=0.3).pos(0.7, 0.3),
    Polygon(nsides=10, r=0.2).pos(0.3, 0.7),
)
lines = pols.boundaries()
grid.cut_with_cookiecutter(lines)
show(grid, lines, axes=8, bg='blackboard').close()

Check out also:

cut_with_line() and cut_with_point_loop()

Note:

In case of a warning message like: "Mesh and trim loop point data attributes are different" consider interpolating the mesh point data to the loop points, Eg. (in the above example):

lines = pols.boundaries().interpolate_data_from(grid, n=2)
Note:

trying to invert the selection by reversing the loop order will have no effect in this method, hence it does not have the invert option.

def cut_with_cylinder(self, center=(0, 0, 0), axis=(0, 0, 1), r=1, invert=False):
4103    def cut_with_cylinder(self, center=(0, 0, 0), axis=(0, 0, 1), r=1, invert=False):
4104        """
4105        Cut the current mesh with an infinite cylinder.
4106        This is much faster than `cut_with_mesh()`.
4107
4108        Arguments:
4109            center : (array)
4110                the center of the cylinder
4111            normal : (array)
4112                direction of the cylinder axis
4113            r : (float)
4114                radius of the cylinder
4115
4116        Example:
4117            ```python
4118            from vedo import Disc, show
4119            disc = Disc(r1=1, r2=1.2)
4120            mesh = disc.extrude(3, res=50).linewidth(1)
4121            mesh.cut_with_cylinder([0,0,2], r=0.4, axis='y', invert=True)
4122            show(mesh, axes=1)
4123            ```
4124            ![](https://vedo.embl.es/images/feats/cut_with_cylinder.png)
4125
4126        Examples:
4127            - [optics_main1.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/optics_main1.py)
4128
4129        Check out also:
4130            `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()`
4131        """
4132        s = str(axis)
4133        if "x" in s:
4134            axis = (1, 0, 0)
4135        elif "y" in s:
4136            axis = (0, 1, 0)
4137        elif "z" in s:
4138            axis = (0, 0, 1)
4139        cyl = vtk.vtkCylinder()
4140        cyl.SetCenter(center)
4141        cyl.SetAxis(axis[0], axis[1], axis[2])
4142        cyl.SetRadius(r)
4143
4144        clipper = vtk.vtkClipPolyData()
4145        clipper.SetInputData(self.polydata(True))  # must be True
4146        clipper.SetClipFunction(cyl)
4147        clipper.SetInsideOut(not invert)
4148        clipper.GenerateClippedOutputOff()
4149        clipper.GenerateClipScalarsOff()
4150        clipper.SetValue(0)
4151        clipper.Update()
4152        cpoly = clipper.GetOutput()
4153
4154        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4155            self._update(cpoly)
4156        else:
4157            # bring the underlying polydata to where _data is
4158            M = vtk.vtkMatrix4x4()
4159            M.DeepCopy(self.GetMatrix())
4160            M.Invert()
4161            tr = vtk.vtkTransform()
4162            tr.SetMatrix(M)
4163            tf = vtk.vtkTransformPolyDataFilter()
4164            tf.SetTransform(tr)
4165            tf.SetInputData(cpoly)
4166            tf.Update()
4167            self._update(tf.GetOutput())
4168
4169        self.pipeline = utils.OperationNode("cut_with_cylinder", parents=[self])
4170        return self

Cut the current mesh with an infinite cylinder. This is much faster than cut_with_mesh().

Arguments:
  • center : (array) the center of the cylinder
  • normal : (array) direction of the cylinder axis
  • r : (float) radius of the cylinder
Example:
from vedo import Disc, show
disc = Disc(r1=1, r2=1.2)
mesh = disc.extrude(3, res=50).linewidth(1)
mesh.cut_with_cylinder([0,0,2], r=0.4, axis='y', invert=True)
show(mesh, axes=1)

Examples:
Check out also:

cut_with_box(), cut_with_plane(), cut_with_sphere()

def cut_with_sphere(self, center=(0, 0, 0), r=1.0, invert=False):
4172    def cut_with_sphere(self, center=(0, 0, 0), r=1.0, invert=False):
4173        """
4174        Cut the current mesh with an sphere.
4175        This is much faster than `cut_with_mesh()`.
4176
4177        Arguments:
4178            center : (array)
4179                the center of the sphere
4180            r : (float)
4181                radius of the sphere
4182
4183        Example:
4184            ```python
4185            from vedo import Disc, show
4186            disc = Disc(r1=1, r2=1.2)
4187            mesh = disc.extrude(3, res=50).linewidth(1)
4188            mesh.cut_with_sphere([1,-0.7,2], r=1.5, invert=True)
4189            show(mesh, axes=1)
4190            ```
4191            ![](https://vedo.embl.es/images/feats/cut_with_sphere.png)
4192
4193        Check out also:
4194            `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()`
4195        """
4196        sph = vtk.vtkSphere()
4197        sph.SetCenter(center)
4198        sph.SetRadius(r)
4199
4200        clipper = vtk.vtkClipPolyData()
4201        clipper.SetInputData(self.polydata(True))  # must be True
4202        clipper.SetClipFunction(sph)
4203        clipper.SetInsideOut(not invert)
4204        clipper.GenerateClippedOutputOff()
4205        clipper.GenerateClipScalarsOff()
4206        clipper.SetValue(0)
4207        clipper.Update()
4208        cpoly = clipper.GetOutput()
4209
4210        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4211            self._update(cpoly)
4212        else:
4213            # bring the underlying polydata to where _data is
4214            M = vtk.vtkMatrix4x4()
4215            M.DeepCopy(self.GetMatrix())
4216            M.Invert()
4217            tr = vtk.vtkTransform()
4218            tr.SetMatrix(M)
4219            tf = vtk.vtkTransformPolyDataFilter()
4220            tf.SetTransform(tr)
4221            tf.SetInputData(cpoly)
4222            tf.Update()
4223            self._update(tf.GetOutput())
4224
4225        self.pipeline = utils.OperationNode("cut_with_sphere", parents=[self])
4226        return self

Cut the current mesh with an sphere. This is much faster than cut_with_mesh().

Arguments:
  • center : (array) the center of the sphere
  • r : (float) radius of the sphere
Example:
from vedo import Disc, show
disc = Disc(r1=1, r2=1.2)
mesh = disc.extrude(3, res=50).linewidth(1)
mesh.cut_with_sphere([1,-0.7,2], r=1.5, invert=True)
show(mesh, axes=1)

Check out also:

cut_with_box(), cut_with_plane(), cut_with_cylinder()

def cut_with_mesh(self, mesh, invert=False, keep=False):
4228    def cut_with_mesh(self, mesh, invert=False, keep=False):
4229        """
4230        Cut an `Mesh` mesh with another `Mesh`.
4231
4232        Use `invert` to invert the selection.
4233
4234        Use `keep` to keep the cutoff part, in this case an `Assembly` is returned:
4235        the "cut" object and the "discarded" part of the original object.
4236        You can access both via `assembly.unpack()` method.
4237
4238        Example:
4239        ```python
4240        from vedo import *
4241        arr = np.random.randn(100000, 3)/2
4242        pts = Points(arr).c('red3').pos(5,0,0)
4243        cube = Cube().pos(4,0.5,0)
4244        assem = pts.cut_with_mesh(cube, keep=True)
4245        show(assem.unpack(), axes=1).close()
4246        ```
4247        ![](https://vedo.embl.es/images/feats/cut_with_mesh.png)
4248
4249       Check out also:
4250            `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()`
4251       """
4252        polymesh = mesh.polydata()
4253        poly = self.polydata()
4254
4255        # Create an array to hold distance information
4256        signed_distances = vtk.vtkFloatArray()
4257        signed_distances.SetNumberOfComponents(1)
4258        signed_distances.SetName("SignedDistances")
4259
4260        # implicit function that will be used to slice the mesh
4261        ippd = vtk.vtkImplicitPolyDataDistance()
4262        ippd.SetInput(polymesh)
4263
4264        # Evaluate the signed distance function at all of the grid points
4265        for pointId in range(poly.GetNumberOfPoints()):
4266            p = poly.GetPoint(pointId)
4267            signed_distance = ippd.EvaluateFunction(p)
4268            signed_distances.InsertNextValue(signed_distance)
4269
4270        currentscals = poly.GetPointData().GetScalars()
4271        if currentscals:
4272            currentscals = currentscals.GetName()
4273
4274        poly.GetPointData().AddArray(signed_distances)
4275        poly.GetPointData().SetActiveScalars("SignedDistances")
4276
4277        clipper = vtk.vtkClipPolyData()
4278        clipper.SetInputData(poly)
4279        clipper.SetInsideOut(not invert)
4280        clipper.SetGenerateClippedOutput(keep)
4281        clipper.SetValue(0.0)
4282        clipper.Update()
4283        cpoly = clipper.GetOutput()
4284        if keep:
4285            kpoly = clipper.GetOutput(1)
4286
4287        vis = False
4288        if currentscals:
4289            cpoly.GetPointData().SetActiveScalars(currentscals)
4290            vis = self.mapper().GetScalarVisibility()
4291
4292        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4293            self._update(cpoly)
4294        else:
4295            # bring the underlying polydata to where _data is
4296            M = vtk.vtkMatrix4x4()
4297            M.DeepCopy(self.GetMatrix())
4298            M.Invert()
4299            tr = vtk.vtkTransform()
4300            tr.SetMatrix(M)
4301            tf = vtk.vtkTransformPolyDataFilter()
4302            tf.SetTransform(tr)
4303            tf.SetInputData(cpoly)
4304            tf.Update()
4305            self._update(tf.GetOutput())
4306
4307        self.pointdata.remove("SignedDistances")
4308        self.mapper().SetScalarVisibility(vis)
4309        if keep:
4310            if isinstance(self, vedo.Mesh):
4311                cutoff = vedo.Mesh(kpoly)
4312            else:
4313                cutoff = vedo.Points(kpoly)
4314            cutoff.property = vtk.vtkProperty()
4315            cutoff.property.DeepCopy(self.property)
4316            cutoff.SetProperty(cutoff.property)
4317            cutoff.c("k5").alpha(0.2)
4318            return vedo.Assembly([self, cutoff])
4319
4320        self.pipeline = utils.OperationNode("cut_with_mesh", parents=[self, mesh])
4321        return self

Cut an Mesh mesh with another Mesh.

Use invert to invert the selection.

Use keep to keep the cutoff part, in this case an Assembly is returned: the "cut" object and the "discarded" part of the original object. You can access both via assembly.unpack() method.

Example:

from vedo import *
 arr = np.random.randn(100000, 3)/2
 pts = Points(arr).c('red3').pos(5,0,0)
 cube = Cube().pos(4,0.5,0)
 assem = pts.cut_with_mesh(cube, keep=True)
 show(assem.unpack(), axes=1).close()
 

Check out also:

cut_with_box(), cut_with_plane(), cut_with_cylinder()

def cut_with_point_loop(self, points, invert=False, on='points', include_boundary=False):
4323    def cut_with_point_loop(self, points, invert=False, on="points", include_boundary=False):
4324        """
4325        Cut an `Mesh` object with a set of points forming a closed loop.
4326
4327        Arguments:
4328            invert : (bool)
4329                invert selection (inside-out)
4330            on : (str)
4331                if 'cells' will extract the whole cells lying inside (or outside) the point loop
4332            include_boundary : (bool)
4333                include cells lying exactly on the boundary line. Only relevant on 'cells' mode
4334
4335        Examples:
4336            - [cut_with_points1.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points1.py)
4337
4338                ![](https://vedo.embl.es/images/advanced/cutWithPoints1.png)
4339
4340            - [cut_with_points2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points2.py)
4341
4342                ![](https://vedo.embl.es/images/advanced/cutWithPoints2.png)
4343        """
4344        if isinstance(points, Points):
4345            parents = [points]
4346            vpts = points.polydata().GetPoints()
4347            points = points.points()
4348        else:
4349            parents = [self]
4350            vpts = vtk.vtkPoints()
4351            points = utils.make3d(points)
4352            for p in points:
4353                vpts.InsertNextPoint(p)
4354
4355        if "cell" in on:
4356            ippd = vtk.vtkImplicitSelectionLoop()
4357            ippd.SetLoop(vpts)
4358            ippd.AutomaticNormalGenerationOn()
4359            clipper = vtk.vtkExtractPolyDataGeometry()
4360            clipper.SetInputData(self.polydata())
4361            clipper.SetImplicitFunction(ippd)
4362            clipper.SetExtractInside(not invert)
4363            clipper.SetExtractBoundaryCells(include_boundary)
4364        else:
4365            spol = vtk.vtkSelectPolyData()
4366            spol.SetLoop(vpts)
4367            spol.GenerateSelectionScalarsOn()
4368            spol.GenerateUnselectedOutputOff()
4369            spol.SetInputData(self.polydata())
4370            spol.Update()
4371            clipper = vtk.vtkClipPolyData()
4372            clipper.SetInputData(spol.GetOutput())
4373            clipper.SetInsideOut(not invert)
4374            clipper.SetValue(0.0)
4375        clipper.Update()
4376        cpoly = clipper.GetOutput()
4377
4378        if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0:
4379            self._update(cpoly)
4380        else:
4381            # bring the underlying polydata to where _data is
4382            M = vtk.vtkMatrix4x4()
4383            M.DeepCopy(self.GetMatrix())
4384            M.Invert()
4385            tr = vtk.vtkTransform()
4386            tr.SetMatrix(M)
4387            tf = vtk.vtkTransformPolyDataFilter()
4388            tf.SetTransform(tr)
4389            tf.SetInputData(clipper.GetOutput())
4390            tf.Update()
4391            self._update(tf.GetOutput())
4392
4393        self.pipeline = utils.OperationNode("cut_with_pointloop", parents=parents)
4394        return self

Cut an Mesh object with a set of points forming a closed loop.

Arguments:
  • invert : (bool) invert selection (inside-out)
  • on : (str) if 'cells' will extract the whole cells lying inside (or outside) the point loop
  • include_boundary : (bool) include cells lying exactly on the boundary line. Only relevant on 'cells' mode
Examples:
def cut_with_scalar(self, value, name='', invert=False):
4396    def cut_with_scalar(self, value, name="", invert=False):
4397        """
4398        Cut a mesh or point cloud with some input scalar point-data.
4399
4400        Arguments:
4401            value : (float)
4402                cutting value
4403            name : (str)
4404                array name of the scalars to be used
4405            invert : (bool)
4406                flip selection
4407
4408        Example:
4409            ```python
4410            from vedo import *
4411            s = Sphere().lw(1)
4412            pts = s.points()
4413            scalars = np.sin(3*pts[:,2]) + pts[:,0]
4414            s.pointdata["somevalues"] = scalars
4415            s.cut_with_scalar(0.3)
4416            s.cmap("Spectral", "somevalues").add_scalarbar()
4417            s.show(axes=1).close()
4418            ```
4419            ![](https://vedo.embl.es/images/feats/cut_with_scalars.png)
4420        """
4421        if name:
4422            self.pointdata.select(name)
4423        clipper = vtk.vtkClipPolyData()
4424        clipper.SetInputData(self._data)
4425        clipper.SetValue(value)
4426        clipper.GenerateClippedOutputOff()
4427        clipper.SetInsideOut(not invert)
4428        clipper.Update()
4429        self._update(clipper.GetOutput())
4430
4431        self.pipeline = utils.OperationNode("cut_with_scalars", parents=[self])
4432        return self

Cut a mesh or point cloud with some input scalar point-data.

Arguments:
  • value : (float) cutting value
  • name : (str) array name of the scalars to be used
  • invert : (bool) flip selection
Example:
from vedo import *
s = Sphere().lw(1)
pts = s.points()
scalars = np.sin(3*pts[:,2]) + pts[:,0]
s.pointdata["somevalues"] = scalars
s.cut_with_scalar(0.3)
s.cmap("Spectral", "somevalues").add_scalarbar()
s.show(axes=1).close()

def implicit_modeller(self, distance=0.05, res=(50, 50, 50), bounds=(), maxdist=None):
4434    def implicit_modeller(self, distance=0.05, res=(50, 50, 50), bounds=(), maxdist=None):
4435        """Find the surface which sits at the specified distance from the input one."""
4436        if not bounds:
4437            bounds = self.bounds()
4438
4439        if not maxdist:
4440            maxdist = self.diagonal_size() / 2
4441
4442        imp = vtk.vtkImplicitModeller()
4443        imp.SetInputData(self.polydata())
4444        imp.SetSampleDimensions(res)
4445        imp.SetMaximumDistance(maxdist)
4446        imp.SetModelBounds(bounds)
4447        contour = vtk.vtkContourFilter()
4448        contour.SetInputConnection(imp.GetOutputPort())
4449        contour.SetValue(0, distance)
4450        contour.Update()
4451        poly = contour.GetOutput()
4452        out = vedo.Mesh(poly, c="lb")
4453
4454        out.pipeline = utils.OperationNode("implicit_modeller", parents=[self])
4455        return out

Find the surface which sits at the specified distance from the input one.

def generate_mesh( self, line_resolution=None, mesh_resolution=None, smooth=0.0, jitter=0.001, grid=None, quads=False, invert=False):
4458    def generate_mesh(
4459        self,
4460        line_resolution=None,
4461        mesh_resolution=None,
4462        smooth=0.0,
4463        jitter=0.001,
4464        grid=None,
4465        quads=False,
4466        invert=False,
4467    ):
4468        """
4469        Generate a polygonal Mesh from a closed contour line.
4470        If line is not closed it will be closed with a straight segment.
4471
4472        Arguments:
4473            line_resolution : (int)
4474                resolution of the contour line. The default is None, in this case
4475                the contour is not resampled.
4476            mesh_resolution : (int)
4477                resolution of the internal triangles not touching the boundary.
4478            smooth : (float)
4479                smoothing of the contour before meshing.
4480            jitter : (float)
4481                add a small noise to the internal points.
4482            grid : (Grid)
4483                manually pass a Grid object. The default is True.
4484            quads : (bool)
4485                generate a mesh of quads instead of triangles.
4486            invert : (bool)
4487                flip the line orientation. The default is False.
4488
4489        Examples:
4490            - [line2mesh_tri.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_tri.py)
4491
4492                ![](https://vedo.embl.es/images/advanced/line2mesh_tri.jpg)
4493
4494            - [line2mesh_quads.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_quads.py)
4495
4496                ![](https://vedo.embl.es/images/advanced/line2mesh_quads.png)
4497        """
4498        if line_resolution is None:
4499            contour = vedo.shapes.Line(self.points())
4500        else:
4501            contour = vedo.shapes.Spline(self.points(), smooth=smooth, res=line_resolution)
4502        contour.clean()
4503
4504        length = contour.length()
4505        density = length / contour.npoints
4506        vedo.logger.debug(f"tomesh():\n\tline length = {length}")
4507        vedo.logger.debug(f"\tdensity = {density} length/pt_separation")
4508
4509        x0, x1 = contour.xbounds()
4510        y0, y1 = contour.ybounds()
4511
4512        if grid is None:
4513            if mesh_resolution is None:
4514                resx = int((x1 - x0) / density + 0.5)
4515                resy = int((y1 - y0) / density + 0.5)
4516                vedo.logger.debug(f"tmesh_resolution = {[resx, resy]}")
4517            else:
4518                if utils.is_sequence(mesh_resolution):
4519                    resx, resy = mesh_resolution
4520                else:
4521                    resx, resy = mesh_resolution, mesh_resolution
4522            grid = vedo.shapes.Grid(
4523                [(x0 + x1) / 2, (y0 + y1) / 2, 0],
4524                s=((x1 - x0) * 1.025, (y1 - y0) * 1.025),
4525                res=(resx, resy),
4526            )
4527        else:
4528            grid = grid.clone()
4529
4530        cpts = contour.points()
4531
4532        # make sure it's closed
4533        p0, p1 = cpts[0], cpts[-1]
4534        nj = max(2, int(utils.mag(p1 - p0) / density + 0.5))
4535        joinline = vedo.shapes.Line(p1, p0, res=nj)
4536        contour = vedo.merge(contour, joinline).subsample(0.0001)
4537
4538        ####################################### quads
4539        if quads:
4540            cmesh = grid.clone().cut_with_point_loop(contour, on="cells", invert=invert)
4541            cmesh.wireframe(False).lw(0.5)
4542            cmesh.pipeline = utils.OperationNode(
4543                "generate_mesh",
4544                parents=[self, contour],
4545                comment=f"#quads {cmesh.inputdata().GetNumberOfCells()}",
4546            )
4547            return cmesh
4548        #############################################
4549
4550        grid_tmp = grid.points()
4551
4552        if jitter:
4553            np.random.seed(0)
4554            sigma = 1.0 / np.sqrt(grid.npoints) * grid.diagonal_size() * jitter
4555            vedo.logger.debug(f"\tsigma jittering = {sigma}")
4556            grid_tmp += np.random.rand(grid.npoints, 3) * sigma
4557            grid_tmp[:, 2] = 0.0
4558
4559        todel = []
4560        density /= np.sqrt(3)
4561        vgrid_tmp = Points(grid_tmp)
4562
4563        for p in contour.points():
4564            out = vgrid_tmp.closest_point(p, radius=density, return_point_id=True)
4565            todel += out.tolist()
4566        # cpoints = contour.points()
4567        # for i, p in enumerate(cpoints):
4568        #     if i:
4569        #         den = utils.mag(p-cpoints[i-1])/1.732
4570        #     else:
4571        #         den = density
4572        #     todel += vgrid_tmp.closest_point(p, radius=den, return_point_id=True)
4573
4574        grid_tmp = grid_tmp.tolist()
4575        for index in sorted(list(set(todel)), reverse=True):
4576            del grid_tmp[index]
4577
4578        points = contour.points().tolist() + grid_tmp
4579        if invert:
4580            boundary = reversed(range(contour.npoints))
4581        else:
4582            boundary = range(contour.npoints)
4583
4584        dln = delaunay2d(points, mode="xy", boundaries=[boundary])
4585        dln.compute_normals(points=False)  # fixes reversd faces
4586        dln.lw(0.5)
4587
4588        dln.pipeline = utils.OperationNode(
4589            "generate_mesh",
4590            parents=[self, contour],
4591            comment=f"#cells {dln.inputdata().GetNumberOfCells()}",
4592        )
4593        return dln

Generate a polygonal Mesh from a closed contour line. If line is not closed it will be closed with a straight segment.

Arguments:
  • line_resolution : (int) resolution of the contour line. The default is None, in this case the contour is not resampled.
  • mesh_resolution : (int) resolution of the internal triangles not touching the boundary.
  • smooth : (float) smoothing of the contour before meshing.
  • jitter : (float) add a small noise to the internal points.
  • grid : (Grid) manually pass a Grid object. The default is True.
  • quads : (bool) generate a mesh of quads instead of triangles.
  • invert : (bool) flip the line orientation. The default is False.
Examples:
def reconstruct_surface( self, dims=(100, 100, 100), radius=None, sample_size=None, hole_filling=True, bounds=(), padding=0.05):
4595    def reconstruct_surface(
4596        self,
4597        dims=(100, 100, 100),
4598        radius=None,
4599        sample_size=None,
4600        hole_filling=True,
4601        bounds=(),
4602        padding=0.05,
4603    ):
4604        """
4605        Surface reconstruction from a scattered cloud of points.
4606
4607        Arguments:
4608            dims : (int)
4609                number of voxels in x, y and z to control precision.
4610            radius : (float)
4611                radius of influence of each point.
4612                Smaller values generally improve performance markedly.
4613                Note that after the signed distance function is computed,
4614                any voxel taking on the value >= radius
4615                is presumed to be "unseen" or uninitialized.
4616            sample_size : (int)
4617                if normals are not present
4618                they will be calculated using this sample size per point.
4619            hole_filling : (bool)
4620                enables hole filling, this generates
4621                separating surfaces between the empty and unseen portions of the volume.
4622            bounds : (list)
4623                region in space in which to perform the sampling
4624                in format (xmin,xmax, ymin,ymax, zim, zmax)
4625            padding : (float)
4626                increase by this fraction the bounding box
4627
4628        Examples:
4629            - [recosurface.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/recosurface.py)
4630
4631                ![](https://vedo.embl.es/images/advanced/recosurface.png)
4632        """
4633        if not utils.is_sequence(dims):
4634            dims = (dims, dims, dims)
4635
4636        sdf = vtk.vtkSignedDistance()
4637
4638        if len(bounds) == 6:
4639            sdf.SetBounds(bounds)
4640        else:
4641            x0, x1, y0, y1, z0, z1 = self.bounds()
4642            sdf.SetBounds(
4643                x0 - (x1 - x0) * padding,
4644                x1 + (x1 - x0) * padding,
4645                y0 - (y1 - y0) * padding,
4646                y1 + (y1 - y0) * padding,
4647                z0 - (z1 - z0) * padding,
4648                z1 + (z1 - z0) * padding,
4649            )
4650
4651        pd = self.polydata()
4652
4653        if pd.GetPointData().GetNormals():
4654            sdf.SetInputData(pd)
4655        else:
4656            normals = vtk.vtkPCANormalEstimation()
4657            normals.SetInputData(pd)
4658            if not sample_size:
4659                sample_size = int(pd.GetNumberOfPoints() / 50)
4660            normals.SetSampleSize(sample_size)
4661            normals.SetNormalOrientationToGraphTraversal()
4662            sdf.SetInputConnection(normals.GetOutputPort())
4663            # print("Recalculating normals with sample size =", sample_size)
4664
4665        if radius is None:
4666            radius = self.diagonal_size() / (sum(dims) / 3) * 5
4667            # print("Calculating mesh from points with radius =", radius)
4668
4669        sdf.SetRadius(radius)
4670        sdf.SetDimensions(dims)
4671        sdf.Update()
4672
4673        surface = vtk.vtkExtractSurface()
4674        surface.SetRadius(radius * 0.99)
4675        surface.SetHoleFilling(hole_filling)
4676        surface.ComputeNormalsOff()
4677        surface.ComputeGradientsOff()
4678        surface.SetInputConnection(sdf.GetOutputPort())
4679        surface.Update()
4680        m = vedo.mesh.Mesh(surface.GetOutput(), c=self.color())
4681
4682        m.pipeline = utils.OperationNode(
4683            "reconstruct_surface", parents=[self],
4684            comment=f"#pts {m.inputdata().GetNumberOfPoints()}"
4685        )
4686        return m

Surface reconstruction from a scattered cloud of points.

Arguments:
  • dims : (int) number of voxels in x, y and z to control precision.
  • radius : (float) radius of influence of each point. Smaller values generally improve performance markedly. Note that after the signed distance function is computed, any voxel taking on the value >= radius is presumed to be "unseen" or uninitialized.
  • sample_size : (int) if normals are not present they will be calculated using this sample size per point.
  • hole_filling : (bool) enables hole filling, this generates separating surfaces between the empty and unseen portions of the volume.
  • bounds : (list) region in space in which to perform the sampling in format (xmin,xmax, ymin,ymax, zim, zmax)
  • padding : (float) increase by this fraction the bounding box
Examples:
def compute_clustering(self, radius):
4688    def compute_clustering(self, radius):
4689        """
4690        Cluster points in space. The `radius` is the radius of local search.
4691        An array named "ClusterId" is added to the vertex points.
4692
4693        Examples:
4694            - [clustering.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/clustering.py)
4695
4696                ![](https://vedo.embl.es/images/basic/clustering.png)
4697        """
4698        cluster = vtk.vtkEuclideanClusterExtraction()
4699        cluster.SetInputData(self.inputdata())
4700        cluster.SetExtractionModeToAllClusters()
4701        cluster.SetRadius(radius)
4702        cluster.ColorClustersOn()
4703        cluster.Update()
4704        idsarr = cluster.GetOutput().GetPointData().GetArray("ClusterId")
4705        self.inputdata().GetPointData().AddArray(idsarr)
4706
4707        self.pipeline = utils.OperationNode(
4708            "compute_clustering", parents=[self], comment=f"radius = {radius}"
4709        )
4710        return self

Cluster points in space. The radius is the radius of local search. An array named "ClusterId" is added to the vertex points.

Examples:
def compute_connections(self, radius, mode=0, regions=(), vrange=(0, 1), seeds=(), angle=0):
4712    def compute_connections(self, radius, mode=0, regions=(), vrange=(0, 1), seeds=(), angle=0):
4713        """
4714        Extracts and/or segments points from a point cloud based on geometric distance measures
4715        (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range.
4716        The default operation is to segment the points into "connected" regions where the connection
4717        is determined by an appropriate distance measure. Each region is given a region id.
4718
4719        Optionally, the filter can output the largest connected region of points; a particular region
4720        (via id specification); those regions that are seeded using a list of input point ids;
4721        or the region of points closest to a specified position.
4722
4723        The key parameter of this filter is the radius defining a sphere around each point which defines
4724        a local neighborhood: any other points in the local neighborhood are assumed connected to the point.
4725        Note that the radius is defined in absolute terms.
4726
4727        Other parameters are used to further qualify what it means to be a neighboring point.
4728        For example, scalar range and/or point normals can be used to further constrain the neighborhood.
4729        Also the extraction mode defines how the filter operates.
4730        By default, all regions are extracted but it is possible to extract particular regions;
4731        the region closest to a seed point; seeded regions; or the largest region found while processing.
4732        By default, all regions are extracted.
4733
4734        On output, all points are labeled with a region number.
4735        However note that the number of input and output points may not be the same:
4736        if not extracting all regions then the output size may be less than the input size.
4737
4738        Arguments:
4739            radius : (float)
4740                variable specifying a local sphere used to define local point neighborhood
4741            mode : (int)
4742                - 0,  Extract all regions
4743                - 1,  Extract point seeded regions
4744                - 2,  Extract largest region
4745                - 3,  Test specified regions
4746                - 4,  Extract all regions with scalar connectivity
4747                - 5,  Extract point seeded regions
4748            regions : (list)
4749                a list of non-negative regions id to extract
4750            vrange : (list)
4751                scalar range to use to extract points based on scalar connectivity
4752            seeds : (list)
4753                a list of non-negative point seed ids
4754            angle : (list)
4755                points are connected if the angle between their normals is
4756                within this angle threshold (expressed in degrees).
4757        """
4758        # https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html
4759        cpf = vtk.vtkConnectedPointsFilter()
4760        cpf.SetInputData(self.polydata())
4761        cpf.SetRadius(radius)
4762        if mode == 0:  # Extract all regions
4763            pass
4764
4765        elif mode == 1:  # Extract point seeded regions
4766            cpf.SetExtractionModeToPointSeededRegions()
4767            for s in seeds:
4768                cpf.AddSeed(s)
4769
4770        elif mode == 2:  # Test largest region
4771            cpf.SetExtractionModeToLargestRegion()
4772
4773        elif mode == 3:  # Test specified regions
4774            cpf.SetExtractionModeToSpecifiedRegions()
4775            for r in regions:
4776                cpf.AddSpecifiedRegion(r)
4777
4778        elif mode == 4:  # Extract all regions with scalar connectivity
4779            cpf.SetExtractionModeToLargestRegion()
4780            cpf.ScalarConnectivityOn()
4781            cpf.SetScalarRange(vrange[0], vrange[1])
4782
4783        elif mode == 5:  # Extract point seeded regions
4784            cpf.SetExtractionModeToLargestRegion()
4785            cpf.ScalarConnectivityOn()
4786            cpf.SetScalarRange(vrange[0], vrange[1])
4787            cpf.AlignedNormalsOn()
4788            cpf.SetNormalAngle(angle)
4789
4790        cpf.Update()
4791        return self._update(cpf.GetOutput())

Extracts and/or segments points from a point cloud based on geometric distance measures (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range. The default operation is to segment the points into "connected" regions where the connection is determined by an appropriate distance measure. Each region is given a region id.

Optionally, the filter can output the largest connected region of points; a particular region (via id specification); those regions that are seeded using a list of input point ids; or the region of points closest to a specified position.

The key parameter of this filter is the radius defining a sphere around each point which defines a local neighborhood: any other points in the local neighborhood are assumed connected to the point. Note that the radius is defined in absolute terms.

Other parameters are used to further qualify what it means to be a neighboring point. For example, scalar range and/or point normals can be used to further constrain the neighborhood. Also the extraction mode defines how the filter operates. By default, all regions are extracted but it is possible to extract particular regions; the region closest to a seed point; seeded regions; or the largest region found while processing. By default, all regions are extracted.

On output, all points are labeled with a region number. However note that the number of input and output points may not be the same: if not extracting all regions then the output size may be less than the input size.

Arguments:
  • radius : (float) variable specifying a local sphere used to define local point neighborhood
  • mode : (int)
    • 0, Extract all regions
    • 1, Extract point seeded regions
    • 2, Extract largest region
    • 3, Test specified regions
    • 4, Extract all regions with scalar connectivity
    • 5, Extract point seeded regions
  • regions : (list) a list of non-negative regions id to extract
  • vrange : (list) scalar range to use to extract points based on scalar connectivity
  • seeds : (list) a list of non-negative point seed ids
  • angle : (list) points are connected if the angle between their normals is within this angle threshold (expressed in degrees).
def compute_camera_distance(self):
4793    def compute_camera_distance(self):
4794        """
4795        Calculate the distance from points to the camera.
4796        A pointdata array is created with name 'DistanceToCamera'.
4797        """
4798        if vedo.plotter_instance.renderer:
4799            poly = self.polydata()
4800            dc = vtk.vtkDistanceToCamera()
4801            dc.SetInputData(poly)
4802            dc.SetRenderer(vedo.plotter_instance.renderer)
4803            dc.Update()
4804            return self._update(dc.GetOutput())
4805        return self

Calculate the distance from points to the camera. A pointdata array is created with name 'DistanceToCamera'.

def density( self, dims=(40, 40, 40), bounds=None, radius=None, compute_gradient=False, locator=None):
4807    def density(
4808        self, dims=(40, 40, 40), bounds=None, radius=None, compute_gradient=False, locator=None
4809    ):
4810        """
4811        Generate a density field from a point cloud. Input can also be a set of 3D coordinates.
4812        Output is a `Volume`.
4813
4814        The local neighborhood is specified as the `radius` around each sample position (each voxel).
4815        The density is expressed as the number of counts in the radius search.
4816
4817        Arguments:
4818            dims : (int,list)
4819                number of voxels in x, y and z of the output Volume.
4820            compute_gradient : (bool)
4821                Turn on/off the generation of the gradient vector,
4822                gradient magnitude scalar, and function classification scalar.
4823                By default this is off. Note that this will increase execution time
4824                and the size of the output. (The names of these point data arrays are:
4825                "Gradient", "Gradient Magnitude", and "Classification")
4826            locator : (vtkPointLocator)
4827                can be assigned from a previous call for speed (access it via `object.point_locator`).
4828
4829        Examples:
4830            - [plot_density3d.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/plot_density3d.py)
4831
4832                ![](https://vedo.embl.es/images/pyplot/plot_density3d.png)
4833        """
4834        pdf = vtk.vtkPointDensityFilter()
4835        pdf.SetInputData(self.polydata())
4836
4837        if not utils.is_sequence(dims):
4838            dims = [dims, dims, dims]
4839
4840        if bounds is None:
4841            bounds = list(self.bounds())
4842        elif len(bounds) == 4:
4843            bounds = [*bounds, 0, 0]
4844
4845        if bounds[5] - bounds[4] == 0 or len(dims) == 2:  # its 2D
4846            dims = list(dims)
4847            dims = [dims[0], dims[1], 2]
4848            diag = self.diagonal_size()
4849            bounds[5] = bounds[4] + diag / 1000
4850        pdf.SetModelBounds(bounds)
4851
4852        pdf.SetSampleDimensions(dims)
4853
4854        if locator:
4855            pdf.SetLocator(locator)
4856
4857        pdf.SetDensityEstimateToFixedRadius()
4858        if radius is None:
4859            radius = self.diagonal_size() / 20
4860        pdf.SetRadius(radius)
4861
4862        pdf.SetComputeGradient(compute_gradient)
4863        pdf.Update()
4864        img = pdf.GetOutput()
4865        vol = vedo.volume.Volume(img).mode(1)
4866        vol.name = "PointDensity"
4867        vol.info["radius"] = radius
4868        vol.locator = pdf.GetLocator()
4869
4870        vol.pipeline = utils.OperationNode(
4871            "density", parents=[self], comment=f"dims = {tuple(vol.dimensions())}"
4872        )
4873        return vol

Generate a density field from a point cloud. Input can also be a set of 3D coordinates. Output is a Volume.

The local neighborhood is specified as the radius around each sample position (each voxel). The density is expressed as the number of counts in the radius search.

Arguments:
  • dims : (int,list) number of voxels in x, y and z of the output Volume.
  • compute_gradient : (bool) Turn on/off the generation of the gradient vector, gradient magnitude scalar, and function classification scalar. By default this is off. Note that this will increase execution time and the size of the output. (The names of these point data arrays are: "Gradient", "Gradient Magnitude", and "Classification")
  • locator : (vtkPointLocator) can be assigned from a previous call for speed (access it via object.point_locator).
Examples:
def densify( self, target_distance=0.1, nclosest=6, radius=None, niter=1, nmax=None):
4875    def densify(self, target_distance=0.1, nclosest=6, radius=None, niter=1, nmax=None):
4876        """
4877        Return a copy of the cloud with new added points.
4878        The new points are created in such a way that all points in any local neighborhood are
4879        within a target distance of one another.
4880
4881        For each input point, the distance to all points in its neighborhood is computed.
4882        If any of its neighbors is further than the target distance,
4883        the edge connecting the point and its neighbor is bisected and
4884        a new point is inserted at the bisection point.
4885        A single pass is completed once all the input points are visited.
4886        Then the process repeats to the number of iterations.
4887
4888        Examples:
4889            - [densifycloud.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/densifycloud.py)
4890
4891                ![](https://vedo.embl.es/images/volumetric/densifycloud.png)
4892
4893        .. note::
4894            Points will be created in an iterative fashion until all points in their
4895            local neighborhood are the target distance apart or less.
4896            Note that the process may terminate early due to the
4897            number of iterations. By default the target distance is set to 0.5.
4898            Note that the target_distance should be less than the radius
4899            or nothing will change on output.
4900
4901        .. warning::
4902            This class can generate a lot of points very quickly.
4903            The maximum number of iterations is by default set to =1.0 for this reason.
4904            Increase the number of iterations very carefully.
4905            Also, `nmax` can be set to limit the explosion of points.
4906            It is also recommended that a N closest neighborhood is used.
4907
4908        """
4909        src = vtk.vtkProgrammableSource()
4910        opts = self.points()
4911
4912        def _readPoints():
4913            output = src.GetPolyDataOutput()
4914            points = vtk.vtkPoints()
4915            for p in opts:
4916                points.InsertNextPoint(p)
4917            output.SetPoints(points)
4918
4919        src.SetExecuteMethod(_readPoints)
4920
4921        dens = vtk.vtkDensifyPointCloudFilter()
4922        dens.SetInputConnection(src.GetOutputPort())
4923        dens.InterpolateAttributeDataOn()
4924        dens.SetTargetDistance(target_distance)
4925        dens.SetMaximumNumberOfIterations(niter)
4926        if nmax:
4927            dens.SetMaximumNumberOfPoints(nmax)
4928
4929        if radius:
4930            dens.SetNeighborhoodTypeToRadius()
4931            dens.SetRadius(radius)
4932        elif nclosest:
4933            dens.SetNeighborhoodTypeToNClosest()
4934            dens.SetNumberOfClosestPoints(nclosest)
4935        else:
4936            vedo.logger.error("set either radius or nclosest")
4937            raise RuntimeError()
4938        dens.Update()
4939        pts = utils.vtk2numpy(dens.GetOutput().GetPoints().GetData())
4940        cld = Points(pts, c=None).point_size(self.GetProperty().GetPointSize())
4941        cld.interpolate_data_from(self, n=nclosest, radius=radius)
4942        cld.name = "densifiedCloud"
4943
4944        cld.pipeline = utils.OperationNode(
4945            "densify", parents=[self], c="#e9c46a:",
4946            comment=f"#pts {cld.inputdata().GetNumberOfPoints()}"
4947        )
4948        return cld

Return a copy of the cloud with new added points. The new points are created in such a way that all points in any local neighborhood are within a target distance of one another.

For each input point, the distance to all points in its neighborhood is computed. If any of its neighbors is further than the target distance, the edge connecting the point and its neighbor is bisected and a new point is inserted at the bisection point. A single pass is completed once all the input points are visited. Then the process repeats to the number of iterations.

Examples:

Points will be created in an iterative fashion until all points in their local neighborhood are the target distance apart or less. Note that the process may terminate early due to the number of iterations. By default the target distance is set to 0.5. Note that the target_distance should be less than the radius or nothing will change on output.

This class can generate a lot of points very quickly. The maximum number of iterations is by default set to =1.0 for this reason. Increase the number of iterations very carefully. Also, nmax can be set to limit the explosion of points. It is also recommended that a N closest neighborhood is used.

def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None):
4954    def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None):
4955        """
4956        Compute the `Volume` object whose voxels contains the signed distance from
4957        the point cloud. The point cloud must have Normals.
4958
4959        Arguments:
4960            bounds : (list, actor)
4961                bounding box sizes
4962            dims : (list)
4963                dimensions (nr. of voxels) of the output volume.
4964            invert : (bool)
4965                flip the sign
4966            maxradius : (float)
4967                specify how far out to propagate distance calculation
4968
4969        Examples:
4970            - [distance2mesh.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/distance2mesh.py)
4971
4972                ![](https://vedo.embl.es/images/basic/distance2mesh.png)
4973        """
4974        if bounds is None:
4975            bounds = self.bounds()
4976        if maxradius is None:
4977            maxradius = self.diagonal_size() / 2
4978        dist = vtk.vtkSignedDistance()
4979        dist.SetInputData(self.polydata())
4980        dist.SetRadius(maxradius)
4981        dist.SetBounds(bounds)
4982        dist.SetDimensions(dims)
4983        dist.Update()
4984        img = dist.GetOutput()
4985        if invert:
4986            mat = vtk.vtkImageMathematics()
4987            mat.SetInput1Data(img)
4988            mat.SetOperationToMultiplyByK()
4989            mat.SetConstantK(-1)
4990            mat.Update()
4991            img = mat.GetOutput()
4992
4993        vol = vedo.Volume(img)
4994        vol.name = "SignedDistanceVolume"
4995
4996        vol.pipeline = utils.OperationNode(
4997            "signed_distance",
4998            parents=[self],
4999            comment=f"dim = {tuple(vol.dimensions())}",
5000            c="#e9c46a:#0096c7",
5001        )
5002        return vol

Compute the Volume object whose voxels contains the signed distance from the point cloud. The point cloud must have Normals.

Arguments:
  • bounds : (list, actor) bounding box sizes
  • dims : (list) dimensions (nr. of voxels) of the output volume.
  • invert : (bool) flip the sign
  • maxradius : (float) specify how far out to propagate distance calculation
Examples:
def tovolume( self, kernel='shepard', radius=None, n=None, bounds=None, null_value=None, dims=(25, 25, 25)):
5004    def tovolume(
5005        self, kernel="shepard", radius=None, n=None, bounds=None, null_value=None, dims=(25, 25, 25)
5006    ):
5007        """
5008        Generate a `Volume` by interpolating a scalar
5009        or vector field which is only known on a scattered set of points or mesh.
5010        Available interpolation kernels are: shepard, gaussian, or linear.
5011
5012        Arguments:
5013            kernel : (str)
5014                interpolation kernel type [shepard]
5015            radius : (float)
5016                radius of the local search
5017            n : (int)
5018                number of point to use for interpolation
5019            bounds : (list)
5020                bounding box of the output Volume object
5021            dims : (list)
5022                dimensions of the output Volume object
5023            null_value : (float)
5024                value to be assigned to invalid points
5025
5026        Examples:
5027            - [interpolate_volume.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/interpolate_volume.py)
5028
5029                ![](https://vedo.embl.es/images/volumetric/59095175-1ec5a300-8918-11e9-8bc0-fd35c8981e2b.jpg)
5030        """
5031        if radius is None and not n:
5032            vedo.logger.error("please set either radius or n")
5033            raise RuntimeError
5034
5035        poly = self.polydata()
5036
5037        # Create a probe volume
5038        probe = vtk.vtkImageData()
5039        probe.SetDimensions(dims)
5040        if bounds is None:
5041            bounds = self.bounds()
5042        probe.SetOrigin(bounds[0], bounds[2], bounds[4])
5043        probe.SetSpacing(
5044            (bounds[1] - bounds[0]) / dims[0],
5045            (bounds[3] - bounds[2]) / dims[1],
5046            (bounds[5] - bounds[4]) / dims[2],
5047        )
5048
5049        if not self.point_locator:
5050            self.point_locator = vtk.vtkPointLocator()
5051            self.point_locator.SetDataSet(poly)
5052            self.point_locator.BuildLocator()
5053
5054        if kernel == "shepard":
5055            kern = vtk.vtkShepardKernel()
5056            kern.SetPowerParameter(2)
5057        elif kernel == "gaussian":
5058            kern = vtk.vtkGaussianKernel()
5059        elif kernel == "linear":
5060            kern = vtk.vtkLinearKernel()
5061        else:
5062            vedo.logger.error("Error in tovolume(), available kernels are:")
5063            vedo.logger.error(" [shepard, gaussian, linear]")
5064            raise RuntimeError()
5065
5066        if radius:
5067            kern.SetRadius(radius)
5068
5069        interpolator = vtk.vtkPointInterpolator()
5070        interpolator.SetInputData(probe)
5071        interpolator.SetSourceData(poly)
5072        interpolator.SetKernel(kern)
5073        interpolator.SetLocator(self.point_locator)
5074
5075        if n:
5076            kern.SetNumberOfPoints(n)
5077            kern.SetKernelFootprintToNClosest()
5078        else:
5079            kern.SetRadius(radius)
5080
5081        if null_value is not None:
5082            interpolator.SetNullValue(null_value)
5083        else:
5084            interpolator.SetNullPointsStrategyToClosestPoint()
5085        interpolator.Update()
5086
5087        vol = vedo.Volume(interpolator.GetOutput())
5088
5089        vol.pipeline = utils.OperationNode(
5090            "signed_distance",
5091            parents=[self],
5092            comment=f"dim = {tuple(vol.dimensions())}",
5093            c="#e9c46a:#0096c7",
5094        )
5095        return vol

Generate a Volume by interpolating a scalar or vector field which is only known on a scattered set of points or mesh. Available interpolation kernels are: shepard, gaussian, or linear.

Arguments:
  • kernel : (str) interpolation kernel type [shepard]
  • radius : (float) radius of the local search
  • n : (int) number of point to use for interpolation
  • bounds : (list) bounding box of the output Volume object
  • dims : (list) dimensions of the output Volume object
  • null_value : (float) value to be assigned to invalid points
Examples:
def generate_random_data(self):
5097    def generate_random_data(self):
5098        """Fill a dataset with random attributes"""
5099        gen = vtk.vtkRandomAttributeGenerator()
5100        gen.SetInputData(self._data)
5101        gen.GenerateAllDataOn()
5102        gen.SetDataTypeToFloat()
5103        gen.GeneratePointNormalsOff()
5104        gen.GeneratePointTensorsOn()
5105        gen.GenerateCellScalarsOn()
5106        gen.Update()
5107
5108        m = self._update(gen.GetOutput())
5109
5110        m.pipeline = utils.OperationNode("generate\nrandom data", parents=[self])
5111        return m

Fill a dataset with random attributes

def Point(pos=(0, 0, 0), r=12, c='red', alpha=1.0):
698def Point(pos=(0, 0, 0), r=12, c="red", alpha=1.0):
699    """
700    Create a simple point in space.
701
702    .. note:: if you are creating many points you should definitely use class `Points` instead!
703    """
704    if isinstance(pos, vtk.vtkActor):
705        pos = pos.GetPosition()
706    pd = utils.buildPolyData([[0, 0, 0]])
707    if len(pos) == 2:
708        pos = (pos[0], pos[1], 0.0)
709    pt = Points(pd, r, c, alpha)
710    pt.SetPosition(pos)
711    pt.name = "Point"
712    return pt

Create a simple point in space.

if you are creating many points you should definitely use class Points instead!
def merge(*meshs, flag=False):
41def merge(*meshs, flag=False):
42    """
43    Build a new Mesh (or Points) formed by the fusion of the inputs.
44
45    Similar to Assembly, but in this case the input objects become a single entity.
46
47    To keep track of the original identities of the inputs you can use `flag`.
48    In this case a point array of IDs is added to the output.
49
50    Examples:
51        - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py)
52
53            ![](https://vedo.embl.es/images/advanced/warp1.png)
54
55        - [value_iteration.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/value_iteration.py)
56
57    """
58    acts = [a for a in utils.flatten(meshs) if a]
59
60    if not acts:
61        return None
62
63    idarr = []
64    polyapp = vtk.vtkAppendPolyData()
65    for i, a in enumerate(acts):
66        try:
67            poly = a.polydata()
68        except AttributeError:
69            # so a vtkPolydata can also be passed
70            poly = a
71        polyapp.AddInputData(poly)
72        if flag:
73            idarr += [i] * poly.GetNumberOfPoints()
74    polyapp.Update()
75    mpoly = polyapp.GetOutput()
76
77    if flag:
78        varr = utils.numpy2vtk(idarr, dtype=np.uint16, name="OriginalMeshID")
79        mpoly.GetPointData().AddArray(varr)
80
81    if isinstance(acts[0], vedo.Mesh):
82        msh = vedo.Mesh(mpoly)
83    else:
84        msh = Points(mpoly)
85
86    if isinstance(acts[0], vtk.vtkActor):
87        cprp = vtk.vtkProperty()
88        cprp.DeepCopy(acts[0].GetProperty())
89        msh.SetProperty(cprp)
90        msh.property = cprp
91
92    msh.pipeline = utils.OperationNode(
93        "merge", parents=acts,
94        comment=f"#pts {msh.inputdata().GetNumberOfPoints()}",
95    )
96    return msh

Build a new Mesh (or Points) formed by the fusion of the inputs.

Similar to Assembly, but in this case the input objects become a single entity.

To keep track of the original identities of the inputs you can use flag. In this case a point array of IDs is added to the output.

Examples:
def visible_points(mesh, area=(), tol=None, invert=False):
100def visible_points(mesh, area=(), tol=None, invert=False):
101    """
102    Extract points based on whether they are visible or not.
103    Visibility is determined by accessing the z-buffer of a rendering window.
104    The position of each input point is converted into display coordinates,
105    and then the z-value at that point is obtained.
106    If within the user-specified tolerance, the point is considered visible.
107    Associated data attributes are passed to the output as well.
108
109    This filter also allows you to specify a rectangular window in display (pixel)
110    coordinates in which the visible points must lie.
111
112    Arguments:
113        area : (list)
114            specify a rectangular region as (xmin,xmax,ymin,ymax)
115        tol : (float)
116            a tolerance in normalized display coordinate system
117        invert : (bool)
118            select invisible points instead.
119
120    Example:
121        ```python
122        from vedo import Ellipsoid, show, visible_points
123        s = Ellipsoid().rotate_y(30)
124
125        #Camera options: pos, focal_point, viewup, distance,
126        camopts = dict(pos=(0,0,25), focal_point=(0,0,0))
127        show(s, camera=camopts, offscreen=True)
128
129        m = visible_points(s)
130        #print('visible pts:', m.points()) # numpy array
131        show(m, new=True, axes=1) # optionally draw result on a new window
132        ```
133        ![](https://vedo.embl.es/images/feats/visible_points.png)
134    """
135    # specify a rectangular region
136    svp = vtk.vtkSelectVisiblePoints()
137    svp.SetInputData(mesh.polydata())
138    svp.SetRenderer(vedo.plotter_instance.renderer)
139
140    if len(area) == 4:
141        svp.SetSelection(area[0], area[1], area[2], area[3])
142    if tol is not None:
143        svp.SetTolerance(tol)
144    if invert:
145        svp.SelectInvisibleOn()
146    svp.Update()
147
148    m = Points(svp.GetOutput()).point_size(5)
149    m.name = "VisiblePoints"
150    return m

Extract points based on whether they are visible or not. Visibility is determined by accessing the z-buffer of a rendering window. The position of each input point is converted into display coordinates, and then the z-value at that point is obtained. If within the user-specified tolerance, the point is considered visible. Associated data attributes are passed to the output as well.

This filter also allows you to specify a rectangular window in display (pixel) coordinates in which the visible points must lie.

Arguments:
  • area : (list) specify a rectangular region as (xmin,xmax,ymin,ymax)
  • tol : (float) a tolerance in normalized display coordinate system
  • invert : (bool) select invisible points instead.
Example:
from vedo import Ellipsoid, show, visible_points
s = Ellipsoid().rotate_y(30)

#Camera options: pos, focal_point, viewup, distance,
camopts = dict(pos=(0,0,25), focal_point=(0,0,0))
show(s, camera=camopts, offscreen=True)

m = visible_points(s)
#print('visible pts:', m.points()) # numpy array
show(m, new=True, axes=1) # optionally draw result on a new window

def delaunay2d( plist, mode='scipy', boundaries=(), tol=None, alpha=0.0, offset=0.0, transform=None):
153def delaunay2d(plist, mode="scipy", boundaries=(), tol=None, alpha=0.0, offset=0.0, transform=None):
154    """
155    Create a mesh from points in the XY plane.
156    If `mode='fit'` then the filter computes a best fitting
157    plane and projects the points onto it.
158
159    Arguments:
160        tol : (float)
161            specify a tolerance to control discarding of closely spaced points.
162            This tolerance is specified as a fraction of the diagonal length of the bounding box of the points.
163        alpha : (float)
164            for a non-zero alpha value, only edges or triangles contained
165            within a sphere centered at mesh vertices will be output.
166            Otherwise, only triangles will be output.
167        offset : (float)
168            multiplier to control the size of the initial, bounding Delaunay triangulation.
169        transform: vtkTransform
170            a VTK transformation (eg. a thinplate spline)
171            which is applied to points to generate a 2D problem.
172            This maps a 3D dataset into a 2D dataset where triangulation can be done on the XY plane.
173            The points are transformed and triangulated.
174            The topology of triangulated points is used as the output topology.
175
176    Examples:
177        - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py)
178
179            ![](https://vedo.embl.es/images/basic/delaunay2d.png)
180    """
181    if isinstance(plist, Points):
182        parents = [plist]
183        plist = plist.points()
184    else:
185        parents = []
186        plist = np.ascontiguousarray(plist)
187        plist = utils.make3d(plist)
188
189    #############################################
190    if mode == "scipy":
191        from scipy.spatial import Delaunay as scipy_delaunay
192
193        tri = scipy_delaunay(plist[:, 0:2])
194        return vedo.mesh.Mesh([plist, tri.simplices])
195    #############################################
196
197    pd = vtk.vtkPolyData()
198    vpts = vtk.vtkPoints()
199    vpts.SetData(utils.numpy2vtk(plist, dtype=np.float32))
200    pd.SetPoints(vpts)
201
202    delny = vtk.vtkDelaunay2D()
203    delny.SetInputData(pd)
204    if tol:
205        delny.SetTolerance(tol)
206    delny.SetAlpha(alpha)
207    delny.SetOffset(offset)
208    if transform:
209        if hasattr(transform, "transform"):
210            transform = transform.transform
211        delny.SetTransform(transform)
212
213    if mode == "xy" and boundaries:
214        boundary = vtk.vtkPolyData()
215        boundary.SetPoints(vpts)
216        cell_array = vtk.vtkCellArray()
217        for b in boundaries:
218            cpolygon = vtk.vtkPolygon()
219            for idd in b:
220                cpolygon.GetPointIds().InsertNextId(idd)
221            cell_array.InsertNextCell(cpolygon)
222        boundary.SetPolys(cell_array)
223        delny.SetSourceData(boundary)
224
225    if mode == "fit":
226        delny.SetProjectionPlaneMode(vtk.VTK_BEST_FITTING_PLANE)
227    delny.Update()
228    msh = vedo.mesh.Mesh(delny.GetOutput()).clean().lighting("off")
229
230    msh.pipeline = utils.OperationNode(
231        "delaunay2d", parents=parents,
232        comment=f"#cells {msh.inputdata().GetNumberOfCells()}"
233    )
234    return msh

Create a mesh from points in the XY plane. If mode='fit' then the filter computes a best fitting plane and projects the points onto it.

Arguments:
  • tol : (float) specify a tolerance to control discarding of closely spaced points. This tolerance is specified as a fraction of the diagonal length of the bounding box of the points.
  • alpha : (float) for a non-zero alpha value, only edges or triangles contained within a sphere centered at mesh vertices will be output. Otherwise, only triangles will be output.
  • offset : (float) multiplier to control the size of the initial, bounding Delaunay triangulation.
  • transform: vtkTransform a VTK transformation (eg. a thinplate spline) which is applied to points to generate a 2D problem. This maps a 3D dataset into a 2D dataset where triangulation can be done on the XY plane. The points are transformed and triangulated. The topology of triangulated points is used as the output topology.
Examples:
def voronoi(pts, padding=0.0, fit=False, method='vtk'):
237def voronoi(pts, padding=0.0, fit=False, method="vtk"):
238    """
239    Generate the 2D Voronoi convex tiling of the input points (z is ignored).
240    The points are assumed to lie in a plane. The output is a Mesh. Each output cell is a convex polygon.
241
242    The 2D Voronoi tessellation is a tiling of space, where each Voronoi tile represents the region nearest
243    to one of the input points. Voronoi tessellations are important in computational geometry
244    (and many other fields), and are the dual of Delaunay triangulations.
245
246    Thus the triangulation is constructed in the x-y plane, and the z coordinate is ignored
247    (although carried through to the output).
248    If you desire to triangulate in a different plane, you can use fit=True.
249
250    A brief summary is as follows. Each (generating) input point is associated with
251    an initial Voronoi tile, which is simply the bounding box of the point set.
252    A locator is then used to identify nearby points: each neighbor in turn generates a
253    clipping line positioned halfway between the generating point and the neighboring point,
254    and orthogonal to the line connecting them. Clips are readily performed by evaluationg the
255    vertices of the convex Voronoi tile as being on either side (inside,outside) of the clip line.
256    If two intersections of the Voronoi tile are found, the portion of the tile "outside" the clip
257    line is discarded, resulting in a new convex, Voronoi tile. As each clip occurs,
258    the Voronoi "Flower" error metric (the union of error spheres) is compared to the extent of the region
259    containing the neighboring clip points. The clip region (along with the points contained in it) is grown
260    by careful expansion (e.g., outward spiraling iterator over all candidate clip points).
261    When the Voronoi Flower is contained within the clip region, the algorithm terminates and the Voronoi
262    tile is output. Once complete, it is possible to construct the Delaunay triangulation from the Voronoi
263    tessellation. Note that topological and geometric information is used to generate a valid triangulation
264    (e.g., merging points and validating topology).
265
266    Arguments:
267        pts : (list)
268            list of input points.
269        padding : (float)
270            padding distance. The default is 0.
271        fit : (bool)
272            detect automatically the best fitting plane. The default is False.
273
274    Examples:
275        - [voronoi1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/voronoi1.py)
276
277            ![](https://vedo.embl.es/images/basic/voronoi1.png)
278
279        - [voronoi2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/voronoi2.py)
280
281            ![](https://vedo.embl.es/images/advanced/voronoi2.png)
282    """
283    if method == "scipy":
284        from scipy.spatial import Voronoi as scipy_voronoi
285
286        pts = np.asarray(pts)[:, (0, 1)]
287        vor = scipy_voronoi(pts)
288        regs = []  # filter out invalid indices
289        for r in vor.regions:
290            flag = True
291            for x in r:
292                if x < 0:
293                    flag = False
294                    break
295            if flag and len(r) > 0:
296                regs.append(r)
297
298        m = vedo.Mesh([vor.vertices, regs], c="orange5")
299        m.celldata["VoronoiID"] = np.array(list(range(len(regs)))).astype(int)
300        m.locator = None
301
302    elif method == "vtk":
303        vor = vtk.vtkVoronoi2D()
304        if isinstance(pts, Points):
305            vor.SetInputData(pts.polydata())
306        else:
307            pts = np.asarray(pts)
308            if pts.shape[1] == 2:
309                pts = np.c_[pts, np.zeros(len(pts))]
310            pd = vtk.vtkPolyData()
311            vpts = vtk.vtkPoints()
312            vpts.SetData(utils.numpy2vtk(pts, dtype=np.float32))
313            pd.SetPoints(vpts)
314            vor.SetInputData(pd)
315        vor.SetPadding(padding)
316        vor.SetGenerateScalarsToPointIds()
317        if fit:
318            vor.SetProjectionPlaneModeToBestFittingPlane()
319        else:
320            vor.SetProjectionPlaneModeToXYPlane()
321        vor.Update()
322        poly = vor.GetOutput()
323        arr = poly.GetCellData().GetArray(0)
324        if arr:
325            arr.SetName("VoronoiID")
326        m = vedo.Mesh(poly, c="orange5")
327        m.locator = vor.GetLocator()
328
329    else:
330        vedo.logger.error(f"Unknown method {method} in voronoi()")
331        raise RuntimeError
332
333    m.lw(2).lighting("off").wireframe()
334    m.name = "Voronoi"
335    return m

Generate the 2D Voronoi convex tiling of the input points (z is ignored). The points are assumed to lie in a plane. The output is a Mesh. Each output cell is a convex polygon.

The 2D Voronoi tessellation is a tiling of space, where each Voronoi tile represents the region nearest to one of the input points. Voronoi tessellations are important in computational geometry (and many other fields), and are the dual of Delaunay triangulations.

Thus the triangulation is constructed in the x-y plane, and the z coordinate is ignored (although carried through to the output). If you desire to triangulate in a different plane, you can use fit=True.

A brief summary is as follows. Each (generating) input point is associated with an initial Voronoi tile, which is simply the bounding box of the point set. A locator is then used to identify nearby points: each neighbor in turn generates a clipping line positioned halfway between the generating point and the neighboring point, and orthogonal to the line connecting them. Clips are readily performed by evaluationg the vertices of the convex Voronoi tile as being on either side (inside,outside) of the clip line. If two intersections of the Voronoi tile are found, the portion of the tile "outside" the clip line is discarded, resulting in a new convex, Voronoi tile. As each clip occurs, the Voronoi "Flower" error metric (the union of error spheres) is compared to the extent of the region containing the neighboring clip points. The clip region (along with the points contained in it) is grown by careful expansion (e.g., outward spiraling iterator over all candidate clip points). When the Voronoi Flower is contained within the clip region, the algorithm terminates and the Voronoi tile is output. Once complete, it is possible to construct the Delaunay triangulation from the Voronoi tessellation. Note that topological and geometric information is used to generate a valid triangulation (e.g., merging points and validating topology).

Arguments:
  • pts : (list) list of input points.
  • padding : (float) padding distance. The default is 0.
  • fit : (bool) detect automatically the best fitting plane. The default is False.
Examples:
def fit_line(points):
378def fit_line(points):
379    """
380    Fits a line through points.
381
382    Extra info is stored in `Line.slope`, `Line.center`, `Line.variances`.
383
384    Examples:
385        - [fitline.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitline.py)
386
387            ![](https://vedo.embl.es/images/advanced/fitline.png)
388    """
389    if isinstance(points, Points):
390        points = points.points()
391    data = np.array(points)
392    datamean = data.mean(axis=0)
393    _, dd, vv = np.linalg.svd(data - datamean)
394    vv = vv[0] / np.linalg.norm(vv[0])
395    # vv contains the first principal component, i.e. the direction
396    # vector of the best fit line in the least squares sense.
397    xyz_min = points.min(axis=0)
398    xyz_max = points.max(axis=0)
399    a = np.linalg.norm(xyz_min - datamean)
400    b = np.linalg.norm(xyz_max - datamean)
401    p1 = datamean - a * vv
402    p2 = datamean + b * vv
403    line = vedo.shapes.Line(p1, p2, lw=1)
404    line.slope = vv
405    line.center = datamean
406    line.variances = dd
407    return line

Fits a line through points.

Extra info is stored in Line.slope, Line.center, Line.variances.

Examples:
def fit_circle(points):
410def fit_circle(points):
411    """
412    Fits a circle through a set of 3D points, with a very fast non-iterative method.
413
414    Returns the tuple `(center, radius, normal_to_circle)`.
415
416    .. warning::
417        trying to fit s-shaped points will inevitably lead to instabilities and
418        circles of small radius.
419
420    References:
421        *J.F. Crawford, Nucl. Instr. Meth. 211, 1983, 223-225.*
422    """
423    data = np.asarray(points)
424
425    offs = data.mean(axis=0)
426    data, n0 = _rotate_points(data - offs)
427
428    xi = data[:, 0]
429    yi = data[:, 1]
430
431    x = sum(xi)
432    xi2 = xi * xi
433    xx = sum(xi2)
434    xxx = sum(xi2 * xi)
435
436    y = sum(yi)
437    yi2 = yi * yi
438    yy = sum(yi2)
439    yyy = sum(yi2 * yi)
440
441    xiyi = xi * yi
442    xy = sum(xiyi)
443    xyy = sum(xiyi * yi)
444    xxy = sum(xi * xiyi)
445
446    N = len(xi)
447    k = (xx + yy) / N
448
449    a1 = xx - x * x / N
450    b1 = xy - x * y / N
451    c1 = 0.5 * (xxx + xyy - x * k)
452
453    a2 = xy - x * y / N
454    b2 = yy - y * y / N
455    c2 = 0.5 * (xxy + yyy - y * k)
456
457    d = a2 * b1 - a1 * b2
458    if not d:
459        return offs, 0, n0
460    x0 = (b1 * c2 - b2 * c1) / d
461    y0 = (c1 - a1 * x0) / b1
462
463    R = np.sqrt(x0 * x0 + y0 * y0 - 1 / N * (2 * x0 * x + 2 * y0 * y - xx - yy))
464
465    c, _ = _rotate_points([x0, y0, 0], (0, 0, 1), n0)
466
467    return c[0] + offs, R, n0

Fits a circle through a set of 3D points, with a very fast non-iterative method.

Returns the tuple (center, radius, normal_to_circle).

trying to fit s-shaped points will inevitably lead to instabilities and circles of small radius.

References:

J.F. Crawford, Nucl. Instr. Meth. 211, 1983, 223-225.

def fit_plane(points, signed=False):
470def fit_plane(points, signed=False):
471    """
472    Fits a plane to a set of points.
473
474    Extra info is stored in `Plane.normal`, `Plane.center`, `Plane.variance`.
475
476    Arguments:
477    signed : (bool)
478        if True flip sign of the normal based on the ordering of the points
479
480    Examples:
481        - [fitline.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitline.py)
482
483            ![](https://vedo.embl.es/images/advanced/fitline.png)
484    """
485    if isinstance(points, Points):
486        points = points.points()
487    data = np.asarray(points)
488    datamean = data.mean(axis=0)
489    pts = data - datamean
490    res = np.linalg.svd(pts)
491    dd, vv = res[1], res[2]
492    n = np.cross(vv[0], vv[1])
493    if signed:
494        v = np.zeros_like(pts)
495        for i in range(len(pts) - 1):
496            vi = np.cross(pts[i], pts[i + 1])
497            v[i] = vi / np.linalg.norm(vi)
498        ns = np.mean(v, axis=0)  # normal to the points plane
499        if np.dot(n, ns) < 0:
500            n = -n
501    xyz_min = data.min(axis=0)
502    xyz_max = data.max(axis=0)
503    s = np.linalg.norm(xyz_max - xyz_min)
504    pla = vedo.shapes.Plane(datamean, n, s=[s, s])
505    pla.normal = n
506    pla.center = datamean
507    pla.variance = dd[2]
508    pla.name = "FitPlane"
509    pla.top = n
510    return pla

Fits a plane to a set of points.

Extra info is stored in Plane.normal, Plane.center, Plane.variance.

Arguments: signed : (bool) if True flip sign of the normal based on the ordering of the points

Examples:
def fit_sphere(coords):
513def fit_sphere(coords):
514    """
515    Fits a sphere to a set of points.
516
517    Extra info is stored in `Sphere.radius`, `Sphere.center`, `Sphere.residue`.
518
519    Examples:
520        - [fitspheres1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitspheres1.py)
521
522            ![](https://vedo.embl.es/images/advanced/fitspheres1.jpg)
523    """
524    if isinstance(coords, Points):
525        coords = coords.points()
526    coords = np.array(coords)
527    n = len(coords)
528    A = np.zeros((n, 4))
529    A[:, :-1] = coords * 2
530    A[:, 3] = 1
531    f = np.zeros((n, 1))
532    x = coords[:, 0]
533    y = coords[:, 1]
534    z = coords[:, 2]
535    f[:, 0] = x * x + y * y + z * z
536    try:
537        C, residue, rank, _ = np.linalg.lstsq(A, f, rcond=-1)  # solve AC=f
538    except:
539        C, residue, rank, _ = np.linalg.lstsq(A, f)  # solve AC=f
540    if rank < 4:
541        return None
542    t = (C[0] * C[0]) + (C[1] * C[1]) + (C[2] * C[2]) + C[3]
543    radius = np.sqrt(t)[0]
544    center = np.array([C[0][0], C[1][0], C[2][0]])
545    if len(residue) > 0:
546        residue = np.sqrt(residue[0]) / n
547    else:
548        residue = 0
549    sph = vedo.shapes.Sphere(center, radius, c=(1, 0, 0)).wireframe(1)
550    sph.radius = radius
551    sph.center = center
552    sph.residue = residue
553    sph.name = "FitSphere"
554    return sph

Fits a sphere to a set of points.

Extra info is stored in Sphere.radius, Sphere.center, Sphere.residue.

Examples:
def pca_ellipse(points, pvalue=0.673, res=60):
557def pca_ellipse(points, pvalue=0.673, res=60):
558    """
559    Show the oriented PCA 2D ellipse that contains the fraction `pvalue` of points.
560
561    Parameter `pvalue` sets the specified fraction of points inside the ellipse.
562    Normalized directions are stored in `ellipse.axis1`, `ellipse.axis12`
563    axes sizes are stored in `ellipse.va`, `ellipse.vb`
564
565    Arguments:
566        pvalue : (float)
567            ellipse will include this fraction of points
568        res : (int)
569            resolution of the ellipse
570
571    Examples:
572        - [histo_pca.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_pca.py)
573
574            ![](https://vedo.embl.es/images/pyplot/histo_pca.png)
575    """
576    from scipy.stats import f
577
578    if isinstance(points, Points):
579        coords = points.points()
580    else:
581        coords = points
582    if len(coords) < 4:
583        vedo.logger.warning("in pca_ellipse(), there are not enough points!")
584        return None
585
586    P = np.array(coords, dtype=float)[:,(0,1)]
587    cov = np.cov(P, rowvar=0)     # covariance matrix
588    _, s, R = np.linalg.svd(cov)  # singular value decomposition
589    p, n = s.size, P.shape[0]
590    fppf = f.ppf(pvalue, p, n-p)  # f % point function
591    ua, ub = np.sqrt(s*fppf/2)*2  # semi-axes (largest first)
592    center = np.mean(P, axis=0)   # centroid of the ellipse
593
594    matri = vtk.vtkMatrix4x4()
595    matri.DeepCopy((
596        R[0][0] * ua, R[1][0] * ub, 0, center[0],
597        R[0][1] * ua, R[1][1] * ub, 0, center[1],
598                   0,            0, 1,         0,
599        0, 0, 0, 1)
600    )
601    vtra = vtk.vtkTransform()
602    vtra.SetMatrix(matri)
603
604    elli = vedo.shapes.Circle(alpha=0.75, res=res)
605
606    # assign the transformation
607    elli.SetScale(vtra.GetScale())
608    elli.SetOrientation(vtra.GetOrientation())
609    elli.SetPosition(vtra.GetPosition())
610
611    elli.center = np.array(vtra.GetPosition())
612    elli.nr_of_points = n
613    elli.va = ua
614    elli.vb = ub
615    elli.axis1 = np.array(vtra.TransformPoint([1, 0, 0])) - elli.center
616    elli.axis2 = np.array(vtra.TransformPoint([0, 1, 0])) - elli.center
617    elli.axis1 /= np.linalg.norm(elli.axis1)
618    elli.axis2 /= np.linalg.norm(elli.axis2)
619    elli.transformation = vtra
620    elli.name = "PCAEllipse"
621    return elli

Show the oriented PCA 2D ellipse that contains the fraction pvalue of points.

Parameter pvalue sets the specified fraction of points inside the ellipse. Normalized directions are stored in ellipse.axis1, ellipse.axis12 axes sizes are stored in ellipse.va, ellipse.vb

Arguments:
  • pvalue : (float) ellipse will include this fraction of points
  • res : (int) resolution of the ellipse
Examples:
def pca_ellipsoid(points, pvalue=0.673):
624def pca_ellipsoid(points, pvalue=0.673):
625    """
626    Show the oriented PCA ellipsoid that contains fraction `pvalue` of points.
627
628    Parameter `pvalue` sets the specified fraction of points inside the ellipsoid.
629
630    Extra can be calculated with `mesh.asphericity()`, `mesh.asphericity_error()`
631    (asphericity is equal to 0 for a perfect sphere).
632
633    Axes sizes can be accessed in `ellips.va`, `ellips.vb`, `ellips.vc`,
634    normalized directions are stored in `ellips.axis1`, `ellips.axis12`
635    and `ellips.axis3`.
636
637    .. warning:: the meaning of `ellips.axis1`, has changed wrt `vedo==2022.1.0`
638        in that it's now the direction wrt the origin (e.i. the center is subtracted)
639
640    Examples:
641        - [pca_ellipsoid.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/pca_ellipsoid.py)
642
643            ![](https://vedo.embl.es/images/basic/pca.png)
644    """
645    from scipy.stats import f
646
647    if isinstance(points, Points):
648        coords = points.points()
649    else:
650        coords = points
651    if len(coords) < 4:
652        vedo.logger.warning("in pcaEllipsoid(), there are not enough points!")
653        return None
654
655    P = np.array(coords, ndmin=2, dtype=float)
656    cov = np.cov(P, rowvar=0)     # covariance matrix
657    _, s, R = np.linalg.svd(cov)  # singular value decomposition
658    p, n = s.size, P.shape[0]
659    fppf = f.ppf(pvalue, p, n-p)*(n-1)*p*(n+1)/n/(n-p)  # f % point function
660    cfac = 1 + 6/(n-1)            # correction factor for low statistics
661    ua, ub, uc = np.sqrt(s*fppf)/cfac  # semi-axes (largest first)
662    center = np.mean(P, axis=0)   # centroid of the hyperellipsoid
663
664    elli = vedo.shapes.Ellipsoid((0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), alpha=0.25)
665
666    matri = vtk.vtkMatrix4x4()
667    matri.DeepCopy((
668        R[0][0] * ua*2, R[1][0] * ub*2, R[2][0] * uc*2, center[0],
669        R[0][1] * ua*2, R[1][1] * ub*2, R[2][1] * uc*2, center[1],
670        R[0][2] * ua*2, R[1][2] * ub*2, R[2][2] * uc*2, center[2],
671        0, 0, 0, 1)
672    )
673    vtra = vtk.vtkTransform()
674    vtra.SetMatrix(matri)
675
676    # assign the transformation
677    elli.SetScale(vtra.GetScale())
678    elli.SetOrientation(vtra.GetOrientation())
679    elli.SetPosition(vtra.GetPosition())
680
681    elli.center = np.array(vtra.GetPosition())
682    elli.nr_of_points = n
683    elli.va = ua
684    elli.vb = ub
685    elli.vc = uc
686    elli.axis1 = np.array(vtra.TransformPoint([1, 0, 0])) - elli.center
687    elli.axis2 = np.array(vtra.TransformPoint([0, 1, 0])) - elli.center
688    elli.axis3 = np.array(vtra.TransformPoint([0, 0, 1])) - elli.center
689    elli.axis1 /= np.linalg.norm(elli.axis1)
690    elli.axis2 /= np.linalg.norm(elli.axis2)
691    elli.axis3 /= np.linalg.norm(elli.axis3)
692    elli.transformation = vtra
693    elli.name = "PCAEllipsoid"
694    return elli

Show the oriented PCA ellipsoid that contains fraction pvalue of points.

Parameter pvalue sets the specified fraction of points inside the ellipsoid.

Extra can be calculated with mesh.asphericity(), mesh.asphericity_error() (asphericity is equal to 0 for a perfect sphere).

Axes sizes can be accessed in ellips.va, ellips.vb, ellips.vc, normalized directions are stored in ellips.axis1, ellips.axis12 and ellips.axis3.

the meaning of ellips.axis1, has changed wrt vedo==2022.1.0

in that it's now the direction wrt the origin (e.i. the center is subtracted)

Examples: