vedo.shapes

Submodule to generate simple and complex geometric shapes

   1#!/usr/bin/env python3
   2# -*- coding: utf-8 -*-
   3import os
   4from typing import List, Union, Any
   5from functools import lru_cache
   6from weakref import ref as weak_ref_to
   7
   8import numpy as np
   9import vedo.vtkclasses as vtki
  10
  11import vedo
  12from vedo import settings
  13from vedo.transformations import LinearTransform, pol2cart, cart2spher, spher2cart
  14from vedo.colors import cmaps_names, get_color, printc
  15from vedo import utils
  16from vedo.pointcloud import Points, merge
  17from vedo.mesh import Mesh
  18from vedo.image import Image
  19
  20__docformat__ = "google"
  21
  22__doc__ = """
  23Submodule to generate simple and complex geometric shapes
  24
  25![](https://vedo.embl.es/images/basic/extrude.png)
  26"""
  27
  28__all__ = [
  29    "Marker",
  30    "Line",
  31    "DashedLine",
  32    "RoundedLine",
  33    "Tube",
  34    "Tubes",
  35    "ThickTube",
  36    "Lines",
  37    "Spline",
  38    "KSpline",
  39    "CSpline",
  40    "Bezier",
  41    "Brace",
  42    "NormalLines",
  43    "Ribbon",
  44    "Arrow",
  45    "Arrows",
  46    "Arrow2D",
  47    "Arrows2D",
  48    "FlatArrow",
  49    "Polygon",
  50    "Triangle",
  51    "Rectangle",
  52    "Disc",
  53    "Circle",
  54    "GeoCircle",
  55    "Arc",
  56    "Star",
  57    "Star3D",
  58    "Cross3D",
  59    "IcoSphere",
  60    "Sphere",
  61    "Spheres",
  62    "Earth",
  63    "Ellipsoid",
  64    "Grid",
  65    "TessellatedBox",
  66    "Plane",
  67    "Box",
  68    "Cube",
  69    "Spring",
  70    "Cylinder",
  71    "Cone",
  72    "Pyramid",
  73    "Torus",
  74    "Paraboloid",
  75    "Hyperboloid",
  76    "TextBase",
  77    "Text3D",
  78    "Text2D",
  79    "CornerAnnotation",
  80    "Latex",
  81    "Glyph",
  82    "Tensors",
  83    "ParametricShape",
  84    "ConvexHull",
  85    "VedoLogo",
  86]
  87
  88##############################################
  89_reps = (
  90    (":nabla", "∇"),
  91    (":inf", "∞"),
  92    (":rightarrow", "→"),
  93    (":leftarrow", "←"),
  94    (":partial", "∂"),
  95    (":sqrt", "√"),
  96    (":approx", "≈"),
  97    (":neq", "≠"),
  98    (":leq", "≤"),
  99    (":geq", "≥"),
 100    (":foreach", "∀"),
 101    (":permille", "‰"),
 102    (":euro", "€"),
 103    (":dot", "·"),
 104    (":int", "∫"),
 105    (":pm", "±"),
 106    (":times", "×"),
 107    (":Gamma", "Γ"),
 108    (":Delta", "Δ"),
 109    (":Theta", "Θ"),
 110    (":Lambda", "Λ"),
 111    (":Pi", "Π"),
 112    (":Sigma", "Σ"),
 113    (":Phi", "Φ"),
 114    (":Chi", "X"),
 115    (":Xi", "Ξ"),
 116    (":Psi", "Ψ"),
 117    (":Omega", "Ω"),
 118    (":alpha", "α"),
 119    (":beta", "β"),
 120    (":gamma", "γ"),
 121    (":delta", "δ"),
 122    (":epsilon", "ε"),
 123    (":zeta", "ζ"),
 124    (":eta", "η"),
 125    (":theta", "θ"),
 126    (":kappa", "κ"),
 127    (":lambda", "λ"),
 128    (":mu", "μ"),
 129    (":lowerxi", "ξ"),
 130    (":nu", "ν"),
 131    (":pi", "π"),
 132    (":rho", "ρ"),
 133    (":sigma", "σ"),
 134    (":tau", "τ"),
 135    (":varphi", "φ"),
 136    (":phi", "φ"),
 137    (":chi", "χ"),
 138    (":psi", "ψ"),
 139    (":omega", "ω"),
 140    (":circ", "°"),
 141    (":onehalf", "½"),
 142    (":onefourth", "¼"),
 143    (":threefourths", "¾"),
 144    (":^1", "¹"),
 145    (":^2", "²"),
 146    (":^3", "³"),
 147    (":,", "~"),
 148)
 149
 150
 151########################################################################
 152class Glyph(Mesh):
 153    """
 154    At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with
 155    various orientation options and coloring.
 156
 157    The input can also be a simple list of 2D or 3D coordinates.
 158    Color can be specified as a colormap which maps the size of the orientation
 159    vectors in `orientation_array`.
 160    """
 161
 162    def __init__(
 163        self,
 164        mesh,
 165        glyph,
 166        orientation_array=None,
 167        scale_by_scalar=False,
 168        scale_by_vector_size=False,
 169        scale_by_vector_components=False,
 170        color_by_scalar=False,
 171        color_by_vector_size=False,
 172        c="k8",
 173        alpha=1.0,
 174    ) -> None:
 175        """
 176        Arguments:
 177            orientation_array: (list, str, vtkArray)
 178                list of vectors, `vtkArray` or name of an already existing pointdata array
 179            scale_by_scalar : (bool)
 180                glyph mesh is scaled by the active scalars
 181            scale_by_vector_size : (bool)
 182                glyph mesh is scaled by the size of the vectors
 183            scale_by_vector_components : (bool)
 184                glyph mesh is scaled by the 3 vectors components
 185            color_by_scalar : (bool)
 186                glyph mesh is colored based on the scalar value
 187            color_by_vector_size : (bool)
 188                glyph mesh is colored based on the vector size
 189
 190        Examples:
 191            - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py)
 192            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
 193
 194            ![](https://vedo.embl.es/images/basic/glyphs.png)
 195        """
 196        if utils.is_sequence(mesh):
 197            # create a cloud of points
 198            poly = utils.buildPolyData(mesh)
 199        else:
 200            poly = mesh.dataset
 201
 202        cmap = ""
 203        if isinstance(c, str) and c in cmaps_names:
 204            cmap = c
 205            c = None
 206        elif utils.is_sequence(c):  # user passing an array of point colors
 207            ucols = vtki.vtkUnsignedCharArray()
 208            ucols.SetNumberOfComponents(3)
 209            ucols.SetName("GlyphRGB")
 210            for col in c:
 211                cl = get_color(col)
 212                ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255)
 213            poly.GetPointData().AddArray(ucols)
 214            poly.GetPointData().SetActiveScalars("GlyphRGB")
 215            c = None
 216
 217        gly = vtki.vtkGlyph3D()
 218        gly.GeneratePointIdsOn()
 219        gly.SetInputData(poly)
 220        try:
 221            gly.SetSourceData(glyph)
 222        except TypeError:
 223            gly.SetSourceData(glyph.dataset)
 224
 225        if scale_by_scalar:
 226            gly.SetScaleModeToScaleByScalar()
 227        elif scale_by_vector_size:
 228            gly.SetScaleModeToScaleByVector()
 229        elif scale_by_vector_components:
 230            gly.SetScaleModeToScaleByVectorComponents()
 231        else:
 232            gly.SetScaleModeToDataScalingOff()
 233
 234        if color_by_vector_size:
 235            gly.SetVectorModeToUseVector()
 236            gly.SetColorModeToColorByVector()
 237        elif color_by_scalar:
 238            gly.SetColorModeToColorByScalar()
 239        else:
 240            gly.SetColorModeToColorByScale()
 241
 242        if orientation_array is not None:
 243            gly.OrientOn()
 244            if isinstance(orientation_array, str):
 245                if orientation_array.lower() == "normals":
 246                    gly.SetVectorModeToUseNormal()
 247                else:  # passing a name
 248                    poly.GetPointData().SetActiveVectors(orientation_array)
 249                    gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array)
 250                    gly.SetVectorModeToUseVector()
 251            elif utils.is_sequence(orientation_array):  # passing a list
 252                varr = vtki.vtkFloatArray()
 253                varr.SetNumberOfComponents(3)
 254                varr.SetName("glyph_vectors")
 255                for v in orientation_array:
 256                    varr.InsertNextTuple(v)
 257                poly.GetPointData().AddArray(varr)
 258                poly.GetPointData().SetActiveVectors("glyph_vectors")
 259                gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors")
 260                gly.SetVectorModeToUseVector()
 261
 262        gly.Update()
 263
 264        super().__init__(gly.GetOutput(), c, alpha)
 265        self.flat()
 266
 267        if cmap:
 268            self.cmap(cmap, "VectorMagnitude")
 269        elif c is None:
 270            self.pointdata.select("GlyphRGB")
 271
 272        self.name = "Glyph"
 273
 274
 275class Tensors(Mesh):
 276    """
 277    Geometric representation of tensors defined on a domain or set of points.
 278    Tensors can be scaled and/or rotated according to the source at each input point.
 279    Scaling and rotation is controlled by the eigenvalues/eigenvectors of the
 280    symmetrical part of the tensor as follows:
 281
 282    For each tensor, the eigenvalues (and associated eigenvectors) are sorted
 283    to determine the major, medium, and minor eigenvalues/eigenvectors.
 284    The eigenvalue decomposition only makes sense for symmetric tensors,
 285    hence the need to only consider the symmetric part of the tensor,
 286    which is `1/2*(T+T.transposed())`.
 287    """
 288
 289    def __init__(
 290        self,
 291        domain,
 292        source="ellipsoid",
 293        use_eigenvalues=True,
 294        is_symmetric=True,
 295        three_axes=False,
 296        scale=1.0,
 297        max_scale=None,
 298        length=None,
 299        res=24,
 300        c=None,
 301        alpha=1.0,
 302    ) -> None:
 303        """
 304        Arguments:
 305            source : (str, Mesh)
 306                preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object.
 307            use_eigenvalues : (bool)
 308                color source glyph using the eigenvalues or by scalars
 309            three_axes : (bool)
 310                if `False` scale the source in the x-direction,
 311                the medium in the y-direction, and the minor in the z-direction.
 312                Then, the source is rotated so that the glyph's local x-axis lies
 313                along the major eigenvector, y-axis along the medium eigenvector,
 314                and z-axis along the minor.
 315
 316                If `True` three sources are produced, each of them oriented along an eigenvector
 317                and scaled according to the corresponding eigenvector.
 318            is_symmetric : (bool)
 319                If `True` each source glyph is mirrored (2 or 6 glyphs will be produced).
 320                The x-axis of the source glyph will correspond to the eigenvector on output.
 321            length : (float)
 322                distance from the origin to the tip of the source glyph along the x-axis
 323            scale : (float)
 324                scaling factor of the source glyph.
 325            max_scale : (float)
 326                clamp scaling at this factor.
 327
 328        Examples:
 329            - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py)
 330            - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py)
 331            - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py)
 332
 333            ![](https://vedo.embl.es/images/volumetric/tensor_grid.png)
 334        """
 335        if isinstance(source, Points):
 336            src = source.dataset
 337        else: # is string
 338            if "ellip" in source:
 339                src = vtki.new("SphereSource")
 340                src.SetPhiResolution(res)
 341                src.SetThetaResolution(res*2)
 342            elif "cyl" in source:
 343                src = vtki.new("CylinderSource")
 344                src.SetResolution(res)
 345                src.CappingOn()
 346            elif source == "cube":
 347                src = vtki.new("CubeSource")
 348            else:
 349                vedo.logger.error(f"Unknown source type {source}")
 350                raise ValueError()
 351            src.Update()
 352            src = src.GetOutput()
 353
 354        tg = vtki.new("TensorGlyph")
 355        if isinstance(domain, vtki.vtkPolyData):
 356            tg.SetInputData(domain)
 357        else:
 358            tg.SetInputData(domain.dataset)
 359        tg.SetSourceData(src)
 360
 361        if c is None:
 362            tg.ColorGlyphsOn()
 363        else:
 364            tg.ColorGlyphsOff()
 365
 366        tg.SetSymmetric(int(is_symmetric))
 367
 368        if length is not None:
 369            tg.SetLength(length)
 370        if use_eigenvalues:
 371            tg.ExtractEigenvaluesOn()
 372            tg.SetColorModeToEigenvalues()
 373        else:
 374            tg.SetColorModeToScalars()
 375
 376        tg.SetThreeGlyphs(three_axes)
 377        tg.ScalingOn()
 378        tg.SetScaleFactor(scale)
 379        if max_scale is None:
 380            tg.ClampScalingOn()
 381            max_scale = scale * 10
 382        tg.SetMaxScaleFactor(max_scale)
 383
 384        tg.Update()
 385        tgn = vtki.new("PolyDataNormals")
 386        tgn.ComputeCellNormalsOff()
 387        tgn.SetInputData(tg.GetOutput())
 388        tgn.Update()
 389
 390        super().__init__(tgn.GetOutput(), c, alpha)
 391        self.name = "Tensors"
 392
 393
 394class Line(Mesh):
 395    """
 396    Build the line segment between point `p0` and point `p1`.
 397
 398    If `p0` is already a list of points, return the line connecting them.
 399
 400    A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`.
 401    """
 402
 403    def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None:
 404        """
 405        Arguments:
 406            closed : (bool)
 407                join last to first point
 408            res : (int)
 409                resolution, number of points along the line
 410                (only relevant if only 2 points are specified)
 411            lw : (int)
 412                line width in pixel units
 413        """
 414
 415        if isinstance(p1, Points):
 416            p1 = p1.pos()
 417            if isinstance(p0, Points):
 418                p0 = p0.pos()
 419        try:
 420            p0 = p0.dataset
 421        except AttributeError:
 422            pass
 423
 424        if isinstance(p0, vtki.vtkPolyData):
 425            poly = p0
 426            top  = np.array([0,0,1])
 427            base = np.array([0,0,0])
 428
 429        elif utils.is_sequence(p0[0]): # detect if user is passing a list of points
 430
 431            p0 = utils.make3d(p0)
 432            ppoints = vtki.vtkPoints()  # Generate the polyline
 433            ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32))
 434            lines = vtki.vtkCellArray()
 435            npt = len(p0)
 436            if closed:
 437                lines.InsertNextCell(npt + 1)
 438            else:
 439                lines.InsertNextCell(npt)
 440            for i in range(npt):
 441                lines.InsertCellPoint(i)
 442            if closed:
 443                lines.InsertCellPoint(0)
 444            poly = vtki.vtkPolyData()
 445            poly.SetPoints(ppoints)
 446            poly.SetLines(lines)
 447            top = p0[-1]
 448            base = p0[0]
 449            if res != 2:
 450                printc(f"Warning: calling Line(res={res}), try remove []?", c='y')
 451                res = 2
 452
 453        else:  # or just 2 points to link
 454
 455            line_source = vtki.new("LineSource")
 456            p0 = utils.make3d(p0)
 457            p1 = utils.make3d(p1)
 458            line_source.SetPoint1(p0)
 459            line_source.SetPoint2(p1)
 460            line_source.SetResolution(res - 1)
 461            line_source.Update()
 462            poly = line_source.GetOutput()
 463            top = np.asarray(p1, dtype=float)
 464            base = np.asarray(p0, dtype=float)
 465
 466        super().__init__(poly, c, alpha)
 467
 468        self.slope: List[float] = []  # populated by analysis.fit_line
 469        self.center: List[float] = []
 470        self.variances: List[float] = []
 471
 472        self.coefficients: List[float] = []  # populated by pyplot.fit()
 473        self.covariance_matrix: List[float] = []
 474        self.coefficient_errors: List[float] = []
 475        self.monte_carlo_coefficients: List[float] = []
 476        self.reduced_chi2 = -1
 477        self.ndof = 0
 478        self.data_sigma = 0
 479        self.error_lines: List[Any] = []
 480        self.error_band = None
 481        self.res = res
 482
 483        self.lw(lw)
 484        self.properties.LightingOff()
 485        self.actor.PickableOff()
 486        self.actor.DragableOff()
 487        self.base = base
 488        self.top = top
 489        self.name = "Line"
 490
 491    def clone(self, deep=True) -> "Line":
 492        """
 493        Return a copy of the ``Line`` object.
 494
 495        Example:
 496            ```python
 497            from vedo import *
 498            ln1 = Line([1,1,1], [2,2,2], lw=3).print()
 499            ln2 = ln1.clone().shift(0,0,1).c('red').print()
 500            show(ln1, ln2, axes=1, viewup='z').close()
 501            ```
 502            ![](https://vedo.embl.es/images/feats/line_clone.png)
 503        """
 504        poly = vtki.vtkPolyData()
 505        if deep:
 506            poly.DeepCopy(self.dataset)
 507        else:
 508            poly.ShallowCopy(self.dataset)
 509        ln = Line(poly)
 510        ln.copy_properties_from(self)
 511        ln.transform = self.transform.clone()
 512        ln.name = self.name
 513        ln.base = self.base
 514        ln.top = self.top
 515        ln.pipeline = utils.OperationNode(
 516            "clone", parents=[self], shape="diamond", c="#edede9")
 517        return ln
 518
 519    def linecolor(self, lc=None) -> "Line":
 520        """Assign a color to the line"""
 521        # overrides mesh.linecolor which would have no effect here
 522        return self.color(lc)
 523
 524    def eval(self, x: float) -> np.ndarray:
 525        """
 526        Calculate the position of an intermediate point
 527        as a fraction of the length of the line,
 528        being x=0 the first point and x=1 the last point.
 529        This corresponds to an imaginary point that travels along the line
 530        at constant speed.
 531
 532        Can be used in conjunction with `lin_interpolate()`
 533        to map any range to the [0,1] range.
 534        """
 535        distance1 = 0.0
 536        length = self.length()
 537        pts = self.vertices
 538        for i in range(1, len(pts)):
 539            p0 = pts[i - 1]
 540            p1 = pts[i]
 541            seg = p1 - p0
 542            distance0 = distance1
 543            distance1 += np.linalg.norm(seg)
 544            w1 = distance1 / length
 545            if w1 >= x:
 546                break
 547        w0 = distance0 / length
 548        v = p0 + seg * (x - w0) / (w1 - w0)
 549        return v
 550
 551    def find_index_at_position(self, p) -> float:
 552        """
 553        Find the index of the line vertex that is closest to the point `p`.
 554        Note that the returned index can be fractional if `p` is not exactly
 555        one of the vertices of the line.
 556        """
 557        q = self.closest_point(p)
 558        a, b = sorted(self.closest_point(q, n=2, return_point_id=True))
 559        pts = self.vertices
 560        d = np.linalg.norm(pts[a] - pts[b])
 561        t = a + np.linalg.norm(pts[a] - q) / d
 562        return t
 563
 564    def pattern(self, stipple, repeats=10) -> "Line":
 565        """
 566        Define a stipple pattern for dashing the line.
 567        Pass the stipple pattern as a string like `'- - -'`.
 568        Repeats controls the number of times the pattern repeats in a single segment.
 569
 570        Examples are: `'- -', '--  -  --'`, etc.
 571
 572        The resolution of the line (nr of points) can affect how pattern will show up.
 573
 574        Example:
 575            ```python
 576            from vedo import Line
 577            pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]]
 578            ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10)
 579            ln.show(axes=1).close()
 580            ```
 581            ![](https://vedo.embl.es/images/feats/line_pattern.png)
 582        """
 583        stipple = str(stipple) * int(2 * repeats)
 584        dimension = len(stipple)
 585
 586        image = vtki.vtkImageData()
 587        image.SetDimensions(dimension, 1, 1)
 588        image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4)
 589        image.SetExtent(0, dimension - 1, 0, 0, 0, 0)
 590        i_dim = 0
 591        while i_dim < dimension:
 592            for i in range(dimension):
 593                image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255)
 594                image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255)
 595                image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255)
 596                if stipple[i] == " ":
 597                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0)
 598                else:
 599                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255)
 600                i_dim += 1
 601
 602        poly = self.dataset
 603
 604        # Create texture coordinates
 605        tcoords = vtki.vtkDoubleArray()
 606        tcoords.SetName("TCoordsStippledLine")
 607        tcoords.SetNumberOfComponents(1)
 608        tcoords.SetNumberOfTuples(poly.GetNumberOfPoints())
 609        for i in range(poly.GetNumberOfPoints()):
 610            tcoords.SetTypedTuple(i, [i / 2])
 611        poly.GetPointData().SetTCoords(tcoords)
 612        poly.GetPointData().Modified()
 613        texture = vtki.vtkTexture()
 614        texture.SetInputData(image)
 615        texture.InterpolateOff()
 616        texture.RepeatOn()
 617        self.actor.SetTexture(texture)
 618        return self
 619
 620    def length(self) -> float:
 621        """Calculate length of the line."""
 622        distance = 0.0
 623        pts = self.vertices
 624        for i in range(1, len(pts)):
 625            distance += np.linalg.norm(pts[i] - pts[i - 1])
 626        return distance
 627
 628    def tangents(self) -> np.ndarray:
 629        """
 630        Compute the tangents of a line in space.
 631
 632        Example:
 633            ```python
 634            from vedo import *
 635            shape = Assembly(dataurl+"timecourse1d.npy")[58]
 636            pts = shape.rotate_x(30).vertices
 637            tangents = Line(pts).tangents()
 638            arrs = Arrows(pts, pts+tangents, c='blue9')
 639            show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()
 640            ```
 641            ![](https://vedo.embl.es/images/feats/line_tangents.png)
 642        """
 643        v = np.gradient(self.vertices)[0]
 644        ds_dt = np.linalg.norm(v, axis=1)
 645        tangent = np.array([1 / ds_dt] * 3).transpose() * v
 646        return tangent
 647
 648    def curvature(self) -> np.ndarray:
 649        """
 650        Compute the signed curvature of a line in space.
 651        The signed is computed assuming the line is about coplanar to the xy plane.
 652
 653        Example:
 654            ```python
 655            from vedo import *
 656            from vedo.pyplot import plot
 657            shape = Assembly(dataurl+"timecourse1d.npy")[55]
 658            curvs = Line(shape.vertices).curvature()
 659            shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w')
 660            shape.render_lines_as_tubes().lw(12)
 661            pp = plot(curvs, ac='white', lc='yellow5')
 662            show(shape, pp, N=2, bg='bb', sharecam=False).close()
 663            ```
 664            ![](https://vedo.embl.es/images/feats/line_curvature.png)
 665        """
 666        v = np.gradient(self.vertices)[0]
 667        a = np.gradient(v)[0]
 668        av = np.cross(a, v)
 669        mav = np.linalg.norm(av, axis=1)
 670        mv = utils.mag2(v)
 671        val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5)
 672        val[0] = val[1]
 673        val[-1] = val[-2]
 674        return val
 675
 676    def compute_curvature(self, method=0) -> "Line":
 677        """
 678        Add a pointdata array named 'Curvatures' which contains
 679        the curvature value at each point.
 680
 681        NB: keyword `method` is overridden in Mesh and has no effect here.
 682        """
 683        # overrides mesh.compute_curvature
 684        curvs = self.curvature()
 685        vmin, vmax = np.min(curvs), np.max(curvs)
 686        if vmin < 0 and vmax > 0:
 687            v = max(-vmin, vmax)
 688            self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature")
 689        else:
 690            self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature")
 691        return self
 692
 693    def plot_scalar(
 694            self,
 695            radius=0.0, 
 696            height=1.1,
 697            normal=(),
 698            camera=None,
 699        ) -> "Line":
 700        """
 701        Generate a new `Line` which plots the active scalar along the line.
 702
 703        Arguments:
 704            radius : (float)
 705                distance radius to the line
 706            height: (float)
 707                height of the plot
 708            normal: (list)
 709                normal vector to the plane of the plot
 710            camera: (vtkCamera) 
 711                camera object to use for the plot orientation
 712        
 713        Example:
 714            ```python
 715            from vedo import *
 716            circle = Circle(res=360).rotate_y(20)
 717            pts = circle.vertices
 718            bore = Line(pts).lw(5)
 719            values = np.arctan2(pts[:,1], pts[:,0])
 720            bore.pointdata["scalars"] = values + np.random.randn(360)/5
 721            vap = bore.plot_scalar(radius=0, height=1)
 722            show(bore, vap, axes=1, viewup='z').close()
 723            ```
 724            ![](https://vedo.embl.es/images/feats/line_plot_scalar.png)
 725        """
 726        ap = vtki.new("ArcPlotter")
 727        ap.SetInputData(self.dataset)
 728        ap.SetCamera(camera)
 729        ap.SetRadius(radius)
 730        ap.SetHeight(height)
 731        if len(normal)>0:
 732            ap.UseDefaultNormalOn()
 733            ap.SetDefaultNormal(normal)
 734        ap.Update()
 735        vap = Line(ap.GetOutput())
 736        vap.linewidth(3).lighting('off')
 737        vap.name = "ArcPlot"
 738        return vap
 739
 740    def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh":
 741        """
 742        Sweep the `Line` along the specified vector direction.
 743
 744        Returns a `Mesh` surface.
 745        Line position is updated to allow for additional sweepings.
 746
 747        Example:
 748            ```python
 749            from vedo import Line, show
 750            aline = Line([(0,0,0),(1,3,0),(2,4,0)])
 751            surf1 = aline.sweep((1,0.2,0), res=3)
 752            surf2 = aline.sweep((0.2,0,1)).alpha(0.5)
 753            aline.color('r').linewidth(4)
 754            show(surf1, surf2, aline, axes=1).close()
 755            ```
 756            ![](https://vedo.embl.es/images/feats/sweepline.png)
 757        """
 758        line = self.dataset
 759        rows = line.GetNumberOfPoints()
 760
 761        spacing = 1 / res
 762        surface = vtki.vtkPolyData()
 763
 764        res += 1
 765        npts = rows * res
 766        npolys = (rows - 1) * (res - 1)
 767        points = vtki.vtkPoints()
 768        points.Allocate(npts)
 769
 770        cnt = 0
 771        x = [0.0, 0.0, 0.0]
 772        for row in range(rows):
 773            for col in range(res):
 774                p = [0.0, 0.0, 0.0]
 775                line.GetPoint(row, p)
 776                x[0] = p[0] + direction[0] * col * spacing
 777                x[1] = p[1] + direction[1] * col * spacing
 778                x[2] = p[2] + direction[2] * col * spacing
 779                points.InsertPoint(cnt, x)
 780                cnt += 1
 781
 782        # Generate the quads
 783        polys = vtki.vtkCellArray()
 784        polys.Allocate(npolys * 4)
 785        pts = [0, 0, 0, 0]
 786        for row in range(rows - 1):
 787            for col in range(res - 1):
 788                pts[0] = col + row * res
 789                pts[1] = pts[0] + 1
 790                pts[2] = pts[0] + res + 1
 791                pts[3] = pts[0] + res
 792                polys.InsertNextCell(4, pts)
 793        surface.SetPoints(points)
 794        surface.SetPolys(polys)
 795        asurface = Mesh(surface)
 796        asurface.copy_properties_from(self)
 797        asurface.lighting("default")
 798        self.vertices = self.vertices + direction
 799        return asurface
 800
 801    def reverse(self):
 802        """Reverse the points sequence order."""
 803        pts = np.flip(self.vertices, axis=0)
 804        self.vertices = pts
 805        return self
 806
 807
 808class DashedLine(Mesh):
 809    """
 810    Consider using `Line.pattern()` instead.
 811
 812    Build a dashed line segment between points `p0` and `p1`.
 813    If `p0` is a list of points returns the line connecting them.
 814    A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`.
 815    """
 816
 817    def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None:
 818        """
 819        Arguments:
 820            closed : (bool)
 821                join last to first point
 822            spacing : (float)
 823                relative size of the dash
 824            lw : (int)
 825                line width in pixels
 826        """
 827        if isinstance(p1, vtki.vtkActor):
 828            p1 = p1.GetPosition()
 829            if isinstance(p0, vtki.vtkActor):
 830                p0 = p0.GetPosition()
 831        if isinstance(p0, Points):
 832            p0 = p0.vertices
 833
 834        # detect if user is passing a 2D list of points as p0=xlist, p1=ylist:
 835        if len(p0) > 3:
 836            if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1):
 837                # assume input is 2D xlist, ylist
 838                p0 = np.stack((p0, p1), axis=1)
 839                p1 = None
 840            p0 = utils.make3d(p0)
 841            if closed:
 842                p0 = np.append(p0, [p0[0]], axis=0)
 843
 844        if p1 is not None:  # assume passing p0=[x,y]
 845            if len(p0) == 2 and not utils.is_sequence(p0[0]):
 846                p0 = (p0[0], p0[1], 0)
 847            if len(p1) == 2 and not utils.is_sequence(p1[0]):
 848                p1 = (p1[0], p1[1], 0)
 849
 850        # detect if user is passing a list of points:
 851        if utils.is_sequence(p0[0]):
 852            listp = p0
 853        else:  # or just 2 points to link
 854            listp = [p0, p1]
 855
 856        listp = np.array(listp)
 857        if listp.shape[1] == 2:
 858            listp = np.c_[listp, np.zeros(listp.shape[0])]
 859
 860        xmn = np.min(listp, axis=0)
 861        xmx = np.max(listp, axis=0)
 862        dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10
 863        if not dlen:
 864            super().__init__(vtki.vtkPolyData(), c, alpha)
 865            self.name = "DashedLine (void)"
 866            return
 867
 868        qs = []
 869        for ipt in range(len(listp) - 1):
 870            p0 = listp[ipt]
 871            p1 = listp[ipt + 1]
 872            v = p1 - p0
 873            vdist = np.linalg.norm(v)
 874            n1 = int(vdist / dlen)
 875            if not n1:
 876                continue
 877
 878            res = 0.0
 879            for i in range(n1 + 2):
 880                ist = (i - 0.5) / n1
 881                ist = max(ist, 0)
 882                qi = p0 + v * (ist - res / vdist)
 883                if ist > 1:
 884                    qi = p1
 885                    res = np.linalg.norm(qi - p1)
 886                    qs.append(qi)
 887                    break
 888                qs.append(qi)
 889
 890        polylns = vtki.new("AppendPolyData")
 891        for i, q1 in enumerate(qs):
 892            if not i % 2:
 893                continue
 894            q0 = qs[i - 1]
 895            line_source = vtki.new("LineSource")
 896            line_source.SetPoint1(q0)
 897            line_source.SetPoint2(q1)
 898            line_source.Update()
 899            polylns.AddInputData(line_source.GetOutput())
 900        polylns.Update()
 901
 902        super().__init__(polylns.GetOutput(), c, alpha)
 903        self.lw(lw).lighting("off")
 904        self.base = listp[0]
 905        if closed:
 906            self.top = listp[-2]
 907        else:
 908            self.top = listp[-1]
 909        self.name = "DashedLine"
 910
 911
 912class RoundedLine(Mesh):
 913    """
 914    Create a 2D line of specified thickness (in absolute units) passing through
 915    a list of input points. Borders of the line are rounded.
 916    """
 917
 918    def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None:
 919        """
 920        Arguments:
 921            pts : (list)
 922                a list of points in 2D or 3D (z will be ignored).
 923            lw : (float)
 924                thickness of the line.
 925            res : (int)
 926                resolution of the rounded regions
 927
 928        Example:
 929            ```python
 930            from vedo import *
 931            pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)]
 932            ln = Line(pts).z(0.01)
 933            ln.color("red5").linewidth(2)
 934            rl = RoundedLine(pts, 0.6)
 935            show(Points(pts), ln, rl, axes=1).close()
 936            ```
 937            ![](https://vedo.embl.es/images/feats/rounded_line.png)
 938        """
 939        pts = utils.make3d(pts)
 940
 941        def _getpts(pts, revd=False):
 942
 943            if revd:
 944                pts = list(reversed(pts))
 945
 946            if len(pts) == 2:
 947                p0, p1 = pts
 948                v = p1 - p0
 949                dv = np.linalg.norm(v)
 950                nv = np.cross(v, (0, 0, -1))
 951                nv = nv / np.linalg.norm(nv) * lw
 952                return [p0 + nv, p1 + nv]
 953
 954            ptsnew = []
 955            for k in range(len(pts) - 2):
 956                p0 = pts[k]
 957                p1 = pts[k + 1]
 958                p2 = pts[k + 2]
 959                v = p1 - p0
 960                u = p2 - p1
 961                du = np.linalg.norm(u)
 962                dv = np.linalg.norm(v)
 963                nv = np.cross(v, (0, 0, -1))
 964                nv = nv / np.linalg.norm(nv) * lw
 965                nu = np.cross(u, (0, 0, -1))
 966                nu = nu / np.linalg.norm(nu) * lw
 967                uv = np.cross(u, v)
 968                if k == 0:
 969                    ptsnew.append(p0 + nv)
 970                if uv[2] <= 0:
 971                    # the following computation can return a value
 972                    # ever so slightly > 1.0 causing arccos to fail.
 973                    uv_arg = np.dot(u, v) / du / dv
 974                    if uv_arg > 1.0:
 975                        # since the argument to arcos is 1, simply
 976                        # assign alpha to 0.0 without calculating the
 977                        # arccos
 978                        alpha = 0.0
 979                    else:
 980                        alpha = np.arccos(uv_arg)
 981                    db = lw * np.tan(alpha / 2)
 982                    p1new = p1 + nv - v / dv * db
 983                    ptsnew.append(p1new)
 984                else:
 985                    p1a = p1 + nv
 986                    p1b = p1 + nu
 987                    for i in range(0, res + 1):
 988                        pab = p1a * (res - i) / res + p1b * i / res
 989                        vpab = pab - p1
 990                        vpab = vpab / np.linalg.norm(vpab) * lw
 991                        ptsnew.append(p1 + vpab)
 992                if k == len(pts) - 3:
 993                    ptsnew.append(p2 + nu)
 994                    if revd:
 995                        ptsnew.append(p2 - nu)
 996            return ptsnew
 997
 998        ptsnew = _getpts(pts) + _getpts(pts, revd=True)
 999
1000        ppoints = vtki.vtkPoints()  # Generate the polyline
1001        ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32))
1002        lines = vtki.vtkCellArray()
1003        npt = len(ptsnew)
1004        lines.InsertNextCell(npt)
1005        for i in range(npt):
1006            lines.InsertCellPoint(i)
1007        poly = vtki.vtkPolyData()
1008        poly.SetPoints(ppoints)
1009        poly.SetLines(lines)
1010        vct = vtki.new("ContourTriangulator")
1011        vct.SetInputData(poly)
1012        vct.Update()
1013
1014        super().__init__(vct.GetOutput(), c, alpha)
1015        self.flat()
1016        self.properties.LightingOff()
1017        self.name = "RoundedLine"
1018        self.base = ptsnew[0]
1019        self.top = ptsnew[-1]
1020
1021
1022class Lines(Mesh):
1023    """
1024    Build the line segments between two lists of points `start_pts` and `end_pts`.
1025    `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1026    """
1027
1028    def __init__(
1029        self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0
1030    ) -> None:
1031        """
1032        Arguments:
1033            scale : (float)
1034                apply a rescaling factor to the lengths.
1035            c : (color, int, str, list)
1036                color name, number, or list of [R,G,B] colors
1037            alpha : (float)
1038                opacity in range [0,1]
1039            lw : (int)
1040                line width in pixel units
1041            dotted : (bool)
1042                draw a dotted line
1043            res : (int)
1044                resolution, number of points along the line
1045                (only relevant if only 2 points are specified)
1046
1047        Examples:
1048            - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py)
1049
1050            ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png)
1051        """
1052
1053        if isinstance(start_pts, vtki.vtkPolyData):########
1054            super().__init__(start_pts, c, alpha)
1055            self.lw(lw).lighting("off")
1056            self.name = "Lines"
1057            return ########################################
1058
1059        if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line):
1060            # passing a list of Line, see tests/issues/issue_950.py
1061            polylns = vtki.new("AppendPolyData")
1062            for ln in start_pts:
1063                polylns.AddInputData(ln.dataset)
1064            polylns.Update()
1065
1066            super().__init__(polylns.GetOutput(), c, alpha)
1067            self.lw(lw).lighting("off")
1068            if dotted:
1069                self.properties.SetLineStipplePattern(0xF0F0)
1070                self.properties.SetLineStippleRepeatFactor(1)
1071            self.name = "Lines"
1072            return ########################################
1073
1074        if isinstance(start_pts, Points):
1075            start_pts = start_pts.vertices
1076        if isinstance(end_pts, Points):
1077            end_pts = end_pts.vertices
1078
1079        if end_pts is not None:
1080            start_pts = np.stack((start_pts, end_pts), axis=1)
1081
1082        polylns = vtki.new("AppendPolyData")
1083
1084        if not utils.is_ragged(start_pts):
1085
1086            for twopts in start_pts:
1087                line_source = vtki.new("LineSource")
1088                line_source.SetResolution(res)
1089                if len(twopts[0]) == 2:
1090                    line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0)
1091                else:
1092                    line_source.SetPoint1(twopts[0])
1093
1094                if scale == 1:
1095                    pt2 = twopts[1]
1096                else:
1097                    vers = (np.array(twopts[1]) - twopts[0]) * scale
1098                    pt2 = np.array(twopts[0]) + vers
1099
1100                if len(pt2) == 2:
1101                    line_source.SetPoint2(pt2[0], pt2[1], 0.0)
1102                else:
1103                    line_source.SetPoint2(pt2)
1104                polylns.AddInputConnection(line_source.GetOutputPort())
1105
1106        else:
1107
1108            polylns = vtki.new("AppendPolyData")
1109            for t in start_pts:
1110                t = utils.make3d(t)
1111                ppoints = vtki.vtkPoints()  # Generate the polyline
1112                ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32))
1113                lines = vtki.vtkCellArray()
1114                npt = len(t)
1115                lines.InsertNextCell(npt)
1116                for i in range(npt):
1117                    lines.InsertCellPoint(i)
1118                poly = vtki.vtkPolyData()
1119                poly.SetPoints(ppoints)
1120                poly.SetLines(lines)
1121                polylns.AddInputData(poly)
1122
1123        polylns.Update()
1124
1125        super().__init__(polylns.GetOutput(), c, alpha)
1126        self.lw(lw).lighting("off")
1127        if dotted:
1128            self.properties.SetLineStipplePattern(0xF0F0)
1129            self.properties.SetLineStippleRepeatFactor(1)
1130
1131        self.name = "Lines"
1132
1133
1134class Spline(Line):
1135    """
1136    Find the B-Spline curve through a set of points. This curve does not necessarily
1137    pass exactly through all the input points. Needs to import `scipy`.
1138    """
1139
1140    def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None:
1141        """
1142        Arguments:
1143            smooth : (float)
1144                smoothing factor.
1145                - 0 = interpolate points exactly [default].
1146                - 1 = average point positions.
1147            degree : (int)
1148                degree of the spline (between 1 and 5).
1149            easing : (str)
1150                control sensity of points along the spline.
1151                Available options are
1152                `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].`
1153                Can be used to create animations (move objects at varying speed).
1154                See e.g.: https://easings.net
1155            res : (int)
1156                number of points on the spline
1157
1158        See also: `CSpline` and `KSpline`.
1159
1160        Examples:
1161            - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py)
1162
1163                ![](https://vedo.embl.es/images/simulations/spline_ease.gif)
1164        """
1165        from scipy.interpolate import splprep, splev
1166
1167        if isinstance(points, Points):
1168            points = points.vertices
1169
1170        points = utils.make3d(points)
1171
1172        per = 0
1173        if closed:
1174            points = np.append(points, [points[0]], axis=0)
1175            per = 1
1176
1177        if res is None:
1178            res = len(points) * 10
1179
1180        points = np.array(points, dtype=float)
1181
1182        minx, miny, minz = np.min(points, axis=0)
1183        maxx, maxy, maxz = np.max(points, axis=0)
1184        maxb = max(maxx - minx, maxy - miny, maxz - minz)
1185        smooth *= maxb / 2  # must be in absolute units
1186
1187        x = np.linspace(0.0, 1.0, res)
1188        if easing:
1189            if easing == "InSine":
1190                x = 1.0 - np.cos((x * np.pi) / 2)
1191            elif easing == "OutSine":
1192                x = np.sin((x * np.pi) / 2)
1193            elif easing == "Sine":
1194                x = -(np.cos(np.pi * x) - 1) / 2
1195            elif easing == "InQuad":
1196                x = x * x
1197            elif easing == "OutQuad":
1198                x = 1.0 - (1 - x) * (1 - x)
1199            elif easing == "InCubic":
1200                x = x * x
1201            elif easing == "OutCubic":
1202                x = 1.0 - np.power(1 - x, 3)
1203            elif easing == "InQuart":
1204                x = x * x * x * x
1205            elif easing == "OutQuart":
1206                x = 1.0 - np.power(1 - x, 4)
1207            elif easing == "InCirc":
1208                x = 1.0 - np.sqrt(1 - np.power(x, 2))
1209            elif easing == "OutCirc":
1210                x = np.sqrt(1.0 - np.power(x - 1, 2))
1211            else:
1212                vedo.logger.error(f"unknown ease mode {easing}")
1213
1214        # find the knots
1215        tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per)
1216        # evaluate spLine, including interpolated points:
1217        xnew, ynew, znew = splev(x, tckp)
1218
1219        super().__init__(np.c_[xnew, ynew, znew], lw=2)
1220        self.name = "Spline"
1221
1222
1223class KSpline(Line):
1224    """
1225    Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline)
1226    which runs exactly through all the input points.
1227    """
1228
1229    def __init__(self, points, 
1230                 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None:
1231        """
1232        Arguments:
1233            continuity : (float)
1234                changes the sharpness in change between tangents
1235            tension : (float)
1236                changes the length of the tangent vector
1237            bias : (float)
1238                changes the direction of the tangent vector
1239            closed : (bool)
1240                join last to first point to produce a closed curve
1241            res : (int)
1242                approximate resolution of the output line.
1243                Default is 20 times the number of input points.
1244
1245        ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png)
1246
1247        Warning:
1248            This class is not necessarily generating the exact number of points
1249            as requested by `res`. Some points may be concident and removed.
1250
1251        See also: `Spline` and `CSpline`.
1252        """
1253        if isinstance(points, Points):
1254            points = points.vertices
1255
1256        if not res:
1257            res = len(points) * 20
1258
1259        points = utils.make3d(points).astype(float)
1260
1261        vtkKochanekSpline = vtki.get_class("KochanekSpline")
1262        xspline = vtkKochanekSpline()
1263        yspline = vtkKochanekSpline()
1264        zspline = vtkKochanekSpline()
1265        for s in [xspline, yspline, zspline]:
1266            if bias:
1267                s.SetDefaultBias(bias)
1268            if tension:
1269                s.SetDefaultTension(tension)
1270            if continuity:
1271                s.SetDefaultContinuity(continuity)
1272            s.SetClosed(closed)
1273
1274        lenp = len(points[0]) > 2
1275
1276        for i, p in enumerate(points):
1277            xspline.AddPoint(i, p[0])
1278            yspline.AddPoint(i, p[1])
1279            if lenp:
1280                zspline.AddPoint(i, p[2])
1281
1282        ln = []
1283        for pos in np.linspace(0, len(points), res):
1284            x = xspline.Evaluate(pos)
1285            y = yspline.Evaluate(pos)
1286            z = 0
1287            if lenp:
1288                z = zspline.Evaluate(pos)
1289            ln.append((x, y, z))
1290
1291        super().__init__(ln, lw=2)
1292        self.clean()
1293        self.lighting("off")
1294        self.name = "KSpline"
1295        self.base = np.array(points[0], dtype=float)
1296        self.top = np.array(points[-1], dtype=float)
1297
1298
1299class CSpline(Line):
1300    """
1301    Return a Cardinal spline which runs exactly through all the input points.
1302    """
1303
1304    def __init__(self, points, closed=False, res=None) -> None:
1305        """
1306        Arguments:
1307            closed : (bool)
1308                join last to first point to produce a closed curve
1309            res : (int)
1310                approximate resolution of the output line.
1311                Default is 20 times the number of input points.
1312
1313        Warning:
1314            This class is not necessarily generating the exact number of points
1315            as requested by `res`. Some points may be concident and removed.
1316
1317        See also: `Spline` and `KSpline`.
1318        """
1319
1320        if isinstance(points, Points):
1321            points = points.vertices
1322
1323        if not res:
1324            res = len(points) * 20
1325
1326        points = utils.make3d(points).astype(float)
1327
1328        vtkCardinalSpline = vtki.get_class("CardinalSpline")
1329        xspline = vtkCardinalSpline()
1330        yspline = vtkCardinalSpline()
1331        zspline = vtkCardinalSpline()
1332        for s in [xspline, yspline, zspline]:
1333            s.SetClosed(closed)
1334
1335        lenp = len(points[0]) > 2
1336
1337        for i, p in enumerate(points):
1338            xspline.AddPoint(i, p[0])
1339            yspline.AddPoint(i, p[1])
1340            if lenp:
1341                zspline.AddPoint(i, p[2])
1342
1343        ln = []
1344        for pos in np.linspace(0, len(points), res):
1345            x = xspline.Evaluate(pos)
1346            y = yspline.Evaluate(pos)
1347            z = 0
1348            if lenp:
1349                z = zspline.Evaluate(pos)
1350            ln.append((x, y, z))
1351
1352        super().__init__(ln, lw=2)
1353        self.clean()
1354        self.lighting("off")
1355        self.name = "CSpline"
1356        self.base = points[0]
1357        self.top = points[-1]
1358
1359
1360class Bezier(Line):
1361    """
1362    Generate the Bezier line that links the first to the last point.
1363    """
1364
1365    def __init__(self, points, res=None) -> None:
1366        """
1367        Example:
1368            ```python
1369            from vedo import *
1370            import numpy as np
1371            pts = np.random.randn(25,3)
1372            for i,p in enumerate(pts):
1373                p += [5*i, 15*sin(i/2), i*i*i/200]
1374            show(Points(pts), Bezier(pts), axes=1).close()
1375            ```
1376            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1377        """
1378        N = len(points)
1379        if res is None:
1380            res = 10 * N
1381        t = np.linspace(0, 1, num=res)
1382        bcurve = np.zeros((res, len(points[0])))
1383
1384        def binom(n, k):
1385            b = 1
1386            for t in range(1, min(k, n - k) + 1):
1387                b *= n / t
1388                n -= 1
1389            return b
1390
1391        def bernstein(n, k):
1392            coeff = binom(n, k)
1393
1394            def _bpoly(x):
1395                return coeff * x ** k * (1 - x) ** (n - k)
1396
1397            return _bpoly
1398
1399        for ii in range(N):
1400            b = bernstein(N - 1, ii)(t)
1401            bcurve += np.outer(b, points[ii])
1402        super().__init__(bcurve, lw=2)
1403        self.name = "BezierLine"
1404
1405
1406class NormalLines(Mesh):
1407    """
1408    Build an `Glyph` to show the normals at cell centers or at mesh vertices.
1409
1410    Arguments:
1411        ratio : (int)
1412            show 1 normal every `ratio` cells.
1413        on : (str)
1414            either "cells" or "points".
1415        scale : (float)
1416            scale factor to control size.
1417    """
1418
1419    def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None:
1420
1421        poly = msh.clone().dataset
1422
1423        if "cell" in on:
1424            centers = vtki.new("CellCenters")
1425            centers.SetInputData(poly)
1426            centers.Update()
1427            poly = centers.GetOutput()
1428
1429        mask_pts = vtki.new("MaskPoints")
1430        mask_pts.SetInputData(poly)
1431        mask_pts.SetOnRatio(ratio)
1432        mask_pts.RandomModeOff()
1433        mask_pts.Update()
1434
1435        ln = vtki.new("LineSource")
1436        ln.SetPoint1(0, 0, 0)
1437        ln.SetPoint2(1, 0, 0)
1438        ln.Update()
1439        glyph = vtki.vtkGlyph3D()
1440        glyph.SetSourceData(ln.GetOutput())
1441        glyph.SetInputData(mask_pts.GetOutput())
1442        glyph.SetVectorModeToUseNormal()
1443
1444        b = poly.GetBounds()
1445        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1446        glyph.SetScaleFactor(f)
1447        glyph.OrientOn()
1448        glyph.Update()
1449
1450        super().__init__(glyph.GetOutput())
1451
1452        self.actor.PickableOff()
1453        prop = vtki.vtkProperty()
1454        prop.DeepCopy(msh.properties)
1455        self.actor.SetProperty(prop)
1456        self.properties = prop
1457        self.properties.LightingOff()
1458        self.mapper.ScalarVisibilityOff()
1459        self.name = "NormalLines"
1460
1461
1462class Tube(Mesh):
1463    """
1464    Build a tube along the line defined by a set of points.
1465    """
1466
1467    def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None:
1468        """
1469        Arguments:
1470            r :  (float, list)
1471                constant radius or list of radii.
1472            res : (int)
1473                resolution, number of the sides of the tube
1474            c : (color)
1475                constant color or list of colors for each point.
1476            
1477        Example:
1478            Create a tube along a line, with data associated to each point:
1479
1480            ```python
1481            from vedo import *
1482            line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5)
1483            scalars = np.array([0, 1, 2, 3])
1484            line.pointdata["myscalars"] = scalars
1485            tube = Tube(line, r=0.1).lw(1)
1486            tube.cmap('viridis', "myscalars").add_scalarbar3d()
1487            show(line, tube, axes=1).close()
1488            ```
1489
1490        Examples:
1491            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1492            - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py)
1493
1494                ![](https://vedo.embl.es/images/basic/tube.png)
1495        """
1496        if utils.is_sequence(points):
1497            vpoints = vtki.vtkPoints()
1498            idx = len(points)
1499            for p in points:
1500                vpoints.InsertNextPoint(p)
1501            line = vtki.new("PolyLine")
1502            line.GetPointIds().SetNumberOfIds(idx)
1503            for i in range(idx):
1504                line.GetPointIds().SetId(i, i)
1505            lines = vtki.vtkCellArray()
1506            lines.InsertNextCell(line)
1507            polyln = vtki.vtkPolyData()
1508            polyln.SetPoints(vpoints)
1509            polyln.SetLines(lines)            
1510            self.base = np.asarray(points[0], dtype=float)
1511            self.top = np.asarray(points[-1], dtype=float)
1512
1513        elif isinstance(points, Mesh):
1514            polyln = points.dataset
1515            n = polyln.GetNumberOfPoints()
1516            self.base = np.array(polyln.GetPoint(0))
1517            self.top = np.array(polyln.GetPoint(n - 1))
1518
1519        # from vtkmodules.vtkFiltersCore import vtkTubeBender
1520        # bender = vtkTubeBender()
1521        # bender.SetInputData(polyln)
1522        # bender.SetRadius(r)
1523        # bender.Update()
1524        # polyln = bender.GetOutput()
1525
1526        tuf = vtki.new("TubeFilter")
1527        tuf.SetCapping(cap)
1528        tuf.SetNumberOfSides(res)
1529        tuf.SetInputData(polyln)
1530        if utils.is_sequence(r):
1531            arr = utils.numpy2vtk(r, dtype=float)
1532            arr.SetName("TubeRadius")
1533            polyln.GetPointData().AddArray(arr)
1534            polyln.GetPointData().SetActiveScalars("TubeRadius")
1535            tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar()
1536        else:
1537            tuf.SetRadius(r)
1538
1539        usingColScals = False
1540        if utils.is_sequence(c):
1541            usingColScals = True
1542            cc = vtki.vtkUnsignedCharArray()
1543            cc.SetName("TubeColors")
1544            cc.SetNumberOfComponents(3)
1545            cc.SetNumberOfTuples(len(c))
1546            for i, ic in enumerate(c):
1547                r, g, b = get_color(ic)
1548                cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b))
1549            polyln.GetPointData().AddArray(cc)
1550            c = None
1551        tuf.Update()
1552
1553        super().__init__(tuf.GetOutput(), c, alpha)
1554        self.phong()
1555        if usingColScals:
1556            self.mapper.SetScalarModeToUsePointFieldData()
1557            self.mapper.ScalarVisibilityOn()
1558            self.mapper.SelectColorArray("TubeColors")
1559            self.mapper.Modified()
1560        self.name = "Tube"
1561
1562
1563def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0) -> Union["Mesh", None]:
1564    """
1565    Create a tube with a thickness along a line of points.
1566
1567    Example:
1568    ```python
1569    from vedo import *
1570    pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)]
1571    vline = Line(pts, lw=5, c='red5')
1572    thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1)
1573    show(vline, thick_tube, axes=1).close()
1574    ```
1575    ![](https://vedo.embl.es/images/feats/thick_tube.png)
1576    """
1577
1578    def make_cap(t1, t2):
1579        newpoints = t1.vertices.tolist() + t2.vertices.tolist()
1580        newfaces = []
1581        for i in range(n - 1):
1582            newfaces.append([i, i + 1, i + n])
1583            newfaces.append([i + n, i + 1, i + n + 1])
1584        newfaces.append([2 * n - 1, 0, n])
1585        newfaces.append([2 * n - 1, n - 1, 0])
1586        capm = utils.buildPolyData(newpoints, newfaces)
1587        return capm
1588
1589    assert r1 < r2
1590
1591    t1 = Tube(pts, r=r1, cap=False, res=res)
1592    t2 = Tube(pts, r=r2, cap=False, res=res)
1593
1594    tc1a, tc1b = t1.boundaries().split()
1595    tc2a, tc2b = t2.boundaries().split()
1596    n = tc1b.npoints
1597
1598    tc1b.join(reset=True).clean()  # needed because indices are flipped
1599    tc2b.join(reset=True).clean()
1600
1601    capa = make_cap(tc1a, tc2a)
1602    capb = make_cap(tc1b, tc2b)
1603
1604    thick_tube = merge(t1, t2, capa, capb)
1605    if thick_tube:
1606        thick_tube.c(c).alpha(alpha)
1607        thick_tube.base = t1.base
1608        thick_tube.top  = t1.top
1609        thick_tube.name = "ThickTube"
1610        return thick_tube
1611    return None
1612
1613
1614class Tubes(Mesh):
1615    """
1616    Build tubes around a `Lines` object.
1617    """
1618    def __init__(
1619            self,
1620            lines,
1621            r=1,
1622            vary_radius_by_scalar=False,
1623            vary_radius_by_vector=False,
1624            vary_radius_by_vector_norm=False,
1625            vary_radius_by_absolute_scalar=False,
1626            max_radius_factor=100,
1627            cap=True,
1628            res=12
1629        ) -> None:
1630        """
1631        Wrap tubes around the input `Lines` object.
1632
1633        Arguments:
1634            lines : (Lines)
1635                input Lines object.
1636            r : (float)
1637                constant radius
1638            vary_radius_by_scalar : (bool)
1639                use scalar array to control radius
1640            vary_radius_by_vector : (bool)
1641                use vector array to control radius
1642            vary_radius_by_vector_norm : (bool)
1643                use vector norm to control radius
1644            vary_radius_by_absolute_scalar : (bool)
1645                use absolute scalar value to control radius
1646            max_radius_factor : (float)
1647                max tube radius as a multiple of the min radius
1648            cap : (bool)
1649                capping of the tube
1650            res : (int)
1651                resolution, number of the sides of the tube
1652            c : (color)
1653                constant color or list of colors for each point.
1654        
1655        Examples:
1656            - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py)
1657        """
1658        plines = lines.dataset
1659        if plines.GetNumberOfLines() == 0:
1660            vedo.logger.warning("Tubes(): input Lines is empty.")
1661
1662        tuf = vtki.new("TubeFilter")
1663        if vary_radius_by_scalar:
1664            tuf.SetVaryRadiusToVaryRadiusByScalar()
1665        elif vary_radius_by_vector:
1666            tuf.SetVaryRadiusToVaryRadiusByVector()
1667        elif vary_radius_by_vector_norm:
1668            tuf.SetVaryRadiusToVaryRadiusByVectorNorm()
1669        elif vary_radius_by_absolute_scalar:
1670            tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar()
1671        tuf.SetRadius(r)
1672        tuf.SetCapping(cap)
1673        tuf.SetGenerateTCoords(0)
1674        tuf.SetSidesShareVertices(1)
1675        tuf.SetRadiusFactor(max_radius_factor)
1676        tuf.SetNumberOfSides(res)
1677        tuf.SetInputData(plines)
1678        tuf.Update()
1679
1680        super().__init__(tuf.GetOutput())
1681        self.name = "Tubes"
1682    
1683
1684class Ribbon(Mesh):
1685    """
1686    Connect two lines to generate the surface inbetween.
1687    Set the mode by which to create the ruled surface.
1688
1689    It also works with a single line in input. In this case the ribbon
1690    is formed by following the local plane of the line in space.
1691    """
1692
1693    def __init__(
1694        self,
1695        line1,
1696        line2=None,
1697        mode=0,
1698        closed=False,
1699        width=None,
1700        res=(200, 5),
1701        c="indigo3",
1702        alpha=1.0,
1703    ) -> None:
1704        """
1705        Arguments:
1706            mode : (int)
1707                If mode=0, resample evenly the input lines (based on length)
1708                and generates triangle strips.
1709
1710                If mode=1, use the existing points and walks around the
1711                polyline using existing points.
1712
1713            closed : (bool)
1714                if True, join the last point with the first to form a closed surface
1715
1716            res : (list)
1717                ribbon resolutions along the line and perpendicularly to it.
1718
1719        Examples:
1720            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1721
1722                ![](https://vedo.embl.es/images/basic/ribbon.png)
1723        """
1724
1725        if isinstance(line1, Points):
1726            line1 = line1.vertices
1727
1728        if isinstance(line2, Points):
1729            line2 = line2.vertices
1730
1731        elif line2 is None:
1732            #############################################
1733            ribbon_filter = vtki.new("RibbonFilter")
1734            aline = Line(line1)
1735            ribbon_filter.SetInputData(aline.dataset)
1736            if width is None:
1737                width = aline.diagonal_size() / 20.0
1738            ribbon_filter.SetWidth(width)
1739            ribbon_filter.Update()
1740            # convert triangle strips to polygons
1741            tris = vtki.new("TriangleFilter")
1742            tris.SetInputData(ribbon_filter.GetOutput())
1743            tris.Update()
1744
1745            super().__init__(tris.GetOutput(), c, alpha)
1746            self.name = "Ribbon"
1747            ##############################################
1748            return  ######################################
1749            ##############################################
1750
1751        line1 = np.asarray(line1)
1752        line2 = np.asarray(line2)
1753
1754        if closed:
1755            line1 = line1.tolist()
1756            line1 += [line1[0]]
1757            line2 = line2.tolist()
1758            line2 += [line2[0]]
1759            line1 = np.array(line1)
1760            line2 = np.array(line2)
1761
1762        if len(line1[0]) == 2:
1763            line1 = np.c_[line1, np.zeros(len(line1))]
1764        if len(line2[0]) == 2:
1765            line2 = np.c_[line2, np.zeros(len(line2))]
1766
1767        ppoints1 = vtki.vtkPoints()  # Generate the polyline1
1768        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1769        lines1 = vtki.vtkCellArray()
1770        lines1.InsertNextCell(len(line1))
1771        for i in range(len(line1)):
1772            lines1.InsertCellPoint(i)
1773        poly1 = vtki.vtkPolyData()
1774        poly1.SetPoints(ppoints1)
1775        poly1.SetLines(lines1)
1776
1777        ppoints2 = vtki.vtkPoints()  # Generate the polyline2
1778        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1779        lines2 = vtki.vtkCellArray()
1780        lines2.InsertNextCell(len(line2))
1781        for i in range(len(line2)):
1782            lines2.InsertCellPoint(i)
1783        poly2 = vtki.vtkPolyData()
1784        poly2.SetPoints(ppoints2)
1785        poly2.SetLines(lines2)
1786
1787        # build the lines
1788        lines1 = vtki.vtkCellArray()
1789        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1790        for i in range(poly1.GetNumberOfPoints()):
1791            lines1.InsertCellPoint(i)
1792
1793        polygon1 = vtki.vtkPolyData()
1794        polygon1.SetPoints(ppoints1)
1795        polygon1.SetLines(lines1)
1796
1797        lines2 = vtki.vtkCellArray()
1798        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1799        for i in range(poly2.GetNumberOfPoints()):
1800            lines2.InsertCellPoint(i)
1801
1802        polygon2 = vtki.vtkPolyData()
1803        polygon2.SetPoints(ppoints2)
1804        polygon2.SetLines(lines2)
1805
1806        merged_pd = vtki.new("AppendPolyData")
1807        merged_pd.AddInputData(polygon1)
1808        merged_pd.AddInputData(polygon2)
1809        merged_pd.Update()
1810
1811        rsf = vtki.new("RuledSurfaceFilter")
1812        rsf.CloseSurfaceOff()
1813        rsf.SetRuledMode(mode)
1814        rsf.SetResolution(res[0], res[1])
1815        rsf.SetInputData(merged_pd.GetOutput())
1816        rsf.Update()
1817        # convert triangle strips to polygons
1818        tris = vtki.new("TriangleFilter")
1819        tris.SetInputData(rsf.GetOutput())
1820        tris.Update()
1821        out = tris.GetOutput()
1822
1823        super().__init__(out, c, alpha)
1824
1825        self.name = "Ribbon"
1826
1827
1828class Arrow(Mesh):
1829    """
1830    Build a 3D arrow from `start_pt` to `end_pt` of section size `s`,
1831    expressed as the fraction of the window size.
1832    """
1833
1834    def __init__(
1835        self,
1836        start_pt=(0, 0, 0),
1837        end_pt=(1, 0, 0),
1838        s=None,
1839        shaft_radius=None,
1840        head_radius=None,
1841        head_length=None,
1842        res=12,
1843        c="r4",
1844        alpha=1.0,
1845    ) -> None:
1846        """
1847        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1848        from white to red.
1849
1850        .. note:: If `s=None` the arrow is scaled proportionally to its length
1851
1852        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1853        """
1854        # in case user is passing meshs
1855        if isinstance(start_pt, vtki.vtkActor):
1856            start_pt = start_pt.GetPosition()
1857        if isinstance(end_pt, vtki.vtkActor):
1858            end_pt = end_pt.GetPosition()
1859
1860        axis = np.asarray(end_pt) - np.asarray(start_pt)
1861        length = float(np.linalg.norm(axis))
1862        if length:
1863            axis = axis / length
1864        if len(axis) < 3:  # its 2d
1865            theta = np.pi / 2
1866            start_pt = [start_pt[0], start_pt[1], 0.0]
1867            end_pt = [end_pt[0], end_pt[1], 0.0]
1868        else:
1869            theta = np.arccos(axis[2])
1870        phi = np.arctan2(axis[1], axis[0])
1871        self.source = vtki.new("ArrowSource")
1872        self.source.SetShaftResolution(res)
1873        self.source.SetTipResolution(res)
1874
1875        if s:
1876            sz = 0.02
1877            self.source.SetTipRadius(sz)
1878            self.source.SetShaftRadius(sz / 1.75)
1879            self.source.SetTipLength(sz * 15)
1880
1881        if head_length:
1882            self.source.SetTipLength(head_length)
1883        if head_radius:
1884            self.source.SetTipRadius(head_radius)
1885        if shaft_radius:
1886            self.source.SetShaftRadius(shaft_radius)
1887
1888        self.source.Update()
1889
1890        t = vtki.vtkTransform()
1891        t.Translate(start_pt)
1892        t.RotateZ(np.rad2deg(phi))
1893        t.RotateY(np.rad2deg(theta))
1894        t.RotateY(-90)  # put it along Z
1895        if s:
1896            sz = 800 * s
1897            t.Scale(length, sz, sz)
1898        else:
1899            t.Scale(length, length, length)
1900
1901        tf = vtki.new("TransformPolyDataFilter")
1902        tf.SetInputData(self.source.GetOutput())
1903        tf.SetTransform(t)
1904        tf.Update()
1905
1906        super().__init__(tf.GetOutput(), c, alpha)
1907
1908        self.transform = LinearTransform().translate(start_pt)
1909        # self.pos(start_pt)
1910
1911        self.phong().lighting("plastic")
1912        self.actor.PickableOff()
1913        self.actor.DragableOff()
1914        self.base = np.array(start_pt, dtype=float)  # used by pyplot
1915        self.top  = np.array(end_pt,   dtype=float)  # used by pyplot
1916        self.top_index = None
1917        self.fill = True                    # used by pyplot.__iadd__()
1918        self.s = s if s is not None else 1  # used by pyplot.__iadd__()
1919        self.name = "Arrow"
1920
1921
1922class Arrows(Glyph):
1923    """
1924    Build arrows between two lists of points.
1925    """
1926
1927    def __init__(
1928        self,
1929        start_pts,
1930        end_pts=None,
1931        s=None,
1932        shaft_radius=None,
1933        head_radius=None,
1934        head_length=None,
1935        thickness=1.0,
1936        res=6,
1937        c='k3',
1938        alpha=1.0,
1939    ) -> None:
1940        """
1941        Build arrows between two lists of points `start_pts` and `end_pts`.
1942         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1943
1944        Color can be specified as a colormap which maps the size of the arrows.
1945
1946        Arguments:
1947            s : (float)
1948                fix aspect-ratio of the arrow and scale its cross section
1949            c : (color)
1950                color or color map name
1951            alpha : (float)
1952                set object opacity
1953            res : (int)
1954                set arrow resolution
1955
1956        Examples:
1957            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
1958
1959            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
1960        """
1961        if isinstance(start_pts, Points):
1962            start_pts = start_pts.vertices
1963        if isinstance(end_pts, Points):
1964            end_pts = end_pts.vertices
1965
1966        start_pts = np.asarray(start_pts)
1967        if end_pts is None:
1968            strt = start_pts[:, 0]
1969            end_pts = start_pts[:, 1]
1970            start_pts = strt
1971        else:
1972            end_pts = np.asarray(end_pts)
1973
1974        start_pts = utils.make3d(start_pts)
1975        end_pts = utils.make3d(end_pts)
1976
1977        arr = vtki.new("ArrowSource")
1978        arr.SetShaftResolution(res)
1979        arr.SetTipResolution(res)
1980
1981        if s:
1982            sz = 0.02 * s
1983            arr.SetTipRadius(sz * 2)
1984            arr.SetShaftRadius(sz * thickness)
1985            arr.SetTipLength(sz * 10)
1986
1987        if head_radius:
1988            arr.SetTipRadius(head_radius)
1989        if shaft_radius:
1990            arr.SetShaftRadius(shaft_radius)
1991        if head_length:
1992            arr.SetTipLength(head_length)
1993
1994        arr.Update()
1995        out = arr.GetOutput()
1996
1997        orients = end_pts - start_pts
1998
1999        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
2000
2001        super().__init__(
2002            start_pts,
2003            out,
2004            orientation_array=orients,
2005            scale_by_vector_size=True,
2006            color_by_vector_size=color_by_vector_size,
2007            c=c,
2008            alpha=alpha,
2009        )
2010        self.lighting("off")
2011        if color_by_vector_size:
2012            vals = np.linalg.norm(orients, axis=1)
2013            self.mapper.SetScalarRange(vals.min(), vals.max())
2014        else:
2015            self.c(c)
2016        self.name = "Arrows"
2017
2018
2019class Arrow2D(Mesh):
2020    """
2021    Build a 2D arrow.
2022    """
2023
2024    def __init__(
2025        self,
2026        start_pt=(0, 0, 0),
2027        end_pt=(1, 0, 0),
2028        s=1,
2029        rotation=0.0,
2030        shaft_length=0.85,
2031        shaft_width=0.055,
2032        head_length=0.175,
2033        head_width=0.175,
2034        fill=True,
2035        c="red4",
2036        alpha=1.0,
2037   ) -> None:
2038        """
2039        Build a 2D arrow from `start_pt` to `end_pt`.
2040
2041        Arguments:
2042            s : (float)
2043                a global multiplicative convenience factor controlling the arrow size
2044            shaft_length : (float)
2045                fractional shaft length
2046            shaft_width : (float)
2047                fractional shaft width
2048            head_length : (float)
2049                fractional head length
2050            head_width : (float)
2051                fractional head width
2052            fill : (bool)
2053                if False only generate the outline
2054        """
2055        self.fill = fill  ## needed by pyplot.__iadd()
2056        self.s = s        ## needed by pyplot.__iadd()
2057
2058        if s != 1:
2059            shaft_width *= s
2060            head_width *= np.sqrt(s)
2061
2062        # in case user is passing meshs
2063        if isinstance(start_pt, vtki.vtkActor):
2064            start_pt = start_pt.GetPosition()
2065        if isinstance(end_pt, vtki.vtkActor):
2066            end_pt = end_pt.GetPosition()
2067        if len(start_pt) == 2:
2068            start_pt = [start_pt[0], start_pt[1], 0]
2069        if len(end_pt) == 2:
2070            end_pt = [end_pt[0], end_pt[1], 0]
2071
2072        headBase = 1 - head_length
2073        head_width = max(head_width, shaft_width)
2074        if head_length is None or headBase > shaft_length:
2075            headBase = shaft_length
2076
2077        verts = []
2078        verts.append([0, -shaft_width / 2, 0])
2079        verts.append([shaft_length, -shaft_width / 2, 0])
2080        verts.append([headBase, -head_width / 2, 0])
2081        verts.append([1, 0, 0])
2082        verts.append([headBase, head_width / 2, 0])
2083        verts.append([shaft_length, shaft_width / 2, 0])
2084        verts.append([0, shaft_width / 2, 0])
2085        if fill:
2086            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2087            poly = utils.buildPolyData(verts, faces)
2088        else:
2089            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2090            poly = utils.buildPolyData(verts, [], lines=lines)
2091
2092        axis = np.array(end_pt) - np.array(start_pt)
2093        length = float(np.linalg.norm(axis))
2094        if length:
2095            axis = axis / length
2096        theta = 0
2097        if len(axis) > 2:
2098            theta = np.arccos(axis[2])
2099        phi = np.arctan2(axis[1], axis[0])
2100
2101        t = vtki.vtkTransform()
2102        t.Translate(start_pt)
2103        if phi:
2104            t.RotateZ(np.rad2deg(phi))
2105        if theta:
2106            t.RotateY(np.rad2deg(theta))
2107        t.RotateY(-90)  # put it along Z
2108        if rotation:
2109            t.RotateX(rotation)
2110        t.Scale(length, length, length)
2111
2112        tf = vtki.new("TransformPolyDataFilter")
2113        tf.SetInputData(poly)
2114        tf.SetTransform(t)
2115        tf.Update()
2116
2117        super().__init__(tf.GetOutput(), c, alpha)
2118
2119        self.transform = LinearTransform().translate(start_pt)
2120
2121        self.lighting("off")
2122        self.actor.DragableOff()
2123        self.actor.PickableOff()
2124        self.base = np.array(start_pt, dtype=float) # used by pyplot
2125        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2126        self.name = "Arrow2D"
2127
2128
2129class Arrows2D(Glyph):
2130    """
2131    Build 2D arrows between two lists of points.
2132    """
2133
2134    def __init__(
2135        self,
2136        start_pts,
2137        end_pts=None,
2138        s=1.0,
2139        rotation=0.0,
2140        shaft_length=0.8,
2141        shaft_width=0.05,
2142        head_length=0.225,
2143        head_width=0.175,
2144        fill=True,
2145        c=None,
2146        alpha=1.0,
2147    ) -> None:
2148        """
2149        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2150        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2151
2152        Color can be specified as a colormap which maps the size of the arrows.
2153
2154        Arguments:
2155            shaft_length : (float)
2156                fractional shaft length
2157            shaft_width : (float)
2158                fractional shaft width
2159            head_length : (float)
2160                fractional head length
2161            head_width : (float)
2162                fractional head width
2163            fill : (bool)
2164                if False only generate the outline
2165        """
2166        if isinstance(start_pts, Points):
2167            start_pts = start_pts.vertices
2168        if isinstance(end_pts, Points):
2169            end_pts = end_pts.vertices
2170
2171        start_pts = np.asarray(start_pts, dtype=float)
2172        if end_pts is None:
2173            strt = start_pts[:, 0]
2174            end_pts = start_pts[:, 1]
2175            start_pts = strt
2176        else:
2177            end_pts = np.asarray(end_pts, dtype=float)
2178
2179        if head_length is None:
2180            head_length = 1 - shaft_length
2181
2182        arr = Arrow2D(
2183            (0, 0, 0),
2184            (1, 0, 0),
2185            s=s,
2186            rotation=rotation,
2187            shaft_length=shaft_length,
2188            shaft_width=shaft_width,
2189            head_length=head_length,
2190            head_width=head_width,
2191            fill=fill,
2192        )
2193
2194        orients = end_pts - start_pts
2195        orients = utils.make3d(orients)
2196
2197        pts = Points(start_pts)
2198        super().__init__(
2199            pts,
2200            arr,
2201            orientation_array=orients,
2202            scale_by_vector_size=True,
2203            c=c,
2204            alpha=alpha,
2205        )
2206        self.flat().lighting("off").pickable(False)
2207        if c is not None:
2208            self.color(c)
2209        self.name = "Arrows2D"
2210
2211
2212class FlatArrow(Ribbon):
2213    """
2214    Build a 2D arrow in 3D space by joining two close lines.
2215    """
2216
2217    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2218        """
2219        Build a 2D arrow in 3D space by joining two close lines.
2220
2221        Examples:
2222            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2223
2224                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2225        """
2226        if isinstance(line1, Points):
2227            line1 = line1.vertices
2228        if isinstance(line2, Points):
2229            line2 = line2.vertices
2230
2231        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2232
2233        v = (sm1 - sm2) / 3 * tip_width
2234        p1 = sm1 + v
2235        p2 = sm2 - v
2236        pm1 = (sm1 + sm2) / 2
2237        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2238        pm12 = pm1 - pm2
2239        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2240
2241        line1.append(p1)
2242        line1.append(tip)
2243        line2.append(p2)
2244        line2.append(tip)
2245        resm = max(100, len(line1))
2246
2247        super().__init__(line1, line2, res=(resm, 1))
2248        self.phong().lighting("off")
2249        self.actor.PickableOff()
2250        self.actor.DragableOff()
2251        self.name = "FlatArrow"
2252
2253
2254class Triangle(Mesh):
2255    """Create a triangle from 3 points in space."""
2256
2257    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2258        """Create a triangle from 3 points in space."""
2259        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2260        self.properties.LightingOff()
2261        self.name = "Triangle"
2262
2263
2264class Polygon(Mesh):
2265    """
2266    Build a polygon in the `xy` plane.
2267    """
2268
2269    def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None:
2270        """
2271        Build a polygon in the `xy` plane of `nsides` of radius `r`.
2272
2273        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png)
2274        """
2275        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False)
2276        pts = pol2cart(np.ones_like(t) * r, t).T
2277        faces = [list(range(nsides))]
2278        # do not use: vtkRegularPolygonSource
2279        super().__init__([pts, faces], c, alpha)
2280        if len(pos) == 2:
2281            pos = (pos[0], pos[1], 0)
2282        self.pos(pos)
2283        self.properties.LightingOff()
2284        self.name = "Polygon " + str(nsides)
2285
2286
2287class Circle(Polygon):
2288    """
2289    Build a Circle of radius `r`.
2290    """
2291
2292    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None:
2293        """
2294        Build a Circle of radius `r`.
2295        """
2296        super().__init__(pos, nsides=res, r=r)
2297
2298        self.nr_of_points = 0
2299        self.va = 0
2300        self.vb = 0
2301        self.axis1: List[float] = []
2302        self.axis2: List[float] = []
2303        self.center: List[float] = []  # filled by pointcloud.pca_ellipse()
2304        self.pvalue = 0.0              # filled by pointcloud.pca_ellipse()
2305        self.alpha(alpha).c(c)
2306        self.name = "Circle"
2307    
2308    def acircularity(self) -> float:
2309        """
2310        Return a measure of how different an ellipse is from a circle.
2311        Values close to zero correspond to a circular object.
2312        """
2313        a, b = self.va, self.vb
2314        value = 0.0
2315        if a+b:
2316            value = ((a-b)/(a+b))**2
2317        return value
2318
2319class GeoCircle(Polygon):
2320    """
2321    Build a Circle of radius `r`.
2322    """
2323
2324    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None:
2325        """
2326        Build a Circle of radius `r` as projected on a geographic map.
2327        Circles near the poles will look very squashed.
2328
2329        See example:
2330            ```bash
2331            vedo -r earthquake
2332            ```
2333        """
2334        coords = []
2335        sinr, cosr = np.sin(r), np.cos(r)
2336        sinlat, coslat = np.sin(lat), np.cos(lat)
2337        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2338            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2339            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2340            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2341
2342        super().__init__(nsides=res, c=c, alpha=alpha)
2343        self.vertices = coords # warp polygon points to match geo projection
2344        self.name = "Circle"
2345
2346
2347class Star(Mesh):
2348    """
2349    Build a 2D star shape.
2350    """
2351
2352    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2353        """
2354        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2355
2356        If line is True then only build the outer line (no internal surface meshing).
2357
2358        Example:
2359            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2360
2361                ![](https://vedo.embl.es/images/basic/extrude.png)
2362        """
2363        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2364        x, y = pol2cart(np.ones_like(t) * r2, t)
2365        pts = np.c_[x, y, np.zeros_like(x)]
2366
2367        apts = []
2368        for i, p in enumerate(pts):
2369            apts.append(p)
2370            if i + 1 < n:
2371                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2372        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2373
2374        if line:
2375            apts.append(pts[0])
2376            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2377            super().__init__(poly, c, alpha)
2378            self.lw(2)
2379        else:
2380            apts.append((0, 0, 0))
2381            cells = []
2382            for i in range(2 * n - 1):
2383                cell = [2 * n, i, i + 1]
2384                cells.append(cell)
2385            cells.append([2 * n, i + 1, 0])
2386            super().__init__([apts, cells], c, alpha)
2387
2388        if len(pos) == 2:
2389            pos = (pos[0], pos[1], 0)
2390
2391        self.properties.LightingOff()
2392        self.name = "Star"
2393
2394
2395class Disc(Mesh):
2396    """
2397    Build a 2D disc.
2398    """
2399
2400    def __init__(
2401        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2402    ) -> None:
2403        """
2404        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2405
2406        Set `res` as the resolution in R and Phi (can be a list).
2407
2408        Use `angle_range` to create a disc sector between the 2 specified angles.
2409
2410        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2411        """
2412        if utils.is_sequence(res):
2413            res_r, res_phi = res
2414        else:
2415            res_r, res_phi = res, 12 * res
2416
2417        if len(angle_range) == 0:
2418            ps = vtki.new("DiskSource")
2419        else:
2420            ps = vtki.new("SectorSource")
2421            ps.SetStartAngle(angle_range[0])
2422            ps.SetEndAngle(angle_range[1])
2423
2424        ps.SetInnerRadius(r1)
2425        ps.SetOuterRadius(r2)
2426        ps.SetRadialResolution(res_r)
2427        ps.SetCircumferentialResolution(res_phi)
2428        ps.Update()
2429        super().__init__(ps.GetOutput(), c, alpha)
2430        self.flat()
2431        self.pos(utils.make3d(pos))
2432        self.name = "Disc"
2433
2434
2435class Arc(Mesh):
2436    """
2437    Build a 2D circular arc between 2 points.
2438    """
2439
2440    def __init__(
2441        self,
2442        center,
2443        point1,
2444        point2=None,
2445        normal=None,
2446        angle=None,
2447        invert=False,
2448        res=50,
2449        c="gray4",
2450        alpha=1.0,
2451    ) -> None:
2452        """
2453        Build a 2D circular arc between 2 points `point1` and `point2`.
2454
2455        If `normal` is specified then `center` is ignored, and
2456        normal vector, a starting `point1` (polar vector)
2457        and an angle defining the arc length need to be assigned.
2458
2459        Arc spans the shortest angular sector point1 and point2,
2460        if `invert=True`, then the opposite happens.
2461        """
2462        if len(point1) == 2:
2463            point1 = (point1[0], point1[1], 0)
2464        if point2 is not None and len(point2) == 2:
2465            point2 = (point2[0], point2[1], 0)
2466
2467        ar = vtki.new("ArcSource")
2468        if point2 is not None:
2469            self.top = point2
2470            point2 = point2 - np.asarray(point1)
2471            ar.UseNormalAndAngleOff()
2472            ar.SetPoint1([0, 0, 0])
2473            ar.SetPoint2(point2)
2474            # ar.SetCenter(center)
2475        elif normal is not None and angle is not None:
2476            ar.UseNormalAndAngleOn()
2477            ar.SetAngle(angle)
2478            ar.SetPolarVector(point1)
2479            ar.SetNormal(normal)
2480        else:
2481            vedo.logger.error("incorrect input combination")
2482            return
2483        ar.SetNegative(invert)
2484        ar.SetResolution(res)
2485        ar.Update()
2486
2487        super().__init__(ar.GetOutput(), c, alpha)
2488        self.pos(center)
2489        self.lw(2).lighting("off")
2490        self.name = "Arc"
2491
2492
2493class IcoSphere(Mesh):
2494    """
2495    Create a sphere made of a uniform triangle mesh.
2496    """
2497
2498    def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None:
2499        """
2500        Create a sphere made of a uniform triangle mesh
2501        (from recursive subdivision of an icosahedron).
2502
2503        Example:
2504        ```python
2505        from vedo import *
2506        icos = IcoSphere(subdivisions=3)
2507        icos.compute_quality().cmap('coolwarm')
2508        icos.show(axes=1).close()
2509        ```
2510        ![](https://vedo.embl.es/images/basic/icosphere.jpg)
2511        """
2512        subdivisions = int(min(subdivisions, 9))  # to avoid disasters
2513
2514        t = (1.0 + np.sqrt(5.0)) / 2.0
2515        points = np.array(
2516            [
2517                [-1, t, 0],
2518                [1, t, 0],
2519                [-1, -t, 0],
2520                [1, -t, 0],
2521                [0, -1, t],
2522                [0, 1, t],
2523                [0, -1, -t],
2524                [0, 1, -t],
2525                [t, 0, -1],
2526                [t, 0, 1],
2527                [-t, 0, -1],
2528                [-t, 0, 1],
2529            ]
2530        )
2531        faces = [
2532            [0, 11, 5],
2533            [0, 5, 1],
2534            [0, 1, 7],
2535            [0, 7, 10],
2536            [0, 10, 11],
2537            [1, 5, 9],
2538            [5, 11, 4],
2539            [11, 10, 2],
2540            [10, 7, 6],
2541            [7, 1, 8],
2542            [3, 9, 4],
2543            [3, 4, 2],
2544            [3, 2, 6],
2545            [3, 6, 8],
2546            [3, 8, 9],
2547            [4, 9, 5],
2548            [2, 4, 11],
2549            [6, 2, 10],
2550            [8, 6, 7],
2551            [9, 8, 1],
2552        ]
2553        super().__init__([points * r, faces], c=c, alpha=alpha)
2554
2555        for _ in range(subdivisions):
2556            self.subdivide(method=1)
2557            pts = utils.versor(self.vertices) * r
2558            self.vertices = pts
2559
2560        self.pos(pos)
2561        self.name = "IcoSphere"
2562
2563
2564class Sphere(Mesh):
2565    """
2566    Build a sphere.
2567    """
2568
2569    def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None:
2570        """
2571        Build a sphere at position `pos` of radius `r`.
2572
2573        Arguments:
2574            r : (float)
2575                sphere radius
2576            res : (int, list)
2577                resolution in phi, resolution in theta is by default `2*res`
2578            quads : (bool)
2579                sphere mesh will be made of quads instead of triangles
2580
2581        [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png)
2582        """
2583        if len(pos) == 2:
2584            pos = np.asarray([pos[0], pos[1], 0])
2585
2586        self.radius = r  # used by fitSphere
2587        self.center = pos
2588        self.residue = 0
2589
2590        if quads:
2591            res = max(res, 4)
2592            img = vtki.vtkImageData()
2593            img.SetDimensions(res - 1, res - 1, res - 1)
2594            rs = 1.0 / (res - 2)
2595            img.SetSpacing(rs, rs, rs)
2596            gf = vtki.new("GeometryFilter")
2597            gf.SetInputData(img)
2598            gf.Update()
2599            super().__init__(gf.GetOutput(), c, alpha)
2600            self.lw(0.1)
2601
2602            cgpts = self.vertices - (0.5, 0.5, 0.5)
2603
2604            x, y, z = cgpts.T
2605            x = x * (1 + x * x) / 2
2606            y = y * (1 + y * y) / 2
2607            z = z * (1 + z * z) / 2
2608            _, theta, phi = cart2spher(x, y, z)
2609
2610            pts = spher2cart(np.ones_like(phi) * r, theta, phi).T
2611            self.vertices = pts
2612
2613        else:
2614            if utils.is_sequence(res):
2615                res_t, res_phi = res
2616            else:
2617                res_t, res_phi = 2 * res, res
2618
2619            ss = vtki.new("SphereSource")
2620            ss.SetRadius(r)
2621            ss.SetThetaResolution(res_t)
2622            ss.SetPhiResolution(res_phi)
2623            ss.Update()
2624
2625            super().__init__(ss.GetOutput(), c, alpha)
2626
2627        self.phong()
2628        self.pos(pos)
2629        self.name = "Sphere"
2630
2631
2632class Spheres(Mesh):
2633    """
2634    Build a large set of spheres.
2635    """
2636
2637    def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None:
2638        """
2639        Build a (possibly large) set of spheres at `centers` of radius `r`.
2640
2641        Either `c` or `r` can be a list of RGB colors or radii.
2642
2643        Examples:
2644            - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py)
2645
2646            ![](https://vedo.embl.es/images/basic/manyspheres.jpg)
2647        """
2648
2649        if isinstance(centers, Points):
2650            centers = centers.vertices
2651        centers = np.asarray(centers, dtype=float)
2652        base = centers[0]
2653
2654        cisseq = False
2655        if utils.is_sequence(c):
2656            cisseq = True
2657
2658        if cisseq:
2659            if len(centers) != len(c):
2660                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors")
2661                raise RuntimeError()
2662
2663        risseq = False
2664        if utils.is_sequence(r):
2665            risseq = True
2666
2667        if risseq:
2668            if len(centers) != len(r):
2669                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii")
2670                raise RuntimeError()
2671        if cisseq and risseq:
2672            vedo.logger.error("Limitation: c and r cannot be both sequences.")
2673            raise RuntimeError()
2674
2675        src = vtki.new("SphereSource")
2676        if not risseq:
2677            src.SetRadius(r)
2678        if utils.is_sequence(res):
2679            res_t, res_phi = res
2680        else:
2681            res_t, res_phi = 2 * res, res
2682
2683        src.SetThetaResolution(res_t)
2684        src.SetPhiResolution(res_phi)
2685        src.Update()
2686
2687        psrc = vtki.new("PointSource")
2688        psrc.SetNumberOfPoints(len(centers))
2689        psrc.Update()
2690        pd = psrc.GetOutput()
2691        vpts = pd.GetPoints()
2692
2693        glyph = vtki.vtkGlyph3D()
2694        glyph.SetSourceConnection(src.GetOutputPort())
2695
2696        if cisseq:
2697            glyph.SetColorModeToColorByScalar()
2698            ucols = vtki.vtkUnsignedCharArray()
2699            ucols.SetNumberOfComponents(3)
2700            ucols.SetName("Colors")
2701            for acol in c:
2702                cx, cy, cz = get_color(acol)
2703                ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255)
2704            pd.GetPointData().AddArray(ucols)
2705            pd.GetPointData().SetActiveScalars("Colors")
2706            glyph.ScalingOff()
2707        elif risseq:
2708            glyph.SetScaleModeToScaleByScalar()
2709            urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32)
2710            urads.SetName("Radii")
2711            pd.GetPointData().AddArray(urads)
2712            pd.GetPointData().SetActiveScalars("Radii")
2713
2714        vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32))
2715
2716        glyph.SetInputData(pd)
2717        glyph.Update()
2718
2719        super().__init__(glyph.GetOutput(), alpha=alpha)
2720        self.pos(base)
2721        self.phong()
2722        if cisseq:
2723            self.mapper.ScalarVisibilityOn()
2724        else:
2725            self.mapper.ScalarVisibilityOff()
2726            self.c(c)
2727        self.name = "Spheres"
2728
2729
2730class Earth(Mesh):
2731    """
2732    Build a textured mesh representing the Earth.
2733    """
2734
2735    def __init__(self, style=1, r=1.0) -> None:
2736        """
2737        Build a textured mesh representing the Earth.
2738
2739        Example:
2740            - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py)
2741
2742                ![](https://vedo.embl.es/images/advanced/geodesic.png)
2743        """
2744        tss = vtki.new("TexturedSphereSource")
2745        tss.SetRadius(r)
2746        tss.SetThetaResolution(72)
2747        tss.SetPhiResolution(36)
2748        tss.Update()
2749        super().__init__(tss.GetOutput(), c="w")
2750        atext = vtki.vtkTexture()
2751        pnm_reader = vtki.new("JPEGReader")
2752        fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False)
2753        pnm_reader.SetFileName(fn)
2754        atext.SetInputConnection(pnm_reader.GetOutputPort())
2755        atext.InterpolateOn()
2756        self.texture(atext)
2757        self.name = "Earth"
2758
2759
2760class Ellipsoid(Mesh):
2761    """Build a 3D ellipsoid."""
2762    def __init__(
2763        self,
2764        pos=(0, 0, 0),
2765        axis1=(0.5, 0, 0),
2766        axis2=(0, 1, 0),
2767        axis3=(0, 0, 1.5),
2768        res=24,
2769        c="cyan4",
2770        alpha=1.0,
2771    ) -> None:
2772        """
2773        Build a 3D ellipsoid centered at position `pos`.
2774
2775        Arguments:
2776            axis1 : (list)
2777                First axis. Length corresponds to semi-axis.
2778            axis2 : (list)
2779                Second axis. Length corresponds to semi-axis.
2780            axis3 : (list)
2781                Third axis. Length corresponds to semi-axis.
2782        """        
2783        self.center = utils.make3d(pos)
2784
2785        self.axis1 = utils.make3d(axis1)
2786        self.axis2 = utils.make3d(axis2)
2787        self.axis3 = utils.make3d(axis3)
2788
2789        self.va = np.linalg.norm(self.axis1)
2790        self.vb = np.linalg.norm(self.axis2)
2791        self.vc = np.linalg.norm(self.axis3)
2792
2793        self.va_error = 0
2794        self.vb_error = 0
2795        self.vc_error = 0
2796
2797        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2798        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2799
2800        if utils.is_sequence(res):
2801            res_t, res_phi = res
2802        else:
2803            res_t, res_phi = 2 * res, res
2804
2805        elli_source = vtki.new("SphereSource")
2806        elli_source.SetRadius(1)
2807        elli_source.SetThetaResolution(res_t)
2808        elli_source.SetPhiResolution(res_phi)
2809        elli_source.Update()
2810
2811        super().__init__(elli_source.GetOutput(), c, alpha)
2812
2813        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2814        lt = LinearTransform(matrix).translate(pos)
2815        self.apply_transform(lt)
2816        self.name = "Ellipsoid"
2817
2818    def asphericity(self) -> float:
2819        """
2820        Return a measure of how different an ellipsoid is from a sphere.
2821        Values close to zero correspond to a spheric object.
2822        """
2823        a, b, c = self.va, self.vb, self.vc
2824        asp = ( ((a-b)/(a+b))**2
2825              + ((a-c)/(a+c))**2
2826              + ((b-c)/(b+c))**2 ) / 3. * 4.
2827        return float(asp)
2828
2829    def asphericity_error(self) -> float:
2830        """
2831        Calculate statistical error on the asphericity value.
2832
2833        Errors on the main axes are stored in
2834        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2835        """
2836        a, b, c = self.va, self.vb, self.vc
2837        sqrtn = np.sqrt(self.nr_of_points)
2838        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2839
2840        # from sympy import *
2841        # init_printing(use_unicode=True)
2842        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2843        # L = (
2844        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2845        #    / 3 * 4)
2846        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2847        # print(dl2)
2848        # exit()
2849
2850        dL2 = (
2851            ea ** 2
2852            * (
2853                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2854                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2855                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2856                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2857            ) ** 2
2858            + eb ** 2
2859            * (
2860                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2861                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2862                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2863                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2864            ) ** 2
2865            + ec ** 2
2866            * (
2867                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2868                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2869                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2870                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2871            ) ** 2
2872        )
2873        err = np.sqrt(dL2)
2874        self.va_error = ea
2875        self.vb_error = eb
2876        self.vc_error = ec
2877        return err
2878
2879
2880class Grid(Mesh):
2881    """
2882    An even or uneven 2D grid.
2883    """
2884
2885    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2886        """
2887        Create an even or uneven 2D grid.
2888        Can also be created from a `np.mgrid` object (see example).
2889
2890        Arguments:
2891            pos : (list, Points, Mesh)
2892                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2893            s : (float, list)
2894                if a float is provided it is interpreted as the total size along x and y,
2895                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2896                In this case keyword `res` is ignored (see example below).
2897            res : (list)
2898                resolutions along x and y, e.i. the number of subdivisions
2899            lw : (int)
2900                line width
2901
2902        Example:
2903            ```python
2904            from vedo import *
2905            xcoords = np.arange(0, 2, 0.2)
2906            ycoords = np.arange(0, 1, 0.2)
2907            sqrtx = sqrt(xcoords)
2908            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2909            grid.show(axes=8).close()
2910
2911            # Can also create a grid from a np.mgrid:
2912            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2913            vgrid = Grid(s=(X[:,0], Y[0]))
2914            vgrid.show(axes=8).close()
2915            ```
2916            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2917        """
2918        resx, resy = res
2919        sx, sy = s
2920        
2921        try:
2922            bb = pos.bounds()
2923            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2924            sx = bb[1] - bb[0]
2925            sy = bb[3] - bb[2]
2926        except AttributeError:
2927            pass        
2928
2929        if len(pos) == 2:
2930            pos = (pos[0], pos[1], 0)
2931        elif len(pos) in [4,6]: # passing a bounding box
2932            bb = pos
2933            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2934            sx = bb[1] - bb[0]
2935            sy = bb[3] - bb[2]
2936            if len(pos)==6:
2937                pos[2] = bb[4] - bb[5]
2938
2939        if utils.is_sequence(sx) and utils.is_sequence(sy):
2940            verts = []
2941            for y in sy:
2942                for x in sx:
2943                    verts.append([x, y, 0])
2944            faces = []
2945            n = len(sx)
2946            m = len(sy)
2947            for j in range(m - 1):
2948                j1n = (j + 1) * n
2949                for i in range(n - 1):
2950                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2951
2952            super().__init__([verts, faces], c, alpha)
2953
2954        else:
2955            ps = vtki.new("PlaneSource")
2956            ps.SetResolution(resx, resy)
2957            ps.Update()
2958
2959            t = vtki.vtkTransform()
2960            t.Translate(pos)
2961            t.Scale(sx, sy, 1)
2962
2963            tf = vtki.new("TransformPolyDataFilter")
2964            tf.SetInputData(ps.GetOutput())
2965            tf.SetTransform(t)
2966            tf.Update()
2967
2968            super().__init__(tf.GetOutput(), c, alpha)
2969
2970        self.wireframe().lw(lw)
2971        self.properties.LightingOff()
2972        self.name = "Grid"
2973
2974
2975class Plane(Mesh):
2976    """Create a plane in space."""
2977
2978    def __init__(
2979            self,
2980            pos=(0, 0, 0),
2981            normal=(0, 0, 1),
2982            s=(1, 1),
2983            res=(1, 1),
2984            c="gray5", alpha=1.0,
2985        ) -> None:
2986        """
2987        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
2988        to vector `normal` so that it passes through point `pos`.
2989
2990        Arguments:
2991            pos : (list)
2992                position of the plane center
2993            normal : (list)
2994                normal vector to the plane
2995            s : (list)
2996                size of the plane along x and y
2997            res : (list)
2998                resolution of the plane along x and y
2999        """
3000        if isinstance(pos, vtki.vtkPolyData):
3001            super().__init__(pos, c, alpha)
3002            # self.transform = LinearTransform().translate(pos)
3003
3004        else:
3005            ps = vtki.new("PlaneSource")
3006            ps.SetResolution(res[0], res[1])
3007            tri = vtki.new("TriangleFilter")
3008            tri.SetInputConnection(ps.GetOutputPort())
3009            tri.Update()
3010            
3011            super().__init__(tri.GetOutput(), c, alpha)
3012
3013            pos = utils.make3d(pos)
3014            normal = np.asarray(normal, dtype=float)
3015            axis = normal / np.linalg.norm(normal)
3016            theta = np.arccos(axis[2])
3017            phi = np.arctan2(axis[1], axis[0])
3018
3019            t = LinearTransform()
3020            t.scale([s[0], s[1], 1])
3021            t.rotate_y(np.rad2deg(theta))
3022            t.rotate_z(np.rad2deg(phi))
3023            t.translate(pos)
3024            self.apply_transform(t)
3025
3026        self.lighting("off")
3027        self.name = "Plane"
3028        self.variance = 0
3029
3030    def clone(self, deep=True) -> "Plane":
3031        newplane = Plane()
3032        if deep:
3033            newplane.dataset.DeepCopy(self.dataset)
3034        else:
3035            newplane.dataset.ShallowCopy(self.dataset)
3036        newplane.copy_properties_from(self)
3037        newplane.transform = self.transform.clone()
3038        newplane.variance = 0
3039        return newplane
3040    
3041    @property
3042    def normal(self) -> np.ndarray:
3043        pts = self.vertices
3044        AB = pts[1] - pts[0]
3045        AC = pts[2] - pts[0]
3046        normal = np.cross(AB, AC)
3047        normal = normal / np.linalg.norm(normal)
3048        return normal
3049
3050    @property
3051    def center(self) -> np.ndarray:
3052        pts = self.vertices
3053        return np.mean(pts, axis=0)
3054
3055    def contains(self, points, tol=0) -> np.ndarray:
3056        """
3057        Check if each of the provided point lies on this plane.
3058        `points` is an array of shape (n, 3).
3059        """
3060        points = np.array(points, dtype=float)
3061        bounds = self.vertices
3062
3063        mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol)
3064
3065        for i in [1, 3]:
3066            AB = bounds[i] - bounds[0]
3067            AP = points - bounds[0]
3068            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3069            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3070            mask = np.logical_and(mask, mask_l)
3071            mask = np.logical_and(mask, mask_g)
3072        return mask
3073
3074
3075class Rectangle(Mesh):
3076    """
3077    Build a rectangle in the xy plane.
3078    """
3079
3080    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3081        """
3082        Build a rectangle in the xy plane identified by any two corner points.
3083
3084        Arguments:
3085            p1 : (list)
3086                bottom-left position of the corner
3087            p2 : (list)
3088                top-right position of the corner
3089            radius : (float, list)
3090                smoothing radius of the corner in world units.
3091                A list can be passed with 4 individual values.
3092        """
3093        if len(p1) == 2:
3094            p1 = np.array([p1[0], p1[1], 0.0])
3095        else:
3096            p1 = np.array(p1, dtype=float)
3097        if len(p2) == 2:
3098            p2 = np.array([p2[0], p2[1], 0.0])
3099        else:
3100            p2 = np.array(p2, dtype=float)
3101
3102        self.corner1 = p1
3103        self.corner2 = p2
3104
3105        color = c
3106        smoothr = False
3107        risseq = False
3108        if utils.is_sequence(radius):
3109            risseq = True
3110            smoothr = True
3111            if max(radius) == 0:
3112                smoothr = False
3113        elif radius:
3114            smoothr = True
3115
3116        if not smoothr:
3117            radius = None
3118        self.radius = radius
3119
3120        if smoothr:
3121            r = radius
3122            if not risseq:
3123                r = [r, r, r, r]
3124            rd, ra, rb, rc = r
3125
3126            if p1[0] > p2[0]:  # flip p1 - p2
3127                p1, p2 = p2, p1
3128            if p1[1] > p2[1]:  # flip p1y - p2y
3129                p1[1], p2[1] = p2[1], p1[1]
3130
3131            px, py, _ = p2 - p1
3132            k = min(px / 2, py / 2)
3133            ra = min(abs(ra), k)
3134            rb = min(abs(rb), k)
3135            rc = min(abs(rc), k)
3136            rd = min(abs(rd), k)
3137            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3138            betas = np.split(beta, 4)
3139            rrx = np.cos(betas)
3140            rry = np.sin(betas)
3141
3142            q1 = (rd, 0)
3143            # q2 = (px-ra, 0)
3144            q3 = (px, ra)
3145            # q4 = (px, py-rb)
3146            q5 = (px - rb, py)
3147            # q6 = (rc, py)
3148            q7 = (0, py - rc)
3149            # q8 = (0, rd)
3150            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3151            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3152            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3153            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3154
3155            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3156            faces = [list(range(len(pts)))]
3157        else:
3158            p1r = np.array([p2[0], p1[1], 0.0])
3159            p2l = np.array([p1[0], p2[1], 0.0])
3160            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3161            faces = [(0, 1, 2, 3)]
3162
3163        super().__init__([pts, faces], color, alpha)
3164        self.pos(p1)
3165        self.properties.LightingOff()
3166        self.name = "Rectangle"
3167
3168
3169class Box(Mesh):
3170    """
3171    Build a box of specified dimensions.
3172    """
3173
3174    def __init__(
3175            self, pos=(0, 0, 0), 
3176            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3177        """
3178        Build a box of dimensions `x=length, y=width and z=height`.
3179        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3180
3181        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3182        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3183
3184        Examples:
3185            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3186
3187                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3188        """
3189        src = vtki.new("CubeSource")
3190
3191        if len(pos) == 2:
3192            pos = (pos[0], pos[1], 0)
3193
3194        if len(pos) == 6:
3195            src.SetBounds(pos)
3196            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3197        elif len(size) == 3:
3198            length, width, height = size
3199            src.SetXLength(length)
3200            src.SetYLength(width)
3201            src.SetZLength(height)
3202            src.SetCenter(pos)
3203        else:
3204            src.SetXLength(length)
3205            src.SetYLength(width)
3206            src.SetZLength(height)
3207            src.SetCenter(pos)
3208
3209        src.Update()
3210        pd = src.GetOutput()
3211
3212        tc = [
3213            [0.0, 0.0],
3214            [1.0, 0.0],
3215            [0.0, 1.0],
3216            [1.0, 1.0],
3217            [1.0, 0.0],
3218            [0.0, 0.0],
3219            [1.0, 1.0],
3220            [0.0, 1.0],
3221            [1.0, 1.0],
3222            [1.0, 0.0],
3223            [0.0, 1.0],
3224            [0.0, 0.0],
3225            [0.0, 1.0],
3226            [0.0, 0.0],
3227            [1.0, 1.0],
3228            [1.0, 0.0],
3229            [1.0, 0.0],
3230            [0.0, 0.0],
3231            [1.0, 1.0],
3232            [0.0, 1.0],
3233            [0.0, 0.0],
3234            [1.0, 0.0],
3235            [0.0, 1.0],
3236            [1.0, 1.0],
3237        ]
3238        vtc = utils.numpy2vtk(tc)
3239        pd.GetPointData().SetTCoords(vtc)
3240        super().__init__(pd, c, alpha)
3241        self.transform = LinearTransform().translate(pos)
3242        self.name = "Box"
3243
3244
3245class Cube(Box):
3246    """Build a cube."""
3247
3248    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3249        """Build a cube of size `side`."""
3250        super().__init__(pos, side, side, side, (), c, alpha)
3251        self.name = "Cube"
3252
3253
3254class TessellatedBox(Mesh):
3255    """
3256    Build a cubic `Mesh` made of quads.
3257    """
3258
3259    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3260        """
3261        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3262
3263        Arguments:
3264            pos : (list)
3265                position of the left bottom corner
3266            n : (int, list)
3267                number of subdivisions along each side
3268            spacing : (float)
3269                size of the side of the single quad in the 3 directions
3270        """
3271        if utils.is_sequence(n):  # slow
3272            img = vtki.vtkImageData()
3273            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3274            img.SetSpacing(spacing)
3275            gf = vtki.new("GeometryFilter")
3276            gf.SetInputData(img)
3277            gf.Update()
3278            poly = gf.GetOutput()
3279        else:  # fast
3280            n -= 1
3281            tbs = vtki.new("TessellatedBoxSource")
3282            tbs.SetLevel(n)
3283            if len(bounds):
3284                tbs.SetBounds(bounds)
3285            else:
3286                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3287            tbs.QuadsOn()
3288            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3289            tbs.Update()
3290            poly = tbs.GetOutput()
3291        super().__init__(poly, c=c, alpha=alpha)
3292        self.pos(pos)
3293        self.lw(1).lighting("off")
3294        self.name = "TessellatedBox"
3295
3296
3297class Spring(Mesh):
3298    """
3299    Build a spring model.
3300    """
3301
3302    def __init__(
3303        self,
3304        start_pt=(0, 0, 0),
3305        end_pt=(1, 0, 0),
3306        coils=20,
3307        r1=0.1,
3308        r2=None,
3309        thickness=None,
3310        c="gray5",
3311        alpha=1.0,
3312    ) -> None:
3313        """
3314        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3315
3316        Arguments:
3317            coils : (int)
3318                number of coils
3319            r1 : (float)
3320                radius at start point
3321            r2 : (float)
3322                radius at end point
3323            thickness : (float)
3324                thickness of the coil section
3325        """
3326        start_pt = utils.make3d(start_pt)
3327        end_pt = utils.make3d(end_pt)
3328
3329        diff = end_pt - start_pt
3330        length = np.linalg.norm(diff)
3331        if not length:
3332            return
3333        if not r1:
3334            r1 = length / 20
3335        trange = np.linspace(0, length, num=50 * coils)
3336        om = 6.283 * (coils - 0.5) / length
3337        if not r2:
3338            r2 = r1
3339        pts = []
3340        for t in trange:
3341            f = (length - t) / length
3342            rd = r1 * f + r2 * (1 - f)
3343            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3344
3345        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3346        diff = diff / length
3347        theta = np.arccos(diff[2])
3348        phi = np.arctan2(diff[1], diff[0])
3349        sp = Line(pts)
3350        
3351        t = vtki.vtkTransform()
3352        t.Translate(start_pt)
3353        t.RotateZ(np.rad2deg(phi))
3354        t.RotateY(np.rad2deg(theta))
3355
3356        tf = vtki.new("TransformPolyDataFilter")
3357        tf.SetInputData(sp.dataset)
3358        tf.SetTransform(t)
3359        tf.Update()
3360
3361        tuf = vtki.new("TubeFilter")
3362        tuf.SetNumberOfSides(12)
3363        tuf.CappingOn()
3364        tuf.SetInputData(tf.GetOutput())
3365        if not thickness:
3366            thickness = r1 / 10
3367        tuf.SetRadius(thickness)
3368        tuf.Update()
3369
3370        super().__init__(tuf.GetOutput(), c, alpha)
3371
3372        self.phong()
3373        self.base = np.array(start_pt, dtype=float)
3374        self.top  = np.array(end_pt, dtype=float)
3375        self.name = "Spring"
3376
3377
3378class Cylinder(Mesh):
3379    """
3380    Build a cylinder of specified height and radius.
3381    """
3382
3383    def __init__(
3384        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3385        cap=True, res=24, c="teal3", alpha=1.0
3386    ) -> None:
3387        """
3388        Build a cylinder of specified height and radius `r`, centered at `pos`.
3389
3390        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3391        centered at `v1` and top at `v2`.
3392
3393        Arguments:
3394            cap : (bool)
3395                enable/disable the caps of the cylinder
3396            res : (int)
3397                resolution of the cylinder sides
3398
3399        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3400        """
3401        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3402            base = np.array(pos[0], dtype=float)
3403            top = np.array(pos[1], dtype=float)
3404            pos = (base + top) / 2
3405            height = np.linalg.norm(top - base)
3406            axis = top - base
3407            axis = utils.versor(axis)
3408        else:
3409            axis = utils.versor(axis)
3410            base = pos - axis * height / 2
3411            top = pos + axis * height / 2
3412
3413        cyl = vtki.new("CylinderSource")
3414        cyl.SetResolution(res)
3415        cyl.SetRadius(r)
3416        cyl.SetHeight(height)
3417        cyl.SetCapping(cap)
3418        cyl.Update()
3419
3420        theta = np.arccos(axis[2])
3421        phi = np.arctan2(axis[1], axis[0])
3422        t = vtki.vtkTransform()
3423        t.PostMultiply()
3424        t.RotateX(90)  # put it along Z
3425        t.RotateY(np.rad2deg(theta))
3426        t.RotateZ(np.rad2deg(phi))
3427        t.Translate(pos)
3428
3429        tf = vtki.new("TransformPolyDataFilter")
3430        tf.SetInputData(cyl.GetOutput())
3431        tf.SetTransform(t)
3432        tf.Update()
3433
3434        super().__init__(tf.GetOutput(), c, alpha)
3435
3436        self.phong()
3437        self.base = base
3438        self.top  = top
3439        self.transform = LinearTransform().translate(pos)
3440        self.name = "Cylinder"
3441
3442
3443class Cone(Mesh):
3444    """Build a cone of specified radius and height."""
3445
3446    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3447                 res=48, c="green3", alpha=1.0) -> None:
3448        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3449        con = vtki.new("ConeSource")
3450        con.SetResolution(res)
3451        con.SetRadius(r)
3452        con.SetHeight(height)
3453        con.SetDirection(axis)
3454        con.Update()
3455        super().__init__(con.GetOutput(), c, alpha)
3456        self.phong()
3457        if len(pos) == 2:
3458            pos = (pos[0], pos[1], 0)
3459        self.pos(pos)
3460        v = utils.versor(axis) * height / 2
3461        self.base = pos - v
3462        self.top  = pos + v
3463        self.name = "Cone"
3464
3465
3466class Pyramid(Cone):
3467    """Build a pyramidal shape."""
3468
3469    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3470                 c="green3", alpha=1) -> None:
3471        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3472        super().__init__(pos, s, height, axis, 4, c, alpha)
3473        self.name = "Pyramid"
3474
3475
3476class Torus(Mesh):
3477    """
3478    Build a toroidal shape.
3479    """
3480
3481    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3482        """
3483        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3484        If `quad=True` a quad-mesh is generated.
3485        """
3486        if utils.is_sequence(res):
3487            res_u, res_v = res
3488        else:
3489            res_u, res_v = 3 * res, res
3490
3491        if quads:
3492            # https://github.com/marcomusy/vedo/issues/710
3493
3494            n = res_v
3495            m = res_u
3496
3497            theta = np.linspace(0, 2.0 * np.pi, n)
3498            phi = np.linspace(0, 2.0 * np.pi, m)
3499            theta, phi = np.meshgrid(theta, phi)
3500            t = r1 + r2 * np.cos(theta)
3501            x = t * np.cos(phi)
3502            y = t * np.sin(phi)
3503            z = r2 * np.sin(theta)
3504            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3505
3506            faces = []
3507            for j in range(m - 1):
3508                j1n = (j + 1) * n
3509                for i in range(n - 1):
3510                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3511
3512            super().__init__([pts, faces], c, alpha)
3513
3514        else:
3515            rs = vtki.new("ParametricTorus")
3516            rs.SetRingRadius(r1)
3517            rs.SetCrossSectionRadius(r2)
3518            pfs = vtki.new("ParametricFunctionSource")
3519            pfs.SetParametricFunction(rs)
3520            pfs.SetUResolution(res_u)
3521            pfs.SetVResolution(res_v)
3522            pfs.Update()
3523
3524            super().__init__(pfs.GetOutput(), c, alpha)
3525
3526        self.phong()
3527        if len(pos) == 2:
3528            pos = (pos[0], pos[1], 0)
3529        self.pos(pos)
3530        self.name = "Torus"
3531
3532
3533class Paraboloid(Mesh):
3534    """
3535    Build a paraboloid.
3536    """
3537
3538    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3539        """
3540        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3541
3542        Full volumetric expression is:
3543            `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9`
3544
3545        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3546        """
3547        quadric = vtki.new("Quadric")
3548        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3549        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3550        #         + a3*x*y + a4*y*z + a5*x*z
3551        #         + a6*x   + a7*y   + a8*z  +a9
3552        sample = vtki.new("SampleFunction")
3553        sample.SetSampleDimensions(res, res, res)
3554        sample.SetImplicitFunction(quadric)
3555
3556        contours = vtki.new("ContourFilter")
3557        contours.SetInputConnection(sample.GetOutputPort())
3558        contours.GenerateValues(1, 0.01, 0.01)
3559        contours.Update()
3560
3561        super().__init__(contours.GetOutput(), c, alpha)
3562        self.compute_normals().phong()
3563        self.mapper.ScalarVisibilityOff()
3564        self.pos(pos)
3565        self.name = "Paraboloid"
3566
3567
3568class Hyperboloid(Mesh):
3569    """
3570    Build a hyperboloid.
3571    """
3572
3573    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3574        """
3575        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3576
3577        Full volumetric expression is:
3578            `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9`
3579        """
3580        q = vtki.new("Quadric")
3581        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3582        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3583        #         + a3*x*y + a4*y*z + a5*x*z
3584        #         + a6*x   + a7*y   + a8*z  +a9
3585        sample = vtki.new("SampleFunction")
3586        sample.SetSampleDimensions(res, res, res)
3587        sample.SetImplicitFunction(q)
3588
3589        contours = vtki.new("ContourFilter")
3590        contours.SetInputConnection(sample.GetOutputPort())
3591        contours.GenerateValues(1, value, value)
3592        contours.Update()
3593
3594        super().__init__(contours.GetOutput(), c, alpha)
3595        self.compute_normals().phong()
3596        self.mapper.ScalarVisibilityOff()
3597        self.pos(pos)
3598        self.name = "Hyperboloid"
3599
3600
3601def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True) -> Any:
3602    """
3603    Generate a marker shape. Typically used in association with `Glyph`.
3604    """
3605    if isinstance(symbol, Mesh):
3606        return symbol.c(c).alpha(alpha).lighting("off")
3607
3608    if isinstance(symbol, int):
3609        symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"]
3610        symbol = symbol % len(symbs)
3611        symbol = symbs[symbol]
3612
3613    if symbol == ".":
3614        mesh = Polygon(nsides=24, r=s * 0.6)
3615    elif symbol == "o":
3616        mesh = Polygon(nsides=24, r=s * 0.75)
3617    elif symbol == "O":
3618        mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24))
3619    elif symbol == "0":
3620        m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24))
3621        m2 = Circle(r=s * 0.36).reverse()
3622        mesh = merge(m1, m2)
3623    elif symbol == "p":
3624        mesh = Polygon(nsides=5, r=s)
3625    elif symbol == "*":
3626        mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled)
3627    elif symbol == "h":
3628        mesh = Polygon(nsides=6, r=s)
3629    elif symbol == "D":
3630        mesh = Polygon(nsides=4, r=s)
3631    elif symbol == "d":
3632        mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1])
3633    elif symbol == "v":
3634        mesh = Polygon(nsides=3, r=s).rotate_z(180)
3635    elif symbol == "^":
3636        mesh = Polygon(nsides=3, r=s)
3637    elif symbol == ">":
3638        mesh = Polygon(nsides=3, r=s).rotate_z(-90)
3639    elif symbol == "<":
3640        mesh = Polygon(nsides=3, r=s).rotate_z(90)
3641    elif symbol == "s":
3642        mesh = Mesh(
3643            [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]]
3644        ).scale(s / 1.4)
3645    elif symbol == "x":
3646        mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0)
3647        # mesh.rotate_z(45)
3648    elif symbol == "a":
3649        mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0)
3650    else:
3651        mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0)
3652    mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha)
3653    if len(pos) == 2:
3654        pos = (pos[0], pos[1], 0)
3655    mesh.pos(pos)
3656    mesh.name = "Marker"
3657    return mesh
3658
3659
3660class Brace(Mesh):
3661    """
3662    Create a brace (bracket) shape.
3663    """
3664
3665    def __init__(
3666        self,
3667        q1,
3668        q2,
3669        style="}",
3670        padding1=0.0,
3671        font="Theemim",
3672        comment="",
3673        justify=None,
3674        angle=0.0,
3675        padding2=0.2,
3676        s=1.0,
3677        italic=0,
3678        c="k1",
3679        alpha=1.0,
3680    ) -> None:
3681        """
3682        Create a brace (bracket) shape which spans from point q1 to point q2.
3683
3684        Arguments:
3685            q1 : (list)
3686                point 1.
3687            q2 : (list)
3688                point 2.
3689            style : (str)
3690                style of the bracket, eg. `{}, [], (), <>`.
3691            padding1 : (float)
3692                padding space in percent form the input points.
3693            font : (str)
3694                font type
3695            comment : (str)
3696                additional text to appear next to the brace symbol.
3697            justify : (str)
3698                specify the anchor point to justify text comment, e.g. "top-left".
3699            italic : float
3700                italicness of the text comment (can be a positive or negative number)
3701            angle : (float)
3702                rotation angle of text. Use `None` to keep it horizontal.
3703            padding2 : (float)
3704                padding space in percent form brace to text comment.
3705            s : (float)
3706                scale factor for the comment
3707
3708        Examples:
3709            - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py)
3710
3711                ![](https://vedo.embl.es/images/pyplot/scatter3.png)
3712        """
3713        if isinstance(q1, vtki.vtkActor):
3714            q1 = q1.GetPosition()
3715        if isinstance(q2, vtki.vtkActor):
3716            q2 = q2.GetPosition()
3717        if len(q1) == 2:
3718            q1 = [q1[0], q1[1], 0.0]
3719        if len(q2) == 2:
3720            q2 = [q2[0], q2[1], 0.0]
3721        q1 = np.array(q1, dtype=float)
3722        q2 = np.array(q2, dtype=float)
3723        mq = (q1 + q2) / 2
3724        q1 = q1 - mq
3725        q2 = q2 - mq
3726        d = np.linalg.norm(q2 - q1)
3727        q2[2] = q1[2]
3728
3729        if style not in "{}[]()<>|I":
3730            vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I")
3731            style = "}"
3732
3733        flip = False
3734        if style in ["{", "[", "(", "<"]:
3735            flip = True
3736            i = ["{", "[", "(", "<"].index(style)
3737            style = ["}", "]", ")", ">"][i]
3738
3739        br = Text3D(style, font="Theemim", justify="center-left")
3740        br.scale([0.4, 1, 1])
3741
3742        angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90
3743        if flip:
3744            angler += 180
3745
3746        _, x1, y0, y1, _, _ = br.bounds()
3747        if comment:
3748            just = "center-top"
3749            if angle is None:
3750                angle = -angler + 90
3751                if not flip:
3752                    angle += 180
3753
3754            if flip:
3755                angle += 180
3756                just = "center-bottom"
3757            if justify is not None:
3758                just = justify
3759            cmt = Text3D(comment, font=font, justify=just, italic=italic)
3760            cx0, cx1 = cmt.xbounds()
3761            cmt.rotate_z(90 + angle)
3762            cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5)
3763            cmt.shift(x1 * (1 + padding2), 0, 0)
3764            poly = merge(br, cmt).dataset
3765
3766        else:
3767            poly = br.dataset
3768
3769        tr = vtki.vtkTransform()
3770        tr.Translate(mq)
3771        tr.RotateZ(angler)
3772        tr.Translate(padding1 * d, 0, 0)
3773        pscale = 1
3774        tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1)
3775
3776        tf = vtki.new("TransformPolyDataFilter")
3777        tf.SetInputData(poly)
3778        tf.SetTransform(tr)
3779        tf.Update()
3780        poly = tf.GetOutput()
3781
3782        super().__init__(poly, c, alpha)
3783
3784        self.base = q1
3785        self.top  = q2
3786        self.name = "Brace"
3787
3788
3789class Star3D(Mesh):
3790    """
3791    Build a 3D starred shape.
3792    """
3793
3794    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3795        """
3796        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3797        """
3798        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3799               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3800               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3801               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3802        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3803               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3804               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3805               [10,1, 0],[10,11, 9]]
3806
3807        super().__init__([pts, fcs], c, alpha)
3808        self.rotate_x(90)
3809        self.scale(r).lighting("shiny")
3810
3811        if len(pos) == 2:
3812            pos = (pos[0], pos[1], 0)
3813        self.pos(pos)
3814        self.name = "Star3D"
3815
3816
3817class Cross3D(Mesh):
3818    """
3819    Build a 3D cross shape.
3820    """
3821
3822    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3823        """
3824        Build a 3D cross shape, mainly useful as a 3D marker.
3825        """
3826        if len(pos) == 2:
3827            pos = (pos[0], pos[1], 0)
3828
3829        c1 = Cylinder(r=thickness * s, height=2 * s)
3830        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3831        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3832        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3833        super().__init__(poly, c, alpha)
3834        self.name = "Cross3D"
3835
3836
3837class ParametricShape(Mesh):
3838    """
3839    A set of built-in shapes mainly for illustration purposes.
3840    """
3841
3842    def __init__(self, name, res=51, n=25, seed=1):
3843        """
3844        A set of built-in shapes mainly for illustration purposes.
3845
3846        Name can be an integer or a string in this list:
3847            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3848            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3849            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3850            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3851
3852        Example:
3853            ```python
3854            from vedo import *
3855            settings.immediate_rendering = False
3856            plt = Plotter(N=18)
3857            for i in range(18):
3858                ps = ParametricShape(i).color(i)
3859                plt.at(i).show(ps, ps.name)
3860            plt.interactive().close()
3861            ```
3862            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3863        """
3864
3865        shapes = [
3866            "Boy",
3867            "ConicSpiral",
3868            "CrossCap",
3869            "Enneper",
3870            "Figure8Klein",
3871            "Klein",
3872            "Dini",
3873            "Mobius",
3874            "RandomHills",
3875            "Roman",
3876            "SuperEllipsoid",
3877            "BohemianDome",
3878            "Bour",
3879            "CatalanMinimal",
3880            "Henneberg",
3881            "Kuen",
3882            "PluckerConoid",
3883            "Pseudosphere",
3884        ]
3885
3886        if isinstance(name, int):
3887            name = name % len(shapes)
3888            name = shapes[name]
3889
3890        if name == "Boy":
3891            ps = vtki.new("ParametricBoy")
3892        elif name == "ConicSpiral":
3893            ps = vtki.new("ParametricConicSpiral")
3894        elif name == "CrossCap":
3895            ps = vtki.new("ParametricCrossCap")
3896        elif name == "Dini":
3897            ps = vtki.new("ParametricDini")
3898        elif name == "Enneper":
3899            ps = vtki.new("ParametricEnneper")
3900        elif name == "Figure8Klein":
3901            ps = vtki.new("ParametricFigure8Klein")
3902        elif name == "Klein":
3903            ps = vtki.new("ParametricKlein")
3904        elif name == "Mobius":
3905            ps = vtki.new("ParametricMobius")
3906            ps.SetRadius(2.0)
3907            ps.SetMinimumV(-0.5)
3908            ps.SetMaximumV(0.5)
3909        elif name == "RandomHills":
3910            ps = vtki.new("ParametricRandomHills")
3911            ps.AllowRandomGenerationOn()
3912            ps.SetRandomSeed(seed)
3913            ps.SetNumberOfHills(n)
3914        elif name == "Roman":
3915            ps = vtki.new("ParametricRoman")
3916        elif name == "SuperEllipsoid":
3917            ps = vtki.new("ParametricSuperEllipsoid")
3918            ps.SetN1(0.5)
3919            ps.SetN2(0.4)
3920        elif name == "BohemianDome":
3921            ps = vtki.new("ParametricBohemianDome")
3922            ps.SetA(5.0)
3923            ps.SetB(1.0)
3924            ps.SetC(2.0)
3925        elif name == "Bour":
3926            ps = vtki.new("ParametricBour")
3927        elif name == "CatalanMinimal":
3928            ps = vtki.new("ParametricCatalanMinimal")
3929        elif name == "Henneberg":
3930            ps = vtki.new("ParametricHenneberg")
3931        elif name == "Kuen":
3932            ps = vtki.new("ParametricKuen")
3933            ps.SetDeltaV0(0.001)
3934        elif name == "PluckerConoid":
3935            ps = vtki.new("ParametricPluckerConoid")
3936        elif name == "Pseudosphere":
3937            ps = vtki.new("ParametricPseudosphere")
3938        else:
3939            vedo.logger.error(f"unknown ParametricShape {name}")
3940            return
3941
3942        pfs = vtki.new("ParametricFunctionSource")
3943        pfs.SetParametricFunction(ps)
3944        pfs.SetUResolution(res)
3945        pfs.SetVResolution(res)
3946        pfs.SetWResolution(res)
3947        pfs.SetScalarModeToZ()
3948        pfs.Update()
3949
3950        super().__init__(pfs.GetOutput())
3951
3952        if name == "RandomHills": self.shift([0,-10,-2.25])
3953        if name != 'Kuen': self.normalize()
3954        if name == 'Dini': self.scale(0.4)
3955        if name == 'Enneper': self.scale(0.4)
3956        if name == 'ConicSpiral': self.bc('tomato')
3957        self.name = name
3958
3959
3960@lru_cache(None)
3961def _load_font(font) -> np.ndarray:
3962    # print('_load_font()', font)
3963
3964    if utils.is_number(font):
3965        font = list(settings.font_parameters.keys())[int(font)]
3966
3967    if font.endswith(".npz"):  # user passed font as a local path
3968        fontfile = font
3969        font = os.path.basename(font).split(".")[0]
3970
3971    elif font.startswith("https"):  # user passed URL link, make it a path
3972        try:
3973            fontfile = vedo.file_io.download(font, verbose=False, force=False)
3974            font = os.path.basename(font).split(".")[0]
3975        except:
3976            vedo.logger.warning(f"font {font} not found")
3977            font = settings.default_font
3978            fontfile = os.path.join(vedo.fonts_path, font + ".npz")
3979
3980    else:  # user passed font by its standard name
3981        font = font[:1].upper() + font[1:]  # capitalize first letter only
3982        fontfile = os.path.join(vedo.fonts_path, font + ".npz")
3983
3984        if font not in settings.font_parameters.keys():
3985            font = "Normografo"
3986            vedo.logger.warning(
3987                f"Unknown font: {font}\n"
3988                f"Available 3D fonts are: "
3989                f"{list(settings.font_parameters.keys())}\n"
3990                f"Using font {font} instead."
3991            )
3992            fontfile = os.path.join(vedo.fonts_path, font + ".npz")
3993
3994        if not settings.font_parameters[font]["islocal"]:
3995            font = "https://vedo.embl.es/fonts/" + font + ".npz"
3996            try:
3997                fontfile = vedo.file_io.download(font, verbose=False, force=False)
3998                font = os.path.basename(font).split(".")[0]
3999            except:
4000                vedo.logger.warning(f"font {font} not found")
4001                font = settings.default_font
4002                fontfile = os.path.join(vedo.fonts_path, font + ".npz")
4003
4004    #####
4005    try:
4006        font_meshes = np.load(fontfile, allow_pickle=True)["font"][0]
4007    except:
4008        vedo.logger.warning(f"font name {font} not found.")
4009        raise RuntimeError
4010    return font_meshes
4011
4012
4013@lru_cache(None)
4014def _get_font_letter(font, letter):
4015    # print("_get_font_letter", font, letter)
4016    font_meshes = _load_font(font)
4017    try:
4018        pts, faces = font_meshes[letter]
4019        return utils.buildPolyData(pts, faces)
4020    except KeyError:
4021        return None
4022
4023
4024class Text3D(Mesh):
4025    """
4026    Generate a 3D polygonal Mesh to represent a text string.
4027    """
4028
4029    def __init__(
4030        self,
4031        txt,
4032        pos=(0, 0, 0),
4033        s=1.0,
4034        font="",
4035        hspacing=1.15,
4036        vspacing=2.15,
4037        depth=0.0,
4038        italic=False,
4039        justify="bottom-left",
4040        literal=False,
4041        c=None,
4042        alpha=1.0,
4043    ) -> None:
4044        """
4045        Generate a 3D polygonal `Mesh` representing a text string.
4046
4047        Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts.
4048        Most Latex symbols are also supported.
4049
4050        Symbols `~ ^ _` are reserved modifiers:
4051        - use ~ to add a short space, 1/4 of the default empty space,
4052        - use ^ and _ to start up/sub scripting, a space terminates their effect.
4053
4054        Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`.
4055
4056        More fonts at: https://vedo.embl.es/fonts/
4057
4058        Arguments:
4059            pos : (list)
4060                position coordinates in 3D space
4061            s : (float)
4062                vertical size of the text (as scaling factor)
4063            depth : (float)
4064                text thickness (along z)
4065            italic : (bool), float
4066                italic font type (can be a signed float too)
4067            justify : (str)
4068                text justification as centering of the bounding box
4069                (bottom-left, bottom-right, top-left, top-right, centered)
4070            font : (str, int)
4071                some of the available 3D-polygonized fonts are:
4072                Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu,
4073                LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK,
4074                Capsmall, Cartoons123, Vega, Justino, Spears, Meson.
4075
4076                Check for more at https://vedo.embl.es/fonts/
4077
4078                Or type in your terminal `vedo --run fonts`.
4079
4080                Default is Normografo, which can be changed using `settings.default_font`.
4081
4082            hspacing : (float)
4083                horizontal spacing of the font
4084            vspacing : (float)
4085                vertical spacing of the font for multiple lines text
4086            literal : (bool)
4087                if set to True will ignore modifiers like _ or ^
4088
4089        Examples:
4090            - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py)
4091            - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py)
4092            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
4093
4094            ![](https://vedo.embl.es/images/pyplot/fonts3d.png)
4095
4096        .. note:: Type `vedo -r fonts` for a demo.
4097        """
4098        if len(pos) == 2:
4099            pos = (pos[0], pos[1], 0)
4100
4101        if c is None:  # automatic black or white
4102            pli = vedo.plotter_instance
4103            if pli and pli.renderer:
4104                c = (0.9, 0.9, 0.9)
4105                if pli.renderer.GetGradientBackground():
4106                    bgcol = pli.renderer.GetBackground2()
4107                else:
4108                    bgcol = pli.renderer.GetBackground()
4109                if np.sum(bgcol) > 1.5:
4110                    c = (0.1, 0.1, 0.1)
4111            else:
4112                c = (0.6, 0.6, 0.6)
4113
4114        tpoly = self._get_text3d_poly(
4115            txt, s, font, hspacing, vspacing, depth, italic, justify, literal
4116        )
4117
4118        super().__init__(tpoly, c, alpha)
4119
4120        self.pos(pos)
4121        self.lighting("off")
4122
4123        self.actor.PickableOff()
4124        self.actor.DragableOff()
4125        self.init_scale = s
4126        self.name = "Text3D"
4127        self.txt = txt
4128        self.justify = justify
4129
4130    def text(
4131        self,
4132        txt=None,
4133        s=1,
4134        font="",
4135        hspacing=1.15,
4136        vspacing=2.15,
4137        depth=0,
4138        italic=False,
4139        justify="",
4140        literal=False,
4141    ) -> "Text3D":
4142        """
4143        Update the text and some of its properties.
4144
4145        Check [available fonts here](https://vedo.embl.es/fonts).
4146        """
4147        if txt is None:
4148            return self.txt
4149        if not justify:
4150            justify = self.justify
4151
4152        poly = self._get_text3d_poly(
4153            txt, self.init_scale * s, font, hspacing, vspacing,
4154            depth, italic, justify, literal
4155        )
4156
4157        # apply the current transformation to the new polydata
4158        tf = vtki.new("TransformPolyDataFilter")
4159        tf.SetInputData(poly)
4160        tf.SetTransform(self.transform.T)
4161        tf.Update()
4162        tpoly = tf.GetOutput()
4163
4164        self._update(tpoly)
4165        self.txt = txt
4166        return self
4167
4168    def _get_text3d_poly(
4169        self,
4170        txt,
4171        s=1,
4172        font="",
4173        hspacing=1.15,
4174        vspacing=2.15,
4175        depth=0,
4176        italic=False,
4177        justify="bottom-left",
4178        literal=False,
4179    ) -> vtki.vtkPolyData:
4180        if not font:
4181            font = settings.default_font
4182
4183        txt = str(txt)
4184
4185        if font == "VTK":  #######################################
4186            vtt = vtki.new("VectorText")
4187            vtt.SetText(txt)
4188            vtt.Update()
4189            tpoly = vtt.GetOutput()
4190
4191        else:  ###################################################
4192
4193            stxt = set(txt)  # check here if null or only spaces
4194            if not txt or (len(stxt) == 1 and " " in stxt):
4195                return vtki.vtkPolyData()
4196
4197            if italic is True:
4198                italic = 1
4199
4200            if isinstance(font, int):
4201                lfonts = list(settings.font_parameters.keys())
4202                font = font % len(lfonts)
4203                font = lfonts[font]
4204
4205            if font not in settings.font_parameters.keys():
4206                fpars = settings.font_parameters["Normografo"]
4207            else:
4208                fpars = settings.font_parameters[font]
4209
4210            # ad hoc adjustments
4211            mono = fpars["mono"]
4212            lspacing = fpars["lspacing"]
4213            hspacing *= fpars["hspacing"]
4214            fscale = fpars["fscale"]
4215            dotsep = fpars["dotsep"]
4216
4217            # replacements
4218            if ":" in txt:
4219                for r in _reps:
4220                    txt = txt.replace(r[0], r[1])
4221
4222            if not literal:
4223                reps2 = [
4224                    (r"\_", "┭"),  # trick to protect ~ _ and ^ chars
4225                    (r"\^", "┮"),  #
4226                    (r"\~", "┯"),  #
4227                    ("**", "^"),  # order matters
4228                    ("e+0", dotsep + "10^"),
4229                    ("e-0", dotsep + "10^-"),
4230                    ("E+0", dotsep + "10^"),
4231                    ("E-0", dotsep + "10^-"),
4232                    ("e+", dotsep + "10^"),
4233                    ("e-", dotsep + "10^-"),
4234                    ("E+", dotsep + "10^"),
4235                    ("E-", dotsep + "10^-"),
4236                ]
4237                for r in reps2:
4238                    txt = txt.replace(r[0], r[1])
4239
4240            xmax, ymax, yshift, scale = 0.0, 0.0, 0.0, 1.0
4241            save_xmax = 0.0
4242
4243            notfounds = set()
4244            polyletters = []
4245            ntxt = len(txt)
4246            for i, t in enumerate(txt):
4247                ##########
4248                if t == "┭":
4249                    t = "_"
4250                elif t == "┮":
4251                    t = "^"
4252                elif t == "┯":
4253                    t = "~"
4254                elif t == "^" and not literal:
4255                    if yshift < 0:
4256                        xmax = save_xmax
4257                    yshift = 0.9 * fscale
4258                    scale = 0.5
4259                    continue
4260                elif t == "_" and not literal:
4261                    if yshift > 0:
4262                        xmax = save_xmax
4263                    yshift = -0.3 * fscale
4264                    scale = 0.5
4265                    continue
4266                elif (t in (" ", "\\n")) and yshift:
4267                    yshift = 0.0
4268                    scale = 1.0
4269                    save_xmax = xmax
4270                    if t == " ":
4271                        continue
4272                elif t == "~":
4273                    if i < ntxt - 1 and txt[i + 1] == "_":
4274                        continue
4275                    xmax += hspacing * scale * fscale / 4
4276                    continue
4277
4278                ############
4279                if t == " ":
4280                    xmax += hspacing * scale * fscale
4281
4282                elif t == "\n":
4283                    xmax = 0.0
4284                    save_xmax = 0.0
4285                    ymax -= vspacing
4286
4287                else:
4288                    poly = _get_font_letter(font, t)
4289                    if not poly:
4290                        notfounds.add(t)
4291                        xmax += hspacing * scale * fscale
4292                        continue
4293                    
4294                    if poly.GetNumberOfPoints() == 0:
4295                        continue
4296
4297                    tr = vtki.vtkTransform()
4298                    tr.Translate(xmax, ymax + yshift, 0)
4299                    pscale = scale * fscale / 1000
4300                    tr.Scale(pscale, pscale, pscale)
4301                    if italic:
4302                        tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1])
4303                    tf = vtki.new("TransformPolyDataFilter")
4304                    tf.SetInputData(poly)
4305                    tf.SetTransform(tr)
4306                    tf.Update()
4307                    poly = tf.GetOutput()
4308                    polyletters.append(poly)
4309
4310                    bx = poly.GetBounds()
4311                    if mono:
4312                        xmax += hspacing * scale * fscale
4313                    else:
4314                        xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing
4315                    if yshift == 0:
4316                        save_xmax = xmax
4317
4318            if len(polyletters) == 1:
4319                tpoly = polyletters[0]
4320            else:
4321                polyapp = vtki.new("AppendPolyData")
4322                for polyd in polyletters:
4323                    polyapp.AddInputData(polyd)
4324                polyapp.Update()
4325                tpoly = polyapp.GetOutput()
4326
4327            if notfounds:
4328                wmsg = f"unavailable characters in font name '{font}': {notfounds}."
4329                wmsg += '\nType "vedo -r fonts" for a demo.'
4330                vedo.logger.warning(wmsg)
4331
4332        bb = tpoly.GetBounds()
4333        dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s
4334        shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2
4335        if "bottom" in justify: shift += np.array([  0, dy, 0.])
4336        if "top"    in justify: shift += np.array([  0,-dy, 0.])
4337        if "left"   in justify: shift += np.array([ dx,  0, 0.])
4338        if "right"  in justify: shift += np.array([-dx,  0, 0.])
4339
4340        if tpoly.GetNumberOfPoints():
4341            t = vtki.vtkTransform()
4342            t.PostMultiply()
4343            t.Scale(s, s, s)
4344            t.Translate(shift)
4345            tf = vtki.new("TransformPolyDataFilter")
4346            tf.SetInputData(tpoly)
4347            tf.SetTransform(t)
4348            tf.Update()
4349            tpoly = tf.GetOutput()
4350
4351            if depth:
4352                extrude = vtki.new("LinearExtrusionFilter")
4353                extrude.SetInputData(tpoly)
4354                extrude.SetExtrusionTypeToVectorExtrusion()
4355                extrude.SetVector(0, 0, 1)
4356                extrude.SetScaleFactor(depth * dy)
4357                extrude.Update()
4358                tpoly = extrude.GetOutput()
4359
4360        return tpoly
4361
4362
4363class TextBase:
4364    "Base class."
4365
4366    def __init__(self):
4367        "Do not instantiate this base class."
4368
4369        self.rendered_at = set()
4370        self.properties = None
4371
4372        self.name = "Text"
4373        self.filename = ""
4374        self.time = 0
4375        self.info = {}
4376
4377        if isinstance(settings.default_font, int):
4378            lfonts = list(settings.font_parameters.keys())
4379            font = settings.default_font % len(lfonts)
4380            self.fontname = lfonts[font]
4381        else:
4382            self.fontname = settings.default_font
4383
4384    def angle(self, value: float):
4385        """Orientation angle in degrees"""
4386        self.properties.SetOrientation(value)
4387        return self
4388
4389    def line_spacing(self, value: float):
4390        """Set the extra spacing between lines
4391        expressed as a text height multiplicative factor."""
4392        self.properties.SetLineSpacing(value)
4393        return self
4394
4395    def line_offset(self, value: float):
4396        """Set/Get the vertical offset (measured in pixels)."""
4397        self.properties.SetLineOffset(value)
4398        return self
4399
4400    def bold(self, value=True):
4401        """Set bold face"""
4402        self.properties.SetBold(value)
4403        return self
4404
4405    def italic(self, value=True):
4406        """Set italic face"""
4407        self.properties.SetItalic(value)
4408        return self
4409
4410    def shadow(self, offset=(1, -1)):
4411        """Text shadowing. Set to `None` to disable it."""
4412        if offset is None:
4413            self.properties.ShadowOff()
4414        else:
4415            self.properties.ShadowOn()
4416            self.properties.SetShadowOffset(offset)
4417        return self
4418
4419    def color(self, c=None):
4420        """Set the text color"""
4421        if c is None:
4422            return get_color(self.properties.GetColor())
4423        self.properties.SetColor(get_color(c))
4424        return self
4425
4426    def c(self, color=None):
4427        """Set the text color"""
4428        if color is None:
4429            return get_color(self.properties.GetColor())
4430        return self.color(color)
4431
4432    def alpha(self, value: float):
4433        """Set the text opacity"""
4434        self.properties.SetBackgroundOpacity(value)
4435        return self
4436
4437    def background(self, color="k9", alpha=1.0):
4438        """Text background. Set to `None` to disable it."""
4439        bg = get_color(color)
4440        if color is None:
4441            self.properties.SetBackgroundOpacity(0)
4442        else:
4443            self.properties.SetBackgroundColor(bg)
4444            if alpha:
4445                self.properties.SetBackgroundOpacity(alpha)
4446        return self
4447
4448    def frame(self, color="k1", lw=2):
4449        """Border color and width"""
4450        if color is None:
4451            self.properties.FrameOff()
4452        else:
4453            c = get_color(color)
4454            self.properties.FrameOn()
4455            self.properties.SetFrameColor(c)
4456            self.properties.SetFrameWidth(lw)
4457        return self
4458
4459    def font(self, font: str):
4460        """Text font face"""
4461        if isinstance(font, int):
4462            lfonts = list(settings.font_parameters.keys())
4463            n = font % len(lfonts)
4464            font = lfonts[n]
4465            self.fontname = font
4466
4467        if not font:  # use default font
4468            font = self.fontname
4469            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4470        elif font.startswith("https"):  # user passed URL link, make it a path
4471            fpath = vedo.file_io.download(font, verbose=False, force=False)
4472        elif font.endswith(".ttf"):  # user passing a local path to font file
4473            fpath = font
4474        else:  # user passing name of preset font
4475            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4476
4477        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4478        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4479        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4480        else:
4481            fpath = utils.get_font_path(font)
4482            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4483            self.properties.SetFontFile(fpath)
4484        self.fontname = font  # io.tonumpy() uses it
4485
4486        return self
4487
4488    def on(self):
4489        """Make text visible"""
4490        self.actor.SetVisibility(True)
4491        return self
4492
4493    def off(self):
4494        """Make text invisible"""
4495        self.actor.SetVisibility(False)
4496        return self
4497
4498class Text2D(TextBase, vedo.visual.Actor2D):
4499    """
4500    Create a 2D text object.
4501    """
4502    def __init__(
4503        self,
4504        txt="",
4505        pos="top-left",
4506        s=1.0,
4507        bg=None,
4508        font="",
4509        justify="",
4510        bold=False,
4511        italic=False,
4512        c=None,
4513        alpha=0.5,
4514    ) -> None:
4515        """
4516        Create a 2D text object.
4517
4518        All properties of the text, and the text itself, can be changed after creation
4519        (which is especially useful in loops).
4520
4521        Arguments:
4522            pos : (str)
4523                text is placed in one of the 8 positions:
4524                - bottom-left
4525                - bottom-right
4526                - top-left
4527                - top-right
4528                - bottom-middle
4529                - middle-right
4530                - middle-left
4531                - top-middle
4532
4533                If a pair (x,y) is passed as input the 2D text is place at that
4534                position in the coordinate system of the 2D screen (with the
4535                origin sitting at the bottom left).
4536
4537            s : (float)
4538                size of text
4539            bg : (color)
4540                background color
4541            alpha : (float)
4542                background opacity
4543            justify : (str)
4544                text justification
4545
4546            font : (str)
4547                built-in available fonts are:
4548                - Antares
4549                - Arial
4550                - Bongas
4551                - Calco
4552                - Comae
4553                - ComicMono
4554                - Courier
4555                - Glasgo
4556                - Kanopus
4557                - LogoType
4558                - Normografo
4559                - Quikhand
4560                - SmartCouric
4561                - Theemim
4562                - Times
4563                - VictorMono
4564                - More fonts at: https://vedo.embl.es/fonts/
4565
4566                A path to a `.otf` or `.ttf` font-file can also be supplied as input.
4567
4568        Examples:
4569            - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py)
4570            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
4571            - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py)
4572
4573                ![](https://vedo.embl.es/images/basic/colorcubes.png)
4574        """
4575        super().__init__()
4576        self.name = "Text2D"
4577
4578        self.mapper = vtki.new("TextMapper")
4579        self.SetMapper(self.mapper)
4580
4581        self.properties = self.mapper.GetTextProperty()
4582        self.actor = self
4583        self.actor.retrieve_object = weak_ref_to(self)
4584
4585        self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport()
4586
4587        # automatic black or white
4588        if c is None:
4589            c = (0.1, 0.1, 0.1)
4590            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4591                if vedo.plotter_instance.renderer.GetGradientBackground():
4592                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4593                else:
4594                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4595                c = (0.9, 0.9, 0.9)
4596                if np.sum(bgcol) > 1.5:
4597                    c = (0.1, 0.1, 0.1)
4598
4599        self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic)
4600        self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5)
4601        self.PickableOff()
4602
4603    def pos(self, pos="top-left", justify=""):
4604        """
4605        Set position of the text to draw. Keyword `pos` can be a string
4606        or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4607        """
4608        ajustify = "top-left"  # autojustify
4609        if isinstance(pos, str):  # corners
4610            ajustify = pos
4611            if "top" in pos:
4612                if "left" in pos:
4613                    pos = (0.008, 0.994)
4614                elif "right" in pos:
4615                    pos = (0.994, 0.994)
4616                elif "mid" in pos or "cent" in pos:
4617                    pos = (0.5, 0.994)
4618            elif "bottom" in pos:
4619                if "left" in pos:
4620                    pos = (0.008, 0.008)
4621                elif "right" in pos:
4622                    pos = (0.994, 0.008)
4623                elif "mid" in pos or "cent" in pos:
4624                    pos = (0.5, 0.008)
4625            elif "mid" in pos or "cent" in pos:
4626                if "left" in pos:
4627                    pos = (0.008, 0.5)
4628                elif "right" in pos:
4629                    pos = (0.994, 0.5)
4630                else:
4631                    pos = (0.5, 0.5)
4632
4633            else:
4634                vedo.logger.warning(f"cannot understand text position {pos}")
4635                pos = (0.008, 0.994)
4636                ajustify = "top-left"
4637
4638        elif len(pos) != 2:
4639            vedo.logger.error("pos must be of length 2 or integer value or string")
4640            raise RuntimeError()
4641
4642        if not justify:
4643            justify = ajustify
4644
4645        self.properties.SetJustificationToLeft()
4646        if "top" in justify:
4647            self.properties.SetVerticalJustificationToTop()
4648        if "bottom" in justify:
4649            self.properties.SetVerticalJustificationToBottom()
4650        if "cent" in justify or "mid" in justify:
4651            self.properties.SetJustificationToCentered()
4652        if "left" in justify:
4653            self.properties.SetJustificationToLeft()
4654        if "right" in justify:
4655            self.properties.SetJustificationToRight()
4656
4657        self.SetPosition(pos)
4658        return self
4659
4660    def text(self, txt=None):
4661        """Set/get the input text string."""
4662        if txt is None:
4663            return self.mapper.GetInput()
4664
4665        if ":" in txt:
4666            for r in _reps:
4667                txt = txt.replace(r[0], r[1])
4668        else:
4669            txt = str(txt)
4670
4671        self.mapper.SetInput(txt)
4672        return self
4673
4674    def size(self, s):
4675        """Set the font size."""
4676        self.properties.SetFontSize(int(s * 22.5))
4677        return self
4678
4679
4680class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation):
4681    # PROBABLY USELESS given that Text2D does pretty much the same ...
4682    """
4683    Annotate the window corner with 2D text.
4684
4685    See `Text2D` description as the basic functionality is very similar.
4686
4687    The added value of this class is the possibility to manage with one single
4688    object the all corner annotations (instead of creating 4 `Text2D` instances).
4689
4690    Examples:
4691        - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
4692    """
4693
4694    def __init__(self, c=None) -> None:
4695
4696        super().__init__()
4697
4698        self.properties = self.GetTextProperty()
4699
4700        # automatic black or white
4701        if c is None:
4702            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4703                c = (0.9, 0.9, 0.9)
4704                if vedo.plotter_instance.renderer.GetGradientBackground():
4705                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4706                else:
4707                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4708                if np.sum(bgcol) > 1.5:
4709                    c = (0.1, 0.1, 0.1)
4710            else:
4711                c = (0.5, 0.5, 0.5)
4712
4713        self.SetNonlinearFontScaleFactor(1 / 2.75)
4714        self.PickableOff()
4715        self.properties.SetColor(get_color(c))
4716        self.properties.SetBold(False)
4717        self.properties.SetItalic(False)
4718
4719    def size(self, s:float, linear=False) -> "CornerAnnotation":
4720        """
4721        The font size is calculated as the largest possible value such that the annotations
4722        for the given viewport do not overlap.
4723
4724        This font size can be scaled non-linearly with the viewport size, to maintain an
4725        acceptable readable size at larger viewport sizes, without being too big.
4726        `f' = linearScale * pow(f,nonlinearScale)`
4727        """
4728        if linear:
4729            self.SetLinearFontScaleFactor(s * 5.5)
4730        else:
4731            self.SetNonlinearFontScaleFactor(s / 2.75)
4732        return self
4733
4734    def text(self, txt: str, pos=2) -> "CornerAnnotation":
4735        """Set text at the assigned position"""
4736
4737        if isinstance(pos, str):  # corners
4738            if "top" in pos:
4739                if "left" in pos: pos = 2
4740                elif "right" in pos: pos = 3
4741                elif "mid" in pos or "cent" in pos: pos = 7
4742            elif "bottom" in pos:
4743                if "left" in pos: pos = 0
4744                elif "right" in pos: pos = 1
4745                elif "mid" in pos or "cent" in pos: pos = 4
4746            else:
4747                if "left" in pos: pos = 6
4748                elif "right" in pos: pos = 5
4749                else: pos = 2
4750
4751        if "\\" in repr(txt):
4752            for r in _reps:
4753                txt = txt.replace(r[0], r[1])
4754        else:
4755            txt = str(txt)
4756
4757        self.SetText(pos, txt)
4758        return self
4759
4760    def clear(self) -> "CornerAnnotation":
4761        """Remove all text from all corners"""
4762        self.ClearAllTexts()
4763        return self
4764
4765
4766class Latex(Image):
4767    """
4768    Render Latex text and formulas.
4769    """
4770
4771    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4772        """
4773        Render Latex text and formulas.
4774
4775        Arguments:
4776            formula : (str)
4777                latex text string
4778            pos : (list)
4779                position coordinates in space
4780            bg : (color)
4781                background color box
4782            res : (int)
4783                dpi resolution
4784            usetex : (bool)
4785                use latex compiler of matplotlib if available
4786
4787        You can access the latex formula in `Latex.formula`.
4788
4789        Examples:
4790            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4791
4792            ![](https://vedo.embl.es/images/pyplot/latex.png)
4793        """
4794        from tempfile import NamedTemporaryFile
4795        import matplotlib.pyplot as mpltib
4796
4797        def build_img_plt(formula, tfile):
4798
4799            mpltib.rc("text", usetex=usetex)
4800
4801            formula1 = "$" + formula + "$"
4802            mpltib.axis("off")
4803            col = get_color(c)
4804            if bg:
4805                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4806            else:
4807                bx = None
4808            mpltib.text(
4809                0.5,
4810                0.5,
4811                formula1,
4812                size=res,
4813                color=col,
4814                alpha=alpha,
4815                ha="center",
4816                va="center",
4817                bbox=bx,
4818            )
4819            mpltib.savefig(
4820                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4821            )
4822            mpltib.close()
4823
4824        if len(pos) == 2:
4825            pos = (pos[0], pos[1], 0)
4826
4827        tmp_file = NamedTemporaryFile(delete=True)
4828        tmp_file.name = tmp_file.name + ".png"
4829
4830        build_img_plt(formula, tmp_file.name)
4831
4832        super().__init__(tmp_file.name, channels=4)
4833        self.alpha(alpha)
4834        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4835        self.pos(pos)
4836        self.name = "Latex"
4837        self.formula = formula
4838
4839        # except:
4840        #     printc("Error in Latex()\n", formula, c="r")
4841        #     printc(" latex or dvipng not installed?", c="r")
4842        #     printc(" Try: usetex=False", c="r")
4843        #     printc(" Try: sudo apt install dvipng", c="r")
4844
4845
4846class ConvexHull(Mesh):
4847    """
4848    Create the 2D/3D convex hull from a set of points.
4849    """
4850
4851    def __init__(self, pts) -> None:
4852        """
4853        Create the 2D/3D convex hull from a set of input points or input Mesh.
4854
4855        Examples:
4856            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4857
4858                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4859        """
4860        if utils.is_sequence(pts):
4861            pts = utils.make3d(pts).astype(float)
4862            mesh = Points(pts)
4863        else:
4864            mesh = pts
4865        apoly = mesh.clean().dataset
4866
4867        # Create the convex hull of the pointcloud
4868        z0, z1 = mesh.zbounds()
4869        d = mesh.diagonal_size()
4870        if (z1 - z0) / d > 0.0001:
4871            delaunay = vtki.new("Delaunay3D")
4872            delaunay.SetInputData(apoly)
4873            delaunay.Update()
4874            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4875            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4876            surfaceFilter.Update()
4877            out = surfaceFilter.GetOutput()
4878        else:
4879            delaunay = vtki.new("Delaunay2D")
4880            delaunay.SetInputData(apoly)
4881            delaunay.Update()
4882            fe = vtki.new("FeatureEdges")
4883            fe.SetInputConnection(delaunay.GetOutputPort())
4884            fe.BoundaryEdgesOn()
4885            fe.Update()
4886            out = fe.GetOutput()
4887
4888        super().__init__(out, c=mesh.color(), alpha=0.75)
4889        self.flat()
4890        self.name = "ConvexHull"
4891
4892
4893def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True) -> "vedo.Assembly":
4894    """
4895    Create the 3D vedo logo.
4896
4897    Arguments:
4898        distance : (float)
4899            send back logo by this distance from camera
4900        version : (bool)
4901            add version text to the right end of the logo
4902        bc : (color)
4903            text back face color
4904    """
4905    if c is None:
4906        c = (0, 0, 0)
4907        if vedo.plotter_instance:
4908            if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5:
4909                c = [0, 0, 0]
4910            else:
4911                c = "linen"
4912
4913    font = "Comae"
4914    vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8)
4915    vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc)
4916    vlogo.properties.LightingOn()
4917
4918    vr, rul = None, None
4919    if version:
4920        vr = Text3D(
4921            vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1
4922        ).scale([1, 0.7, 1])
4923        vr.rotate_z(90).pos(2450, 50, 80)
4924        vr.bc(bc).pickable(False)
4925    elif frame:
4926        rul = vedo.RulerAxes(
4927            (-2600, 2110, 0, 1650, 0, 0),
4928            xlabel="European Molecular Biology Laboratory",
4929            ylabel=vedo.__version__,
4930            font=font,
4931            xpadding=0.09,
4932            ypadding=0.04,
4933        )
4934    fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0)
4935    return vedo.Assembly([vlogo, vr, fakept, rul]).scale(1 / 1725)
def Marker(symbol, pos=(0, 0, 0), c='k', alpha=1.0, s=0.1, filled=True) -> Any:
3602def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True) -> Any:
3603    """
3604    Generate a marker shape. Typically used in association with `Glyph`.
3605    """
3606    if isinstance(symbol, Mesh):
3607        return symbol.c(c).alpha(alpha).lighting("off")
3608
3609    if isinstance(symbol, int):
3610        symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"]
3611        symbol = symbol % len(symbs)
3612        symbol = symbs[symbol]
3613
3614    if symbol == ".":
3615        mesh = Polygon(nsides=24, r=s * 0.6)
3616    elif symbol == "o":
3617        mesh = Polygon(nsides=24, r=s * 0.75)
3618    elif symbol == "O":
3619        mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24))
3620    elif symbol == "0":
3621        m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24))
3622        m2 = Circle(r=s * 0.36).reverse()
3623        mesh = merge(m1, m2)
3624    elif symbol == "p":
3625        mesh = Polygon(nsides=5, r=s)
3626    elif symbol == "*":
3627        mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled)
3628    elif symbol == "h":
3629        mesh = Polygon(nsides=6, r=s)
3630    elif symbol == "D":
3631        mesh = Polygon(nsides=4, r=s)
3632    elif symbol == "d":
3633        mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1])
3634    elif symbol == "v":
3635        mesh = Polygon(nsides=3, r=s).rotate_z(180)
3636    elif symbol == "^":
3637        mesh = Polygon(nsides=3, r=s)
3638    elif symbol == ">":
3639        mesh = Polygon(nsides=3, r=s).rotate_z(-90)
3640    elif symbol == "<":
3641        mesh = Polygon(nsides=3, r=s).rotate_z(90)
3642    elif symbol == "s":
3643        mesh = Mesh(
3644            [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]]
3645        ).scale(s / 1.4)
3646    elif symbol == "x":
3647        mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0)
3648        # mesh.rotate_z(45)
3649    elif symbol == "a":
3650        mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0)
3651    else:
3652        mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0)
3653    mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha)
3654    if len(pos) == 2:
3655        pos = (pos[0], pos[1], 0)
3656    mesh.pos(pos)
3657    mesh.name = "Marker"
3658    return mesh

Generate a marker shape. Typically used in association with Glyph.

class Line(vedo.mesh.Mesh):
395class Line(Mesh):
396    """
397    Build the line segment between point `p0` and point `p1`.
398
399    If `p0` is already a list of points, return the line connecting them.
400
401    A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`.
402    """
403
404    def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None:
405        """
406        Arguments:
407            closed : (bool)
408                join last to first point
409            res : (int)
410                resolution, number of points along the line
411                (only relevant if only 2 points are specified)
412            lw : (int)
413                line width in pixel units
414        """
415
416        if isinstance(p1, Points):
417            p1 = p1.pos()
418            if isinstance(p0, Points):
419                p0 = p0.pos()
420        try:
421            p0 = p0.dataset
422        except AttributeError:
423            pass
424
425        if isinstance(p0, vtki.vtkPolyData):
426            poly = p0
427            top  = np.array([0,0,1])
428            base = np.array([0,0,0])
429
430        elif utils.is_sequence(p0[0]): # detect if user is passing a list of points
431
432            p0 = utils.make3d(p0)
433            ppoints = vtki.vtkPoints()  # Generate the polyline
434            ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32))
435            lines = vtki.vtkCellArray()
436            npt = len(p0)
437            if closed:
438                lines.InsertNextCell(npt + 1)
439            else:
440                lines.InsertNextCell(npt)
441            for i in range(npt):
442                lines.InsertCellPoint(i)
443            if closed:
444                lines.InsertCellPoint(0)
445            poly = vtki.vtkPolyData()
446            poly.SetPoints(ppoints)
447            poly.SetLines(lines)
448            top = p0[-1]
449            base = p0[0]
450            if res != 2:
451                printc(f"Warning: calling Line(res={res}), try remove []?", c='y')
452                res = 2
453
454        else:  # or just 2 points to link
455
456            line_source = vtki.new("LineSource")
457            p0 = utils.make3d(p0)
458            p1 = utils.make3d(p1)
459            line_source.SetPoint1(p0)
460            line_source.SetPoint2(p1)
461            line_source.SetResolution(res - 1)
462            line_source.Update()
463            poly = line_source.GetOutput()
464            top = np.asarray(p1, dtype=float)
465            base = np.asarray(p0, dtype=float)
466
467        super().__init__(poly, c, alpha)
468
469        self.slope: List[float] = []  # populated by analysis.fit_line
470        self.center: List[float] = []
471        self.variances: List[float] = []
472
473        self.coefficients: List[float] = []  # populated by pyplot.fit()
474        self.covariance_matrix: List[float] = []
475        self.coefficient_errors: List[float] = []
476        self.monte_carlo_coefficients: List[float] = []
477        self.reduced_chi2 = -1
478        self.ndof = 0
479        self.data_sigma = 0
480        self.error_lines: List[Any] = []
481        self.error_band = None
482        self.res = res
483
484        self.lw(lw)
485        self.properties.LightingOff()
486        self.actor.PickableOff()
487        self.actor.DragableOff()
488        self.base = base
489        self.top = top
490        self.name = "Line"
491
492    def clone(self, deep=True) -> "Line":
493        """
494        Return a copy of the ``Line`` object.
495
496        Example:
497            ```python
498            from vedo import *
499            ln1 = Line([1,1,1], [2,2,2], lw=3).print()
500            ln2 = ln1.clone().shift(0,0,1).c('red').print()
501            show(ln1, ln2, axes=1, viewup='z').close()
502            ```
503            ![](https://vedo.embl.es/images/feats/line_clone.png)
504        """
505        poly = vtki.vtkPolyData()
506        if deep:
507            poly.DeepCopy(self.dataset)
508        else:
509            poly.ShallowCopy(self.dataset)
510        ln = Line(poly)
511        ln.copy_properties_from(self)
512        ln.transform = self.transform.clone()
513        ln.name = self.name
514        ln.base = self.base
515        ln.top = self.top
516        ln.pipeline = utils.OperationNode(
517            "clone", parents=[self], shape="diamond", c="#edede9")
518        return ln
519
520    def linecolor(self, lc=None) -> "Line":
521        """Assign a color to the line"""
522        # overrides mesh.linecolor which would have no effect here
523        return self.color(lc)
524
525    def eval(self, x: float) -> np.ndarray:
526        """
527        Calculate the position of an intermediate point
528        as a fraction of the length of the line,
529        being x=0 the first point and x=1 the last point.
530        This corresponds to an imaginary point that travels along the line
531        at constant speed.
532
533        Can be used in conjunction with `lin_interpolate()`
534        to map any range to the [0,1] range.
535        """
536        distance1 = 0.0
537        length = self.length()
538        pts = self.vertices
539        for i in range(1, len(pts)):
540            p0 = pts[i - 1]
541            p1 = pts[i]
542            seg = p1 - p0
543            distance0 = distance1
544            distance1 += np.linalg.norm(seg)
545            w1 = distance1 / length
546            if w1 >= x:
547                break
548        w0 = distance0 / length
549        v = p0 + seg * (x - w0) / (w1 - w0)
550        return v
551
552    def find_index_at_position(self, p) -> float:
553        """
554        Find the index of the line vertex that is closest to the point `p`.
555        Note that the returned index can be fractional if `p` is not exactly
556        one of the vertices of the line.
557        """
558        q = self.closest_point(p)
559        a, b = sorted(self.closest_point(q, n=2, return_point_id=True))
560        pts = self.vertices
561        d = np.linalg.norm(pts[a] - pts[b])
562        t = a + np.linalg.norm(pts[a] - q) / d
563        return t
564
565    def pattern(self, stipple, repeats=10) -> "Line":
566        """
567        Define a stipple pattern for dashing the line.
568        Pass the stipple pattern as a string like `'- - -'`.
569        Repeats controls the number of times the pattern repeats in a single segment.
570
571        Examples are: `'- -', '--  -  --'`, etc.
572
573        The resolution of the line (nr of points) can affect how pattern will show up.
574
575        Example:
576            ```python
577            from vedo import Line
578            pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]]
579            ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10)
580            ln.show(axes=1).close()
581            ```
582            ![](https://vedo.embl.es/images/feats/line_pattern.png)
583        """
584        stipple = str(stipple) * int(2 * repeats)
585        dimension = len(stipple)
586
587        image = vtki.vtkImageData()
588        image.SetDimensions(dimension, 1, 1)
589        image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4)
590        image.SetExtent(0, dimension - 1, 0, 0, 0, 0)
591        i_dim = 0
592        while i_dim < dimension:
593            for i in range(dimension):
594                image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255)
595                image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255)
596                image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255)
597                if stipple[i] == " ":
598                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0)
599                else:
600                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255)
601                i_dim += 1
602
603        poly = self.dataset
604
605        # Create texture coordinates
606        tcoords = vtki.vtkDoubleArray()
607        tcoords.SetName("TCoordsStippledLine")
608        tcoords.SetNumberOfComponents(1)
609        tcoords.SetNumberOfTuples(poly.GetNumberOfPoints())
610        for i in range(poly.GetNumberOfPoints()):
611            tcoords.SetTypedTuple(i, [i / 2])
612        poly.GetPointData().SetTCoords(tcoords)
613        poly.GetPointData().Modified()
614        texture = vtki.vtkTexture()
615        texture.SetInputData(image)
616        texture.InterpolateOff()
617        texture.RepeatOn()
618        self.actor.SetTexture(texture)
619        return self
620
621    def length(self) -> float:
622        """Calculate length of the line."""
623        distance = 0.0
624        pts = self.vertices
625        for i in range(1, len(pts)):
626            distance += np.linalg.norm(pts[i] - pts[i - 1])
627        return distance
628
629    def tangents(self) -> np.ndarray:
630        """
631        Compute the tangents of a line in space.
632
633        Example:
634            ```python
635            from vedo import *
636            shape = Assembly(dataurl+"timecourse1d.npy")[58]
637            pts = shape.rotate_x(30).vertices
638            tangents = Line(pts).tangents()
639            arrs = Arrows(pts, pts+tangents, c='blue9')
640            show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()
641            ```
642            ![](https://vedo.embl.es/images/feats/line_tangents.png)
643        """
644        v = np.gradient(self.vertices)[0]
645        ds_dt = np.linalg.norm(v, axis=1)
646        tangent = np.array([1 / ds_dt] * 3).transpose() * v
647        return tangent
648
649    def curvature(self) -> np.ndarray:
650        """
651        Compute the signed curvature of a line in space.
652        The signed is computed assuming the line is about coplanar to the xy plane.
653
654        Example:
655            ```python
656            from vedo import *
657            from vedo.pyplot import plot
658            shape = Assembly(dataurl+"timecourse1d.npy")[55]
659            curvs = Line(shape.vertices).curvature()
660            shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w')
661            shape.render_lines_as_tubes().lw(12)
662            pp = plot(curvs, ac='white', lc='yellow5')
663            show(shape, pp, N=2, bg='bb', sharecam=False).close()
664            ```
665            ![](https://vedo.embl.es/images/feats/line_curvature.png)
666        """
667        v = np.gradient(self.vertices)[0]
668        a = np.gradient(v)[0]
669        av = np.cross(a, v)
670        mav = np.linalg.norm(av, axis=1)
671        mv = utils.mag2(v)
672        val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5)
673        val[0] = val[1]
674        val[-1] = val[-2]
675        return val
676
677    def compute_curvature(self, method=0) -> "Line":
678        """
679        Add a pointdata array named 'Curvatures' which contains
680        the curvature value at each point.
681
682        NB: keyword `method` is overridden in Mesh and has no effect here.
683        """
684        # overrides mesh.compute_curvature
685        curvs = self.curvature()
686        vmin, vmax = np.min(curvs), np.max(curvs)
687        if vmin < 0 and vmax > 0:
688            v = max(-vmin, vmax)
689            self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature")
690        else:
691            self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature")
692        return self
693
694    def plot_scalar(
695            self,
696            radius=0.0, 
697            height=1.1,
698            normal=(),
699            camera=None,
700        ) -> "Line":
701        """
702        Generate a new `Line` which plots the active scalar along the line.
703
704        Arguments:
705            radius : (float)
706                distance radius to the line
707            height: (float)
708                height of the plot
709            normal: (list)
710                normal vector to the plane of the plot
711            camera: (vtkCamera) 
712                camera object to use for the plot orientation
713        
714        Example:
715            ```python
716            from vedo import *
717            circle = Circle(res=360).rotate_y(20)
718            pts = circle.vertices
719            bore = Line(pts).lw(5)
720            values = np.arctan2(pts[:,1], pts[:,0])
721            bore.pointdata["scalars"] = values + np.random.randn(360)/5
722            vap = bore.plot_scalar(radius=0, height=1)
723            show(bore, vap, axes=1, viewup='z').close()
724            ```
725            ![](https://vedo.embl.es/images/feats/line_plot_scalar.png)
726        """
727        ap = vtki.new("ArcPlotter")
728        ap.SetInputData(self.dataset)
729        ap.SetCamera(camera)
730        ap.SetRadius(radius)
731        ap.SetHeight(height)
732        if len(normal)>0:
733            ap.UseDefaultNormalOn()
734            ap.SetDefaultNormal(normal)
735        ap.Update()
736        vap = Line(ap.GetOutput())
737        vap.linewidth(3).lighting('off')
738        vap.name = "ArcPlot"
739        return vap
740
741    def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh":
742        """
743        Sweep the `Line` along the specified vector direction.
744
745        Returns a `Mesh` surface.
746        Line position is updated to allow for additional sweepings.
747
748        Example:
749            ```python
750            from vedo import Line, show
751            aline = Line([(0,0,0),(1,3,0),(2,4,0)])
752            surf1 = aline.sweep((1,0.2,0), res=3)
753            surf2 = aline.sweep((0.2,0,1)).alpha(0.5)
754            aline.color('r').linewidth(4)
755            show(surf1, surf2, aline, axes=1).close()
756            ```
757            ![](https://vedo.embl.es/images/feats/sweepline.png)
758        """
759        line = self.dataset
760        rows = line.GetNumberOfPoints()
761
762        spacing = 1 / res
763        surface = vtki.vtkPolyData()
764
765        res += 1
766        npts = rows * res
767        npolys = (rows - 1) * (res - 1)
768        points = vtki.vtkPoints()
769        points.Allocate(npts)
770
771        cnt = 0
772        x = [0.0, 0.0, 0.0]
773        for row in range(rows):
774            for col in range(res):
775                p = [0.0, 0.0, 0.0]
776                line.GetPoint(row, p)
777                x[0] = p[0] + direction[0] * col * spacing
778                x[1] = p[1] + direction[1] * col * spacing
779                x[2] = p[2] + direction[2] * col * spacing
780                points.InsertPoint(cnt, x)
781                cnt += 1
782
783        # Generate the quads
784        polys = vtki.vtkCellArray()
785        polys.Allocate(npolys * 4)
786        pts = [0, 0, 0, 0]
787        for row in range(rows - 1):
788            for col in range(res - 1):
789                pts[0] = col + row * res
790                pts[1] = pts[0] + 1
791                pts[2] = pts[0] + res + 1
792                pts[3] = pts[0] + res
793                polys.InsertNextCell(4, pts)
794        surface.SetPoints(points)
795        surface.SetPolys(polys)
796        asurface = Mesh(surface)
797        asurface.copy_properties_from(self)
798        asurface.lighting("default")
799        self.vertices = self.vertices + direction
800        return asurface
801
802    def reverse(self):
803        """Reverse the points sequence order."""
804        pts = np.flip(self.vertices, axis=0)
805        self.vertices = pts
806        return self

Build the line segment between point p0 and point p1.

If p0 is already a list of points, return the line connecting them.

A 2D set of coords can also be passed as p0=[x..], p1=[y..].

Line(p0, p1=None, closed=False, res=2, lw=1, c='k1', alpha=1.0)
404    def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None:
405        """
406        Arguments:
407            closed : (bool)
408                join last to first point
409            res : (int)
410                resolution, number of points along the line
411                (only relevant if only 2 points are specified)
412            lw : (int)
413                line width in pixel units
414        """
415
416        if isinstance(p1, Points):
417            p1 = p1.pos()
418            if isinstance(p0, Points):
419                p0 = p0.pos()
420        try:
421            p0 = p0.dataset
422        except AttributeError:
423            pass
424
425        if isinstance(p0, vtki.vtkPolyData):
426            poly = p0
427            top  = np.array([0,0,1])
428            base = np.array([0,0,0])
429
430        elif utils.is_sequence(p0[0]): # detect if user is passing a list of points
431
432            p0 = utils.make3d(p0)
433            ppoints = vtki.vtkPoints()  # Generate the polyline
434            ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32))
435            lines = vtki.vtkCellArray()
436            npt = len(p0)
437            if closed:
438                lines.InsertNextCell(npt + 1)
439            else:
440                lines.InsertNextCell(npt)
441            for i in range(npt):
442                lines.InsertCellPoint(i)
443            if closed:
444                lines.InsertCellPoint(0)
445            poly = vtki.vtkPolyData()
446            poly.SetPoints(ppoints)
447            poly.SetLines(lines)
448            top = p0[-1]
449            base = p0[0]
450            if res != 2:
451                printc(f"Warning: calling Line(res={res}), try remove []?", c='y')
452                res = 2
453
454        else:  # or just 2 points to link
455
456            line_source = vtki.new("LineSource")
457            p0 = utils.make3d(p0)
458            p1 = utils.make3d(p1)
459            line_source.SetPoint1(p0)
460            line_source.SetPoint2(p1)
461            line_source.SetResolution(res - 1)
462            line_source.Update()
463            poly = line_source.GetOutput()
464            top = np.asarray(p1, dtype=float)
465            base = np.asarray(p0, dtype=float)
466
467        super().__init__(poly, c, alpha)
468
469        self.slope: List[float] = []  # populated by analysis.fit_line
470        self.center: List[float] = []
471        self.variances: List[float] = []
472
473        self.coefficients: List[float] = []  # populated by pyplot.fit()
474        self.covariance_matrix: List[float] = []
475        self.coefficient_errors: List[float] = []
476        self.monte_carlo_coefficients: List[float] = []
477        self.reduced_chi2 = -1
478        self.ndof = 0
479        self.data_sigma = 0
480        self.error_lines: List[Any] = []
481        self.error_band = None
482        self.res = res
483
484        self.lw(lw)
485        self.properties.LightingOff()
486        self.actor.PickableOff()
487        self.actor.DragableOff()
488        self.base = base
489        self.top = top
490        self.name = "Line"
Arguments:
  • closed : (bool) join last to first point
  • res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
  • lw : (int) line width in pixel units
def clone(self, deep=True) -> Line:
492    def clone(self, deep=True) -> "Line":
493        """
494        Return a copy of the ``Line`` object.
495
496        Example:
497            ```python
498            from vedo import *
499            ln1 = Line([1,1,1], [2,2,2], lw=3).print()
500            ln2 = ln1.clone().shift(0,0,1).c('red').print()
501            show(ln1, ln2, axes=1, viewup='z').close()
502            ```
503            ![](https://vedo.embl.es/images/feats/line_clone.png)
504        """
505        poly = vtki.vtkPolyData()
506        if deep:
507            poly.DeepCopy(self.dataset)
508        else:
509            poly.ShallowCopy(self.dataset)
510        ln = Line(poly)
511        ln.copy_properties_from(self)
512        ln.transform = self.transform.clone()
513        ln.name = self.name
514        ln.base = self.base
515        ln.top = self.top
516        ln.pipeline = utils.OperationNode(
517            "clone", parents=[self], shape="diamond", c="#edede9")
518        return ln

Return a copy of the Line object.

Example:
from vedo import *
ln1 = Line([1,1,1], [2,2,2], lw=3).print()
ln2 = ln1.clone().shift(0,0,1).c('red').print()
show(ln1, ln2, axes=1, viewup='z').close()

def linecolor(self, lc=None) -> Line:
520    def linecolor(self, lc=None) -> "Line":
521        """Assign a color to the line"""
522        # overrides mesh.linecolor which would have no effect here
523        return self.color(lc)

Assign a color to the line

def eval(self, x: float) -> numpy.ndarray:
525    def eval(self, x: float) -> np.ndarray:
526        """
527        Calculate the position of an intermediate point
528        as a fraction of the length of the line,
529        being x=0 the first point and x=1 the last point.
530        This corresponds to an imaginary point that travels along the line
531        at constant speed.
532
533        Can be used in conjunction with `lin_interpolate()`
534        to map any range to the [0,1] range.
535        """
536        distance1 = 0.0
537        length = self.length()
538        pts = self.vertices
539        for i in range(1, len(pts)):
540            p0 = pts[i - 1]
541            p1 = pts[i]
542            seg = p1 - p0
543            distance0 = distance1
544            distance1 += np.linalg.norm(seg)
545            w1 = distance1 / length
546            if w1 >= x:
547                break
548        w0 = distance0 / length
549        v = p0 + seg * (x - w0) / (w1 - w0)
550        return v

Calculate the position of an intermediate point as a fraction of the length of the line, being x=0 the first point and x=1 the last point. This corresponds to an imaginary point that travels along the line at constant speed.

Can be used in conjunction with lin_interpolate() to map any range to the [0,1] range.

def find_index_at_position(self, p) -> float:
552    def find_index_at_position(self, p) -> float:
553        """
554        Find the index of the line vertex that is closest to the point `p`.
555        Note that the returned index can be fractional if `p` is not exactly
556        one of the vertices of the line.
557        """
558        q = self.closest_point(p)
559        a, b = sorted(self.closest_point(q, n=2, return_point_id=True))
560        pts = self.vertices
561        d = np.linalg.norm(pts[a] - pts[b])
562        t = a + np.linalg.norm(pts[a] - q) / d
563        return t

Find the index of the line vertex that is closest to the point p. Note that the returned index can be fractional if p is not exactly one of the vertices of the line.

def pattern(self, stipple, repeats=10) -> Line:
565    def pattern(self, stipple, repeats=10) -> "Line":
566        """
567        Define a stipple pattern for dashing the line.
568        Pass the stipple pattern as a string like `'- - -'`.
569        Repeats controls the number of times the pattern repeats in a single segment.
570
571        Examples are: `'- -', '--  -  --'`, etc.
572
573        The resolution of the line (nr of points) can affect how pattern will show up.
574
575        Example:
576            ```python
577            from vedo import Line
578            pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]]
579            ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10)
580            ln.show(axes=1).close()
581            ```
582            ![](https://vedo.embl.es/images/feats/line_pattern.png)
583        """
584        stipple = str(stipple) * int(2 * repeats)
585        dimension = len(stipple)
586
587        image = vtki.vtkImageData()
588        image.SetDimensions(dimension, 1, 1)
589        image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4)
590        image.SetExtent(0, dimension - 1, 0, 0, 0, 0)
591        i_dim = 0
592        while i_dim < dimension:
593            for i in range(dimension):
594                image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255)
595                image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255)
596                image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255)
597                if stipple[i] == " ":
598                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0)
599                else:
600                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255)
601                i_dim += 1
602
603        poly = self.dataset
604
605        # Create texture coordinates
606        tcoords = vtki.vtkDoubleArray()
607        tcoords.SetName("TCoordsStippledLine")
608        tcoords.SetNumberOfComponents(1)
609        tcoords.SetNumberOfTuples(poly.GetNumberOfPoints())
610        for i in range(poly.GetNumberOfPoints()):
611            tcoords.SetTypedTuple(i, [i / 2])
612        poly.GetPointData().SetTCoords(tcoords)
613        poly.GetPointData().Modified()
614        texture = vtki.vtkTexture()
615        texture.SetInputData(image)
616        texture.InterpolateOff()
617        texture.RepeatOn()
618        self.actor.SetTexture(texture)
619        return self

Define a stipple pattern for dashing the line. Pass the stipple pattern as a string like '- - -'. Repeats controls the number of times the pattern repeats in a single segment.

Examples are: '- -', '-- - --', etc.

The resolution of the line (nr of points) can affect how pattern will show up.

Example:
from vedo import Line
pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]]
ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10)
ln.show(axes=1).close()

def length(self) -> float:
621    def length(self) -> float:
622        """Calculate length of the line."""
623        distance = 0.0
624        pts = self.vertices
625        for i in range(1, len(pts)):
626            distance += np.linalg.norm(pts[i] - pts[i - 1])
627        return distance

Calculate length of the line.

def tangents(self) -> numpy.ndarray:
629    def tangents(self) -> np.ndarray:
630        """
631        Compute the tangents of a line in space.
632
633        Example:
634            ```python
635            from vedo import *
636            shape = Assembly(dataurl+"timecourse1d.npy")[58]
637            pts = shape.rotate_x(30).vertices
638            tangents = Line(pts).tangents()
639            arrs = Arrows(pts, pts+tangents, c='blue9')
640            show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()
641            ```
642            ![](https://vedo.embl.es/images/feats/line_tangents.png)
643        """
644        v = np.gradient(self.vertices)[0]
645        ds_dt = np.linalg.norm(v, axis=1)
646        tangent = np.array([1 / ds_dt] * 3).transpose() * v
647        return tangent

Compute the tangents of a line in space.

Example:
from vedo import *
shape = Assembly(dataurl+"timecourse1d.npy")[58]
pts = shape.rotate_x(30).vertices
tangents = Line(pts).tangents()
arrs = Arrows(pts, pts+tangents, c='blue9')
show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()

def curvature(self) -> numpy.ndarray:
649    def curvature(self) -> np.ndarray:
650        """
651        Compute the signed curvature of a line in space.
652        The signed is computed assuming the line is about coplanar to the xy plane.
653
654        Example:
655            ```python
656            from vedo import *
657            from vedo.pyplot import plot
658            shape = Assembly(dataurl+"timecourse1d.npy")[55]
659            curvs = Line(shape.vertices).curvature()
660            shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w')
661            shape.render_lines_as_tubes().lw(12)
662            pp = plot(curvs, ac='white', lc='yellow5')
663            show(shape, pp, N=2, bg='bb', sharecam=False).close()
664            ```
665            ![](https://vedo.embl.es/images/feats/line_curvature.png)
666        """
667        v = np.gradient(self.vertices)[0]
668        a = np.gradient(v)[0]
669        av = np.cross(a, v)
670        mav = np.linalg.norm(av, axis=1)
671        mv = utils.mag2(v)
672        val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5)
673        val[0] = val[1]
674        val[-1] = val[-2]
675        return val

Compute the signed curvature of a line in space. The signed is computed assuming the line is about coplanar to the xy plane.

Example:
from vedo import *
from vedo.pyplot import plot
shape = Assembly(dataurl+"timecourse1d.npy")[55]
curvs = Line(shape.vertices).curvature()
shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w')
shape.render_lines_as_tubes().lw(12)
pp = plot(curvs, ac='white', lc='yellow5')
show(shape, pp, N=2, bg='bb', sharecam=False).close()

def compute_curvature(self, method=0) -> Line:
677    def compute_curvature(self, method=0) -> "Line":
678        """
679        Add a pointdata array named 'Curvatures' which contains
680        the curvature value at each point.
681
682        NB: keyword `method` is overridden in Mesh and has no effect here.
683        """
684        # overrides mesh.compute_curvature
685        curvs = self.curvature()
686        vmin, vmax = np.min(curvs), np.max(curvs)
687        if vmin < 0 and vmax > 0:
688            v = max(-vmin, vmax)
689            self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature")
690        else:
691            self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature")
692        return self

Add a pointdata array named 'Curvatures' which contains the curvature value at each point.

NB: keyword method is overridden in Mesh and has no effect here.

def plot_scalar(self, radius=0.0, height=1.1, normal=(), camera=None) -> Line:
694    def plot_scalar(
695            self,
696            radius=0.0, 
697            height=1.1,
698            normal=(),
699            camera=None,
700        ) -> "Line":
701        """
702        Generate a new `Line` which plots the active scalar along the line.
703
704        Arguments:
705            radius : (float)
706                distance radius to the line
707            height: (float)
708                height of the plot
709            normal: (list)
710                normal vector to the plane of the plot
711            camera: (vtkCamera) 
712                camera object to use for the plot orientation
713        
714        Example:
715            ```python
716            from vedo import *
717            circle = Circle(res=360).rotate_y(20)
718            pts = circle.vertices
719            bore = Line(pts).lw(5)
720            values = np.arctan2(pts[:,1], pts[:,0])
721            bore.pointdata["scalars"] = values + np.random.randn(360)/5
722            vap = bore.plot_scalar(radius=0, height=1)
723            show(bore, vap, axes=1, viewup='z').close()
724            ```
725            ![](https://vedo.embl.es/images/feats/line_plot_scalar.png)
726        """
727        ap = vtki.new("ArcPlotter")
728        ap.SetInputData(self.dataset)
729        ap.SetCamera(camera)
730        ap.SetRadius(radius)
731        ap.SetHeight(height)
732        if len(normal)>0:
733            ap.UseDefaultNormalOn()
734            ap.SetDefaultNormal(normal)
735        ap.Update()
736        vap = Line(ap.GetOutput())
737        vap.linewidth(3).lighting('off')
738        vap.name = "ArcPlot"
739        return vap

Generate a new Line which plots the active scalar along the line.

Arguments:
  • radius : (float) distance radius to the line
  • height: (float) height of the plot
  • normal: (list) normal vector to the plane of the plot
  • camera: (vtkCamera) camera object to use for the plot orientation
Example:
from vedo import *
circle = Circle(res=360).rotate_y(20)
pts = circle.vertices
bore = Line(pts).lw(5)
values = np.arctan2(pts[:,1], pts[:,0])
bore.pointdata["scalars"] = values + np.random.randn(360)/5
vap = bore.plot_scalar(radius=0, height=1)
show(bore, vap, axes=1, viewup='z').close()

def sweep(self, direction=(1, 0, 0), res=1) -> vedo.mesh.Mesh:
741    def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh":
742        """
743        Sweep the `Line` along the specified vector direction.
744
745        Returns a `Mesh` surface.
746        Line position is updated to allow for additional sweepings.
747
748        Example:
749            ```python
750            from vedo import Line, show
751            aline = Line([(0,0,0),(1,3,0),(2,4,0)])
752            surf1 = aline.sweep((1,0.2,0), res=3)
753            surf2 = aline.sweep((0.2,0,1)).alpha(0.5)
754            aline.color('r').linewidth(4)
755            show(surf1, surf2, aline, axes=1).close()
756            ```
757            ![](https://vedo.embl.es/images/feats/sweepline.png)
758        """
759        line = self.dataset
760        rows = line.GetNumberOfPoints()
761
762        spacing = 1 / res
763        surface = vtki.vtkPolyData()
764
765        res += 1
766        npts = rows * res
767        npolys = (rows - 1) * (res - 1)
768        points = vtki.vtkPoints()
769        points.Allocate(npts)
770
771        cnt = 0
772        x = [0.0, 0.0, 0.0]
773        for row in range(rows):
774            for col in range(res):
775                p = [0.0, 0.0, 0.0]
776                line.GetPoint(row, p)
777                x[0] = p[0] + direction[0] * col * spacing
778                x[1] = p[1] + direction[1] * col * spacing
779                x[2] = p[2] + direction[2] * col * spacing
780                points.InsertPoint(cnt, x)
781                cnt += 1
782
783        # Generate the quads
784        polys = vtki.vtkCellArray()
785        polys.Allocate(npolys * 4)
786        pts = [0, 0, 0, 0]
787        for row in range(rows - 1):
788            for col in range(res - 1):
789                pts[0] = col + row * res
790                pts[1] = pts[0] + 1
791                pts[2] = pts[0] + res + 1
792                pts[3] = pts[0] + res
793                polys.InsertNextCell(4, pts)
794        surface.SetPoints(points)
795        surface.SetPolys(polys)
796        asurface = Mesh(surface)
797        asurface.copy_properties_from(self)
798        asurface.lighting("default")
799        self.vertices = self.vertices + direction
800        return asurface

Sweep the Line along the specified vector direction.

Returns a Mesh surface. Line position is updated to allow for additional sweepings.

Example:
from vedo import Line, show
aline = Line([(0,0,0),(1,3,0),(2,4,0)])
surf1 = aline.sweep((1,0.2,0), res=3)
surf2 = aline.sweep((0.2,0,1)).alpha(0.5)
aline.color('r').linewidth(4)
show(surf1, surf2, aline, axes=1).close()

def reverse(self):
802    def reverse(self):
803        """Reverse the points sequence order."""
804        pts = np.flip(self.vertices, axis=0)
805        self.vertices = pts
806        return self

Reverse the points sequence order.

class DashedLine(vedo.mesh.Mesh):
809class DashedLine(Mesh):
810    """
811    Consider using `Line.pattern()` instead.
812
813    Build a dashed line segment between points `p0` and `p1`.
814    If `p0` is a list of points returns the line connecting them.
815    A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`.
816    """
817
818    def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None:
819        """
820        Arguments:
821            closed : (bool)
822                join last to first point
823            spacing : (float)
824                relative size of the dash
825            lw : (int)
826                line width in pixels
827        """
828        if isinstance(p1, vtki.vtkActor):
829            p1 = p1.GetPosition()
830            if isinstance(p0, vtki.vtkActor):
831                p0 = p0.GetPosition()
832        if isinstance(p0, Points):
833            p0 = p0.vertices
834
835        # detect if user is passing a 2D list of points as p0=xlist, p1=ylist:
836        if len(p0) > 3:
837            if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1):
838                # assume input is 2D xlist, ylist
839                p0 = np.stack((p0, p1), axis=1)
840                p1 = None
841            p0 = utils.make3d(p0)
842            if closed:
843                p0 = np.append(p0, [p0[0]], axis=0)
844
845        if p1 is not None:  # assume passing p0=[x,y]
846            if len(p0) == 2 and not utils.is_sequence(p0[0]):
847                p0 = (p0[0], p0[1], 0)
848            if len(p1) == 2 and not utils.is_sequence(p1[0]):
849                p1 = (p1[0], p1[1], 0)
850
851        # detect if user is passing a list of points:
852        if utils.is_sequence(p0[0]):
853            listp = p0
854        else:  # or just 2 points to link
855            listp = [p0, p1]
856
857        listp = np.array(listp)
858        if listp.shape[1] == 2:
859            listp = np.c_[listp, np.zeros(listp.shape[0])]
860
861        xmn = np.min(listp, axis=0)
862        xmx = np.max(listp, axis=0)
863        dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10
864        if not dlen:
865            super().__init__(vtki.vtkPolyData(), c, alpha)
866            self.name = "DashedLine (void)"
867            return
868
869        qs = []
870        for ipt in range(len(listp) - 1):
871            p0 = listp[ipt]
872            p1 = listp[ipt + 1]
873            v = p1 - p0
874            vdist = np.linalg.norm(v)
875            n1 = int(vdist / dlen)
876            if not n1:
877                continue
878
879            res = 0.0
880            for i in range(n1 + 2):
881                ist = (i - 0.5) / n1
882                ist = max(ist, 0)
883                qi = p0 + v * (ist - res / vdist)
884                if ist > 1:
885                    qi = p1
886                    res = np.linalg.norm(qi - p1)
887                    qs.append(qi)
888                    break
889                qs.append(qi)
890
891        polylns = vtki.new("AppendPolyData")
892        for i, q1 in enumerate(qs):
893            if not i % 2:
894                continue
895            q0 = qs[i - 1]
896            line_source = vtki.new("LineSource")
897            line_source.SetPoint1(q0)
898            line_source.SetPoint2(q1)
899            line_source.Update()
900            polylns.AddInputData(line_source.GetOutput())
901        polylns.Update()
902
903        super().__init__(polylns.GetOutput(), c, alpha)
904        self.lw(lw).lighting("off")
905        self.base = listp[0]
906        if closed:
907            self.top = listp[-2]
908        else:
909            self.top = listp[-1]
910        self.name = "DashedLine"

Consider using Line.pattern() instead.

Build a dashed line segment between points p0 and p1. If p0 is a list of points returns the line connecting them. A 2D set of coords can also be passed as p0=[x..], p1=[y..].

DashedLine(p0, p1=None, spacing=0.1, closed=False, lw=2, c='k5', alpha=1.0)
818    def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None:
819        """
820        Arguments:
821            closed : (bool)
822                join last to first point
823            spacing : (float)
824                relative size of the dash
825            lw : (int)
826                line width in pixels
827        """
828        if isinstance(p1, vtki.vtkActor):
829            p1 = p1.GetPosition()
830            if isinstance(p0, vtki.vtkActor):
831                p0 = p0.GetPosition()
832        if isinstance(p0, Points):
833            p0 = p0.vertices
834
835        # detect if user is passing a 2D list of points as p0=xlist, p1=ylist:
836        if len(p0) > 3:
837            if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1):
838                # assume input is 2D xlist, ylist
839                p0 = np.stack((p0, p1), axis=1)
840                p1 = None
841            p0 = utils.make3d(p0)
842            if closed:
843                p0 = np.append(p0, [p0[0]], axis=0)
844
845        if p1 is not None:  # assume passing p0=[x,y]
846            if len(p0) == 2 and not utils.is_sequence(p0[0]):
847                p0 = (p0[0], p0[1], 0)
848            if len(p1) == 2 and not utils.is_sequence(p1[0]):
849                p1 = (p1[0], p1[1], 0)
850
851        # detect if user is passing a list of points:
852        if utils.is_sequence(p0[0]):
853            listp = p0
854        else:  # or just 2 points to link
855            listp = [p0, p1]
856
857        listp = np.array(listp)
858        if listp.shape[1] == 2:
859            listp = np.c_[listp, np.zeros(listp.shape[0])]
860
861        xmn = np.min(listp, axis=0)
862        xmx = np.max(listp, axis=0)
863        dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10
864        if not dlen:
865            super().__init__(vtki.vtkPolyData(), c, alpha)
866            self.name = "DashedLine (void)"
867            return
868
869        qs = []
870        for ipt in range(len(listp) - 1):
871            p0 = listp[ipt]
872            p1 = listp[ipt + 1]
873            v = p1 - p0
874            vdist = np.linalg.norm(v)
875            n1 = int(vdist / dlen)
876            if not n1:
877                continue
878
879            res = 0.0
880            for i in range(n1 + 2):
881                ist = (i - 0.5) / n1
882                ist = max(ist, 0)
883                qi = p0 + v * (ist - res / vdist)
884                if ist > 1:
885                    qi = p1
886                    res = np.linalg.norm(qi - p1)
887                    qs.append(qi)
888                    break
889                qs.append(qi)
890
891        polylns = vtki.new("AppendPolyData")
892        for i, q1 in enumerate(qs):
893            if not i % 2:
894                continue
895            q0 = qs[i - 1]
896            line_source = vtki.new("LineSource")
897            line_source.SetPoint1(q0)
898            line_source.SetPoint2(q1)
899            line_source.Update()
900            polylns.AddInputData(line_source.GetOutput())
901        polylns.Update()
902
903        super().__init__(polylns.GetOutput(), c, alpha)
904        self.lw(lw).lighting("off")
905        self.base = listp[0]
906        if closed:
907            self.top = listp[-2]
908        else:
909            self.top = listp[-1]
910        self.name = "DashedLine"
Arguments:
  • closed : (bool) join last to first point
  • spacing : (float) relative size of the dash
  • lw : (int) line width in pixels
class RoundedLine(vedo.mesh.Mesh):
 913class RoundedLine(Mesh):
 914    """
 915    Create a 2D line of specified thickness (in absolute units) passing through
 916    a list of input points. Borders of the line are rounded.
 917    """
 918
 919    def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None:
 920        """
 921        Arguments:
 922            pts : (list)
 923                a list of points in 2D or 3D (z will be ignored).
 924            lw : (float)
 925                thickness of the line.
 926            res : (int)
 927                resolution of the rounded regions
 928
 929        Example:
 930            ```python
 931            from vedo import *
 932            pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)]
 933            ln = Line(pts).z(0.01)
 934            ln.color("red5").linewidth(2)
 935            rl = RoundedLine(pts, 0.6)
 936            show(Points(pts), ln, rl, axes=1).close()
 937            ```
 938            ![](https://vedo.embl.es/images/feats/rounded_line.png)
 939        """
 940        pts = utils.make3d(pts)
 941
 942        def _getpts(pts, revd=False):
 943
 944            if revd:
 945                pts = list(reversed(pts))
 946
 947            if len(pts) == 2:
 948                p0, p1 = pts
 949                v = p1 - p0
 950                dv = np.linalg.norm(v)
 951                nv = np.cross(v, (0, 0, -1))
 952                nv = nv / np.linalg.norm(nv) * lw
 953                return [p0 + nv, p1 + nv]
 954
 955            ptsnew = []
 956            for k in range(len(pts) - 2):
 957                p0 = pts[k]
 958                p1 = pts[k + 1]
 959                p2 = pts[k + 2]
 960                v = p1 - p0
 961                u = p2 - p1
 962                du = np.linalg.norm(u)
 963                dv = np.linalg.norm(v)
 964                nv = np.cross(v, (0, 0, -1))
 965                nv = nv / np.linalg.norm(nv) * lw
 966                nu = np.cross(u, (0, 0, -1))
 967                nu = nu / np.linalg.norm(nu) * lw
 968                uv = np.cross(u, v)
 969                if k == 0:
 970                    ptsnew.append(p0 + nv)
 971                if uv[2] <= 0:
 972                    # the following computation can return a value
 973                    # ever so slightly > 1.0 causing arccos to fail.
 974                    uv_arg = np.dot(u, v) / du / dv
 975                    if uv_arg > 1.0:
 976                        # since the argument to arcos is 1, simply
 977                        # assign alpha to 0.0 without calculating the
 978                        # arccos
 979                        alpha = 0.0
 980                    else:
 981                        alpha = np.arccos(uv_arg)
 982                    db = lw * np.tan(alpha / 2)
 983                    p1new = p1 + nv - v / dv * db
 984                    ptsnew.append(p1new)
 985                else:
 986                    p1a = p1 + nv
 987                    p1b = p1 + nu
 988                    for i in range(0, res + 1):
 989                        pab = p1a * (res - i) / res + p1b * i / res
 990                        vpab = pab - p1
 991                        vpab = vpab / np.linalg.norm(vpab) * lw
 992                        ptsnew.append(p1 + vpab)
 993                if k == len(pts) - 3:
 994                    ptsnew.append(p2 + nu)
 995                    if revd:
 996                        ptsnew.append(p2 - nu)
 997            return ptsnew
 998
 999        ptsnew = _getpts(pts) + _getpts(pts, revd=True)
1000
1001        ppoints = vtki.vtkPoints()  # Generate the polyline
1002        ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32))
1003        lines = vtki.vtkCellArray()
1004        npt = len(ptsnew)
1005        lines.InsertNextCell(npt)
1006        for i in range(npt):
1007            lines.InsertCellPoint(i)
1008        poly = vtki.vtkPolyData()
1009        poly.SetPoints(ppoints)
1010        poly.SetLines(lines)
1011        vct = vtki.new("ContourTriangulator")
1012        vct.SetInputData(poly)
1013        vct.Update()
1014
1015        super().__init__(vct.GetOutput(), c, alpha)
1016        self.flat()
1017        self.properties.LightingOff()
1018        self.name = "RoundedLine"
1019        self.base = ptsnew[0]
1020        self.top = ptsnew[-1]

Create a 2D line of specified thickness (in absolute units) passing through a list of input points. Borders of the line are rounded.

RoundedLine(pts, lw, res=10, c='gray4', alpha=1.0)
 919    def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None:
 920        """
 921        Arguments:
 922            pts : (list)
 923                a list of points in 2D or 3D (z will be ignored).
 924            lw : (float)
 925                thickness of the line.
 926            res : (int)
 927                resolution of the rounded regions
 928
 929        Example:
 930            ```python
 931            from vedo import *
 932            pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)]
 933            ln = Line(pts).z(0.01)
 934            ln.color("red5").linewidth(2)
 935            rl = RoundedLine(pts, 0.6)
 936            show(Points(pts), ln, rl, axes=1).close()
 937            ```
 938            ![](https://vedo.embl.es/images/feats/rounded_line.png)
 939        """
 940        pts = utils.make3d(pts)
 941
 942        def _getpts(pts, revd=False):
 943
 944            if revd:
 945                pts = list(reversed(pts))
 946
 947            if len(pts) == 2:
 948                p0, p1 = pts
 949                v = p1 - p0
 950                dv = np.linalg.norm(v)
 951                nv = np.cross(v, (0, 0, -1))
 952                nv = nv / np.linalg.norm(nv) * lw
 953                return [p0 + nv, p1 + nv]
 954
 955            ptsnew = []
 956            for k in range(len(pts) - 2):
 957                p0 = pts[k]
 958                p1 = pts[k + 1]
 959                p2 = pts[k + 2]
 960                v = p1 - p0
 961                u = p2 - p1
 962                du = np.linalg.norm(u)
 963                dv = np.linalg.norm(v)
 964                nv = np.cross(v, (0, 0, -1))
 965                nv = nv / np.linalg.norm(nv) * lw
 966                nu = np.cross(u, (0, 0, -1))
 967                nu = nu / np.linalg.norm(nu) * lw
 968                uv = np.cross(u, v)
 969                if k == 0:
 970                    ptsnew.append(p0 + nv)
 971                if uv[2] <= 0:
 972                    # the following computation can return a value
 973                    # ever so slightly > 1.0 causing arccos to fail.
 974                    uv_arg = np.dot(u, v) / du / dv
 975                    if uv_arg > 1.0:
 976                        # since the argument to arcos is 1, simply
 977                        # assign alpha to 0.0 without calculating the
 978                        # arccos
 979                        alpha = 0.0
 980                    else:
 981                        alpha = np.arccos(uv_arg)
 982                    db = lw * np.tan(alpha / 2)
 983                    p1new = p1 + nv - v / dv * db
 984                    ptsnew.append(p1new)
 985                else:
 986                    p1a = p1 + nv
 987                    p1b = p1 + nu
 988                    for i in range(0, res + 1):
 989                        pab = p1a * (res - i) / res + p1b * i / res
 990                        vpab = pab - p1
 991                        vpab = vpab / np.linalg.norm(vpab) * lw
 992                        ptsnew.append(p1 + vpab)
 993                if k == len(pts) - 3:
 994                    ptsnew.append(p2 + nu)
 995                    if revd:
 996                        ptsnew.append(p2 - nu)
 997            return ptsnew
 998
 999        ptsnew = _getpts(pts) + _getpts(pts, revd=True)
1000
1001        ppoints = vtki.vtkPoints()  # Generate the polyline
1002        ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32))
1003        lines = vtki.vtkCellArray()
1004        npt = len(ptsnew)
1005        lines.InsertNextCell(npt)
1006        for i in range(npt):
1007            lines.InsertCellPoint(i)
1008        poly = vtki.vtkPolyData()
1009        poly.SetPoints(ppoints)
1010        poly.SetLines(lines)
1011        vct = vtki.new("ContourTriangulator")
1012        vct.SetInputData(poly)
1013        vct.Update()
1014
1015        super().__init__(vct.GetOutput(), c, alpha)
1016        self.flat()
1017        self.properties.LightingOff()
1018        self.name = "RoundedLine"
1019        self.base = ptsnew[0]
1020        self.top = ptsnew[-1]
Arguments:
  • pts : (list) a list of points in 2D or 3D (z will be ignored).
  • lw : (float) thickness of the line.
  • res : (int) resolution of the rounded regions
Example:
from vedo import *
pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)]
ln = Line(pts).z(0.01)
ln.color("red5").linewidth(2)
rl = RoundedLine(pts, 0.6)
show(Points(pts), ln, rl, axes=1).close()

class Tube(vedo.mesh.Mesh):
1463class Tube(Mesh):
1464    """
1465    Build a tube along the line defined by a set of points.
1466    """
1467
1468    def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None:
1469        """
1470        Arguments:
1471            r :  (float, list)
1472                constant radius or list of radii.
1473            res : (int)
1474                resolution, number of the sides of the tube
1475            c : (color)
1476                constant color or list of colors for each point.
1477            
1478        Example:
1479            Create a tube along a line, with data associated to each point:
1480
1481            ```python
1482            from vedo import *
1483            line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5)
1484            scalars = np.array([0, 1, 2, 3])
1485            line.pointdata["myscalars"] = scalars
1486            tube = Tube(line, r=0.1).lw(1)
1487            tube.cmap('viridis', "myscalars").add_scalarbar3d()
1488            show(line, tube, axes=1).close()
1489            ```
1490
1491        Examples:
1492            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1493            - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py)
1494
1495                ![](https://vedo.embl.es/images/basic/tube.png)
1496        """
1497        if utils.is_sequence(points):
1498            vpoints = vtki.vtkPoints()
1499            idx = len(points)
1500            for p in points:
1501                vpoints.InsertNextPoint(p)
1502            line = vtki.new("PolyLine")
1503            line.GetPointIds().SetNumberOfIds(idx)
1504            for i in range(idx):
1505                line.GetPointIds().SetId(i, i)
1506            lines = vtki.vtkCellArray()
1507            lines.InsertNextCell(line)
1508            polyln = vtki.vtkPolyData()
1509            polyln.SetPoints(vpoints)
1510            polyln.SetLines(lines)            
1511            self.base = np.asarray(points[0], dtype=float)
1512            self.top = np.asarray(points[-1], dtype=float)
1513
1514        elif isinstance(points, Mesh):
1515            polyln = points.dataset
1516            n = polyln.GetNumberOfPoints()
1517            self.base = np.array(polyln.GetPoint(0))
1518            self.top = np.array(polyln.GetPoint(n - 1))
1519
1520        # from vtkmodules.vtkFiltersCore import vtkTubeBender
1521        # bender = vtkTubeBender()
1522        # bender.SetInputData(polyln)
1523        # bender.SetRadius(r)
1524        # bender.Update()
1525        # polyln = bender.GetOutput()
1526
1527        tuf = vtki.new("TubeFilter")
1528        tuf.SetCapping(cap)
1529        tuf.SetNumberOfSides(res)
1530        tuf.SetInputData(polyln)
1531        if utils.is_sequence(r):
1532            arr = utils.numpy2vtk(r, dtype=float)
1533            arr.SetName("TubeRadius")
1534            polyln.GetPointData().AddArray(arr)
1535            polyln.GetPointData().SetActiveScalars("TubeRadius")
1536            tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar()
1537        else:
1538            tuf.SetRadius(r)
1539
1540        usingColScals = False
1541        if utils.is_sequence(c):
1542            usingColScals = True
1543            cc = vtki.vtkUnsignedCharArray()
1544            cc.SetName("TubeColors")
1545            cc.SetNumberOfComponents(3)
1546            cc.SetNumberOfTuples(len(c))
1547            for i, ic in enumerate(c):
1548                r, g, b = get_color(ic)
1549                cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b))
1550            polyln.GetPointData().AddArray(cc)
1551            c = None
1552        tuf.Update()
1553
1554        super().__init__(tuf.GetOutput(), c, alpha)
1555        self.phong()
1556        if usingColScals:
1557            self.mapper.SetScalarModeToUsePointFieldData()
1558            self.mapper.ScalarVisibilityOn()
1559            self.mapper.SelectColorArray("TubeColors")
1560            self.mapper.Modified()
1561        self.name = "Tube"

Build a tube along the line defined by a set of points.

Tube(points, r=1.0, cap=True, res=12, c=None, alpha=1.0)
1468    def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None:
1469        """
1470        Arguments:
1471            r :  (float, list)
1472                constant radius or list of radii.
1473            res : (int)
1474                resolution, number of the sides of the tube
1475            c : (color)
1476                constant color or list of colors for each point.
1477            
1478        Example:
1479            Create a tube along a line, with data associated to each point:
1480
1481            ```python
1482            from vedo import *
1483            line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5)
1484            scalars = np.array([0, 1, 2, 3])
1485            line.pointdata["myscalars"] = scalars
1486            tube = Tube(line, r=0.1).lw(1)
1487            tube.cmap('viridis', "myscalars").add_scalarbar3d()
1488            show(line, tube, axes=1).close()
1489            ```
1490
1491        Examples:
1492            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1493            - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py)
1494
1495                ![](https://vedo.embl.es/images/basic/tube.png)
1496        """
1497        if utils.is_sequence(points):
1498            vpoints = vtki.vtkPoints()
1499            idx = len(points)
1500            for p in points:
1501                vpoints.InsertNextPoint(p)
1502            line = vtki.new("PolyLine")
1503            line.GetPointIds().SetNumberOfIds(idx)
1504            for i in range(idx):
1505                line.GetPointIds().SetId(i, i)
1506            lines = vtki.vtkCellArray()
1507            lines.InsertNextCell(line)
1508            polyln = vtki.vtkPolyData()
1509            polyln.SetPoints(vpoints)
1510            polyln.SetLines(lines)            
1511            self.base = np.asarray(points[0], dtype=float)
1512            self.top = np.asarray(points[-1], dtype=float)
1513
1514        elif isinstance(points, Mesh):
1515            polyln = points.dataset
1516            n = polyln.GetNumberOfPoints()
1517            self.base = np.array(polyln.GetPoint(0))
1518            self.top = np.array(polyln.GetPoint(n - 1))
1519
1520        # from vtkmodules.vtkFiltersCore import vtkTubeBender
1521        # bender = vtkTubeBender()
1522        # bender.SetInputData(polyln)
1523        # bender.SetRadius(r)
1524        # bender.Update()
1525        # polyln = bender.GetOutput()
1526
1527        tuf = vtki.new("TubeFilter")
1528        tuf.SetCapping(cap)
1529        tuf.SetNumberOfSides(res)
1530        tuf.SetInputData(polyln)
1531        if utils.is_sequence(r):
1532            arr = utils.numpy2vtk(r, dtype=float)
1533            arr.SetName("TubeRadius")
1534            polyln.GetPointData().AddArray(arr)
1535            polyln.GetPointData().SetActiveScalars("TubeRadius")
1536            tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar()
1537        else:
1538            tuf.SetRadius(r)
1539
1540        usingColScals = False
1541        if utils.is_sequence(c):
1542            usingColScals = True
1543            cc = vtki.vtkUnsignedCharArray()
1544            cc.SetName("TubeColors")
1545            cc.SetNumberOfComponents(3)
1546            cc.SetNumberOfTuples(len(c))
1547            for i, ic in enumerate(c):
1548                r, g, b = get_color(ic)
1549                cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b))
1550            polyln.GetPointData().AddArray(cc)
1551            c = None
1552        tuf.Update()
1553
1554        super().__init__(tuf.GetOutput(), c, alpha)
1555        self.phong()
1556        if usingColScals:
1557            self.mapper.SetScalarModeToUsePointFieldData()
1558            self.mapper.ScalarVisibilityOn()
1559            self.mapper.SelectColorArray("TubeColors")
1560            self.mapper.Modified()
1561        self.name = "Tube"
Arguments:
  • r : (float, list) constant radius or list of radii.
  • res : (int) resolution, number of the sides of the tube
  • c : (color) constant color or list of colors for each point.
Example:

Create a tube along a line, with data associated to each point:

from vedo import *
line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5)
scalars = np.array([0, 1, 2, 3])
line.pointdata["myscalars"] = scalars
tube = Tube(line, r=0.1).lw(1)
tube.cmap('viridis', "myscalars").add_scalarbar3d()
show(line, tube, axes=1).close()
Examples:
class Tubes(vedo.mesh.Mesh):
1615class Tubes(Mesh):
1616    """
1617    Build tubes around a `Lines` object.
1618    """
1619    def __init__(
1620            self,
1621            lines,
1622            r=1,
1623            vary_radius_by_scalar=False,
1624            vary_radius_by_vector=False,
1625            vary_radius_by_vector_norm=False,
1626            vary_radius_by_absolute_scalar=False,
1627            max_radius_factor=100,
1628            cap=True,
1629            res=12
1630        ) -> None:
1631        """
1632        Wrap tubes around the input `Lines` object.
1633
1634        Arguments:
1635            lines : (Lines)
1636                input Lines object.
1637            r : (float)
1638                constant radius
1639            vary_radius_by_scalar : (bool)
1640                use scalar array to control radius
1641            vary_radius_by_vector : (bool)
1642                use vector array to control radius
1643            vary_radius_by_vector_norm : (bool)
1644                use vector norm to control radius
1645            vary_radius_by_absolute_scalar : (bool)
1646                use absolute scalar value to control radius
1647            max_radius_factor : (float)
1648                max tube radius as a multiple of the min radius
1649            cap : (bool)
1650                capping of the tube
1651            res : (int)
1652                resolution, number of the sides of the tube
1653            c : (color)
1654                constant color or list of colors for each point.
1655        
1656        Examples:
1657            - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py)
1658        """
1659        plines = lines.dataset
1660        if plines.GetNumberOfLines() == 0:
1661            vedo.logger.warning("Tubes(): input Lines is empty.")
1662
1663        tuf = vtki.new("TubeFilter")
1664        if vary_radius_by_scalar:
1665            tuf.SetVaryRadiusToVaryRadiusByScalar()
1666        elif vary_radius_by_vector:
1667            tuf.SetVaryRadiusToVaryRadiusByVector()
1668        elif vary_radius_by_vector_norm:
1669            tuf.SetVaryRadiusToVaryRadiusByVectorNorm()
1670        elif vary_radius_by_absolute_scalar:
1671            tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar()
1672        tuf.SetRadius(r)
1673        tuf.SetCapping(cap)
1674        tuf.SetGenerateTCoords(0)
1675        tuf.SetSidesShareVertices(1)
1676        tuf.SetRadiusFactor(max_radius_factor)
1677        tuf.SetNumberOfSides(res)
1678        tuf.SetInputData(plines)
1679        tuf.Update()
1680
1681        super().__init__(tuf.GetOutput())
1682        self.name = "Tubes"

Build tubes around a Lines object.

Tubes( lines, r=1, vary_radius_by_scalar=False, vary_radius_by_vector=False, vary_radius_by_vector_norm=False, vary_radius_by_absolute_scalar=False, max_radius_factor=100, cap=True, res=12)
1619    def __init__(
1620            self,
1621            lines,
1622            r=1,
1623            vary_radius_by_scalar=False,
1624            vary_radius_by_vector=False,
1625            vary_radius_by_vector_norm=False,
1626            vary_radius_by_absolute_scalar=False,
1627            max_radius_factor=100,
1628            cap=True,
1629            res=12
1630        ) -> None:
1631        """
1632        Wrap tubes around the input `Lines` object.
1633
1634        Arguments:
1635            lines : (Lines)
1636                input Lines object.
1637            r : (float)
1638                constant radius
1639            vary_radius_by_scalar : (bool)
1640                use scalar array to control radius
1641            vary_radius_by_vector : (bool)
1642                use vector array to control radius
1643            vary_radius_by_vector_norm : (bool)
1644                use vector norm to control radius
1645            vary_radius_by_absolute_scalar : (bool)
1646                use absolute scalar value to control radius
1647            max_radius_factor : (float)
1648                max tube radius as a multiple of the min radius
1649            cap : (bool)
1650                capping of the tube
1651            res : (int)
1652                resolution, number of the sides of the tube
1653            c : (color)
1654                constant color or list of colors for each point.
1655        
1656        Examples:
1657            - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py)
1658        """
1659        plines = lines.dataset
1660        if plines.GetNumberOfLines() == 0:
1661            vedo.logger.warning("Tubes(): input Lines is empty.")
1662
1663        tuf = vtki.new("TubeFilter")
1664        if vary_radius_by_scalar:
1665            tuf.SetVaryRadiusToVaryRadiusByScalar()
1666        elif vary_radius_by_vector:
1667            tuf.SetVaryRadiusToVaryRadiusByVector()
1668        elif vary_radius_by_vector_norm:
1669            tuf.SetVaryRadiusToVaryRadiusByVectorNorm()
1670        elif vary_radius_by_absolute_scalar:
1671            tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar()
1672        tuf.SetRadius(r)
1673        tuf.SetCapping(cap)
1674        tuf.SetGenerateTCoords(0)
1675        tuf.SetSidesShareVertices(1)
1676        tuf.SetRadiusFactor(max_radius_factor)
1677        tuf.SetNumberOfSides(res)
1678        tuf.SetInputData(plines)
1679        tuf.Update()
1680
1681        super().__init__(tuf.GetOutput())
1682        self.name = "Tubes"

Wrap tubes around the input Lines object.

Arguments:
  • lines : (Lines) input Lines object.
  • r : (float) constant radius
  • vary_radius_by_scalar : (bool) use scalar array to control radius
  • vary_radius_by_vector : (bool) use vector array to control radius
  • vary_radius_by_vector_norm : (bool) use vector norm to control radius
  • vary_radius_by_absolute_scalar : (bool) use absolute scalar value to control radius
  • max_radius_factor : (float) max tube radius as a multiple of the min radius
  • cap : (bool) capping of the tube
  • res : (int) resolution, number of the sides of the tube
  • c : (color) constant color or list of colors for each point.
Examples:
def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0) -> Optional[vedo.mesh.Mesh]:
1564def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0) -> Union["Mesh", None]:
1565    """
1566    Create a tube with a thickness along a line of points.
1567
1568    Example:
1569    ```python
1570    from vedo import *
1571    pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)]
1572    vline = Line(pts, lw=5, c='red5')
1573    thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1)
1574    show(vline, thick_tube, axes=1).close()
1575    ```
1576    ![](https://vedo.embl.es/images/feats/thick_tube.png)
1577    """
1578
1579    def make_cap(t1, t2):
1580        newpoints = t1.vertices.tolist() + t2.vertices.tolist()
1581        newfaces = []
1582        for i in range(n - 1):
1583            newfaces.append([i, i + 1, i + n])
1584            newfaces.append([i + n, i + 1, i + n + 1])
1585        newfaces.append([2 * n - 1, 0, n])
1586        newfaces.append([2 * n - 1, n - 1, 0])
1587        capm = utils.buildPolyData(newpoints, newfaces)
1588        return capm
1589
1590    assert r1 < r2
1591
1592    t1 = Tube(pts, r=r1, cap=False, res=res)
1593    t2 = Tube(pts, r=r2, cap=False, res=res)
1594
1595    tc1a, tc1b = t1.boundaries().split()
1596    tc2a, tc2b = t2.boundaries().split()
1597    n = tc1b.npoints
1598
1599    tc1b.join(reset=True).clean()  # needed because indices are flipped
1600    tc2b.join(reset=True).clean()
1601
1602    capa = make_cap(tc1a, tc2a)
1603    capb = make_cap(tc1b, tc2b)
1604
1605    thick_tube = merge(t1, t2, capa, capb)
1606    if thick_tube:
1607        thick_tube.c(c).alpha(alpha)
1608        thick_tube.base = t1.base
1609        thick_tube.top  = t1.top
1610        thick_tube.name = "ThickTube"
1611        return thick_tube
1612    return None

Create a tube with a thickness along a line of points.

Example:

from vedo import *
pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)]
vline = Line(pts, lw=5, c='red5')
thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1)
show(vline, thick_tube, axes=1).close()

class Lines(vedo.mesh.Mesh):
1023class Lines(Mesh):
1024    """
1025    Build the line segments between two lists of points `start_pts` and `end_pts`.
1026    `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1027    """
1028
1029    def __init__(
1030        self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0
1031    ) -> None:
1032        """
1033        Arguments:
1034            scale : (float)
1035                apply a rescaling factor to the lengths.
1036            c : (color, int, str, list)
1037                color name, number, or list of [R,G,B] colors
1038            alpha : (float)
1039                opacity in range [0,1]
1040            lw : (int)
1041                line width in pixel units
1042            dotted : (bool)
1043                draw a dotted line
1044            res : (int)
1045                resolution, number of points along the line
1046                (only relevant if only 2 points are specified)
1047
1048        Examples:
1049            - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py)
1050
1051            ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png)
1052        """
1053
1054        if isinstance(start_pts, vtki.vtkPolyData):########
1055            super().__init__(start_pts, c, alpha)
1056            self.lw(lw).lighting("off")
1057            self.name = "Lines"
1058            return ########################################
1059
1060        if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line):
1061            # passing a list of Line, see tests/issues/issue_950.py
1062            polylns = vtki.new("AppendPolyData")
1063            for ln in start_pts:
1064                polylns.AddInputData(ln.dataset)
1065            polylns.Update()
1066
1067            super().__init__(polylns.GetOutput(), c, alpha)
1068            self.lw(lw).lighting("off")
1069            if dotted:
1070                self.properties.SetLineStipplePattern(0xF0F0)
1071                self.properties.SetLineStippleRepeatFactor(1)
1072            self.name = "Lines"
1073            return ########################################
1074
1075        if isinstance(start_pts, Points):
1076            start_pts = start_pts.vertices
1077        if isinstance(end_pts, Points):
1078            end_pts = end_pts.vertices
1079
1080        if end_pts is not None:
1081            start_pts = np.stack((start_pts, end_pts), axis=1)
1082
1083        polylns = vtki.new("AppendPolyData")
1084
1085        if not utils.is_ragged(start_pts):
1086
1087            for twopts in start_pts:
1088                line_source = vtki.new("LineSource")
1089                line_source.SetResolution(res)
1090                if len(twopts[0]) == 2:
1091                    line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0)
1092                else:
1093                    line_source.SetPoint1(twopts[0])
1094
1095                if scale == 1:
1096                    pt2 = twopts[1]
1097                else:
1098                    vers = (np.array(twopts[1]) - twopts[0]) * scale
1099                    pt2 = np.array(twopts[0]) + vers
1100
1101                if len(pt2) == 2:
1102                    line_source.SetPoint2(pt2[0], pt2[1], 0.0)
1103                else:
1104                    line_source.SetPoint2(pt2)
1105                polylns.AddInputConnection(line_source.GetOutputPort())
1106
1107        else:
1108
1109            polylns = vtki.new("AppendPolyData")
1110            for t in start_pts:
1111                t = utils.make3d(t)
1112                ppoints = vtki.vtkPoints()  # Generate the polyline
1113                ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32))
1114                lines = vtki.vtkCellArray()
1115                npt = len(t)
1116                lines.InsertNextCell(npt)
1117                for i in range(npt):
1118                    lines.InsertCellPoint(i)
1119                poly = vtki.vtkPolyData()
1120                poly.SetPoints(ppoints)
1121                poly.SetLines(lines)
1122                polylns.AddInputData(poly)
1123
1124        polylns.Update()
1125
1126        super().__init__(polylns.GetOutput(), c, alpha)
1127        self.lw(lw).lighting("off")
1128        if dotted:
1129            self.properties.SetLineStipplePattern(0xF0F0)
1130            self.properties.SetLineStippleRepeatFactor(1)
1131
1132        self.name = "Lines"

Build the line segments between two lists of points start_pts and end_pts. start_pts can be also passed in the form [[point1, point2], ...].

Lines( start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c='k4', alpha=1.0)
1029    def __init__(
1030        self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0
1031    ) -> None:
1032        """
1033        Arguments:
1034            scale : (float)
1035                apply a rescaling factor to the lengths.
1036            c : (color, int, str, list)
1037                color name, number, or list of [R,G,B] colors
1038            alpha : (float)
1039                opacity in range [0,1]
1040            lw : (int)
1041                line width in pixel units
1042            dotted : (bool)
1043                draw a dotted line
1044            res : (int)
1045                resolution, number of points along the line
1046                (only relevant if only 2 points are specified)
1047
1048        Examples:
1049            - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py)
1050
1051            ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png)
1052        """
1053
1054        if isinstance(start_pts, vtki.vtkPolyData):########
1055            super().__init__(start_pts, c, alpha)
1056            self.lw(lw).lighting("off")
1057            self.name = "Lines"
1058            return ########################################
1059
1060        if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line):
1061            # passing a list of Line, see tests/issues/issue_950.py
1062            polylns = vtki.new("AppendPolyData")
1063            for ln in start_pts:
1064                polylns.AddInputData(ln.dataset)
1065            polylns.Update()
1066
1067            super().__init__(polylns.GetOutput(), c, alpha)
1068            self.lw(lw).lighting("off")
1069            if dotted:
1070                self.properties.SetLineStipplePattern(0xF0F0)
1071                self.properties.SetLineStippleRepeatFactor(1)
1072            self.name = "Lines"
1073            return ########################################
1074
1075        if isinstance(start_pts, Points):
1076            start_pts = start_pts.vertices
1077        if isinstance(end_pts, Points):
1078            end_pts = end_pts.vertices
1079
1080        if end_pts is not None:
1081            start_pts = np.stack((start_pts, end_pts), axis=1)
1082
1083        polylns = vtki.new("AppendPolyData")
1084
1085        if not utils.is_ragged(start_pts):
1086
1087            for twopts in start_pts:
1088                line_source = vtki.new("LineSource")
1089                line_source.SetResolution(res)
1090                if len(twopts[0]) == 2:
1091                    line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0)
1092                else:
1093                    line_source.SetPoint1(twopts[0])
1094
1095                if scale == 1:
1096                    pt2 = twopts[1]
1097                else:
1098                    vers = (np.array(twopts[1]) - twopts[0]) * scale
1099                    pt2 = np.array(twopts[0]) + vers
1100
1101                if len(pt2) == 2:
1102                    line_source.SetPoint2(pt2[0], pt2[1], 0.0)
1103                else:
1104                    line_source.SetPoint2(pt2)
1105                polylns.AddInputConnection(line_source.GetOutputPort())
1106
1107        else:
1108
1109            polylns = vtki.new("AppendPolyData")
1110            for t in start_pts:
1111                t = utils.make3d(t)
1112                ppoints = vtki.vtkPoints()  # Generate the polyline
1113                ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32))
1114                lines = vtki.vtkCellArray()
1115                npt = len(t)
1116                lines.InsertNextCell(npt)
1117                for i in range(npt):
1118                    lines.InsertCellPoint(i)
1119                poly = vtki.vtkPolyData()
1120                poly.SetPoints(ppoints)
1121                poly.SetLines(lines)
1122                polylns.AddInputData(poly)
1123
1124        polylns.Update()
1125
1126        super().__init__(polylns.GetOutput(), c, alpha)
1127        self.lw(lw).lighting("off")
1128        if dotted:
1129            self.properties.SetLineStipplePattern(0xF0F0)
1130            self.properties.SetLineStippleRepeatFactor(1)
1131
1132        self.name = "Lines"
Arguments:
  • scale : (float) apply a rescaling factor to the lengths.
  • c : (color, int, str, list) color name, number, or list of [R,G,B] colors
  • alpha : (float) opacity in range [0,1]
  • lw : (int) line width in pixel units
  • dotted : (bool) draw a dotted line
  • res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
Examples:

class Spline(Line):
1135class Spline(Line):
1136    """
1137    Find the B-Spline curve through a set of points. This curve does not necessarily
1138    pass exactly through all the input points. Needs to import `scipy`.
1139    """
1140
1141    def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None:
1142        """
1143        Arguments:
1144            smooth : (float)
1145                smoothing factor.
1146                - 0 = interpolate points exactly [default].
1147                - 1 = average point positions.
1148            degree : (int)
1149                degree of the spline (between 1 and 5).
1150            easing : (str)
1151                control sensity of points along the spline.
1152                Available options are
1153                `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].`
1154                Can be used to create animations (move objects at varying speed).
1155                See e.g.: https://easings.net
1156            res : (int)
1157                number of points on the spline
1158
1159        See also: `CSpline` and `KSpline`.
1160
1161        Examples:
1162            - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py)
1163
1164                ![](https://vedo.embl.es/images/simulations/spline_ease.gif)
1165        """
1166        from scipy.interpolate import splprep, splev
1167
1168        if isinstance(points, Points):
1169            points = points.vertices
1170
1171        points = utils.make3d(points)
1172
1173        per = 0
1174        if closed:
1175            points = np.append(points, [points[0]], axis=0)
1176            per = 1
1177
1178        if res is None:
1179            res = len(points) * 10
1180
1181        points = np.array(points, dtype=float)
1182
1183        minx, miny, minz = np.min(points, axis=0)
1184        maxx, maxy, maxz = np.max(points, axis=0)
1185        maxb = max(maxx - minx, maxy - miny, maxz - minz)
1186        smooth *= maxb / 2  # must be in absolute units
1187
1188        x = np.linspace(0.0, 1.0, res)
1189        if easing:
1190            if easing == "InSine":
1191                x = 1.0 - np.cos((x * np.pi) / 2)
1192            elif easing == "OutSine":
1193                x = np.sin((x * np.pi) / 2)
1194            elif easing == "Sine":
1195                x = -(np.cos(np.pi * x) - 1) / 2
1196            elif easing == "InQuad":
1197                x = x * x
1198            elif easing == "OutQuad":
1199                x = 1.0 - (1 - x) * (1 - x)
1200            elif easing == "InCubic":
1201                x = x * x
1202            elif easing == "OutCubic":
1203                x = 1.0 - np.power(1 - x, 3)
1204            elif easing == "InQuart":
1205                x = x * x * x * x
1206            elif easing == "OutQuart":
1207                x = 1.0 - np.power(1 - x, 4)
1208            elif easing == "InCirc":
1209                x = 1.0 - np.sqrt(1 - np.power(x, 2))
1210            elif easing == "OutCirc":
1211                x = np.sqrt(1.0 - np.power(x - 1, 2))
1212            else:
1213                vedo.logger.error(f"unknown ease mode {easing}")
1214
1215        # find the knots
1216        tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per)
1217        # evaluate spLine, including interpolated points:
1218        xnew, ynew, znew = splev(x, tckp)
1219
1220        super().__init__(np.c_[xnew, ynew, znew], lw=2)
1221        self.name = "Spline"

Find the B-Spline curve through a set of points. This curve does not necessarily pass exactly through all the input points. Needs to import scipy.

Spline(points, smooth=0.0, degree=2, closed=False, res=None, easing='')
1141    def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None:
1142        """
1143        Arguments:
1144            smooth : (float)
1145                smoothing factor.
1146                - 0 = interpolate points exactly [default].
1147                - 1 = average point positions.
1148            degree : (int)
1149                degree of the spline (between 1 and 5).
1150            easing : (str)
1151                control sensity of points along the spline.
1152                Available options are
1153                `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].`
1154                Can be used to create animations (move objects at varying speed).
1155                See e.g.: https://easings.net
1156            res : (int)
1157                number of points on the spline
1158
1159        See also: `CSpline` and `KSpline`.
1160
1161        Examples:
1162            - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py)
1163
1164                ![](https://vedo.embl.es/images/simulations/spline_ease.gif)
1165        """
1166        from scipy.interpolate import splprep, splev
1167
1168        if isinstance(points, Points):
1169            points = points.vertices
1170
1171        points = utils.make3d(points)
1172
1173        per = 0
1174        if closed:
1175            points = np.append(points, [points[0]], axis=0)
1176            per = 1
1177
1178        if res is None:
1179            res = len(points) * 10
1180
1181        points = np.array(points, dtype=float)
1182
1183        minx, miny, minz = np.min(points, axis=0)
1184        maxx, maxy, maxz = np.max(points, axis=0)
1185        maxb = max(maxx - minx, maxy - miny, maxz - minz)
1186        smooth *= maxb / 2  # must be in absolute units
1187
1188        x = np.linspace(0.0, 1.0, res)
1189        if easing:
1190            if easing == "InSine":
1191                x = 1.0 - np.cos((x * np.pi) / 2)
1192            elif easing == "OutSine":
1193                x = np.sin((x * np.pi) / 2)
1194            elif easing == "Sine":
1195                x = -(np.cos(np.pi * x) - 1) / 2
1196            elif easing == "InQuad":
1197                x = x * x
1198            elif easing == "OutQuad":
1199                x = 1.0 - (1 - x) * (1 - x)
1200            elif easing == "InCubic":
1201                x = x * x
1202            elif easing == "OutCubic":
1203                x = 1.0 - np.power(1 - x, 3)
1204            elif easing == "InQuart":
1205                x = x * x * x * x
1206            elif easing == "OutQuart":
1207                x = 1.0 - np.power(1 - x, 4)
1208            elif easing == "InCirc":
1209                x = 1.0 - np.sqrt(1 - np.power(x, 2))
1210            elif easing == "OutCirc":
1211                x = np.sqrt(1.0 - np.power(x - 1, 2))
1212            else:
1213                vedo.logger.error(f"unknown ease mode {easing}")
1214
1215        # find the knots
1216        tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per)
1217        # evaluate spLine, including interpolated points:
1218        xnew, ynew, znew = splev(x, tckp)
1219
1220        super().__init__(np.c_[xnew, ynew, znew], lw=2)
1221        self.name = "Spline"
Arguments:
  • smooth : (float) smoothing factor.
    • 0 = interpolate points exactly [default].
    • 1 = average point positions.
  • degree : (int) degree of the spline (between 1 and 5).
  • easing : (str) control sensity of points along the spline. Available options are [InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc]. Can be used to create animations (move objects at varying speed). See e.g.: https://easings.net
  • res : (int) number of points on the spline

See also: CSpline and KSpline.

Examples:
class KSpline(Line):
1224class KSpline(Line):
1225    """
1226    Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline)
1227    which runs exactly through all the input points.
1228    """
1229
1230    def __init__(self, points, 
1231                 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None:
1232        """
1233        Arguments:
1234            continuity : (float)
1235                changes the sharpness in change between tangents
1236            tension : (float)
1237                changes the length of the tangent vector
1238            bias : (float)
1239                changes the direction of the tangent vector
1240            closed : (bool)
1241                join last to first point to produce a closed curve
1242            res : (int)
1243                approximate resolution of the output line.
1244                Default is 20 times the number of input points.
1245
1246        ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png)
1247
1248        Warning:
1249            This class is not necessarily generating the exact number of points
1250            as requested by `res`. Some points may be concident and removed.
1251
1252        See also: `Spline` and `CSpline`.
1253        """
1254        if isinstance(points, Points):
1255            points = points.vertices
1256
1257        if not res:
1258            res = len(points) * 20
1259
1260        points = utils.make3d(points).astype(float)
1261
1262        vtkKochanekSpline = vtki.get_class("KochanekSpline")
1263        xspline = vtkKochanekSpline()
1264        yspline = vtkKochanekSpline()
1265        zspline = vtkKochanekSpline()
1266        for s in [xspline, yspline, zspline]:
1267            if bias:
1268                s.SetDefaultBias(bias)
1269            if tension:
1270                s.SetDefaultTension(tension)
1271            if continuity:
1272                s.SetDefaultContinuity(continuity)
1273            s.SetClosed(closed)
1274
1275        lenp = len(points[0]) > 2
1276
1277        for i, p in enumerate(points):
1278            xspline.AddPoint(i, p[0])
1279            yspline.AddPoint(i, p[1])
1280            if lenp:
1281                zspline.AddPoint(i, p[2])
1282
1283        ln = []
1284        for pos in np.linspace(0, len(points), res):
1285            x = xspline.Evaluate(pos)
1286            y = yspline.Evaluate(pos)
1287            z = 0
1288            if lenp:
1289                z = zspline.Evaluate(pos)
1290            ln.append((x, y, z))
1291
1292        super().__init__(ln, lw=2)
1293        self.clean()
1294        self.lighting("off")
1295        self.name = "KSpline"
1296        self.base = np.array(points[0], dtype=float)
1297        self.top = np.array(points[-1], dtype=float)

Return a Kochanek spline which runs exactly through all the input points.

KSpline( points, continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None)
1230    def __init__(self, points, 
1231                 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None:
1232        """
1233        Arguments:
1234            continuity : (float)
1235                changes the sharpness in change between tangents
1236            tension : (float)
1237                changes the length of the tangent vector
1238            bias : (float)
1239                changes the direction of the tangent vector
1240            closed : (bool)
1241                join last to first point to produce a closed curve
1242            res : (int)
1243                approximate resolution of the output line.
1244                Default is 20 times the number of input points.
1245
1246        ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png)
1247
1248        Warning:
1249            This class is not necessarily generating the exact number of points
1250            as requested by `res`. Some points may be concident and removed.
1251
1252        See also: `Spline` and `CSpline`.
1253        """
1254        if isinstance(points, Points):
1255            points = points.vertices
1256
1257        if not res:
1258            res = len(points) * 20
1259
1260        points = utils.make3d(points).astype(float)
1261
1262        vtkKochanekSpline = vtki.get_class("KochanekSpline")
1263        xspline = vtkKochanekSpline()
1264        yspline = vtkKochanekSpline()
1265        zspline = vtkKochanekSpline()
1266        for s in [xspline, yspline, zspline]:
1267            if bias:
1268                s.SetDefaultBias(bias)
1269            if tension:
1270                s.SetDefaultTension(tension)
1271            if continuity:
1272                s.SetDefaultContinuity(continuity)
1273            s.SetClosed(closed)
1274
1275        lenp = len(points[0]) > 2
1276
1277        for i, p in enumerate(points):
1278            xspline.AddPoint(i, p[0])
1279            yspline.AddPoint(i, p[1])
1280            if lenp:
1281                zspline.AddPoint(i, p[2])
1282
1283        ln = []
1284        for pos in np.linspace(0, len(points), res):
1285            x = xspline.Evaluate(pos)
1286            y = yspline.Evaluate(pos)
1287            z = 0
1288            if lenp:
1289                z = zspline.Evaluate(pos)
1290            ln.append((x, y, z))
1291
1292        super().__init__(ln, lw=2)
1293        self.clean()
1294        self.lighting("off")
1295        self.name = "KSpline"
1296        self.base = np.array(points[0], dtype=float)
1297        self.top = np.array(points[-1], dtype=float)
Arguments:
  • continuity : (float) changes the sharpness in change between tangents
  • tension : (float) changes the length of the tangent vector
  • bias : (float) changes the direction of the tangent vector
  • closed : (bool) join last to first point to produce a closed curve
  • res : (int) approximate resolution of the output line. Default is 20 times the number of input points.

Warning:

This class is not necessarily generating the exact number of points as requested by res. Some points may be concident and removed.

See also: Spline and CSpline.

class CSpline(Line):
1300class CSpline(Line):
1301    """
1302    Return a Cardinal spline which runs exactly through all the input points.
1303    """
1304
1305    def __init__(self, points, closed=False, res=None) -> None:
1306        """
1307        Arguments:
1308            closed : (bool)
1309                join last to first point to produce a closed curve
1310            res : (int)
1311                approximate resolution of the output line.
1312                Default is 20 times the number of input points.
1313
1314        Warning:
1315            This class is not necessarily generating the exact number of points
1316            as requested by `res`. Some points may be concident and removed.
1317
1318        See also: `Spline` and `KSpline`.
1319        """
1320
1321        if isinstance(points, Points):
1322            points = points.vertices
1323
1324        if not res:
1325            res = len(points) * 20
1326
1327        points = utils.make3d(points).astype(float)
1328
1329        vtkCardinalSpline = vtki.get_class("CardinalSpline")
1330        xspline = vtkCardinalSpline()
1331        yspline = vtkCardinalSpline()
1332        zspline = vtkCardinalSpline()
1333        for s in [xspline, yspline, zspline]:
1334            s.SetClosed(closed)
1335
1336        lenp = len(points[0]) > 2
1337
1338        for i, p in enumerate(points):
1339            xspline.AddPoint(i, p[0])
1340            yspline.AddPoint(i, p[1])
1341            if lenp:
1342                zspline.AddPoint(i, p[2])
1343
1344        ln = []
1345        for pos in np.linspace(0, len(points), res):
1346            x = xspline.Evaluate(pos)
1347            y = yspline.Evaluate(pos)
1348            z = 0
1349            if lenp:
1350                z = zspline.Evaluate(pos)
1351            ln.append((x, y, z))
1352
1353        super().__init__(ln, lw=2)
1354        self.clean()
1355        self.lighting("off")
1356        self.name = "CSpline"
1357        self.base = points[0]
1358        self.top = points[-1]

Return a Cardinal spline which runs exactly through all the input points.

CSpline(points, closed=False, res=None)
1305    def __init__(self, points, closed=False, res=None) -> None:
1306        """
1307        Arguments:
1308            closed : (bool)
1309                join last to first point to produce a closed curve
1310            res : (int)
1311                approximate resolution of the output line.
1312                Default is 20 times the number of input points.
1313
1314        Warning:
1315            This class is not necessarily generating the exact number of points
1316            as requested by `res`. Some points may be concident and removed.
1317
1318        See also: `Spline` and `KSpline`.
1319        """
1320
1321        if isinstance(points, Points):
1322            points = points.vertices
1323
1324        if not res:
1325            res = len(points) * 20
1326
1327        points = utils.make3d(points).astype(float)
1328
1329        vtkCardinalSpline = vtki.get_class("CardinalSpline")
1330        xspline = vtkCardinalSpline()
1331        yspline = vtkCardinalSpline()
1332        zspline = vtkCardinalSpline()
1333        for s in [xspline, yspline, zspline]:
1334            s.SetClosed(closed)
1335
1336        lenp = len(points[0]) > 2
1337
1338        for i, p in enumerate(points):
1339            xspline.AddPoint(i, p[0])
1340            yspline.AddPoint(i, p[1])
1341            if lenp:
1342                zspline.AddPoint(i, p[2])
1343
1344        ln = []
1345        for pos in np.linspace(0, len(points), res):
1346            x = xspline.Evaluate(pos)
1347            y = yspline.Evaluate(pos)
1348            z = 0
1349            if lenp:
1350                z = zspline.Evaluate(pos)
1351            ln.append((x, y, z))
1352
1353        super().__init__(ln, lw=2)
1354        self.clean()
1355        self.lighting("off")
1356        self.name = "CSpline"
1357        self.base = points[0]
1358        self.top = points[-1]
Arguments:
  • closed : (bool) join last to first point to produce a closed curve
  • res : (int) approximate resolution of the output line. Default is 20 times the number of input points.
Warning:

This class is not necessarily generating the exact number of points as requested by res. Some points may be concident and removed.

See also: Spline and KSpline.

class Bezier(Line):
1361class Bezier(Line):
1362    """
1363    Generate the Bezier line that links the first to the last point.
1364    """
1365
1366    def __init__(self, points, res=None) -> None:
1367        """
1368        Example:
1369            ```python
1370            from vedo import *
1371            import numpy as np
1372            pts = np.random.randn(25,3)
1373            for i,p in enumerate(pts):
1374                p += [5*i, 15*sin(i/2), i*i*i/200]
1375            show(Points(pts), Bezier(pts), axes=1).close()
1376            ```
1377            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1378        """
1379        N = len(points)
1380        if res is None:
1381            res = 10 * N
1382        t = np.linspace(0, 1, num=res)
1383        bcurve = np.zeros((res, len(points[0])))
1384
1385        def binom(n, k):
1386            b = 1
1387            for t in range(1, min(k, n - k) + 1):
1388                b *= n / t
1389                n -= 1
1390            return b
1391
1392        def bernstein(n, k):
1393            coeff = binom(n, k)
1394
1395            def _bpoly(x):
1396                return coeff * x ** k * (1 - x) ** (n - k)
1397
1398            return _bpoly
1399
1400        for ii in range(N):
1401            b = bernstein(N - 1, ii)(t)
1402            bcurve += np.outer(b, points[ii])
1403        super().__init__(bcurve, lw=2)
1404        self.name = "BezierLine"

Generate the Bezier line that links the first to the last point.

Bezier(points, res=None)
1366    def __init__(self, points, res=None) -> None:
1367        """
1368        Example:
1369            ```python
1370            from vedo import *
1371            import numpy as np
1372            pts = np.random.randn(25,3)
1373            for i,p in enumerate(pts):
1374                p += [5*i, 15*sin(i/2), i*i*i/200]
1375            show(Points(pts), Bezier(pts), axes=1).close()
1376            ```
1377            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1378        """
1379        N = len(points)
1380        if res is None:
1381            res = 10 * N
1382        t = np.linspace(0, 1, num=res)
1383        bcurve = np.zeros((res, len(points[0])))
1384
1385        def binom(n, k):
1386            b = 1
1387            for t in range(1, min(k, n - k) + 1):
1388                b *= n / t
1389                n -= 1
1390            return b
1391
1392        def bernstein(n, k):
1393            coeff = binom(n, k)
1394
1395            def _bpoly(x):
1396                return coeff * x ** k * (1 - x) ** (n - k)
1397
1398            return _bpoly
1399
1400        for ii in range(N):
1401            b = bernstein(N - 1, ii)(t)
1402            bcurve += np.outer(b, points[ii])
1403        super().__init__(bcurve, lw=2)
1404        self.name = "BezierLine"
Example:
from vedo import *
import numpy as np
pts = np.random.randn(25,3)
for i,p in enumerate(pts):
    p += [5*i, 15*sin(i/2), i*i*i/200]
show(Points(pts), Bezier(pts), axes=1).close()

class Brace(vedo.mesh.Mesh):
3661class Brace(Mesh):
3662    """
3663    Create a brace (bracket) shape.
3664    """
3665
3666    def __init__(
3667        self,
3668        q1,
3669        q2,
3670        style="}",
3671        padding1=0.0,
3672        font="Theemim",
3673        comment="",
3674        justify=None,
3675        angle=0.0,
3676        padding2=0.2,
3677        s=1.0,
3678        italic=0,
3679        c="k1",
3680        alpha=1.0,
3681    ) -> None:
3682        """
3683        Create a brace (bracket) shape which spans from point q1 to point q2.
3684
3685        Arguments:
3686            q1 : (list)
3687                point 1.
3688            q2 : (list)
3689                point 2.
3690            style : (str)
3691                style of the bracket, eg. `{}, [], (), <>`.
3692            padding1 : (float)
3693                padding space in percent form the input points.
3694            font : (str)
3695                font type
3696            comment : (str)
3697                additional text to appear next to the brace symbol.
3698            justify : (str)
3699                specify the anchor point to justify text comment, e.g. "top-left".
3700            italic : float
3701                italicness of the text comment (can be a positive or negative number)
3702            angle : (float)
3703                rotation angle of text. Use `None` to keep it horizontal.
3704            padding2 : (float)
3705                padding space in percent form brace to text comment.
3706            s : (float)
3707                scale factor for the comment
3708
3709        Examples:
3710            - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py)
3711
3712                ![](https://vedo.embl.es/images/pyplot/scatter3.png)
3713        """
3714        if isinstance(q1, vtki.vtkActor):
3715            q1 = q1.GetPosition()
3716        if isinstance(q2, vtki.vtkActor):
3717            q2 = q2.GetPosition()
3718        if len(q1) == 2:
3719            q1 = [q1[0], q1[1], 0.0]
3720        if len(q2) == 2:
3721            q2 = [q2[0], q2[1], 0.0]
3722        q1 = np.array(q1, dtype=float)
3723        q2 = np.array(q2, dtype=float)
3724        mq = (q1 + q2) / 2
3725        q1 = q1 - mq
3726        q2 = q2 - mq
3727        d = np.linalg.norm(q2 - q1)
3728        q2[2] = q1[2]
3729
3730        if style not in "{}[]()<>|I":
3731            vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I")
3732            style = "}"
3733
3734        flip = False
3735        if style in ["{", "[", "(", "<"]:
3736            flip = True
3737            i = ["{", "[", "(", "<"].index(style)
3738            style = ["}", "]", ")", ">"][i]
3739
3740        br = Text3D(style, font="Theemim", justify="center-left")
3741        br.scale([0.4, 1, 1])
3742
3743        angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90
3744        if flip:
3745            angler += 180
3746
3747        _, x1, y0, y1, _, _ = br.bounds()
3748        if comment:
3749            just = "center-top"
3750            if angle is None:
3751                angle = -angler + 90
3752                if not flip:
3753                    angle += 180
3754
3755            if flip:
3756                angle += 180
3757                just = "center-bottom"
3758            if justify is not None:
3759                just = justify
3760            cmt = Text3D(comment, font=font, justify=just, italic=italic)
3761            cx0, cx1 = cmt.xbounds()
3762            cmt.rotate_z(90 + angle)
3763            cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5)
3764            cmt.shift(x1 * (1 + padding2), 0, 0)
3765            poly = merge(br, cmt).dataset
3766
3767        else:
3768            poly = br.dataset
3769
3770        tr = vtki.vtkTransform()
3771        tr.Translate(mq)
3772        tr.RotateZ(angler)
3773        tr.Translate(padding1 * d, 0, 0)
3774        pscale = 1
3775        tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1)
3776
3777        tf = vtki.new("TransformPolyDataFilter")
3778        tf.SetInputData(poly)
3779        tf.SetTransform(tr)
3780        tf.Update()
3781        poly = tf.GetOutput()
3782
3783        super().__init__(poly, c, alpha)
3784
3785        self.base = q1
3786        self.top  = q2
3787        self.name = "Brace"

Create a brace (bracket) shape.

Brace( q1, q2, style='}', padding1=0.0, font='Theemim', comment='', justify=None, angle=0.0, padding2=0.2, s=1.0, italic=0, c='k1', alpha=1.0)
3666    def __init__(
3667        self,
3668        q1,
3669        q2,
3670        style="}",
3671        padding1=0.0,
3672        font="Theemim",
3673        comment="",
3674        justify=None,
3675        angle=0.0,
3676        padding2=0.2,
3677        s=1.0,
3678        italic=0,
3679        c="k1",
3680        alpha=1.0,
3681    ) -> None:
3682        """
3683        Create a brace (bracket) shape which spans from point q1 to point q2.
3684
3685        Arguments:
3686            q1 : (list)
3687                point 1.
3688            q2 : (list)
3689                point 2.
3690            style : (str)
3691                style of the bracket, eg. `{}, [], (), <>`.
3692            padding1 : (float)
3693                padding space in percent form the input points.
3694            font : (str)
3695                font type
3696            comment : (str)
3697                additional text to appear next to the brace symbol.
3698            justify : (str)
3699                specify the anchor point to justify text comment, e.g. "top-left".
3700            italic : float
3701                italicness of the text comment (can be a positive or negative number)
3702            angle : (float)
3703                rotation angle of text. Use `None` to keep it horizontal.
3704            padding2 : (float)
3705                padding space in percent form brace to text comment.
3706            s : (float)
3707                scale factor for the comment
3708
3709        Examples:
3710            - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py)
3711
3712                ![](https://vedo.embl.es/images/pyplot/scatter3.png)
3713        """
3714        if isinstance(q1, vtki.vtkActor):
3715            q1 = q1.GetPosition()
3716        if isinstance(q2, vtki.vtkActor):
3717            q2 = q2.GetPosition()
3718        if len(q1) == 2:
3719            q1 = [q1[0], q1[1], 0.0]
3720        if len(q2) == 2:
3721            q2 = [q2[0], q2[1], 0.0]
3722        q1 = np.array(q1, dtype=float)
3723        q2 = np.array(q2, dtype=float)
3724        mq = (q1 + q2) / 2
3725        q1 = q1 - mq
3726        q2 = q2 - mq
3727        d = np.linalg.norm(q2 - q1)
3728        q2[2] = q1[2]
3729
3730        if style not in "{}[]()<>|I":
3731            vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I")
3732            style = "}"
3733
3734        flip = False
3735        if style in ["{", "[", "(", "<"]:
3736            flip = True
3737            i = ["{", "[", "(", "<"].index(style)
3738            style = ["}", "]", ")", ">"][i]
3739
3740        br = Text3D(style, font="Theemim", justify="center-left")
3741        br.scale([0.4, 1, 1])
3742
3743        angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90
3744        if flip:
3745            angler += 180
3746
3747        _, x1, y0, y1, _, _ = br.bounds()
3748        if comment:
3749            just = "center-top"
3750            if angle is None:
3751                angle = -angler + 90
3752                if not flip:
3753                    angle += 180
3754
3755            if flip:
3756                angle += 180
3757                just = "center-bottom"
3758            if justify is not None:
3759                just = justify
3760            cmt = Text3D(comment, font=font, justify=just, italic=italic)
3761            cx0, cx1 = cmt.xbounds()
3762            cmt.rotate_z(90 + angle)
3763            cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5)
3764            cmt.shift(x1 * (1 + padding2), 0, 0)
3765            poly = merge(br, cmt).dataset
3766
3767        else:
3768            poly = br.dataset
3769
3770        tr = vtki.vtkTransform()
3771        tr.Translate(mq)
3772        tr.RotateZ(angler)
3773        tr.Translate(padding1 * d, 0, 0)
3774        pscale = 1
3775        tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1)
3776
3777        tf = vtki.new("TransformPolyDataFilter")
3778        tf.SetInputData(poly)
3779        tf.SetTransform(tr)
3780        tf.Update()
3781        poly = tf.GetOutput()
3782
3783        super().__init__(poly, c, alpha)
3784
3785        self.base = q1
3786        self.top  = q2
3787        self.name = "Brace"

Create a brace (bracket) shape which spans from point q1 to point q2.

Arguments:
  • q1 : (list) point 1.
  • q2 : (list) point 2.
  • style : (str) style of the bracket, eg. {}, [], (), <>.
  • padding1 : (float) padding space in percent form the input points.
  • font : (str) font type
  • comment : (str) additional text to appear next to the brace symbol.
  • justify : (str) specify the anchor point to justify text comment, e.g. "top-left".
  • italic : float italicness of the text comment (can be a positive or negative number)
  • angle : (float) rotation angle of text. Use None to keep it horizontal.
  • padding2 : (float) padding space in percent form brace to text comment.
  • s : (float) scale factor for the comment
Examples:
class NormalLines(vedo.mesh.Mesh):
1407class NormalLines(Mesh):
1408    """
1409    Build an `Glyph` to show the normals at cell centers or at mesh vertices.
1410
1411    Arguments:
1412        ratio : (int)
1413            show 1 normal every `ratio` cells.
1414        on : (str)
1415            either "cells" or "points".
1416        scale : (float)
1417            scale factor to control size.
1418    """
1419
1420    def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None:
1421
1422        poly = msh.clone().dataset
1423
1424        if "cell" in on:
1425            centers = vtki.new("CellCenters")
1426            centers.SetInputData(poly)
1427            centers.Update()
1428            poly = centers.GetOutput()
1429
1430        mask_pts = vtki.new("MaskPoints")
1431        mask_pts.SetInputData(poly)
1432        mask_pts.SetOnRatio(ratio)
1433        mask_pts.RandomModeOff()
1434        mask_pts.Update()
1435
1436        ln = vtki.new("LineSource")
1437        ln.SetPoint1(0, 0, 0)
1438        ln.SetPoint2(1, 0, 0)
1439        ln.Update()
1440        glyph = vtki.vtkGlyph3D()
1441        glyph.SetSourceData(ln.GetOutput())
1442        glyph.SetInputData(mask_pts.GetOutput())
1443        glyph.SetVectorModeToUseNormal()
1444
1445        b = poly.GetBounds()
1446        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1447        glyph.SetScaleFactor(f)
1448        glyph.OrientOn()
1449        glyph.Update()
1450
1451        super().__init__(glyph.GetOutput())
1452
1453        self.actor.PickableOff()
1454        prop = vtki.vtkProperty()
1455        prop.DeepCopy(msh.properties)
1456        self.actor.SetProperty(prop)
1457        self.properties = prop
1458        self.properties.LightingOff()
1459        self.mapper.ScalarVisibilityOff()
1460        self.name = "NormalLines"

Build an Glyph to show the normals at cell centers or at mesh vertices.

Arguments:
  • ratio : (int) show 1 normal every ratio cells.
  • on : (str) either "cells" or "points".
  • scale : (float) scale factor to control size.
NormalLines(msh, ratio=1, on='cells', scale=1.0)
1420    def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None:
1421
1422        poly = msh.clone().dataset
1423
1424        if "cell" in on:
1425            centers = vtki.new("CellCenters")
1426            centers.SetInputData(poly)
1427            centers.Update()
1428            poly = centers.GetOutput()
1429
1430        mask_pts = vtki.new("MaskPoints")
1431        mask_pts.SetInputData(poly)
1432        mask_pts.SetOnRatio(ratio)
1433        mask_pts.RandomModeOff()
1434        mask_pts.Update()
1435
1436        ln = vtki.new("LineSource")
1437        ln.SetPoint1(0, 0, 0)
1438        ln.SetPoint2(1, 0, 0)
1439        ln.Update()
1440        glyph = vtki.vtkGlyph3D()
1441        glyph.SetSourceData(ln.GetOutput())
1442        glyph.SetInputData(mask_pts.GetOutput())
1443        glyph.SetVectorModeToUseNormal()
1444
1445        b = poly.GetBounds()
1446        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1447        glyph.SetScaleFactor(f)
1448        glyph.OrientOn()
1449        glyph.Update()
1450
1451        super().__init__(glyph.GetOutput())
1452
1453        self.actor.PickableOff()
1454        prop = vtki.vtkProperty()
1455        prop.DeepCopy(msh.properties)
1456        self.actor.SetProperty(prop)
1457        self.properties = prop
1458        self.properties.LightingOff()
1459        self.mapper.ScalarVisibilityOff()
1460        self.name = "NormalLines"

Initialize a Mesh object.

Arguments:
  • inputobj : (str, vtkPolyData, vtkActor, vedo.Mesh) If inputobj is None an empty mesh is created. If inputobj is a str then it is interpreted as the name of a file to load as mesh. If inputobj is an vtkPolyData or vtkActor or vedo.Mesh then a shallow copy of it is created. If inputobj is a vedo.Mesh then a shallow copy of it is created.
Examples:

class Ribbon(vedo.mesh.Mesh):
1685class Ribbon(Mesh):
1686    """
1687    Connect two lines to generate the surface inbetween.
1688    Set the mode by which to create the ruled surface.
1689
1690    It also works with a single line in input. In this case the ribbon
1691    is formed by following the local plane of the line in space.
1692    """
1693
1694    def __init__(
1695        self,
1696        line1,
1697        line2=None,
1698        mode=0,
1699        closed=False,
1700        width=None,
1701        res=(200, 5),
1702        c="indigo3",
1703        alpha=1.0,
1704    ) -> None:
1705        """
1706        Arguments:
1707            mode : (int)
1708                If mode=0, resample evenly the input lines (based on length)
1709                and generates triangle strips.
1710
1711                If mode=1, use the existing points and walks around the
1712                polyline using existing points.
1713
1714            closed : (bool)
1715                if True, join the last point with the first to form a closed surface
1716
1717            res : (list)
1718                ribbon resolutions along the line and perpendicularly to it.
1719
1720        Examples:
1721            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1722
1723                ![](https://vedo.embl.es/images/basic/ribbon.png)
1724        """
1725
1726        if isinstance(line1, Points):
1727            line1 = line1.vertices
1728
1729        if isinstance(line2, Points):
1730            line2 = line2.vertices
1731
1732        elif line2 is None:
1733            #############################################
1734            ribbon_filter = vtki.new("RibbonFilter")
1735            aline = Line(line1)
1736            ribbon_filter.SetInputData(aline.dataset)
1737            if width is None:
1738                width = aline.diagonal_size() / 20.0
1739            ribbon_filter.SetWidth(width)
1740            ribbon_filter.Update()
1741            # convert triangle strips to polygons
1742            tris = vtki.new("TriangleFilter")
1743            tris.SetInputData(ribbon_filter.GetOutput())
1744            tris.Update()
1745
1746            super().__init__(tris.GetOutput(), c, alpha)
1747            self.name = "Ribbon"
1748            ##############################################
1749            return  ######################################
1750            ##############################################
1751
1752        line1 = np.asarray(line1)
1753        line2 = np.asarray(line2)
1754
1755        if closed:
1756            line1 = line1.tolist()
1757            line1 += [line1[0]]
1758            line2 = line2.tolist()
1759            line2 += [line2[0]]
1760            line1 = np.array(line1)
1761            line2 = np.array(line2)
1762
1763        if len(line1[0]) == 2:
1764            line1 = np.c_[line1, np.zeros(len(line1))]
1765        if len(line2[0]) == 2:
1766            line2 = np.c_[line2, np.zeros(len(line2))]
1767
1768        ppoints1 = vtki.vtkPoints()  # Generate the polyline1
1769        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1770        lines1 = vtki.vtkCellArray()
1771        lines1.InsertNextCell(len(line1))
1772        for i in range(len(line1)):
1773            lines1.InsertCellPoint(i)
1774        poly1 = vtki.vtkPolyData()
1775        poly1.SetPoints(ppoints1)
1776        poly1.SetLines(lines1)
1777
1778        ppoints2 = vtki.vtkPoints()  # Generate the polyline2
1779        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1780        lines2 = vtki.vtkCellArray()
1781        lines2.InsertNextCell(len(line2))
1782        for i in range(len(line2)):
1783            lines2.InsertCellPoint(i)
1784        poly2 = vtki.vtkPolyData()
1785        poly2.SetPoints(ppoints2)
1786        poly2.SetLines(lines2)
1787
1788        # build the lines
1789        lines1 = vtki.vtkCellArray()
1790        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1791        for i in range(poly1.GetNumberOfPoints()):
1792            lines1.InsertCellPoint(i)
1793
1794        polygon1 = vtki.vtkPolyData()
1795        polygon1.SetPoints(ppoints1)
1796        polygon1.SetLines(lines1)
1797
1798        lines2 = vtki.vtkCellArray()
1799        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1800        for i in range(poly2.GetNumberOfPoints()):
1801            lines2.InsertCellPoint(i)
1802
1803        polygon2 = vtki.vtkPolyData()
1804        polygon2.SetPoints(ppoints2)
1805        polygon2.SetLines(lines2)
1806
1807        merged_pd = vtki.new("AppendPolyData")
1808        merged_pd.AddInputData(polygon1)
1809        merged_pd.AddInputData(polygon2)
1810        merged_pd.Update()
1811
1812        rsf = vtki.new("RuledSurfaceFilter")
1813        rsf.CloseSurfaceOff()
1814        rsf.SetRuledMode(mode)
1815        rsf.SetResolution(res[0], res[1])
1816        rsf.SetInputData(merged_pd.GetOutput())
1817        rsf.Update()
1818        # convert triangle strips to polygons
1819        tris = vtki.new("TriangleFilter")
1820        tris.SetInputData(rsf.GetOutput())
1821        tris.Update()
1822        out = tris.GetOutput()
1823
1824        super().__init__(out, c, alpha)
1825
1826        self.name = "Ribbon"

Connect two lines to generate the surface inbetween. Set the mode by which to create the ruled surface.

It also works with a single line in input. In this case the ribbon is formed by following the local plane of the line in space.

Ribbon( line1, line2=None, mode=0, closed=False, width=None, res=(200, 5), c='indigo3', alpha=1.0)
1694    def __init__(
1695        self,
1696        line1,
1697        line2=None,
1698        mode=0,
1699        closed=False,
1700        width=None,
1701        res=(200, 5),
1702        c="indigo3",
1703        alpha=1.0,
1704    ) -> None:
1705        """
1706        Arguments:
1707            mode : (int)
1708                If mode=0, resample evenly the input lines (based on length)
1709                and generates triangle strips.
1710
1711                If mode=1, use the existing points and walks around the
1712                polyline using existing points.
1713
1714            closed : (bool)
1715                if True, join the last point with the first to form a closed surface
1716
1717            res : (list)
1718                ribbon resolutions along the line and perpendicularly to it.
1719
1720        Examples:
1721            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1722
1723                ![](https://vedo.embl.es/images/basic/ribbon.png)
1724        """
1725
1726        if isinstance(line1, Points):
1727            line1 = line1.vertices
1728
1729        if isinstance(line2, Points):
1730            line2 = line2.vertices
1731
1732        elif line2 is None:
1733            #############################################
1734            ribbon_filter = vtki.new("RibbonFilter")
1735            aline = Line(line1)
1736            ribbon_filter.SetInputData(aline.dataset)
1737            if width is None:
1738                width = aline.diagonal_size() / 20.0
1739            ribbon_filter.SetWidth(width)
1740            ribbon_filter.Update()
1741            # convert triangle strips to polygons
1742            tris = vtki.new("TriangleFilter")
1743            tris.SetInputData(ribbon_filter.GetOutput())
1744            tris.Update()
1745
1746            super().__init__(tris.GetOutput(), c, alpha)
1747            self.name = "Ribbon"
1748            ##############################################
1749            return  ######################################
1750            ##############################################
1751
1752        line1 = np.asarray(line1)
1753        line2 = np.asarray(line2)
1754
1755        if closed:
1756            line1 = line1.tolist()
1757            line1 += [line1[0]]
1758            line2 = line2.tolist()
1759            line2 += [line2[0]]
1760            line1 = np.array(line1)
1761            line2 = np.array(line2)
1762
1763        if len(line1[0]) == 2:
1764            line1 = np.c_[line1, np.zeros(len(line1))]
1765        if len(line2[0]) == 2:
1766            line2 = np.c_[line2, np.zeros(len(line2))]
1767
1768        ppoints1 = vtki.vtkPoints()  # Generate the polyline1
1769        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1770        lines1 = vtki.vtkCellArray()
1771        lines1.InsertNextCell(len(line1))
1772        for i in range(len(line1)):
1773            lines1.InsertCellPoint(i)
1774        poly1 = vtki.vtkPolyData()
1775        poly1.SetPoints(ppoints1)
1776        poly1.SetLines(lines1)
1777
1778        ppoints2 = vtki.vtkPoints()  # Generate the polyline2
1779        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1780        lines2 = vtki.vtkCellArray()
1781        lines2.InsertNextCell(len(line2))
1782        for i in range(len(line2)):
1783            lines2.InsertCellPoint(i)
1784        poly2 = vtki.vtkPolyData()
1785        poly2.SetPoints(ppoints2)
1786        poly2.SetLines(lines2)
1787
1788        # build the lines
1789        lines1 = vtki.vtkCellArray()
1790        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1791        for i in range(poly1.GetNumberOfPoints()):
1792            lines1.InsertCellPoint(i)
1793
1794        polygon1 = vtki.vtkPolyData()
1795        polygon1.SetPoints(ppoints1)
1796        polygon1.SetLines(lines1)
1797
1798        lines2 = vtki.vtkCellArray()
1799        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1800        for i in range(poly2.GetNumberOfPoints()):
1801            lines2.InsertCellPoint(i)
1802
1803        polygon2 = vtki.vtkPolyData()
1804        polygon2.SetPoints(ppoints2)
1805        polygon2.SetLines(lines2)
1806
1807        merged_pd = vtki.new("AppendPolyData")
1808        merged_pd.AddInputData(polygon1)
1809        merged_pd.AddInputData(polygon2)
1810        merged_pd.Update()
1811
1812        rsf = vtki.new("RuledSurfaceFilter")
1813        rsf.CloseSurfaceOff()
1814        rsf.SetRuledMode(mode)
1815        rsf.SetResolution(res[0], res[1])
1816        rsf.SetInputData(merged_pd.GetOutput())
1817        rsf.Update()
1818        # convert triangle strips to polygons
1819        tris = vtki.new("TriangleFilter")
1820        tris.SetInputData(rsf.GetOutput())
1821        tris.Update()
1822        out = tris.GetOutput()
1823
1824        super().__init__(out, c, alpha)
1825
1826        self.name = "Ribbon"
Arguments:
  • mode : (int) If mode=0, resample evenly the input lines (based on length) and generates triangle strips.

    If mode=1, use the existing points and walks around the polyline using existing points.

  • closed : (bool) if True, join the last point with the first to form a closed surface
  • res : (list) ribbon resolutions along the line and perpendicularly to it.
Examples:
class Arrow(vedo.mesh.Mesh):
1829class Arrow(Mesh):
1830    """
1831    Build a 3D arrow from `start_pt` to `end_pt` of section size `s`,
1832    expressed as the fraction of the window size.
1833    """
1834
1835    def __init__(
1836        self,
1837        start_pt=(0, 0, 0),
1838        end_pt=(1, 0, 0),
1839        s=None,
1840        shaft_radius=None,
1841        head_radius=None,
1842        head_length=None,
1843        res=12,
1844        c="r4",
1845        alpha=1.0,
1846    ) -> None:
1847        """
1848        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1849        from white to red.
1850
1851        .. note:: If `s=None` the arrow is scaled proportionally to its length
1852
1853        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1854        """
1855        # in case user is passing meshs
1856        if isinstance(start_pt, vtki.vtkActor):
1857            start_pt = start_pt.GetPosition()
1858        if isinstance(end_pt, vtki.vtkActor):
1859            end_pt = end_pt.GetPosition()
1860
1861        axis = np.asarray(end_pt) - np.asarray(start_pt)
1862        length = float(np.linalg.norm(axis))
1863        if length:
1864            axis = axis / length
1865        if len(axis) < 3:  # its 2d
1866            theta = np.pi / 2
1867            start_pt = [start_pt[0], start_pt[1], 0.0]
1868            end_pt = [end_pt[0], end_pt[1], 0.0]
1869        else:
1870            theta = np.arccos(axis[2])
1871        phi = np.arctan2(axis[1], axis[0])
1872        self.source = vtki.new("ArrowSource")
1873        self.source.SetShaftResolution(res)
1874        self.source.SetTipResolution(res)
1875
1876        if s:
1877            sz = 0.02
1878            self.source.SetTipRadius(sz)
1879            self.source.SetShaftRadius(sz / 1.75)
1880            self.source.SetTipLength(sz * 15)
1881
1882        if head_length:
1883            self.source.SetTipLength(head_length)
1884        if head_radius:
1885            self.source.SetTipRadius(head_radius)
1886        if shaft_radius:
1887            self.source.SetShaftRadius(shaft_radius)
1888
1889        self.source.Update()
1890
1891        t = vtki.vtkTransform()
1892        t.Translate(start_pt)
1893        t.RotateZ(np.rad2deg(phi))
1894        t.RotateY(np.rad2deg(theta))
1895        t.RotateY(-90)  # put it along Z
1896        if s:
1897            sz = 800 * s
1898            t.Scale(length, sz, sz)
1899        else:
1900            t.Scale(length, length, length)
1901
1902        tf = vtki.new("TransformPolyDataFilter")
1903        tf.SetInputData(self.source.GetOutput())
1904        tf.SetTransform(t)
1905        tf.Update()
1906
1907        super().__init__(tf.GetOutput(), c, alpha)
1908
1909        self.transform = LinearTransform().translate(start_pt)
1910        # self.pos(start_pt)
1911
1912        self.phong().lighting("plastic")
1913        self.actor.PickableOff()
1914        self.actor.DragableOff()
1915        self.base = np.array(start_pt, dtype=float)  # used by pyplot
1916        self.top  = np.array(end_pt,   dtype=float)  # used by pyplot
1917        self.top_index = None
1918        self.fill = True                    # used by pyplot.__iadd__()
1919        self.s = s if s is not None else 1  # used by pyplot.__iadd__()
1920        self.name = "Arrow"

Build a 3D arrow from start_pt to end_pt of section size s, expressed as the fraction of the window size.

Arrow( start_pt=(0, 0, 0), end_pt=(1, 0, 0), s=None, shaft_radius=None, head_radius=None, head_length=None, res=12, c='r4', alpha=1.0)
1835    def __init__(
1836        self,
1837        start_pt=(0, 0, 0),
1838        end_pt=(1, 0, 0),
1839        s=None,
1840        shaft_radius=None,
1841        head_radius=None,
1842        head_length=None,
1843        res=12,
1844        c="r4",
1845        alpha=1.0,
1846    ) -> None:
1847        """
1848        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1849        from white to red.
1850
1851        .. note:: If `s=None` the arrow is scaled proportionally to its length
1852
1853        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1854        """
1855        # in case user is passing meshs
1856        if isinstance(start_pt, vtki.vtkActor):
1857            start_pt = start_pt.GetPosition()
1858        if isinstance(end_pt, vtki.vtkActor):
1859            end_pt = end_pt.GetPosition()
1860
1861        axis = np.asarray(end_pt) - np.asarray(start_pt)
1862        length = float(np.linalg.norm(axis))
1863        if length:
1864            axis = axis / length
1865        if len(axis) < 3:  # its 2d
1866            theta = np.pi / 2
1867            start_pt = [start_pt[0], start_pt[1], 0.0]
1868            end_pt = [end_pt[0], end_pt[1], 0.0]
1869        else:
1870            theta = np.arccos(axis[2])
1871        phi = np.arctan2(axis[1], axis[0])
1872        self.source = vtki.new("ArrowSource")
1873        self.source.SetShaftResolution(res)
1874        self.source.SetTipResolution(res)
1875
1876        if s:
1877            sz = 0.02
1878            self.source.SetTipRadius(sz)
1879            self.source.SetShaftRadius(sz / 1.75)
1880            self.source.SetTipLength(sz * 15)
1881
1882        if head_length:
1883            self.source.SetTipLength(head_length)
1884        if head_radius:
1885            self.source.SetTipRadius(head_radius)
1886        if shaft_radius:
1887            self.source.SetShaftRadius(shaft_radius)
1888
1889        self.source.Update()
1890
1891        t = vtki.vtkTransform()
1892        t.Translate(start_pt)
1893        t.RotateZ(np.rad2deg(phi))
1894        t.RotateY(np.rad2deg(theta))
1895        t.RotateY(-90)  # put it along Z
1896        if s:
1897            sz = 800 * s
1898            t.Scale(length, sz, sz)
1899        else:
1900            t.Scale(length, length, length)
1901
1902        tf = vtki.new("TransformPolyDataFilter")
1903        tf.SetInputData(self.source.GetOutput())
1904        tf.SetTransform(t)
1905        tf.Update()
1906
1907        super().__init__(tf.GetOutput(), c, alpha)
1908
1909        self.transform = LinearTransform().translate(start_pt)
1910        # self.pos(start_pt)
1911
1912        self.phong().lighting("plastic")
1913        self.actor.PickableOff()
1914        self.actor.DragableOff()
1915        self.base = np.array(start_pt, dtype=float)  # used by pyplot
1916        self.top  = np.array(end_pt,   dtype=float)  # used by pyplot
1917        self.top_index = None
1918        self.fill = True                    # used by pyplot.__iadd__()
1919        self.s = s if s is not None else 1  # used by pyplot.__iadd__()
1920        self.name = "Arrow"

If c is a float less than 1, the arrow is rendered as a in a color scale from white to red.

If s=None the arrow is scaled proportionally to its length

class Arrows(Glyph):
1923class Arrows(Glyph):
1924    """
1925    Build arrows between two lists of points.
1926    """
1927
1928    def __init__(
1929        self,
1930        start_pts,
1931        end_pts=None,
1932        s=None,
1933        shaft_radius=None,
1934        head_radius=None,
1935        head_length=None,
1936        thickness=1.0,
1937        res=6,
1938        c='k3',
1939        alpha=1.0,
1940    ) -> None:
1941        """
1942        Build arrows between two lists of points `start_pts` and `end_pts`.
1943         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1944
1945        Color can be specified as a colormap which maps the size of the arrows.
1946
1947        Arguments:
1948            s : (float)
1949                fix aspect-ratio of the arrow and scale its cross section
1950            c : (color)
1951                color or color map name
1952            alpha : (float)
1953                set object opacity
1954            res : (int)
1955                set arrow resolution
1956
1957        Examples:
1958            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
1959
1960            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
1961        """
1962        if isinstance(start_pts, Points):
1963            start_pts = start_pts.vertices
1964        if isinstance(end_pts, Points):
1965            end_pts = end_pts.vertices
1966
1967        start_pts = np.asarray(start_pts)
1968        if end_pts is None:
1969            strt = start_pts[:, 0]
1970            end_pts = start_pts[:, 1]
1971            start_pts = strt
1972        else:
1973            end_pts = np.asarray(end_pts)
1974
1975        start_pts = utils.make3d(start_pts)
1976        end_pts = utils.make3d(end_pts)
1977
1978        arr = vtki.new("ArrowSource")
1979        arr.SetShaftResolution(res)
1980        arr.SetTipResolution(res)
1981
1982        if s:
1983            sz = 0.02 * s
1984            arr.SetTipRadius(sz * 2)
1985            arr.SetShaftRadius(sz * thickness)
1986            arr.SetTipLength(sz * 10)
1987
1988        if head_radius:
1989            arr.SetTipRadius(head_radius)
1990        if shaft_radius:
1991            arr.SetShaftRadius(shaft_radius)
1992        if head_length:
1993            arr.SetTipLength(head_length)
1994
1995        arr.Update()
1996        out = arr.GetOutput()
1997
1998        orients = end_pts - start_pts
1999
2000        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
2001
2002        super().__init__(
2003            start_pts,
2004            out,
2005            orientation_array=orients,
2006            scale_by_vector_size=True,
2007            color_by_vector_size=color_by_vector_size,
2008            c=c,
2009            alpha=alpha,
2010        )
2011        self.lighting("off")
2012        if color_by_vector_size:
2013            vals = np.linalg.norm(orients, axis=1)
2014            self.mapper.SetScalarRange(vals.min(), vals.max())
2015        else:
2016            self.c(c)
2017        self.name = "Arrows"

Build arrows between two lists of points.

Arrows( start_pts, end_pts=None, s=None, shaft_radius=None, head_radius=None, head_length=None, thickness=1.0, res=6, c='k3', alpha=1.0)
1928    def __init__(
1929        self,
1930        start_pts,
1931        end_pts=None,
1932        s=None,
1933        shaft_radius=None,
1934        head_radius=None,
1935        head_length=None,
1936        thickness=1.0,
1937        res=6,
1938        c='k3',
1939        alpha=1.0,
1940    ) -> None:
1941        """
1942        Build arrows between two lists of points `start_pts` and `end_pts`.
1943         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1944
1945        Color can be specified as a colormap which maps the size of the arrows.
1946
1947        Arguments:
1948            s : (float)
1949                fix aspect-ratio of the arrow and scale its cross section
1950            c : (color)
1951                color or color map name
1952            alpha : (float)
1953                set object opacity
1954            res : (int)
1955                set arrow resolution
1956
1957        Examples:
1958            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
1959
1960            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
1961        """
1962        if isinstance(start_pts, Points):
1963            start_pts = start_pts.vertices
1964        if isinstance(end_pts, Points):
1965            end_pts = end_pts.vertices
1966
1967        start_pts = np.asarray(start_pts)
1968        if end_pts is None:
1969            strt = start_pts[:, 0]
1970            end_pts = start_pts[:, 1]
1971            start_pts = strt
1972        else:
1973            end_pts = np.asarray(end_pts)
1974
1975        start_pts = utils.make3d(start_pts)
1976        end_pts = utils.make3d(end_pts)
1977
1978        arr = vtki.new("ArrowSource")
1979        arr.SetShaftResolution(res)
1980        arr.SetTipResolution(res)
1981
1982        if s:
1983            sz = 0.02 * s
1984            arr.SetTipRadius(sz * 2)
1985            arr.SetShaftRadius(sz * thickness)
1986            arr.SetTipLength(sz * 10)
1987
1988        if head_radius:
1989            arr.SetTipRadius(head_radius)
1990        if shaft_radius:
1991            arr.SetShaftRadius(shaft_radius)
1992        if head_length:
1993            arr.SetTipLength(head_length)
1994
1995        arr.Update()
1996        out = arr.GetOutput()
1997
1998        orients = end_pts - start_pts
1999
2000        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
2001
2002        super().__init__(
2003            start_pts,
2004            out,
2005            orientation_array=orients,
2006            scale_by_vector_size=True,
2007            color_by_vector_size=color_by_vector_size,
2008            c=c,
2009            alpha=alpha,
2010        )
2011        self.lighting("off")
2012        if color_by_vector_size:
2013            vals = np.linalg.norm(orients, axis=1)
2014            self.mapper.SetScalarRange(vals.min(), vals.max())
2015        else:
2016            self.c(c)
2017        self.name = "Arrows"

Build arrows between two lists of points start_pts and end_pts. start_pts can be also passed in the form [[point1, point2], ...].

Color can be specified as a colormap which maps the size of the arrows.

Arguments:
  • s : (float) fix aspect-ratio of the arrow and scale its cross section
  • c : (color) color or color map name
  • alpha : (float) set object opacity
  • res : (int) set arrow resolution
Examples:

class Arrow2D(vedo.mesh.Mesh):
2020class Arrow2D(Mesh):
2021    """
2022    Build a 2D arrow.
2023    """
2024
2025    def __init__(
2026        self,
2027        start_pt=(0, 0, 0),
2028        end_pt=(1, 0, 0),
2029        s=1,
2030        rotation=0.0,
2031        shaft_length=0.85,
2032        shaft_width=0.055,
2033        head_length=0.175,
2034        head_width=0.175,
2035        fill=True,
2036        c="red4",
2037        alpha=1.0,
2038   ) -> None:
2039        """
2040        Build a 2D arrow from `start_pt` to `end_pt`.
2041
2042        Arguments:
2043            s : (float)
2044                a global multiplicative convenience factor controlling the arrow size
2045            shaft_length : (float)
2046                fractional shaft length
2047            shaft_width : (float)
2048                fractional shaft width
2049            head_length : (float)
2050                fractional head length
2051            head_width : (float)
2052                fractional head width
2053            fill : (bool)
2054                if False only generate the outline
2055        """
2056        self.fill = fill  ## needed by pyplot.__iadd()
2057        self.s = s        ## needed by pyplot.__iadd()
2058
2059        if s != 1:
2060            shaft_width *= s
2061            head_width *= np.sqrt(s)
2062
2063        # in case user is passing meshs
2064        if isinstance(start_pt, vtki.vtkActor):
2065            start_pt = start_pt.GetPosition()
2066        if isinstance(end_pt, vtki.vtkActor):
2067            end_pt = end_pt.GetPosition()
2068        if len(start_pt) == 2:
2069            start_pt = [start_pt[0], start_pt[1], 0]
2070        if len(end_pt) == 2:
2071            end_pt = [end_pt[0], end_pt[1], 0]
2072
2073        headBase = 1 - head_length
2074        head_width = max(head_width, shaft_width)
2075        if head_length is None or headBase > shaft_length:
2076            headBase = shaft_length
2077
2078        verts = []
2079        verts.append([0, -shaft_width / 2, 0])
2080        verts.append([shaft_length, -shaft_width / 2, 0])
2081        verts.append([headBase, -head_width / 2, 0])
2082        verts.append([1, 0, 0])
2083        verts.append([headBase, head_width / 2, 0])
2084        verts.append([shaft_length, shaft_width / 2, 0])
2085        verts.append([0, shaft_width / 2, 0])
2086        if fill:
2087            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2088            poly = utils.buildPolyData(verts, faces)
2089        else:
2090            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2091            poly = utils.buildPolyData(verts, [], lines=lines)
2092
2093        axis = np.array(end_pt) - np.array(start_pt)
2094        length = float(np.linalg.norm(axis))
2095        if length:
2096            axis = axis / length
2097        theta = 0
2098        if len(axis) > 2:
2099            theta = np.arccos(axis[2])
2100        phi = np.arctan2(axis[1], axis[0])
2101
2102        t = vtki.vtkTransform()
2103        t.Translate(start_pt)
2104        if phi:
2105            t.RotateZ(np.rad2deg(phi))
2106        if theta:
2107            t.RotateY(np.rad2deg(theta))
2108        t.RotateY(-90)  # put it along Z
2109        if rotation:
2110            t.RotateX(rotation)
2111        t.Scale(length, length, length)
2112
2113        tf = vtki.new("TransformPolyDataFilter")
2114        tf.SetInputData(poly)
2115        tf.SetTransform(t)
2116        tf.Update()
2117
2118        super().__init__(tf.GetOutput(), c, alpha)
2119
2120        self.transform = LinearTransform().translate(start_pt)
2121
2122        self.lighting("off")
2123        self.actor.DragableOff()
2124        self.actor.PickableOff()
2125        self.base = np.array(start_pt, dtype=float) # used by pyplot
2126        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2127        self.name = "Arrow2D"

Build a 2D arrow.

Arrow2D( start_pt=(0, 0, 0), end_pt=(1, 0, 0), s=1, rotation=0.0, shaft_length=0.85, shaft_width=0.055, head_length=0.175, head_width=0.175, fill=True, c='red4', alpha=1.0)
2025    def __init__(
2026        self,
2027        start_pt=(0, 0, 0),
2028        end_pt=(1, 0, 0),
2029        s=1,
2030        rotation=0.0,
2031        shaft_length=0.85,
2032        shaft_width=0.055,
2033        head_length=0.175,
2034        head_width=0.175,
2035        fill=True,
2036        c="red4",
2037        alpha=1.0,
2038   ) -> None:
2039        """
2040        Build a 2D arrow from `start_pt` to `end_pt`.
2041
2042        Arguments:
2043            s : (float)
2044                a global multiplicative convenience factor controlling the arrow size
2045            shaft_length : (float)
2046                fractional shaft length
2047            shaft_width : (float)
2048                fractional shaft width
2049            head_length : (float)
2050                fractional head length
2051            head_width : (float)
2052                fractional head width
2053            fill : (bool)
2054                if False only generate the outline
2055        """
2056        self.fill = fill  ## needed by pyplot.__iadd()
2057        self.s = s        ## needed by pyplot.__iadd()
2058
2059        if s != 1:
2060            shaft_width *= s
2061            head_width *= np.sqrt(s)
2062
2063        # in case user is passing meshs
2064        if isinstance(start_pt, vtki.vtkActor):
2065            start_pt = start_pt.GetPosition()
2066        if isinstance(end_pt, vtki.vtkActor):
2067            end_pt = end_pt.GetPosition()
2068        if len(start_pt) == 2:
2069            start_pt = [start_pt[0], start_pt[1], 0]
2070        if len(end_pt) == 2:
2071            end_pt = [end_pt[0], end_pt[1], 0]
2072
2073        headBase = 1 - head_length
2074        head_width = max(head_width, shaft_width)
2075        if head_length is None or headBase > shaft_length:
2076            headBase = shaft_length
2077
2078        verts = []
2079        verts.append([0, -shaft_width / 2, 0])
2080        verts.append([shaft_length, -shaft_width / 2, 0])
2081        verts.append([headBase, -head_width / 2, 0])
2082        verts.append([1, 0, 0])
2083        verts.append([headBase, head_width / 2, 0])
2084        verts.append([shaft_length, shaft_width / 2, 0])
2085        verts.append([0, shaft_width / 2, 0])
2086        if fill:
2087            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2088            poly = utils.buildPolyData(verts, faces)
2089        else:
2090            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2091            poly = utils.buildPolyData(verts, [], lines=lines)
2092
2093        axis = np.array(end_pt) - np.array(start_pt)
2094        length = float(np.linalg.norm(axis))
2095        if length:
2096            axis = axis / length
2097        theta = 0
2098        if len(axis) > 2:
2099            theta = np.arccos(axis[2])
2100        phi = np.arctan2(axis[1], axis[0])
2101
2102        t = vtki.vtkTransform()
2103        t.Translate(start_pt)
2104        if phi:
2105            t.RotateZ(np.rad2deg(phi))
2106        if theta:
2107            t.RotateY(np.rad2deg(theta))
2108        t.RotateY(-90)  # put it along Z
2109        if rotation:
2110            t.RotateX(rotation)
2111        t.Scale(length, length, length)
2112
2113        tf = vtki.new("TransformPolyDataFilter")
2114        tf.SetInputData(poly)
2115        tf.SetTransform(t)
2116        tf.Update()
2117
2118        super().__init__(tf.GetOutput(), c, alpha)
2119
2120        self.transform = LinearTransform().translate(start_pt)
2121
2122        self.lighting("off")
2123        self.actor.DragableOff()
2124        self.actor.PickableOff()
2125        self.base = np.array(start_pt, dtype=float) # used by pyplot
2126        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2127        self.name = "Arrow2D"

Build a 2D arrow from start_pt to end_pt.

Arguments:
  • s : (float) a global multiplicative convenience factor controlling the arrow size
  • shaft_length : (float) fractional shaft length
  • shaft_width : (float) fractional shaft width
  • head_length : (float) fractional head length
  • head_width : (float) fractional head width
  • fill : (bool) if False only generate the outline
class Arrows2D(Glyph):
2130class Arrows2D(Glyph):
2131    """
2132    Build 2D arrows between two lists of points.
2133    """
2134
2135    def __init__(
2136        self,
2137        start_pts,
2138        end_pts=None,
2139        s=1.0,
2140        rotation=0.0,
2141        shaft_length=0.8,
2142        shaft_width=0.05,
2143        head_length=0.225,
2144        head_width=0.175,
2145        fill=True,
2146        c=None,
2147        alpha=1.0,
2148    ) -> None:
2149        """
2150        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2151        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2152
2153        Color can be specified as a colormap which maps the size of the arrows.
2154
2155        Arguments:
2156            shaft_length : (float)
2157                fractional shaft length
2158            shaft_width : (float)
2159                fractional shaft width
2160            head_length : (float)
2161                fractional head length
2162            head_width : (float)
2163                fractional head width
2164            fill : (bool)
2165                if False only generate the outline
2166        """
2167        if isinstance(start_pts, Points):
2168            start_pts = start_pts.vertices
2169        if isinstance(end_pts, Points):
2170            end_pts = end_pts.vertices
2171
2172        start_pts = np.asarray(start_pts, dtype=float)
2173        if end_pts is None:
2174            strt = start_pts[:, 0]
2175            end_pts = start_pts[:, 1]
2176            start_pts = strt
2177        else:
2178            end_pts = np.asarray(end_pts, dtype=float)
2179
2180        if head_length is None:
2181            head_length = 1 - shaft_length
2182
2183        arr = Arrow2D(
2184            (0, 0, 0),
2185            (1, 0, 0),
2186            s=s,
2187            rotation=rotation,
2188            shaft_length=shaft_length,
2189            shaft_width=shaft_width,
2190            head_length=head_length,
2191            head_width=head_width,
2192            fill=fill,
2193        )
2194
2195        orients = end_pts - start_pts
2196        orients = utils.make3d(orients)
2197
2198        pts = Points(start_pts)
2199        super().__init__(
2200            pts,
2201            arr,
2202            orientation_array=orients,
2203            scale_by_vector_size=True,
2204            c=c,
2205            alpha=alpha,
2206        )
2207        self.flat().lighting("off").pickable(False)
2208        if c is not None:
2209            self.color(c)
2210        self.name = "Arrows2D"

Build 2D arrows between two lists of points.

Arrows2D( start_pts, end_pts=None, s=1.0, rotation=0.0, shaft_length=0.8, shaft_width=0.05, head_length=0.225, head_width=0.175, fill=True, c=None, alpha=1.0)
2135    def __init__(
2136        self,
2137        start_pts,
2138        end_pts=None,
2139        s=1.0,
2140        rotation=0.0,
2141        shaft_length=0.8,
2142        shaft_width=0.05,
2143        head_length=0.225,
2144        head_width=0.175,
2145        fill=True,
2146        c=None,
2147        alpha=1.0,
2148    ) -> None:
2149        """
2150        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2151        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2152
2153        Color can be specified as a colormap which maps the size of the arrows.
2154
2155        Arguments:
2156            shaft_length : (float)
2157                fractional shaft length
2158            shaft_width : (float)
2159                fractional shaft width
2160            head_length : (float)
2161                fractional head length
2162            head_width : (float)
2163                fractional head width
2164            fill : (bool)
2165                if False only generate the outline
2166        """
2167        if isinstance(start_pts, Points):
2168            start_pts = start_pts.vertices
2169        if isinstance(end_pts, Points):
2170            end_pts = end_pts.vertices
2171
2172        start_pts = np.asarray(start_pts, dtype=float)
2173        if end_pts is None:
2174            strt = start_pts[:, 0]
2175            end_pts = start_pts[:, 1]
2176            start_pts = strt
2177        else:
2178            end_pts = np.asarray(end_pts, dtype=float)
2179
2180        if head_length is None:
2181            head_length = 1 - shaft_length
2182
2183        arr = Arrow2D(
2184            (0, 0, 0),
2185            (1, 0, 0),
2186            s=s,
2187            rotation=rotation,
2188            shaft_length=shaft_length,
2189            shaft_width=shaft_width,
2190            head_length=head_length,
2191            head_width=head_width,
2192            fill=fill,
2193        )
2194
2195        orients = end_pts - start_pts
2196        orients = utils.make3d(orients)
2197
2198        pts = Points(start_pts)
2199        super().__init__(
2200            pts,
2201            arr,
2202            orientation_array=orients,
2203            scale_by_vector_size=True,
2204            c=c,
2205            alpha=alpha,
2206        )
2207        self.flat().lighting("off").pickable(False)
2208        if c is not None:
2209            self.color(c)
2210        self.name = "Arrows2D"

Build 2D arrows between two lists of points start_pts and end_pts. start_pts can be also passed in the form [[point1, point2], ...].

Color can be specified as a colormap which maps the size of the arrows.

Arguments:
  • shaft_length : (float) fractional shaft length
  • shaft_width : (float) fractional shaft width
  • head_length : (float) fractional head length
  • head_width : (float) fractional head width
  • fill : (bool) if False only generate the outline
class FlatArrow(Ribbon):
2213class FlatArrow(Ribbon):
2214    """
2215    Build a 2D arrow in 3D space by joining two close lines.
2216    """
2217
2218    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2219        """
2220        Build a 2D arrow in 3D space by joining two close lines.
2221
2222        Examples:
2223            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2224
2225                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2226        """
2227        if isinstance(line1, Points):
2228            line1 = line1.vertices
2229        if isinstance(line2, Points):
2230            line2 = line2.vertices
2231
2232        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2233
2234        v = (sm1 - sm2) / 3 * tip_width
2235        p1 = sm1 + v
2236        p2 = sm2 - v
2237        pm1 = (sm1 + sm2) / 2
2238        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2239        pm12 = pm1 - pm2
2240        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2241
2242        line1.append(p1)
2243        line1.append(tip)
2244        line2.append(p2)
2245        line2.append(tip)
2246        resm = max(100, len(line1))
2247
2248        super().__init__(line1, line2, res=(resm, 1))
2249        self.phong().lighting("off")
2250        self.actor.PickableOff()
2251        self.actor.DragableOff()
2252        self.name = "FlatArrow"

Build a 2D arrow in 3D space by joining two close lines.

FlatArrow(line1, line2, tip_size=1.0, tip_width=1.0)
2218    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2219        """
2220        Build a 2D arrow in 3D space by joining two close lines.
2221
2222        Examples:
2223            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2224
2225                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2226        """
2227        if isinstance(line1, Points):
2228            line1 = line1.vertices
2229        if isinstance(line2, Points):
2230            line2 = line2.vertices
2231
2232        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2233
2234        v = (sm1 - sm2) / 3 * tip_width
2235        p1 = sm1 + v
2236        p2 = sm2 - v
2237        pm1 = (sm1 + sm2) / 2
2238        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2239        pm12 = pm1 - pm2
2240        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2241
2242        line1.append(p1)
2243        line1.append(tip)
2244        line2.append(p2)
2245        line2.append(tip)
2246        resm = max(100, len(line1))
2247
2248        super().__init__(line1, line2, res=(resm, 1))
2249        self.phong().lighting("off")
2250        self.actor.PickableOff()
2251        self.actor.DragableOff()
2252        self.name = "FlatArrow"

Build a 2D arrow in 3D space by joining two close lines.

Examples:
class Polygon(vedo.mesh.Mesh):
2265class Polygon(Mesh):
2266    """
2267    Build a polygon in the `xy` plane.
2268    """
2269
2270    def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None:
2271        """
2272        Build a polygon in the `xy` plane of `nsides` of radius `r`.
2273
2274        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png)
2275        """
2276        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False)
2277        pts = pol2cart(np.ones_like(t) * r, t).T
2278        faces = [list(range(nsides))]
2279        # do not use: vtkRegularPolygonSource
2280        super().__init__([pts, faces], c, alpha)
2281        if len(pos) == 2:
2282            pos = (pos[0], pos[1], 0)
2283        self.pos(pos)
2284        self.properties.LightingOff()
2285        self.name = "Polygon " + str(nsides)

Build a polygon in the xy plane.

Polygon(pos=(0, 0, 0), nsides=6, r=1.0, c='coral', alpha=1.0)
2270    def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None:
2271        """
2272        Build a polygon in the `xy` plane of `nsides` of radius `r`.
2273
2274        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png)
2275        """
2276        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False)
2277        pts = pol2cart(np.ones_like(t) * r, t).T
2278        faces = [list(range(nsides))]
2279        # do not use: vtkRegularPolygonSource
2280        super().__init__([pts, faces], c, alpha)
2281        if len(pos) == 2:
2282            pos = (pos[0], pos[1], 0)
2283        self.pos(pos)
2284        self.properties.LightingOff()
2285        self.name = "Polygon " + str(nsides)

Build a polygon in the xy plane of nsides of radius r.

class Triangle(vedo.mesh.Mesh):
2255class Triangle(Mesh):
2256    """Create a triangle from 3 points in space."""
2257
2258    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2259        """Create a triangle from 3 points in space."""
2260        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2261        self.properties.LightingOff()
2262        self.name = "Triangle"

Create a triangle from 3 points in space.

Triangle(p1, p2, p3, c='green7', alpha=1.0)
2258    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2259        """Create a triangle from 3 points in space."""
2260        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2261        self.properties.LightingOff()
2262        self.name = "Triangle"

Create a triangle from 3 points in space.

class Rectangle(vedo.mesh.Mesh):
3076class Rectangle(Mesh):
3077    """
3078    Build a rectangle in the xy plane.
3079    """
3080
3081    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3082        """
3083        Build a rectangle in the xy plane identified by any two corner points.
3084
3085        Arguments:
3086            p1 : (list)
3087                bottom-left position of the corner
3088            p2 : (list)
3089                top-right position of the corner
3090            radius : (float, list)
3091                smoothing radius of the corner in world units.
3092                A list can be passed with 4 individual values.
3093        """
3094        if len(p1) == 2:
3095            p1 = np.array([p1[0], p1[1], 0.0])
3096        else:
3097            p1 = np.array(p1, dtype=float)
3098        if len(p2) == 2:
3099            p2 = np.array([p2[0], p2[1], 0.0])
3100        else:
3101            p2 = np.array(p2, dtype=float)
3102
3103        self.corner1 = p1
3104        self.corner2 = p2
3105
3106        color = c
3107        smoothr = False
3108        risseq = False
3109        if utils.is_sequence(radius):
3110            risseq = True
3111            smoothr = True
3112            if max(radius) == 0:
3113                smoothr = False
3114        elif radius:
3115            smoothr = True
3116
3117        if not smoothr:
3118            radius = None
3119        self.radius = radius
3120
3121        if smoothr:
3122            r = radius
3123            if not risseq:
3124                r = [r, r, r, r]
3125            rd, ra, rb, rc = r
3126
3127            if p1[0] > p2[0]:  # flip p1 - p2
3128                p1, p2 = p2, p1
3129            if p1[1] > p2[1]:  # flip p1y - p2y
3130                p1[1], p2[1] = p2[1], p1[1]
3131
3132            px, py, _ = p2 - p1
3133            k = min(px / 2, py / 2)
3134            ra = min(abs(ra), k)
3135            rb = min(abs(rb), k)
3136            rc = min(abs(rc), k)
3137            rd = min(abs(rd), k)
3138            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3139            betas = np.split(beta, 4)
3140            rrx = np.cos(betas)
3141            rry = np.sin(betas)
3142
3143            q1 = (rd, 0)
3144            # q2 = (px-ra, 0)
3145            q3 = (px, ra)
3146            # q4 = (px, py-rb)
3147            q5 = (px - rb, py)
3148            # q6 = (rc, py)
3149            q7 = (0, py - rc)
3150            # q8 = (0, rd)
3151            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3152            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3153            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3154            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3155
3156            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3157            faces = [list(range(len(pts)))]
3158        else:
3159            p1r = np.array([p2[0], p1[1], 0.0])
3160            p2l = np.array([p1[0], p2[1], 0.0])
3161            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3162            faces = [(0, 1, 2, 3)]
3163
3164        super().__init__([pts, faces], color, alpha)
3165        self.pos(p1)
3166        self.properties.LightingOff()
3167        self.name = "Rectangle"

Build a rectangle in the xy plane.

Rectangle(p1=(0, 0), p2=(1, 1), radius=None, res=12, c='gray5', alpha=1.0)
3081    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3082        """
3083        Build a rectangle in the xy plane identified by any two corner points.
3084
3085        Arguments:
3086            p1 : (list)
3087                bottom-left position of the corner
3088            p2 : (list)
3089                top-right position of the corner
3090            radius : (float, list)
3091                smoothing radius of the corner in world units.
3092                A list can be passed with 4 individual values.
3093        """
3094        if len(p1) == 2:
3095            p1 = np.array([p1[0], p1[1], 0.0])
3096        else:
3097            p1 = np.array(p1, dtype=float)
3098        if len(p2) == 2:
3099            p2 = np.array([p2[0], p2[1], 0.0])
3100        else:
3101            p2 = np.array(p2, dtype=float)
3102
3103        self.corner1 = p1
3104        self.corner2 = p2
3105
3106        color = c
3107        smoothr = False
3108        risseq = False
3109        if utils.is_sequence(radius):
3110            risseq = True
3111            smoothr = True
3112            if max(radius) == 0:
3113                smoothr = False
3114        elif radius:
3115            smoothr = True
3116
3117        if not smoothr:
3118            radius = None
3119        self.radius = radius
3120
3121        if smoothr:
3122            r = radius
3123            if not risseq:
3124                r = [r, r, r, r]
3125            rd, ra, rb, rc = r
3126
3127            if p1[0] > p2[0]:  # flip p1 - p2
3128                p1, p2 = p2, p1
3129            if p1[1] > p2[1]:  # flip p1y - p2y
3130                p1[1], p2[1] = p2[1], p1[1]
3131
3132            px, py, _ = p2 - p1
3133            k = min(px / 2, py / 2)
3134            ra = min(abs(ra), k)
3135            rb = min(abs(rb), k)
3136            rc = min(abs(rc), k)
3137            rd = min(abs(rd), k)
3138            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3139            betas = np.split(beta, 4)
3140            rrx = np.cos(betas)
3141            rry = np.sin(betas)
3142
3143            q1 = (rd, 0)
3144            # q2 = (px-ra, 0)
3145            q3 = (px, ra)
3146            # q4 = (px, py-rb)
3147            q5 = (px - rb, py)
3148            # q6 = (rc, py)
3149            q7 = (0, py - rc)
3150            # q8 = (0, rd)
3151            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3152            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3153            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3154            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3155
3156            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3157            faces = [list(range(len(pts)))]
3158        else:
3159            p1r = np.array([p2[0], p1[1], 0.0])
3160            p2l = np.array([p1[0], p2[1], 0.0])
3161            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3162            faces = [(0, 1, 2, 3)]
3163
3164        super().__init__([pts, faces], color, alpha)
3165        self.pos(p1)
3166        self.properties.LightingOff()
3167        self.name = "Rectangle"

Build a rectangle in the xy plane identified by any two corner points.

Arguments:
  • p1 : (list) bottom-left position of the corner
  • p2 : (list) top-right position of the corner
  • radius : (float, list) smoothing radius of the corner in world units. A list can be passed with 4 individual values.
class Disc(vedo.mesh.Mesh):
2396class Disc(Mesh):
2397    """
2398    Build a 2D disc.
2399    """
2400
2401    def __init__(
2402        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2403    ) -> None:
2404        """
2405        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2406
2407        Set `res` as the resolution in R and Phi (can be a list).
2408
2409        Use `angle_range` to create a disc sector between the 2 specified angles.
2410
2411        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2412        """
2413        if utils.is_sequence(res):
2414            res_r, res_phi = res
2415        else:
2416            res_r, res_phi = res, 12 * res
2417
2418        if len(angle_range) == 0:
2419            ps = vtki.new("DiskSource")
2420        else:
2421            ps = vtki.new("SectorSource")
2422            ps.SetStartAngle(angle_range[0])
2423            ps.SetEndAngle(angle_range[1])
2424
2425        ps.SetInnerRadius(r1)
2426        ps.SetOuterRadius(r2)
2427        ps.SetRadialResolution(res_r)
2428        ps.SetCircumferentialResolution(res_phi)
2429        ps.Update()
2430        super().__init__(ps.GetOutput(), c, alpha)
2431        self.flat()
2432        self.pos(utils.make3d(pos))
2433        self.name = "Disc"

Build a 2D disc.

Disc( pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c='gray4', alpha=1.0)
2401    def __init__(
2402        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2403    ) -> None:
2404        """
2405        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2406
2407        Set `res` as the resolution in R and Phi (can be a list).
2408
2409        Use `angle_range` to create a disc sector between the 2 specified angles.
2410
2411        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2412        """
2413        if utils.is_sequence(res):
2414            res_r, res_phi = res
2415        else:
2416            res_r, res_phi = res, 12 * res
2417
2418        if len(angle_range) == 0:
2419            ps = vtki.new("DiskSource")
2420        else:
2421            ps = vtki.new("SectorSource")
2422            ps.SetStartAngle(angle_range[0])
2423            ps.SetEndAngle(angle_range[1])
2424
2425        ps.SetInnerRadius(r1)
2426        ps.SetOuterRadius(r2)
2427        ps.SetRadialResolution(res_r)
2428        ps.SetCircumferentialResolution(res_phi)
2429        ps.Update()
2430        super().__init__(ps.GetOutput(), c, alpha)
2431        self.flat()
2432        self.pos(utils.make3d(pos))
2433        self.name = "Disc"

Build a 2D disc of inner radius r1 and outer radius r2.

Set res as the resolution in R and Phi (can be a list).

Use angle_range to create a disc sector between the 2 specified angles.

class Circle(Polygon):
2288class Circle(Polygon):
2289    """
2290    Build a Circle of radius `r`.
2291    """
2292
2293    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None:
2294        """
2295        Build a Circle of radius `r`.
2296        """
2297        super().__init__(pos, nsides=res, r=r)
2298
2299        self.nr_of_points = 0
2300        self.va = 0
2301        self.vb = 0
2302        self.axis1: List[float] = []
2303        self.axis2: List[float] = []
2304        self.center: List[float] = []  # filled by pointcloud.pca_ellipse()
2305        self.pvalue = 0.0              # filled by pointcloud.pca_ellipse()
2306        self.alpha(alpha).c(c)
2307        self.name = "Circle"
2308    
2309    def acircularity(self) -> float:
2310        """
2311        Return a measure of how different an ellipse is from a circle.
2312        Values close to zero correspond to a circular object.
2313        """
2314        a, b = self.va, self.vb
2315        value = 0.0
2316        if a+b:
2317            value = ((a-b)/(a+b))**2
2318        return value

Build a Circle of radius r.

Circle(pos=(0, 0, 0), r=1.0, res=120, c='gray5', alpha=1.0)
2293    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None:
2294        """
2295        Build a Circle of radius `r`.
2296        """
2297        super().__init__(pos, nsides=res, r=r)
2298
2299        self.nr_of_points = 0
2300        self.va = 0
2301        self.vb = 0
2302        self.axis1: List[float] = []
2303        self.axis2: List[float] = []
2304        self.center: List[float] = []  # filled by pointcloud.pca_ellipse()
2305        self.pvalue = 0.0              # filled by pointcloud.pca_ellipse()
2306        self.alpha(alpha).c(c)
2307        self.name = "Circle"

Build a Circle of radius r.

def acircularity(self) -> float:
2309    def acircularity(self) -> float:
2310        """
2311        Return a measure of how different an ellipse is from a circle.
2312        Values close to zero correspond to a circular object.
2313        """
2314        a, b = self.va, self.vb
2315        value = 0.0
2316        if a+b:
2317            value = ((a-b)/(a+b))**2
2318        return value

Return a measure of how different an ellipse is from a circle. Values close to zero correspond to a circular object.

class GeoCircle(Polygon):
2320class GeoCircle(Polygon):
2321    """
2322    Build a Circle of radius `r`.
2323    """
2324
2325    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None:
2326        """
2327        Build a Circle of radius `r` as projected on a geographic map.
2328        Circles near the poles will look very squashed.
2329
2330        See example:
2331            ```bash
2332            vedo -r earthquake
2333            ```
2334        """
2335        coords = []
2336        sinr, cosr = np.sin(r), np.cos(r)
2337        sinlat, coslat = np.sin(lat), np.cos(lat)
2338        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2339            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2340            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2341            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2342
2343        super().__init__(nsides=res, c=c, alpha=alpha)
2344        self.vertices = coords # warp polygon points to match geo projection
2345        self.name = "Circle"

Build a Circle of radius r.

GeoCircle(lat, lon, r=1.0, res=60, c='red4', alpha=1.0)
2325    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None:
2326        """
2327        Build a Circle of radius `r` as projected on a geographic map.
2328        Circles near the poles will look very squashed.
2329
2330        See example:
2331            ```bash
2332            vedo -r earthquake
2333            ```
2334        """
2335        coords = []
2336        sinr, cosr = np.sin(r), np.cos(r)
2337        sinlat, coslat = np.sin(lat), np.cos(lat)
2338        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2339            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2340            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2341            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2342
2343        super().__init__(nsides=res, c=c, alpha=alpha)
2344        self.vertices = coords # warp polygon points to match geo projection
2345        self.name = "Circle"

Build a Circle of radius r as projected on a geographic map. Circles near the poles will look very squashed.

See example:
vedo -r earthquake
vertices
812    @property
813    def vertices(self):
814        """Return the vertices (points) coordinates."""
815        try:
816            # for polydata and unstructured grid
817            varr = self.dataset.GetPoints().GetData()
818        except (AttributeError, TypeError):
819            try:
820                # for RectilinearGrid, StructuredGrid
821                vpts = vtki.vtkPoints()
822                self.dataset.GetPoints(vpts)
823                varr = vpts.GetData()
824            except (AttributeError, TypeError):
825                try:
826                    # for ImageData
827                    v2p = vtki.new("ImageToPoints")
828                    v2p.SetInputData(self.dataset)
829                    v2p.Update()
830                    varr = v2p.GetOutput().GetPoints().GetData()
831                except AttributeError:
832                    return np.array([])
833
834        return utils.vtk2numpy(varr)

Return the vertices (points) coordinates.

class Arc(vedo.mesh.Mesh):
2436class Arc(Mesh):
2437    """
2438    Build a 2D circular arc between 2 points.
2439    """
2440
2441    def __init__(
2442        self,
2443        center,
2444        point1,
2445        point2=None,
2446        normal=None,
2447        angle=None,
2448        invert=False,
2449        res=50,
2450        c="gray4",
2451        alpha=1.0,
2452    ) -> None:
2453        """
2454        Build a 2D circular arc between 2 points `point1` and `point2`.
2455
2456        If `normal` is specified then `center` is ignored, and
2457        normal vector, a starting `point1` (polar vector)
2458        and an angle defining the arc length need to be assigned.
2459
2460        Arc spans the shortest angular sector point1 and point2,
2461        if `invert=True`, then the opposite happens.
2462        """
2463        if len(point1) == 2:
2464            point1 = (point1[0], point1[1], 0)
2465        if point2 is not None and len(point2) == 2:
2466            point2 = (point2[0], point2[1], 0)
2467
2468        ar = vtki.new("ArcSource")
2469        if point2 is not None:
2470            self.top = point2
2471            point2 = point2 - np.asarray(point1)
2472            ar.UseNormalAndAngleOff()
2473            ar.SetPoint1([0, 0, 0])
2474            ar.SetPoint2(point2)
2475            # ar.SetCenter(center)
2476        elif normal is not None and angle is not None:
2477            ar.UseNormalAndAngleOn()
2478            ar.SetAngle(angle)
2479            ar.SetPolarVector(point1)
2480            ar.SetNormal(normal)
2481        else:
2482            vedo.logger.error("incorrect input combination")
2483            return
2484        ar.SetNegative(invert)
2485        ar.SetResolution(res)
2486        ar.Update()
2487
2488        super().__init__(ar.GetOutput(), c, alpha)
2489        self.pos(center)
2490        self.lw(2).lighting("off")
2491        self.name = "Arc"

Build a 2D circular arc between 2 points.

Arc( center, point1, point2=None, normal=None, angle=None, invert=False, res=50, c='gray4', alpha=1.0)
2441    def __init__(
2442        self,
2443        center,
2444        point1,
2445        point2=None,
2446        normal=None,
2447        angle=None,
2448        invert=False,
2449        res=50,
2450        c="gray4",
2451        alpha=1.0,
2452    ) -> None:
2453        """
2454        Build a 2D circular arc between 2 points `point1` and `point2`.
2455
2456        If `normal` is specified then `center` is ignored, and
2457        normal vector, a starting `point1` (polar vector)
2458        and an angle defining the arc length need to be assigned.
2459
2460        Arc spans the shortest angular sector point1 and point2,
2461        if `invert=True`, then the opposite happens.
2462        """
2463        if len(point1) == 2:
2464            point1 = (point1[0], point1[1], 0)
2465        if point2 is not None and len(point2) == 2:
2466            point2 = (point2[0], point2[1], 0)
2467
2468        ar = vtki.new("ArcSource")
2469        if point2 is not None:
2470            self.top = point2
2471            point2 = point2 - np.asarray(point1)
2472            ar.UseNormalAndAngleOff()
2473            ar.SetPoint1([0, 0, 0])
2474            ar.SetPoint2(point2)
2475            # ar.SetCenter(center)
2476        elif normal is not None and angle is not None:
2477            ar.UseNormalAndAngleOn()
2478            ar.SetAngle(angle)
2479            ar.SetPolarVector(point1)
2480            ar.SetNormal(normal)
2481        else:
2482            vedo.logger.error("incorrect input combination")
2483            return
2484        ar.SetNegative(invert)
2485        ar.SetResolution(res)
2486        ar.Update()
2487
2488        super().__init__(ar.GetOutput(), c, alpha)
2489        self.pos(center)
2490        self.lw(2).lighting("off")
2491        self.name = "Arc"

Build a 2D circular arc between 2 points point1 and point2.

If normal is specified then center is ignored, and normal vector, a starting point1 (polar vector) and an angle defining the arc length need to be assigned.

Arc spans the shortest angular sector point1 and point2, if invert=True, then the opposite happens.

class Star(vedo.mesh.Mesh):
2348class Star(Mesh):
2349    """
2350    Build a 2D star shape.
2351    """
2352
2353    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2354        """
2355        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2356
2357        If line is True then only build the outer line (no internal surface meshing).
2358
2359        Example:
2360            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2361
2362                ![](https://vedo.embl.es/images/basic/extrude.png)
2363        """
2364        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2365        x, y = pol2cart(np.ones_like(t) * r2, t)
2366        pts = np.c_[x, y, np.zeros_like(x)]
2367
2368        apts = []
2369        for i, p in enumerate(pts):
2370            apts.append(p)
2371            if i + 1 < n:
2372                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2373        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2374
2375        if line:
2376            apts.append(pts[0])
2377            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2378            super().__init__(poly, c, alpha)
2379            self.lw(2)
2380        else:
2381            apts.append((0, 0, 0))
2382            cells = []
2383            for i in range(2 * n - 1):
2384                cell = [2 * n, i, i + 1]
2385                cells.append(cell)
2386            cells.append([2 * n, i + 1, 0])
2387            super().__init__([apts, cells], c, alpha)
2388
2389        if len(pos) == 2:
2390            pos = (pos[0], pos[1], 0)
2391
2392        self.properties.LightingOff()
2393        self.name = "Star"

Build a 2D star shape.

Star(pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c='blue6', alpha=1.0)
2353    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2354        """
2355        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2356
2357        If line is True then only build the outer line (no internal surface meshing).
2358
2359        Example:
2360            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2361
2362                ![](https://vedo.embl.es/images/basic/extrude.png)
2363        """
2364        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2365        x, y = pol2cart(np.ones_like(t) * r2, t)
2366        pts = np.c_[x, y, np.zeros_like(x)]
2367
2368        apts = []
2369        for i, p in enumerate(pts):
2370            apts.append(p)
2371            if i + 1 < n:
2372                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2373        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2374
2375        if line:
2376            apts.append(pts[0])
2377            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2378            super().__init__(poly, c, alpha)
2379            self.lw(2)
2380        else:
2381            apts.append((0, 0, 0))
2382            cells = []
2383            for i in range(2 * n - 1):
2384                cell = [2 * n, i, i + 1]
2385                cells.append(cell)
2386            cells.append([2 * n, i + 1, 0])
2387            super().__init__([apts, cells], c, alpha)
2388
2389        if len(pos) == 2:
2390            pos = (pos[0], pos[1], 0)
2391
2392        self.properties.LightingOff()
2393        self.name = "Star"

Build a 2D star shape of n cusps of inner radius r1 and outer radius r2.

If line is True then only build the outer line (no internal surface meshing).

Example:
class Star3D(vedo.mesh.Mesh):
3790class Star3D(Mesh):
3791    """
3792    Build a 3D starred shape.
3793    """
3794
3795    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3796        """
3797        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3798        """
3799        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3800               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3801               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3802               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3803        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3804               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3805               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3806               [10,1, 0],[10,11, 9]]
3807
3808        super().__init__([pts, fcs], c, alpha)
3809        self.rotate_x(90)
3810        self.scale(r).lighting("shiny")
3811
3812        if len(pos) == 2:
3813            pos = (pos[0], pos[1], 0)
3814        self.pos(pos)
3815        self.name = "Star3D"

Build a 3D starred shape.

Star3D(pos=(0, 0, 0), r=1.0, thickness=0.1, c='blue4', alpha=1.0)
3795    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3796        """
3797        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3798        """
3799        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3800               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3801               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3802               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3803        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3804               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3805               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3806               [10,1, 0],[10,11, 9]]
3807
3808        super().__init__([pts, fcs], c, alpha)
3809        self.rotate_x(90)
3810        self.scale(r).lighting("shiny")
3811
3812        if len(pos) == 2:
3813            pos = (pos[0], pos[1], 0)
3814        self.pos(pos)
3815        self.name = "Star3D"

Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.

class Cross3D(vedo.mesh.Mesh):
3818class Cross3D(Mesh):
3819    """
3820    Build a 3D cross shape.
3821    """
3822
3823    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3824        """
3825        Build a 3D cross shape, mainly useful as a 3D marker.
3826        """
3827        if len(pos) == 2:
3828            pos = (pos[0], pos[1], 0)
3829
3830        c1 = Cylinder(r=thickness * s, height=2 * s)
3831        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3832        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3833        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3834        super().__init__(poly, c, alpha)
3835        self.name = "Cross3D"

Build a 3D cross shape.

Cross3D(pos=(0, 0, 0), s=1.0, thickness=0.3, c='b', alpha=1.0)
3823    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3824        """
3825        Build a 3D cross shape, mainly useful as a 3D marker.
3826        """
3827        if len(pos) == 2:
3828            pos = (pos[0], pos[1], 0)
3829
3830        c1 = Cylinder(r=thickness * s, height=2 * s)
3831        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3832        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3833        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3834        super().__init__(poly, c, alpha)
3835        self.name = "Cross3D"

Build a 3D cross shape, mainly useful as a 3D marker.

class IcoSphere(vedo.mesh.Mesh):
2494class IcoSphere(Mesh):
2495    """
2496    Create a sphere made of a uniform triangle mesh.
2497    """
2498
2499    def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None:
2500        """
2501        Create a sphere made of a uniform triangle mesh
2502        (from recursive subdivision of an icosahedron).
2503
2504        Example:
2505        ```python
2506        from vedo import *
2507        icos = IcoSphere(subdivisions=3)
2508        icos.compute_quality().cmap('coolwarm')
2509        icos.show(axes=1).close()
2510        ```
2511        ![](https://vedo.embl.es/images/basic/icosphere.jpg)
2512        """
2513        subdivisions = int(min(subdivisions, 9))  # to avoid disasters
2514
2515        t = (1.0 + np.sqrt(5.0)) / 2.0
2516        points = np.array(
2517            [
2518                [-1, t, 0],
2519                [1, t, 0],
2520                [-1, -t, 0],
2521                [1, -t, 0],
2522                [0, -1, t],
2523                [0, 1, t],
2524                [0, -1, -t],
2525                [0, 1, -t],
2526                [t, 0, -1],
2527                [t, 0, 1],
2528                [-t, 0, -1],
2529                [-t, 0, 1],
2530            ]
2531        )
2532        faces = [
2533            [0, 11, 5],
2534            [0, 5, 1],
2535            [0, 1, 7],
2536            [0, 7, 10],
2537            [0, 10, 11],
2538            [1, 5, 9],
2539            [5, 11, 4],
2540            [11, 10, 2],
2541            [10, 7, 6],
2542            [7, 1, 8],
2543            [3, 9, 4],
2544            [3, 4, 2],
2545            [3, 2, 6],
2546            [3, 6, 8],
2547            [3, 8, 9],
2548            [4, 9, 5],
2549            [2, 4, 11],
2550            [6, 2, 10],
2551            [8, 6, 7],
2552            [9, 8, 1],
2553        ]
2554        super().__init__([points * r, faces], c=c, alpha=alpha)
2555
2556        for _ in range(subdivisions):
2557            self.subdivide(method=1)
2558            pts = utils.versor(self.vertices) * r
2559            self.vertices = pts
2560
2561        self.pos(pos)
2562        self.name = "IcoSphere"

Create a sphere made of a uniform triangle mesh.

IcoSphere(pos=(0, 0, 0), r=1.0, subdivisions=4, c='r5', alpha=1.0)
2499    def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None:
2500        """
2501        Create a sphere made of a uniform triangle mesh
2502        (from recursive subdivision of an icosahedron).
2503
2504        Example:
2505        ```python
2506        from vedo import *
2507        icos = IcoSphere(subdivisions=3)
2508        icos.compute_quality().cmap('coolwarm')
2509        icos.show(axes=1).close()
2510        ```
2511        ![](https://vedo.embl.es/images/basic/icosphere.jpg)
2512        """
2513        subdivisions = int(min(subdivisions, 9))  # to avoid disasters
2514
2515        t = (1.0 + np.sqrt(5.0)) / 2.0
2516        points = np.array(
2517            [
2518                [-1, t, 0],
2519                [1, t, 0],
2520                [-1, -t, 0],
2521                [1, -t, 0],
2522                [0, -1, t],
2523                [0, 1, t],
2524                [0, -1, -t],
2525                [0, 1, -t],
2526                [t, 0, -1],
2527                [t, 0, 1],
2528                [-t, 0, -1],
2529                [-t, 0, 1],
2530            ]
2531        )
2532        faces = [
2533            [0, 11, 5],
2534            [0, 5, 1],
2535            [0, 1, 7],
2536            [0, 7, 10],
2537            [0, 10, 11],
2538            [1, 5, 9],
2539            [5, 11, 4],
2540            [11, 10, 2],
2541            [10, 7, 6],
2542            [7, 1, 8],
2543            [3, 9, 4],
2544            [3, 4, 2],
2545            [3, 2, 6],
2546            [3, 6, 8],
2547            [3, 8, 9],
2548            [4, 9, 5],
2549            [2, 4, 11],
2550            [6, 2, 10],
2551            [8, 6, 7],
2552            [9, 8, 1],
2553        ]
2554        super().__init__([points * r, faces], c=c, alpha=alpha)
2555
2556        for _ in range(subdivisions):
2557            self.subdivide(method=1)
2558            pts = utils.versor(self.vertices) * r
2559            self.vertices = pts
2560
2561        self.pos(pos)
2562        self.name = "IcoSphere"

Create a sphere made of a uniform triangle mesh (from recursive subdivision of an icosahedron).

Example:

from vedo import *
icos = IcoSphere(subdivisions=3)
icos.compute_quality().cmap('coolwarm')
icos.show(axes=1).close()

class Sphere(vedo.mesh.Mesh):
2565class Sphere(Mesh):
2566    """
2567    Build a sphere.
2568    """
2569
2570    def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None:
2571        """
2572        Build a sphere at position `pos` of radius `r`.
2573
2574        Arguments:
2575            r : (float)
2576                sphere radius
2577            res : (int, list)
2578                resolution in phi, resolution in theta is by default `2*res`
2579            quads : (bool)
2580                sphere mesh will be made of quads instead of triangles
2581
2582        [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png)
2583        """
2584        if len(pos) == 2:
2585            pos = np.asarray([pos[0], pos[1], 0])
2586
2587        self.radius = r  # used by fitSphere
2588        self.center = pos
2589        self.residue = 0
2590
2591        if quads:
2592            res = max(res, 4)
2593            img = vtki.vtkImageData()
2594            img.SetDimensions(res - 1, res - 1, res - 1)
2595            rs = 1.0 / (res - 2)
2596            img.SetSpacing(rs, rs, rs)
2597            gf = vtki.new("GeometryFilter")
2598            gf.SetInputData(img)
2599            gf.Update()
2600            super().__init__(gf.GetOutput(), c, alpha)
2601            self.lw(0.1)
2602
2603            cgpts = self.vertices - (0.5, 0.5, 0.5)
2604
2605            x, y, z = cgpts.T
2606            x = x * (1 + x * x) / 2
2607            y = y * (1 + y * y) / 2
2608            z = z * (1 + z * z) / 2
2609            _, theta, phi = cart2spher(x, y, z)
2610
2611            pts = spher2cart(np.ones_like(phi) * r, theta, phi).T
2612            self.vertices = pts
2613
2614        else:
2615            if utils.is_sequence(res):
2616                res_t, res_phi = res
2617            else:
2618                res_t, res_phi = 2 * res, res
2619
2620            ss = vtki.new("SphereSource")
2621            ss.SetRadius(r)
2622            ss.SetThetaResolution(res_t)
2623            ss.SetPhiResolution(res_phi)
2624            ss.Update()
2625
2626            super().__init__(ss.GetOutput(), c, alpha)
2627
2628        self.phong()
2629        self.pos(pos)
2630        self.name = "Sphere"

Build a sphere.

Sphere(pos=(0, 0, 0), r=1.0, res=24, quads=False, c='r5', alpha=1.0)
2570    def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None:
2571        """
2572        Build a sphere at position `pos` of radius `r`.
2573
2574        Arguments:
2575            r : (float)
2576                sphere radius
2577            res : (int, list)
2578                resolution in phi, resolution in theta is by default `2*res`
2579            quads : (bool)
2580                sphere mesh will be made of quads instead of triangles
2581
2582        [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png)
2583        """
2584        if len(pos) == 2:
2585            pos = np.asarray([pos[0], pos[1], 0])
2586
2587        self.radius = r  # used by fitSphere
2588        self.center = pos
2589        self.residue = 0
2590
2591        if quads:
2592            res = max(res, 4)
2593            img = vtki.vtkImageData()
2594            img.SetDimensions(res - 1, res - 1, res - 1)
2595            rs = 1.0 / (res - 2)
2596            img.SetSpacing(rs, rs, rs)
2597            gf = vtki.new("GeometryFilter")
2598            gf.SetInputData(img)
2599            gf.Update()
2600            super().__init__(gf.GetOutput(), c, alpha)
2601            self.lw(0.1)
2602
2603            cgpts = self.vertices - (0.5, 0.5, 0.5)
2604
2605            x, y, z = cgpts.T
2606            x = x * (1 + x * x) / 2
2607            y = y * (1 + y * y) / 2
2608            z = z * (1 + z * z) / 2
2609            _, theta, phi = cart2spher(x, y, z)
2610
2611            pts = spher2cart(np.ones_like(phi) * r, theta, phi).T
2612            self.vertices = pts
2613
2614        else:
2615            if utils.is_sequence(res):
2616                res_t, res_phi = res
2617            else:
2618                res_t, res_phi = 2 * res, res
2619
2620            ss = vtki.new("SphereSource")
2621            ss.SetRadius(r)
2622            ss.SetThetaResolution(res_t)
2623            ss.SetPhiResolution(res_phi)
2624            ss.Update()
2625
2626            super().__init__(ss.GetOutput(), c, alpha)
2627
2628        self.phong()
2629        self.pos(pos)
2630        self.name = "Sphere"

Build a sphere at position pos of radius r.

Arguments:
  • r : (float) sphere radius
  • res : (int, list) resolution in phi, resolution in theta is by default 2*res
  • quads : (bool) sphere mesh will be made of quads instead of triangles

class Spheres(vedo.mesh.Mesh):
2633class Spheres(Mesh):
2634    """
2635    Build a large set of spheres.
2636    """
2637
2638    def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None:
2639        """
2640        Build a (possibly large) set of spheres at `centers` of radius `r`.
2641
2642        Either `c` or `r` can be a list of RGB colors or radii.
2643
2644        Examples:
2645            - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py)
2646
2647            ![](https://vedo.embl.es/images/basic/manyspheres.jpg)
2648        """
2649
2650        if isinstance(centers, Points):
2651            centers = centers.vertices
2652        centers = np.asarray(centers, dtype=float)
2653        base = centers[0]
2654
2655        cisseq = False
2656        if utils.is_sequence(c):
2657            cisseq = True
2658
2659        if cisseq:
2660            if len(centers) != len(c):
2661                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors")
2662                raise RuntimeError()
2663
2664        risseq = False
2665        if utils.is_sequence(r):
2666            risseq = True
2667
2668        if risseq:
2669            if len(centers) != len(r):
2670                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii")
2671                raise RuntimeError()
2672        if cisseq and risseq:
2673            vedo.logger.error("Limitation: c and r cannot be both sequences.")
2674            raise RuntimeError()
2675
2676        src = vtki.new("SphereSource")
2677        if not risseq:
2678            src.SetRadius(r)
2679        if utils.is_sequence(res):
2680            res_t, res_phi = res
2681        else:
2682            res_t, res_phi = 2 * res, res
2683
2684        src.SetThetaResolution(res_t)
2685        src.SetPhiResolution(res_phi)
2686        src.Update()
2687
2688        psrc = vtki.new("PointSource")
2689        psrc.SetNumberOfPoints(len(centers))
2690        psrc.Update()
2691        pd = psrc.GetOutput()
2692        vpts = pd.GetPoints()
2693
2694        glyph = vtki.vtkGlyph3D()
2695        glyph.SetSourceConnection(src.GetOutputPort())
2696
2697        if cisseq:
2698            glyph.SetColorModeToColorByScalar()
2699            ucols = vtki.vtkUnsignedCharArray()
2700            ucols.SetNumberOfComponents(3)
2701            ucols.SetName("Colors")
2702            for acol in c:
2703                cx, cy, cz = get_color(acol)
2704                ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255)
2705            pd.GetPointData().AddArray(ucols)
2706            pd.GetPointData().SetActiveScalars("Colors")
2707            glyph.ScalingOff()
2708        elif risseq:
2709            glyph.SetScaleModeToScaleByScalar()
2710            urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32)
2711            urads.SetName("Radii")
2712            pd.GetPointData().AddArray(urads)
2713            pd.GetPointData().SetActiveScalars("Radii")
2714
2715        vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32))
2716
2717        glyph.SetInputData(pd)
2718        glyph.Update()
2719
2720        super().__init__(glyph.GetOutput(), alpha=alpha)
2721        self.pos(base)
2722        self.phong()
2723        if cisseq:
2724            self.mapper.ScalarVisibilityOn()
2725        else:
2726            self.mapper.ScalarVisibilityOff()
2727            self.c(c)
2728        self.name = "Spheres"

Build a large set of spheres.

Spheres(centers, r=1.0, res=8, c='red5', alpha=1)
2638    def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None:
2639        """
2640        Build a (possibly large) set of spheres at `centers` of radius `r`.
2641
2642        Either `c` or `r` can be a list of RGB colors or radii.
2643
2644        Examples:
2645            - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py)
2646
2647            ![](https://vedo.embl.es/images/basic/manyspheres.jpg)
2648        """
2649
2650        if isinstance(centers, Points):
2651            centers = centers.vertices
2652        centers = np.asarray(centers, dtype=float)
2653        base = centers[0]
2654
2655        cisseq = False
2656        if utils.is_sequence(c):
2657            cisseq = True
2658
2659        if cisseq:
2660            if len(centers) != len(c):
2661                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors")
2662                raise RuntimeError()
2663
2664        risseq = False
2665        if utils.is_sequence(r):
2666            risseq = True
2667
2668        if risseq:
2669            if len(centers) != len(r):
2670                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii")
2671                raise RuntimeError()
2672        if cisseq and risseq:
2673            vedo.logger.error("Limitation: c and r cannot be both sequences.")
2674            raise RuntimeError()
2675
2676        src = vtki.new("SphereSource")
2677        if not risseq:
2678            src.SetRadius(r)
2679        if utils.is_sequence(res):
2680            res_t, res_phi = res
2681        else:
2682            res_t, res_phi = 2 * res, res
2683
2684        src.SetThetaResolution(res_t)
2685        src.SetPhiResolution(res_phi)
2686        src.Update()
2687
2688        psrc = vtki.new("PointSource")
2689        psrc.SetNumberOfPoints(len(centers))
2690        psrc.Update()
2691        pd = psrc.GetOutput()
2692        vpts = pd.GetPoints()
2693
2694        glyph = vtki.vtkGlyph3D()
2695        glyph.SetSourceConnection(src.GetOutputPort())
2696
2697        if cisseq:
2698            glyph.SetColorModeToColorByScalar()
2699            ucols = vtki.vtkUnsignedCharArray()
2700            ucols.SetNumberOfComponents(3)
2701            ucols.SetName("Colors")
2702            for acol in c:
2703                cx, cy, cz = get_color(acol)
2704                ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255)
2705            pd.GetPointData().AddArray(ucols)
2706            pd.GetPointData().SetActiveScalars("Colors")
2707            glyph.ScalingOff()
2708        elif risseq:
2709            glyph.SetScaleModeToScaleByScalar()
2710            urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32)
2711            urads.SetName("Radii")
2712            pd.GetPointData().AddArray(urads)
2713            pd.GetPointData().SetActiveScalars("Radii")
2714
2715        vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32))
2716
2717        glyph.SetInputData(pd)
2718        glyph.Update()
2719
2720        super().__init__(glyph.GetOutput(), alpha=alpha)
2721        self.pos(base)
2722        self.phong()
2723        if cisseq:
2724            self.mapper.ScalarVisibilityOn()
2725        else:
2726            self.mapper.ScalarVisibilityOff()
2727            self.c(c)
2728        self.name = "Spheres"

Build a (possibly large) set of spheres at centers of radius r.

Either c or r can be a list of RGB colors or radii.

Examples:

class Earth(vedo.mesh.Mesh):
2731class Earth(Mesh):
2732    """
2733    Build a textured mesh representing the Earth.
2734    """
2735
2736    def __init__(self, style=1, r=1.0) -> None:
2737        """
2738        Build a textured mesh representing the Earth.
2739
2740        Example:
2741            - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py)
2742
2743                ![](https://vedo.embl.es/images/advanced/geodesic.png)
2744        """
2745        tss = vtki.new("TexturedSphereSource")
2746        tss.SetRadius(r)
2747        tss.SetThetaResolution(72)
2748        tss.SetPhiResolution(36)
2749        tss.Update()
2750        super().__init__(tss.GetOutput(), c="w")
2751        atext = vtki.vtkTexture()
2752        pnm_reader = vtki.new("JPEGReader")
2753        fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False)
2754        pnm_reader.SetFileName(fn)
2755        atext.SetInputConnection(pnm_reader.GetOutputPort())
2756        atext.InterpolateOn()
2757        self.texture(atext)
2758        self.name = "Earth"

Build a textured mesh representing the Earth.

Earth(style=1, r=1.0)
2736    def __init__(self, style=1, r=1.0) -> None:
2737        """
2738        Build a textured mesh representing the Earth.
2739
2740        Example:
2741            - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py)
2742
2743                ![](https://vedo.embl.es/images/advanced/geodesic.png)
2744        """
2745        tss = vtki.new("TexturedSphereSource")
2746        tss.SetRadius(r)
2747        tss.SetThetaResolution(72)
2748        tss.SetPhiResolution(36)
2749        tss.Update()
2750        super().__init__(tss.GetOutput(), c="w")
2751        atext = vtki.vtkTexture()
2752        pnm_reader = vtki.new("JPEGReader")
2753        fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False)
2754        pnm_reader.SetFileName(fn)
2755        atext.SetInputConnection(pnm_reader.GetOutputPort())
2756        atext.InterpolateOn()
2757        self.texture(atext)
2758        self.name = "Earth"

Build a textured mesh representing the Earth.

Example:
class Ellipsoid(vedo.mesh.Mesh):
2761class Ellipsoid(Mesh):
2762    """Build a 3D ellipsoid."""
2763    def __init__(
2764        self,
2765        pos=(0, 0, 0),
2766        axis1=(0.5, 0, 0),
2767        axis2=(0, 1, 0),
2768        axis3=(0, 0, 1.5),
2769        res=24,
2770        c="cyan4",
2771        alpha=1.0,
2772    ) -> None:
2773        """
2774        Build a 3D ellipsoid centered at position `pos`.
2775
2776        Arguments:
2777            axis1 : (list)
2778                First axis. Length corresponds to semi-axis.
2779            axis2 : (list)
2780                Second axis. Length corresponds to semi-axis.
2781            axis3 : (list)
2782                Third axis. Length corresponds to semi-axis.
2783        """        
2784        self.center = utils.make3d(pos)
2785
2786        self.axis1 = utils.make3d(axis1)
2787        self.axis2 = utils.make3d(axis2)
2788        self.axis3 = utils.make3d(axis3)
2789
2790        self.va = np.linalg.norm(self.axis1)
2791        self.vb = np.linalg.norm(self.axis2)
2792        self.vc = np.linalg.norm(self.axis3)
2793
2794        self.va_error = 0
2795        self.vb_error = 0
2796        self.vc_error = 0
2797
2798        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2799        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2800
2801        if utils.is_sequence(res):
2802            res_t, res_phi = res
2803        else:
2804            res_t, res_phi = 2 * res, res
2805
2806        elli_source = vtki.new("SphereSource")
2807        elli_source.SetRadius(1)
2808        elli_source.SetThetaResolution(res_t)
2809        elli_source.SetPhiResolution(res_phi)
2810        elli_source.Update()
2811
2812        super().__init__(elli_source.GetOutput(), c, alpha)
2813
2814        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2815        lt = LinearTransform(matrix).translate(pos)
2816        self.apply_transform(lt)
2817        self.name = "Ellipsoid"
2818
2819    def asphericity(self) -> float:
2820        """
2821        Return a measure of how different an ellipsoid is from a sphere.
2822        Values close to zero correspond to a spheric object.
2823        """
2824        a, b, c = self.va, self.vb, self.vc
2825        asp = ( ((a-b)/(a+b))**2
2826              + ((a-c)/(a+c))**2
2827              + ((b-c)/(b+c))**2 ) / 3. * 4.
2828        return float(asp)
2829
2830    def asphericity_error(self) -> float:
2831        """
2832        Calculate statistical error on the asphericity value.
2833
2834        Errors on the main axes are stored in
2835        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2836        """
2837        a, b, c = self.va, self.vb, self.vc
2838        sqrtn = np.sqrt(self.nr_of_points)
2839        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2840
2841        # from sympy import *
2842        # init_printing(use_unicode=True)
2843        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2844        # L = (
2845        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2846        #    / 3 * 4)
2847        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2848        # print(dl2)
2849        # exit()
2850
2851        dL2 = (
2852            ea ** 2
2853            * (
2854                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2855                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2856                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2857                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2858            ) ** 2
2859            + eb ** 2
2860            * (
2861                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2862                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2863                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2864                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2865            ) ** 2
2866            + ec ** 2
2867            * (
2868                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2869                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2870                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2871                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2872            ) ** 2
2873        )
2874        err = np.sqrt(dL2)
2875        self.va_error = ea
2876        self.vb_error = eb
2877        self.vc_error = ec
2878        return err

Build a 3D ellipsoid.

Ellipsoid( pos=(0, 0, 0), axis1=(0.5, 0, 0), axis2=(0, 1, 0), axis3=(0, 0, 1.5), res=24, c='cyan4', alpha=1.0)
2763    def __init__(
2764        self,
2765        pos=(0, 0, 0),
2766        axis1=(0.5, 0, 0),
2767        axis2=(0, 1, 0),
2768        axis3=(0, 0, 1.5),
2769        res=24,
2770        c="cyan4",
2771        alpha=1.0,
2772    ) -> None:
2773        """
2774        Build a 3D ellipsoid centered at position `pos`.
2775
2776        Arguments:
2777            axis1 : (list)
2778                First axis. Length corresponds to semi-axis.
2779            axis2 : (list)
2780                Second axis. Length corresponds to semi-axis.
2781            axis3 : (list)
2782                Third axis. Length corresponds to semi-axis.
2783        """        
2784        self.center = utils.make3d(pos)
2785
2786        self.axis1 = utils.make3d(axis1)
2787        self.axis2 = utils.make3d(axis2)
2788        self.axis3 = utils.make3d(axis3)
2789
2790        self.va = np.linalg.norm(self.axis1)
2791        self.vb = np.linalg.norm(self.axis2)
2792        self.vc = np.linalg.norm(self.axis3)
2793
2794        self.va_error = 0
2795        self.vb_error = 0
2796        self.vc_error = 0
2797
2798        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2799        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2800
2801        if utils.is_sequence(res):
2802            res_t, res_phi = res
2803        else:
2804            res_t, res_phi = 2 * res, res
2805
2806        elli_source = vtki.new("SphereSource")
2807        elli_source.SetRadius(1)
2808        elli_source.SetThetaResolution(res_t)
2809        elli_source.SetPhiResolution(res_phi)
2810        elli_source.Update()
2811
2812        super().__init__(elli_source.GetOutput(), c, alpha)
2813
2814        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2815        lt = LinearTransform(matrix).translate(pos)
2816        self.apply_transform(lt)
2817        self.name = "Ellipsoid"

Build a 3D ellipsoid centered at position pos.

Arguments:
  • axis1 : (list) First axis. Length corresponds to semi-axis.
  • axis2 : (list) Second axis. Length corresponds to semi-axis.
  • axis3 : (list) Third axis. Length corresponds to semi-axis.
def asphericity(self) -> float:
2819    def asphericity(self) -> float:
2820        """
2821        Return a measure of how different an ellipsoid is from a sphere.
2822        Values close to zero correspond to a spheric object.
2823        """
2824        a, b, c = self.va, self.vb, self.vc
2825        asp = ( ((a-b)/(a+b))**2
2826              + ((a-c)/(a+c))**2
2827              + ((b-c)/(b+c))**2 ) / 3. * 4.
2828        return float(asp)

Return a measure of how different an ellipsoid is from a sphere. Values close to zero correspond to a spheric object.

def asphericity_error(self) -> float:
2830    def asphericity_error(self) -> float:
2831        """
2832        Calculate statistical error on the asphericity value.
2833
2834        Errors on the main axes are stored in
2835        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2836        """
2837        a, b, c = self.va, self.vb, self.vc
2838        sqrtn = np.sqrt(self.nr_of_points)
2839        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2840
2841        # from sympy import *
2842        # init_printing(use_unicode=True)
2843        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2844        # L = (
2845        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2846        #    / 3 * 4)
2847        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2848        # print(dl2)
2849        # exit()
2850
2851        dL2 = (
2852            ea ** 2
2853            * (
2854                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2855                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2856                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2857                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2858            ) ** 2
2859            + eb ** 2
2860            * (
2861                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2862                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2863                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2864                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2865            ) ** 2
2866            + ec ** 2
2867            * (
2868                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2869                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2870                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2871                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2872            ) ** 2
2873        )
2874        err = np.sqrt(dL2)
2875        self.va_error = ea
2876        self.vb_error = eb
2877        self.vc_error = ec
2878        return err

Calculate statistical error on the asphericity value.

Errors on the main axes are stored in Ellipsoid.va_error, Ellipsoid.vb_errorandEllipsoid.vc_error`.

class Grid(vedo.mesh.Mesh):
2881class Grid(Mesh):
2882    """
2883    An even or uneven 2D grid.
2884    """
2885
2886    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2887        """
2888        Create an even or uneven 2D grid.
2889        Can also be created from a `np.mgrid` object (see example).
2890
2891        Arguments:
2892            pos : (list, Points, Mesh)
2893                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2894            s : (float, list)
2895                if a float is provided it is interpreted as the total size along x and y,
2896                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2897                In this case keyword `res` is ignored (see example below).
2898            res : (list)
2899                resolutions along x and y, e.i. the number of subdivisions
2900            lw : (int)
2901                line width
2902
2903        Example:
2904            ```python
2905            from vedo import *
2906            xcoords = np.arange(0, 2, 0.2)
2907            ycoords = np.arange(0, 1, 0.2)
2908            sqrtx = sqrt(xcoords)
2909            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2910            grid.show(axes=8).close()
2911
2912            # Can also create a grid from a np.mgrid:
2913            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2914            vgrid = Grid(s=(X[:,0], Y[0]))
2915            vgrid.show(axes=8).close()
2916            ```
2917            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2918        """
2919        resx, resy = res
2920        sx, sy = s
2921        
2922        try:
2923            bb = pos.bounds()
2924            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2925            sx = bb[1] - bb[0]
2926            sy = bb[3] - bb[2]
2927        except AttributeError:
2928            pass        
2929
2930        if len(pos) == 2:
2931            pos = (pos[0], pos[1], 0)
2932        elif len(pos) in [4,6]: # passing a bounding box
2933            bb = pos
2934            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2935            sx = bb[1] - bb[0]
2936            sy = bb[3] - bb[2]
2937            if len(pos)==6:
2938                pos[2] = bb[4] - bb[5]
2939
2940        if utils.is_sequence(sx) and utils.is_sequence(sy):
2941            verts = []
2942            for y in sy:
2943                for x in sx:
2944                    verts.append([x, y, 0])
2945            faces = []
2946            n = len(sx)
2947            m = len(sy)
2948            for j in range(m - 1):
2949                j1n = (j + 1) * n
2950                for i in range(n - 1):
2951                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2952
2953            super().__init__([verts, faces], c, alpha)
2954
2955        else:
2956            ps = vtki.new("PlaneSource")
2957            ps.SetResolution(resx, resy)
2958            ps.Update()
2959
2960            t = vtki.vtkTransform()
2961            t.Translate(pos)
2962            t.Scale(sx, sy, 1)
2963
2964            tf = vtki.new("TransformPolyDataFilter")
2965            tf.SetInputData(ps.GetOutput())
2966            tf.SetTransform(t)
2967            tf.Update()
2968
2969            super().__init__(tf.GetOutput(), c, alpha)
2970
2971        self.wireframe().lw(lw)
2972        self.properties.LightingOff()
2973        self.name = "Grid"

An even or uneven 2D grid.

Grid(pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c='k3', alpha=1.0)
2886    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2887        """
2888        Create an even or uneven 2D grid.
2889        Can also be created from a `np.mgrid` object (see example).
2890
2891        Arguments:
2892            pos : (list, Points, Mesh)
2893                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2894            s : (float, list)
2895                if a float is provided it is interpreted as the total size along x and y,
2896                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2897                In this case keyword `res` is ignored (see example below).
2898            res : (list)
2899                resolutions along x and y, e.i. the number of subdivisions
2900            lw : (int)
2901                line width
2902
2903        Example:
2904            ```python
2905            from vedo import *
2906            xcoords = np.arange(0, 2, 0.2)
2907            ycoords = np.arange(0, 1, 0.2)
2908            sqrtx = sqrt(xcoords)
2909            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2910            grid.show(axes=8).close()
2911
2912            # Can also create a grid from a np.mgrid:
2913            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2914            vgrid = Grid(s=(X[:,0], Y[0]))
2915            vgrid.show(axes=8).close()
2916            ```
2917            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2918        """
2919        resx, resy = res
2920        sx, sy = s
2921        
2922        try:
2923            bb = pos.bounds()
2924            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2925            sx = bb[1] - bb[0]
2926            sy = bb[3] - bb[2]
2927        except AttributeError:
2928            pass        
2929
2930        if len(pos) == 2:
2931            pos = (pos[0], pos[1], 0)
2932        elif len(pos) in [4,6]: # passing a bounding box
2933            bb = pos
2934            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2935            sx = bb[1] - bb[0]
2936            sy = bb[3] - bb[2]
2937            if len(pos)==6:
2938                pos[2] = bb[4] - bb[5]
2939
2940        if utils.is_sequence(sx) and utils.is_sequence(sy):
2941            verts = []
2942            for y in sy:
2943                for x in sx:
2944                    verts.append([x, y, 0])
2945            faces = []
2946            n = len(sx)
2947            m = len(sy)
2948            for j in range(m - 1):
2949                j1n = (j + 1) * n
2950                for i in range(n - 1):
2951                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2952
2953            super().__init__([verts, faces], c, alpha)
2954
2955        else:
2956            ps = vtki.new("PlaneSource")
2957            ps.SetResolution(resx, resy)
2958            ps.Update()
2959
2960            t = vtki.vtkTransform()
2961            t.Translate(pos)
2962            t.Scale(sx, sy, 1)
2963
2964            tf = vtki.new("TransformPolyDataFilter")
2965            tf.SetInputData(ps.GetOutput())
2966            tf.SetTransform(t)
2967            tf.Update()
2968
2969            super().__init__(tf.GetOutput(), c, alpha)
2970
2971        self.wireframe().lw(lw)
2972        self.properties.LightingOff()
2973        self.name = "Grid"

Create an even or uneven 2D grid. Can also be created from a np.mgrid object (see example).

Arguments:
  • pos : (list, Points, Mesh) position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
  • s : (float, list) if a float is provided it is interpreted as the total size along x and y, if a list of coords is provided they are interpreted as the vertices of the grid along x and y. In this case keyword res is ignored (see example below).
  • res : (list) resolutions along x and y, e.i. the number of subdivisions
  • lw : (int) line width
Example:
from vedo import *
xcoords = np.arange(0, 2, 0.2)
ycoords = np.arange(0, 1, 0.2)
sqrtx = sqrt(xcoords)
grid = Grid(s=(sqrtx, ycoords)).lw(2)
grid.show(axes=8).close()

# Can also create a grid from a np.mgrid:
X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
vgrid = Grid(s=(X[:,0], Y[0]))
vgrid.show(axes=8).close()

class TessellatedBox(vedo.mesh.Mesh):
3255class TessellatedBox(Mesh):
3256    """
3257    Build a cubic `Mesh` made of quads.
3258    """
3259
3260    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3261        """
3262        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3263
3264        Arguments:
3265            pos : (list)
3266                position of the left bottom corner
3267            n : (int, list)
3268                number of subdivisions along each side
3269            spacing : (float)
3270                size of the side of the single quad in the 3 directions
3271        """
3272        if utils.is_sequence(n):  # slow
3273            img = vtki.vtkImageData()
3274            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3275            img.SetSpacing(spacing)
3276            gf = vtki.new("GeometryFilter")
3277            gf.SetInputData(img)
3278            gf.Update()
3279            poly = gf.GetOutput()
3280        else:  # fast
3281            n -= 1
3282            tbs = vtki.new("TessellatedBoxSource")
3283            tbs.SetLevel(n)
3284            if len(bounds):
3285                tbs.SetBounds(bounds)
3286            else:
3287                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3288            tbs.QuadsOn()
3289            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3290            tbs.Update()
3291            poly = tbs.GetOutput()
3292        super().__init__(poly, c=c, alpha=alpha)
3293        self.pos(pos)
3294        self.lw(1).lighting("off")
3295        self.name = "TessellatedBox"

Build a cubic Mesh made of quads.

TessellatedBox(pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c='k5', alpha=0.5)
3260    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3261        """
3262        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3263
3264        Arguments:
3265            pos : (list)
3266                position of the left bottom corner
3267            n : (int, list)
3268                number of subdivisions along each side
3269            spacing : (float)
3270                size of the side of the single quad in the 3 directions
3271        """
3272        if utils.is_sequence(n):  # slow
3273            img = vtki.vtkImageData()
3274            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3275            img.SetSpacing(spacing)
3276            gf = vtki.new("GeometryFilter")
3277            gf.SetInputData(img)
3278            gf.Update()
3279            poly = gf.GetOutput()
3280        else:  # fast
3281            n -= 1
3282            tbs = vtki.new("TessellatedBoxSource")
3283            tbs.SetLevel(n)
3284            if len(bounds):
3285                tbs.SetBounds(bounds)
3286            else:
3287                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3288            tbs.QuadsOn()
3289            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3290            tbs.Update()
3291            poly = tbs.GetOutput()
3292        super().__init__(poly, c=c, alpha=alpha)
3293        self.pos(pos)
3294        self.lw(1).lighting("off")
3295        self.name = "TessellatedBox"

Build a cubic Mesh made of n small quads in the 3 axis directions.

Arguments:
  • pos : (list) position of the left bottom corner
  • n : (int, list) number of subdivisions along each side
  • spacing : (float) size of the side of the single quad in the 3 directions
class Plane(vedo.mesh.Mesh):
2976class Plane(Mesh):
2977    """Create a plane in space."""
2978
2979    def __init__(
2980            self,
2981            pos=(0, 0, 0),
2982            normal=(0, 0, 1),
2983            s=(1, 1),
2984            res=(1, 1),
2985            c="gray5", alpha=1.0,
2986        ) -> None:
2987        """
2988        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
2989        to vector `normal` so that it passes through point `pos`.
2990
2991        Arguments:
2992            pos : (list)
2993                position of the plane center
2994            normal : (list)
2995                normal vector to the plane
2996            s : (list)
2997                size of the plane along x and y
2998            res : (list)
2999                resolution of the plane along x and y
3000        """
3001        if isinstance(pos, vtki.vtkPolyData):
3002            super().__init__(pos, c, alpha)
3003            # self.transform = LinearTransform().translate(pos)
3004
3005        else:
3006            ps = vtki.new("PlaneSource")
3007            ps.SetResolution(res[0], res[1])
3008            tri = vtki.new("TriangleFilter")
3009            tri.SetInputConnection(ps.GetOutputPort())
3010            tri.Update()
3011            
3012            super().__init__(tri.GetOutput(), c, alpha)
3013
3014            pos = utils.make3d(pos)
3015            normal = np.asarray(normal, dtype=float)
3016            axis = normal / np.linalg.norm(normal)
3017            theta = np.arccos(axis[2])
3018            phi = np.arctan2(axis[1], axis[0])
3019
3020            t = LinearTransform()
3021            t.scale([s[0], s[1], 1])
3022            t.rotate_y(np.rad2deg(theta))
3023            t.rotate_z(np.rad2deg(phi))
3024            t.translate(pos)
3025            self.apply_transform(t)
3026
3027        self.lighting("off")
3028        self.name = "Plane"
3029        self.variance = 0
3030
3031    def clone(self, deep=True) -> "Plane":
3032        newplane = Plane()
3033        if deep:
3034            newplane.dataset.DeepCopy(self.dataset)
3035        else:
3036            newplane.dataset.ShallowCopy(self.dataset)
3037        newplane.copy_properties_from(self)
3038        newplane.transform = self.transform.clone()
3039        newplane.variance = 0
3040        return newplane
3041    
3042    @property
3043    def normal(self) -> np.ndarray:
3044        pts = self.vertices
3045        AB = pts[1] - pts[0]
3046        AC = pts[2] - pts[0]
3047        normal = np.cross(AB, AC)
3048        normal = normal / np.linalg.norm(normal)
3049        return normal
3050
3051    @property
3052    def center(self) -> np.ndarray:
3053        pts = self.vertices
3054        return np.mean(pts, axis=0)
3055
3056    def contains(self, points, tol=0) -> np.ndarray:
3057        """
3058        Check if each of the provided point lies on this plane.
3059        `points` is an array of shape (n, 3).
3060        """
3061        points = np.array(points, dtype=float)
3062        bounds = self.vertices
3063
3064        mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol)
3065
3066        for i in [1, 3]:
3067            AB = bounds[i] - bounds[0]
3068            AP = points - bounds[0]
3069            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3070            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3071            mask = np.logical_and(mask, mask_l)
3072            mask = np.logical_and(mask, mask_g)
3073        return mask

Create a plane in space.

Plane( pos=(0, 0, 0), normal=(0, 0, 1), s=(1, 1), res=(1, 1), c='gray5', alpha=1.0)
2979    def __init__(
2980            self,
2981            pos=(0, 0, 0),
2982            normal=(0, 0, 1),
2983            s=(1, 1),
2984            res=(1, 1),
2985            c="gray5", alpha=1.0,
2986        ) -> None:
2987        """
2988        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
2989        to vector `normal` so that it passes through point `pos`.
2990
2991        Arguments:
2992            pos : (list)
2993                position of the plane center
2994            normal : (list)
2995                normal vector to the plane
2996            s : (list)
2997                size of the plane along x and y
2998            res : (list)
2999                resolution of the plane along x and y
3000        """
3001        if isinstance(pos, vtki.vtkPolyData):
3002            super().__init__(pos, c, alpha)
3003            # self.transform = LinearTransform().translate(pos)
3004
3005        else:
3006            ps = vtki.new("PlaneSource")
3007            ps.SetResolution(res[0], res[1])
3008            tri = vtki.new("TriangleFilter")
3009            tri.SetInputConnection(ps.GetOutputPort())
3010            tri.Update()
3011            
3012            super().__init__(tri.GetOutput(), c, alpha)
3013
3014            pos = utils.make3d(pos)
3015            normal = np.asarray(normal, dtype=float)
3016            axis = normal / np.linalg.norm(normal)
3017            theta = np.arccos(axis[2])
3018            phi = np.arctan2(axis[1], axis[0])
3019
3020            t = LinearTransform()
3021            t.scale([s[0], s[1], 1])
3022            t.rotate_y(np.rad2deg(theta))
3023            t.rotate_z(np.rad2deg(phi))
3024            t.translate(pos)
3025            self.apply_transform(t)
3026
3027        self.lighting("off")
3028        self.name = "Plane"
3029        self.variance = 0

Create a plane of size s=(xsize, ysize) oriented perpendicular to vector normal so that it passes through point pos.

Arguments:
  • pos : (list) position of the plane center
  • normal : (list) normal vector to the plane
  • s : (list) size of the plane along x and y
  • res : (list) resolution of the plane along x and y
def clone(self, deep=True) -> Plane:
3031    def clone(self, deep=True) -> "Plane":
3032        newplane = Plane()
3033        if deep:
3034            newplane.dataset.DeepCopy(self.dataset)
3035        else:
3036            newplane.dataset.ShallowCopy(self.dataset)
3037        newplane.copy_properties_from(self)
3038        newplane.transform = self.transform.clone()
3039        newplane.variance = 0
3040        return newplane

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

Arguments:
  • deep : (bool) if False return a shallow copy of the mesh without copying the points array.
Examples:
def contains(self, points, tol=0) -> numpy.ndarray:
3056    def contains(self, points, tol=0) -> np.ndarray:
3057        """
3058        Check if each of the provided point lies on this plane.
3059        `points` is an array of shape (n, 3).
3060        """
3061        points = np.array(points, dtype=float)
3062        bounds = self.vertices
3063
3064        mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol)
3065
3066        for i in [1, 3]:
3067            AB = bounds[i] - bounds[0]
3068            AP = points - bounds[0]
3069            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3070            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3071            mask = np.logical_and(mask, mask_l)
3072            mask = np.logical_and(mask, mask_g)
3073        return mask

Check if each of the provided point lies on this plane. points is an array of shape (n, 3).

class Box(vedo.mesh.Mesh):
3170class Box(Mesh):
3171    """
3172    Build a box of specified dimensions.
3173    """
3174
3175    def __init__(
3176            self, pos=(0, 0, 0), 
3177            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3178        """
3179        Build a box of dimensions `x=length, y=width and z=height`.
3180        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3181
3182        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3183        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3184
3185        Examples:
3186            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3187
3188                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3189        """
3190        src = vtki.new("CubeSource")
3191
3192        if len(pos) == 2:
3193            pos = (pos[0], pos[1], 0)
3194
3195        if len(pos) == 6:
3196            src.SetBounds(pos)
3197            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3198        elif len(size) == 3:
3199            length, width, height = size
3200            src.SetXLength(length)
3201            src.SetYLength(width)
3202            src.SetZLength(height)
3203            src.SetCenter(pos)
3204        else:
3205            src.SetXLength(length)
3206            src.SetYLength(width)
3207            src.SetZLength(height)
3208            src.SetCenter(pos)
3209
3210        src.Update()
3211        pd = src.GetOutput()
3212
3213        tc = [
3214            [0.0, 0.0],
3215            [1.0, 0.0],
3216            [0.0, 1.0],
3217            [1.0, 1.0],
3218            [1.0, 0.0],
3219            [0.0, 0.0],
3220            [1.0, 1.0],
3221            [0.0, 1.0],
3222            [1.0, 1.0],
3223            [1.0, 0.0],
3224            [0.0, 1.0],
3225            [0.0, 0.0],
3226            [0.0, 1.0],
3227            [0.0, 0.0],
3228            [1.0, 1.0],
3229            [1.0, 0.0],
3230            [1.0, 0.0],
3231            [0.0, 0.0],
3232            [1.0, 1.0],
3233            [0.0, 1.0],
3234            [0.0, 0.0],
3235            [1.0, 0.0],
3236            [0.0, 1.0],
3237            [1.0, 1.0],
3238        ]
3239        vtc = utils.numpy2vtk(tc)
3240        pd.GetPointData().SetTCoords(vtc)
3241        super().__init__(pd, c, alpha)
3242        self.transform = LinearTransform().translate(pos)
3243        self.name = "Box"

Build a box of specified dimensions.

Box( pos=(0, 0, 0), length=1.0, width=2.0, height=3.0, size=(), c='g4', alpha=1.0)
3175    def __init__(
3176            self, pos=(0, 0, 0), 
3177            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3178        """
3179        Build a box of dimensions `x=length, y=width and z=height`.
3180        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3181
3182        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3183        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3184
3185        Examples:
3186            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3187
3188                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3189        """
3190        src = vtki.new("CubeSource")
3191
3192        if len(pos) == 2:
3193            pos = (pos[0], pos[1], 0)
3194
3195        if len(pos) == 6:
3196            src.SetBounds(pos)
3197            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3198        elif len(size) == 3:
3199            length, width, height = size
3200            src.SetXLength(length)
3201            src.SetYLength(width)
3202            src.SetZLength(height)
3203            src.SetCenter(pos)
3204        else:
3205            src.SetXLength(length)
3206            src.SetYLength(width)
3207            src.SetZLength(height)
3208            src.SetCenter(pos)
3209
3210        src.Update()
3211        pd = src.GetOutput()
3212
3213        tc = [
3214            [0.0, 0.0],
3215            [1.0, 0.0],
3216            [0.0, 1.0],
3217            [1.0, 1.0],
3218            [1.0, 0.0],
3219            [0.0, 0.0],
3220            [1.0, 1.0],
3221            [0.0, 1.0],
3222            [1.0, 1.0],
3223            [1.0, 0.0],
3224            [0.0, 1.0],
3225            [0.0, 0.0],
3226            [0.0, 1.0],
3227            [0.0, 0.0],
3228            [1.0, 1.0],
3229            [1.0, 0.0],
3230            [1.0, 0.0],
3231            [0.0, 0.0],
3232            [1.0, 1.0],
3233            [0.0, 1.0],
3234            [0.0, 0.0],
3235            [1.0, 0.0],
3236            [0.0, 1.0],
3237            [1.0, 1.0],
3238        ]
3239        vtc = utils.numpy2vtk(tc)
3240        pd.GetPointData().SetTCoords(vtc)
3241        super().__init__(pd, c, alpha)
3242        self.transform = LinearTransform().translate(pos)
3243        self.name = "Box"

Build a box of dimensions x=length, y=width and z=height. Alternatively dimensions can be defined by setting size keyword with a tuple.

If pos is a list of 6 numbers, this will be interpreted as the bounding box: [xmin,xmax, ymin,ymax, zmin,zmax]

Examples:
class Cube(Box):
3246class Cube(Box):
3247    """Build a cube."""
3248
3249    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3250        """Build a cube of size `side`."""
3251        super().__init__(pos, side, side, side, (), c, alpha)
3252        self.name = "Cube"

Build a cube.

Cube(pos=(0, 0, 0), side=1.0, c='g4', alpha=1.0)
3249    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3250        """Build a cube of size `side`."""
3251        super().__init__(pos, side, side, side, (), c, alpha)
3252        self.name = "Cube"

Build a cube of size side.

class Spring(vedo.mesh.Mesh):
3298class Spring(Mesh):
3299    """
3300    Build a spring model.
3301    """
3302
3303    def __init__(
3304        self,
3305        start_pt=(0, 0, 0),
3306        end_pt=(1, 0, 0),
3307        coils=20,
3308        r1=0.1,
3309        r2=None,
3310        thickness=None,
3311        c="gray5",
3312        alpha=1.0,
3313    ) -> None:
3314        """
3315        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3316
3317        Arguments:
3318            coils : (int)
3319                number of coils
3320            r1 : (float)
3321                radius at start point
3322            r2 : (float)
3323                radius at end point
3324            thickness : (float)
3325                thickness of the coil section
3326        """
3327        start_pt = utils.make3d(start_pt)
3328        end_pt = utils.make3d(end_pt)
3329
3330        diff = end_pt - start_pt
3331        length = np.linalg.norm(diff)
3332        if not length:
3333            return
3334        if not r1:
3335            r1 = length / 20
3336        trange = np.linspace(0, length, num=50 * coils)
3337        om = 6.283 * (coils - 0.5) / length
3338        if not r2:
3339            r2 = r1
3340        pts = []
3341        for t in trange:
3342            f = (length - t) / length
3343            rd = r1 * f + r2 * (1 - f)
3344            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3345
3346        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3347        diff = diff / length
3348        theta = np.arccos(diff[2])
3349        phi = np.arctan2(diff[1], diff[0])
3350        sp = Line(pts)
3351        
3352        t = vtki.vtkTransform()
3353        t.Translate(start_pt)
3354        t.RotateZ(np.rad2deg(phi))
3355        t.RotateY(np.rad2deg(theta))
3356
3357        tf = vtki.new("TransformPolyDataFilter")
3358        tf.SetInputData(sp.dataset)
3359        tf.SetTransform(t)
3360        tf.Update()
3361
3362        tuf = vtki.new("TubeFilter")
3363        tuf.SetNumberOfSides(12)
3364        tuf.CappingOn()
3365        tuf.SetInputData(tf.GetOutput())
3366        if not thickness:
3367            thickness = r1 / 10
3368        tuf.SetRadius(thickness)
3369        tuf.Update()
3370
3371        super().__init__(tuf.GetOutput(), c, alpha)
3372
3373        self.phong()
3374        self.base = np.array(start_pt, dtype=float)
3375        self.top  = np.array(end_pt, dtype=float)
3376        self.name = "Spring"

Build a spring model.

Spring( start_pt=(0, 0, 0), end_pt=(1, 0, 0), coils=20, r1=0.1, r2=None, thickness=None, c='gray5', alpha=1.0)
3303    def __init__(
3304        self,
3305        start_pt=(0, 0, 0),
3306        end_pt=(1, 0, 0),
3307        coils=20,
3308        r1=0.1,
3309        r2=None,
3310        thickness=None,
3311        c="gray5",
3312        alpha=1.0,
3313    ) -> None:
3314        """
3315        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3316
3317        Arguments:
3318            coils : (int)
3319                number of coils
3320            r1 : (float)
3321                radius at start point
3322            r2 : (float)
3323                radius at end point
3324            thickness : (float)
3325                thickness of the coil section
3326        """
3327        start_pt = utils.make3d(start_pt)
3328        end_pt = utils.make3d(end_pt)
3329
3330        diff = end_pt - start_pt
3331        length = np.linalg.norm(diff)
3332        if not length:
3333            return
3334        if not r1:
3335            r1 = length / 20
3336        trange = np.linspace(0, length, num=50 * coils)
3337        om = 6.283 * (coils - 0.5) / length
3338        if not r2:
3339            r2 = r1
3340        pts = []
3341        for t in trange:
3342            f = (length - t) / length
3343            rd = r1 * f + r2 * (1 - f)
3344            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3345
3346        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3347        diff = diff / length
3348        theta = np.arccos(diff[2])
3349        phi = np.arctan2(diff[1], diff[0])
3350        sp = Line(pts)
3351        
3352        t = vtki.vtkTransform()
3353        t.Translate(start_pt)
3354        t.RotateZ(np.rad2deg(phi))
3355        t.RotateY(np.rad2deg(theta))
3356
3357        tf = vtki.new("TransformPolyDataFilter")
3358        tf.SetInputData(sp.dataset)
3359        tf.SetTransform(t)
3360        tf.Update()
3361
3362        tuf = vtki.new("TubeFilter")
3363        tuf.SetNumberOfSides(12)
3364        tuf.CappingOn()
3365        tuf.SetInputData(tf.GetOutput())
3366        if not thickness:
3367            thickness = r1 / 10
3368        tuf.SetRadius(thickness)
3369        tuf.Update()
3370
3371        super().__init__(tuf.GetOutput(), c, alpha)
3372
3373        self.phong()
3374        self.base = np.array(start_pt, dtype=float)
3375        self.top  = np.array(end_pt, dtype=float)
3376        self.name = "Spring"

Build a spring of specified nr of coils between start_pt and end_pt.

Arguments:
  • coils : (int) number of coils
  • r1 : (float) radius at start point
  • r2 : (float) radius at end point
  • thickness : (float) thickness of the coil section
class Cylinder(vedo.mesh.Mesh):
3379class Cylinder(Mesh):
3380    """
3381    Build a cylinder of specified height and radius.
3382    """
3383
3384    def __init__(
3385        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3386        cap=True, res=24, c="teal3", alpha=1.0
3387    ) -> None:
3388        """
3389        Build a cylinder of specified height and radius `r`, centered at `pos`.
3390
3391        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3392        centered at `v1` and top at `v2`.
3393
3394        Arguments:
3395            cap : (bool)
3396                enable/disable the caps of the cylinder
3397            res : (int)
3398                resolution of the cylinder sides
3399
3400        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3401        """
3402        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3403            base = np.array(pos[0], dtype=float)
3404            top = np.array(pos[1], dtype=float)
3405            pos = (base + top) / 2
3406            height = np.linalg.norm(top - base)
3407            axis = top - base
3408            axis = utils.versor(axis)
3409        else:
3410            axis = utils.versor(axis)
3411            base = pos - axis * height / 2
3412            top = pos + axis * height / 2
3413
3414        cyl = vtki.new("CylinderSource")
3415        cyl.SetResolution(res)
3416        cyl.SetRadius(r)
3417        cyl.SetHeight(height)
3418        cyl.SetCapping(cap)
3419        cyl.Update()
3420
3421        theta = np.arccos(axis[2])
3422        phi = np.arctan2(axis[1], axis[0])
3423        t = vtki.vtkTransform()
3424        t.PostMultiply()
3425        t.RotateX(90)  # put it along Z
3426        t.RotateY(np.rad2deg(theta))
3427        t.RotateZ(np.rad2deg(phi))
3428        t.Translate(pos)
3429
3430        tf = vtki.new("TransformPolyDataFilter")
3431        tf.SetInputData(cyl.GetOutput())
3432        tf.SetTransform(t)
3433        tf.Update()
3434
3435        super().__init__(tf.GetOutput(), c, alpha)
3436
3437        self.phong()
3438        self.base = base
3439        self.top  = top
3440        self.transform = LinearTransform().translate(pos)
3441        self.name = "Cylinder"

Build a cylinder of specified height and radius.

Cylinder( pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), cap=True, res=24, c='teal3', alpha=1.0)
3384    def __init__(
3385        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3386        cap=True, res=24, c="teal3", alpha=1.0
3387    ) -> None:
3388        """
3389        Build a cylinder of specified height and radius `r`, centered at `pos`.
3390
3391        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3392        centered at `v1` and top at `v2`.
3393
3394        Arguments:
3395            cap : (bool)
3396                enable/disable the caps of the cylinder
3397            res : (int)
3398                resolution of the cylinder sides
3399
3400        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3401        """
3402        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3403            base = np.array(pos[0], dtype=float)
3404            top = np.array(pos[1], dtype=float)
3405            pos = (base + top) / 2
3406            height = np.linalg.norm(top - base)
3407            axis = top - base
3408            axis = utils.versor(axis)
3409        else:
3410            axis = utils.versor(axis)
3411            base = pos - axis * height / 2
3412            top = pos + axis * height / 2
3413
3414        cyl = vtki.new("CylinderSource")
3415        cyl.SetResolution(res)
3416        cyl.SetRadius(r)
3417        cyl.SetHeight(height)
3418        cyl.SetCapping(cap)
3419        cyl.Update()
3420
3421        theta = np.arccos(axis[2])
3422        phi = np.arctan2(axis[1], axis[0])
3423        t = vtki.vtkTransform()
3424        t.PostMultiply()
3425        t.RotateX(90)  # put it along Z
3426        t.RotateY(np.rad2deg(theta))
3427        t.RotateZ(np.rad2deg(phi))
3428        t.Translate(pos)
3429
3430        tf = vtki.new("TransformPolyDataFilter")
3431        tf.SetInputData(cyl.GetOutput())
3432        tf.SetTransform(t)
3433        tf.Update()
3434
3435        super().__init__(tf.GetOutput(), c, alpha)
3436
3437        self.phong()
3438        self.base = base
3439        self.top  = top
3440        self.transform = LinearTransform().translate(pos)
3441        self.name = "Cylinder"

Build a cylinder of specified height and radius r, centered at pos.

If pos is a list of 2 points, e.g. pos=[v1, v2], build a cylinder with base centered at v1 and top at v2.

Arguments:
  • cap : (bool) enable/disable the caps of the cylinder
  • res : (int) resolution of the cylinder sides

class Cone(vedo.mesh.Mesh):
3444class Cone(Mesh):
3445    """Build a cone of specified radius and height."""
3446
3447    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3448                 res=48, c="green3", alpha=1.0) -> None:
3449        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3450        con = vtki.new("ConeSource")
3451        con.SetResolution(res)
3452        con.SetRadius(r)
3453        con.SetHeight(height)
3454        con.SetDirection(axis)
3455        con.Update()
3456        super().__init__(con.GetOutput(), c, alpha)
3457        self.phong()
3458        if len(pos) == 2:
3459            pos = (pos[0], pos[1], 0)
3460        self.pos(pos)
3461        v = utils.versor(axis) * height / 2
3462        self.base = pos - v
3463        self.top  = pos + v
3464        self.name = "Cone"

Build a cone of specified radius and height.

Cone( pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), res=48, c='green3', alpha=1.0)
3447    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3448                 res=48, c="green3", alpha=1.0) -> None:
3449        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3450        con = vtki.new("ConeSource")
3451        con.SetResolution(res)
3452        con.SetRadius(r)
3453        con.SetHeight(height)
3454        con.SetDirection(axis)
3455        con.Update()
3456        super().__init__(con.GetOutput(), c, alpha)
3457        self.phong()
3458        if len(pos) == 2:
3459            pos = (pos[0], pos[1], 0)
3460        self.pos(pos)
3461        v = utils.versor(axis) * height / 2
3462        self.base = pos - v
3463        self.top  = pos + v
3464        self.name = "Cone"

Build a cone of specified radius r and height, centered at pos.

class Pyramid(Cone):
3467class Pyramid(Cone):
3468    """Build a pyramidal shape."""
3469
3470    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3471                 c="green3", alpha=1) -> None:
3472        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3473        super().__init__(pos, s, height, axis, 4, c, alpha)
3474        self.name = "Pyramid"

Build a pyramidal shape.

Pyramid( pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), c='green3', alpha=1)
3470    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3471                 c="green3", alpha=1) -> None:
3472        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3473        super().__init__(pos, s, height, axis, 4, c, alpha)
3474        self.name = "Pyramid"

Build a pyramid of specified base size s and height, centered at pos.

class Torus(vedo.mesh.Mesh):
3477class Torus(Mesh):
3478    """
3479    Build a toroidal shape.
3480    """
3481
3482    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3483        """
3484        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3485        If `quad=True` a quad-mesh is generated.
3486        """
3487        if utils.is_sequence(res):
3488            res_u, res_v = res
3489        else:
3490            res_u, res_v = 3 * res, res
3491
3492        if quads:
3493            # https://github.com/marcomusy/vedo/issues/710
3494
3495            n = res_v
3496            m = res_u
3497
3498            theta = np.linspace(0, 2.0 * np.pi, n)
3499            phi = np.linspace(0, 2.0 * np.pi, m)
3500            theta, phi = np.meshgrid(theta, phi)
3501            t = r1 + r2 * np.cos(theta)
3502            x = t * np.cos(phi)
3503            y = t * np.sin(phi)
3504            z = r2 * np.sin(theta)
3505            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3506
3507            faces = []
3508            for j in range(m - 1):
3509                j1n = (j + 1) * n
3510                for i in range(n - 1):
3511                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3512
3513            super().__init__([pts, faces], c, alpha)
3514
3515        else:
3516            rs = vtki.new("ParametricTorus")
3517            rs.SetRingRadius(r1)
3518            rs.SetCrossSectionRadius(r2)
3519            pfs = vtki.new("ParametricFunctionSource")
3520            pfs.SetParametricFunction(rs)
3521            pfs.SetUResolution(res_u)
3522            pfs.SetVResolution(res_v)
3523            pfs.Update()
3524
3525            super().__init__(pfs.GetOutput(), c, alpha)
3526
3527        self.phong()
3528        if len(pos) == 2:
3529            pos = (pos[0], pos[1], 0)
3530        self.pos(pos)
3531        self.name = "Torus"

Build a toroidal shape.

Torus( pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c='yellow3', alpha=1.0)
3482    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3483        """
3484        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3485        If `quad=True` a quad-mesh is generated.
3486        """
3487        if utils.is_sequence(res):
3488            res_u, res_v = res
3489        else:
3490            res_u, res_v = 3 * res, res
3491
3492        if quads:
3493            # https://github.com/marcomusy/vedo/issues/710
3494
3495            n = res_v
3496            m = res_u
3497
3498            theta = np.linspace(0, 2.0 * np.pi, n)
3499            phi = np.linspace(0, 2.0 * np.pi, m)
3500            theta, phi = np.meshgrid(theta, phi)
3501            t = r1 + r2 * np.cos(theta)
3502            x = t * np.cos(phi)
3503            y = t * np.sin(phi)
3504            z = r2 * np.sin(theta)
3505            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3506
3507            faces = []
3508            for j in range(m - 1):
3509                j1n = (j + 1) * n
3510                for i in range(n - 1):
3511                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3512
3513            super().__init__([pts, faces], c, alpha)
3514
3515        else:
3516            rs = vtki.new("ParametricTorus")
3517            rs.SetRingRadius(r1)
3518            rs.SetCrossSectionRadius(r2)
3519            pfs = vtki.new("ParametricFunctionSource")
3520            pfs.SetParametricFunction(rs)
3521            pfs.SetUResolution(res_u)
3522            pfs.SetVResolution(res_v)
3523            pfs.Update()
3524
3525            super().__init__(pfs.GetOutput(), c, alpha)
3526
3527        self.phong()
3528        if len(pos) == 2:
3529            pos = (pos[0], pos[1], 0)
3530        self.pos(pos)
3531        self.name = "Torus"

Build a torus of specified outer radius r1 internal radius r2, centered at pos. If quad=True a quad-mesh is generated.

class Paraboloid(vedo.mesh.Mesh):
3534class Paraboloid(Mesh):
3535    """
3536    Build a paraboloid.
3537    """
3538
3539    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3540        """
3541        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3542
3543        Full volumetric expression is:
3544            `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9`
3545
3546        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3547        """
3548        quadric = vtki.new("Quadric")
3549        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3550        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3551        #         + a3*x*y + a4*y*z + a5*x*z
3552        #         + a6*x   + a7*y   + a8*z  +a9
3553        sample = vtki.new("SampleFunction")
3554        sample.SetSampleDimensions(res, res, res)
3555        sample.SetImplicitFunction(quadric)
3556
3557        contours = vtki.new("ContourFilter")
3558        contours.SetInputConnection(sample.GetOutputPort())
3559        contours.GenerateValues(1, 0.01, 0.01)
3560        contours.Update()
3561
3562        super().__init__(contours.GetOutput(), c, alpha)
3563        self.compute_normals().phong()
3564        self.mapper.ScalarVisibilityOff()
3565        self.pos(pos)
3566        self.name = "Paraboloid"

Build a paraboloid.

Paraboloid(pos=(0, 0, 0), height=1.0, res=50, c='cyan5', alpha=1.0)
3539    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3540        """
3541        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3542
3543        Full volumetric expression is:
3544            `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9`
3545
3546        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3547        """
3548        quadric = vtki.new("Quadric")
3549        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3550        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3551        #         + a3*x*y + a4*y*z + a5*x*z
3552        #         + a6*x   + a7*y   + a8*z  +a9
3553        sample = vtki.new("SampleFunction")
3554        sample.SetSampleDimensions(res, res, res)
3555        sample.SetImplicitFunction(quadric)
3556
3557        contours = vtki.new("ContourFilter")
3558        contours.SetInputConnection(sample.GetOutputPort())
3559        contours.GenerateValues(1, 0.01, 0.01)
3560        contours.Update()
3561
3562        super().__init__(contours.GetOutput(), c, alpha)
3563        self.compute_normals().phong()
3564        self.mapper.ScalarVisibilityOff()
3565        self.pos(pos)
3566        self.name = "Paraboloid"

Build a paraboloid of specified height and radius r, centered at pos.

Full volumetric expression is:

F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9

class Hyperboloid(vedo.mesh.Mesh):
3569class Hyperboloid(Mesh):
3570    """
3571    Build a hyperboloid.
3572    """
3573
3574    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3575        """
3576        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3577
3578        Full volumetric expression is:
3579            `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9`
3580        """
3581        q = vtki.new("Quadric")
3582        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3583        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3584        #         + a3*x*y + a4*y*z + a5*x*z
3585        #         + a6*x   + a7*y   + a8*z  +a9
3586        sample = vtki.new("SampleFunction")
3587        sample.SetSampleDimensions(res, res, res)
3588        sample.SetImplicitFunction(q)
3589
3590        contours = vtki.new("ContourFilter")
3591        contours.SetInputConnection(sample.GetOutputPort())
3592        contours.GenerateValues(1, value, value)
3593        contours.Update()
3594
3595        super().__init__(contours.GetOutput(), c, alpha)
3596        self.compute_normals().phong()
3597        self.mapper.ScalarVisibilityOff()
3598        self.pos(pos)
3599        self.name = "Hyperboloid"

Build a hyperboloid.

Hyperboloid(pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c='pink4', alpha=1.0)
3574    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3575        """
3576        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3577
3578        Full volumetric expression is:
3579            `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9`
3580        """
3581        q = vtki.new("Quadric")
3582        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3583        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3584        #         + a3*x*y + a4*y*z + a5*x*z
3585        #         + a6*x   + a7*y   + a8*z  +a9
3586        sample = vtki.new("SampleFunction")
3587        sample.SetSampleDimensions(res, res, res)
3588        sample.SetImplicitFunction(q)
3589
3590        contours = vtki.new("ContourFilter")
3591        contours.SetInputConnection(sample.GetOutputPort())
3592        contours.GenerateValues(1, value, value)
3593        contours.Update()
3594
3595        super().__init__(contours.GetOutput(), c, alpha)
3596        self.compute_normals().phong()
3597        self.mapper.ScalarVisibilityOff()
3598        self.pos(pos)
3599        self.name = "Hyperboloid"

Build a hyperboloid of specified aperture a2 and height, centered at pos.

Full volumetric expression is:

F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9

class TextBase:
4364class TextBase:
4365    "Base class."
4366
4367    def __init__(self):
4368        "Do not instantiate this base class."
4369
4370        self.rendered_at = set()
4371        self.properties = None
4372
4373        self.name = "Text"
4374        self.filename = ""
4375        self.time = 0
4376        self.info = {}
4377
4378        if isinstance(settings.default_font, int):
4379            lfonts = list(settings.font_parameters.keys())
4380            font = settings.default_font % len(lfonts)
4381            self.fontname = lfonts[font]
4382        else:
4383            self.fontname = settings.default_font
4384
4385    def angle(self, value: float):
4386        """Orientation angle in degrees"""
4387        self.properties.SetOrientation(value)
4388        return self
4389
4390    def line_spacing(self, value: float):
4391        """Set the extra spacing between lines
4392        expressed as a text height multiplicative factor."""
4393        self.properties.SetLineSpacing(value)
4394        return self
4395
4396    def line_offset(self, value: float):
4397        """Set/Get the vertical offset (measured in pixels)."""
4398        self.properties.SetLineOffset(value)
4399        return self
4400
4401    def bold(self, value=True):
4402        """Set bold face"""
4403        self.properties.SetBold(value)
4404        return self
4405
4406    def italic(self, value=True):
4407        """Set italic face"""
4408        self.properties.SetItalic(value)
4409        return self
4410
4411    def shadow(self, offset=(1, -1)):
4412        """Text shadowing. Set to `None` to disable it."""
4413        if offset is None:
4414            self.properties.ShadowOff()
4415        else:
4416            self.properties.ShadowOn()
4417            self.properties.SetShadowOffset(offset)
4418        return self
4419
4420    def color(self, c=None):
4421        """Set the text color"""
4422        if c is None:
4423            return get_color(self.properties.GetColor())
4424        self.properties.SetColor(get_color(c))
4425        return self
4426
4427    def c(self, color=None):
4428        """Set the text color"""
4429        if color is None:
4430            return get_color(self.properties.GetColor())
4431        return self.color(color)
4432
4433    def alpha(self, value: float):
4434        """Set the text opacity"""
4435        self.properties.SetBackgroundOpacity(value)
4436        return self
4437
4438    def background(self, color="k9", alpha=1.0):
4439        """Text background. Set to `None` to disable it."""
4440        bg = get_color(color)
4441        if color is None:
4442            self.properties.SetBackgroundOpacity(0)
4443        else:
4444            self.properties.SetBackgroundColor(bg)
4445            if alpha:
4446                self.properties.SetBackgroundOpacity(alpha)
4447        return self
4448
4449    def frame(self, color="k1", lw=2):
4450        """Border color and width"""
4451        if color is None:
4452            self.properties.FrameOff()
4453        else:
4454            c = get_color(color)
4455            self.properties.FrameOn()
4456            self.properties.SetFrameColor(c)
4457            self.properties.SetFrameWidth(lw)
4458        return self
4459
4460    def font(self, font: str):
4461        """Text font face"""
4462        if isinstance(font, int):
4463            lfonts = list(settings.font_parameters.keys())
4464            n = font % len(lfonts)
4465            font = lfonts[n]
4466            self.fontname = font
4467
4468        if not font:  # use default font
4469            font = self.fontname
4470            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4471        elif font.startswith("https"):  # user passed URL link, make it a path
4472            fpath = vedo.file_io.download(font, verbose=False, force=False)
4473        elif font.endswith(".ttf"):  # user passing a local path to font file
4474            fpath = font
4475        else:  # user passing name of preset font
4476            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4477
4478        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4479        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4480        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4481        else:
4482            fpath = utils.get_font_path(font)
4483            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4484            self.properties.SetFontFile(fpath)
4485        self.fontname = font  # io.tonumpy() uses it
4486
4487        return self
4488
4489    def on(self):
4490        """Make text visible"""
4491        self.actor.SetVisibility(True)
4492        return self
4493
4494    def off(self):
4495        """Make text invisible"""
4496        self.actor.SetVisibility(False)
4497        return self

Base class.

TextBase()
4367    def __init__(self):
4368        "Do not instantiate this base class."
4369
4370        self.rendered_at = set()
4371        self.properties = None
4372
4373        self.name = "Text"
4374        self.filename = ""
4375        self.time = 0
4376        self.info = {}
4377
4378        if isinstance(settings.default_font, int):
4379            lfonts = list(settings.font_parameters.keys())
4380            font = settings.default_font % len(lfonts)
4381            self.fontname = lfonts[font]
4382        else:
4383            self.fontname = settings.default_font

Do not instantiate this base class.

def angle(self, value: float):
4385    def angle(self, value: float):
4386        """Orientation angle in degrees"""
4387        self.properties.SetOrientation(value)
4388        return self

Orientation angle in degrees

def line_spacing(self, value: float):
4390    def line_spacing(self, value: float):
4391        """Set the extra spacing between lines
4392        expressed as a text height multiplicative factor."""
4393        self.properties.SetLineSpacing(value)
4394        return self

Set the extra spacing between lines expressed as a text height multiplicative factor.

def line_offset(self, value: float):
4396    def line_offset(self, value: float):
4397        """Set/Get the vertical offset (measured in pixels)."""
4398        self.properties.SetLineOffset(value)
4399        return self

Set/Get the vertical offset (measured in pixels).

def bold(self, value=True):
4401    def bold(self, value=True):
4402        """Set bold face"""
4403        self.properties.SetBold(value)
4404        return self

Set bold face

def italic(self, value=True):
4406    def italic(self, value=True):
4407        """Set italic face"""
4408        self.properties.SetItalic(value)
4409        return self

Set italic face

def shadow(self, offset=(1, -1)):
4411    def shadow(self, offset=(1, -1)):
4412        """Text shadowing. Set to `None` to disable it."""
4413        if offset is None:
4414            self.properties.ShadowOff()
4415        else:
4416            self.properties.ShadowOn()
4417            self.properties.SetShadowOffset(offset)
4418        return self

Text shadowing. Set to None to disable it.

def color(self, c=None):
4420    def color(self, c=None):
4421        """Set the text color"""
4422        if c is None:
4423            return get_color(self.properties.GetColor())
4424        self.properties.SetColor(get_color(c))
4425        return self

Set the text color

def c(self, color=None):
4427    def c(self, color=None):
4428        """Set the text color"""
4429        if color is None:
4430            return get_color(self.properties.GetColor())
4431        return self.color(color)

Set the text color

def alpha(self, value: float):
4433    def alpha(self, value: float):
4434        """Set the text opacity"""
4435        self.properties.SetBackgroundOpacity(value)
4436        return self

Set the text opacity

def background(self, color='k9', alpha=1.0):
4438    def background(self, color="k9", alpha=1.0):
4439        """Text background. Set to `None` to disable it."""
4440        bg = get_color(color)
4441        if color is None:
4442            self.properties.SetBackgroundOpacity(0)
4443        else:
4444            self.properties.SetBackgroundColor(bg)
4445            if alpha:
4446                self.properties.SetBackgroundOpacity(alpha)
4447        return self

Text background. Set to None to disable it.

def frame(self, color='k1', lw=2):
4449    def frame(self, color="k1", lw=2):
4450        """Border color and width"""
4451        if color is None:
4452            self.properties.FrameOff()
4453        else:
4454            c = get_color(color)
4455            self.properties.FrameOn()
4456            self.properties.SetFrameColor(c)
4457            self.properties.SetFrameWidth(lw)
4458        return self

Border color and width

def font(self, font: str):
4460    def font(self, font: str):
4461        """Text font face"""
4462        if isinstance(font, int):
4463            lfonts = list(settings.font_parameters.keys())
4464            n = font % len(lfonts)
4465            font = lfonts[n]
4466            self.fontname = font
4467
4468        if not font:  # use default font
4469            font = self.fontname
4470            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4471        elif font.startswith("https"):  # user passed URL link, make it a path
4472            fpath = vedo.file_io.download(font, verbose=False, force=False)
4473        elif font.endswith(".ttf"):  # user passing a local path to font file
4474            fpath = font
4475        else:  # user passing name of preset font
4476            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4477
4478        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4479        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4480        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4481        else:
4482            fpath = utils.get_font_path(font)
4483            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4484            self.properties.SetFontFile(fpath)
4485        self.fontname = font  # io.tonumpy() uses it
4486
4487        return self

Text font face

def on(self):
4489    def on(self):
4490        """Make text visible"""
4491        self.actor.SetVisibility(True)
4492        return self

Make text visible

def off(self):
4494    def off(self):
4495        """Make text invisible"""
4496        self.actor.SetVisibility(False)
4497        return self

Make text invisible

class Text3D(vedo.mesh.Mesh):
4025class Text3D(Mesh):
4026    """
4027    Generate a 3D polygonal Mesh to represent a text string.
4028    """
4029
4030    def __init__(
4031        self,
4032        txt,
4033        pos=(0, 0, 0),
4034        s=1.0,
4035        font="",
4036        hspacing=1.15,
4037        vspacing=2.15,
4038        depth=0.0,
4039        italic=False,
4040        justify="bottom-left",
4041        literal=False,
4042        c=None,
4043        alpha=1.0,
4044    ) -> None:
4045        """
4046        Generate a 3D polygonal `Mesh` representing a text string.
4047
4048        Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts.
4049        Most Latex symbols are also supported.
4050
4051        Symbols `~ ^ _` are reserved modifiers:
4052        - use ~ to add a short space, 1/4 of the default empty space,
4053        - use ^ and _ to start up/sub scripting, a space terminates their effect.
4054
4055        Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`.
4056
4057        More fonts at: https://vedo.embl.es/fonts/
4058
4059        Arguments:
4060            pos : (list)
4061                position coordinates in 3D space
4062            s : (float)
4063                vertical size of the text (as scaling factor)
4064            depth : (float)
4065                text thickness (along z)
4066            italic : (bool), float
4067                italic font type (can be a signed float too)
4068            justify : (str)
4069                text justification as centering of the bounding box
4070                (bottom-left, bottom-right, top-left, top-right, centered)
4071            font : (str, int)
4072                some of the available 3D-polygonized fonts are:
4073                Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu,
4074                LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK,
4075                Capsmall, Cartoons123, Vega, Justino, Spears, Meson.
4076
4077                Check for more at https://vedo.embl.es/fonts/
4078
4079                Or type in your terminal `vedo --run fonts`.
4080
4081                Default is Normografo, which can be changed using `settings.default_font`.
4082
4083            hspacing : (float)
4084                horizontal spacing of the font
4085            vspacing : (float)
4086                vertical spacing of the font for multiple lines text
4087            literal : (bool)
4088                if set to True will ignore modifiers like _ or ^
4089
4090        Examples:
4091            - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py)
4092            - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py)
4093            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
4094
4095            ![](https://vedo.embl.es/images/pyplot/fonts3d.png)
4096
4097        .. note:: Type `vedo -r fonts` for a demo.
4098        """
4099        if len(pos) == 2:
4100            pos = (pos[0], pos[1], 0)
4101
4102        if c is None:  # automatic black or white
4103            pli = vedo.plotter_instance
4104            if pli and pli.renderer:
4105                c = (0.9, 0.9, 0.9)
4106                if pli.renderer.GetGradientBackground():
4107                    bgcol = pli.renderer.GetBackground2()
4108                else:
4109                    bgcol = pli.renderer.GetBackground()
4110                if np.sum(bgcol) > 1.5:
4111                    c = (0.1, 0.1, 0.1)
4112            else:
4113                c = (0.6, 0.6, 0.6)
4114
4115        tpoly = self._get_text3d_poly(
4116            txt, s, font, hspacing, vspacing, depth, italic, justify, literal
4117        )
4118
4119        super().__init__(tpoly, c, alpha)
4120
4121        self.pos(pos)
4122        self.lighting("off")
4123
4124        self.actor.PickableOff()
4125        self.actor.DragableOff()
4126        self.init_scale = s
4127        self.name = "Text3D"
4128        self.txt = txt
4129        self.justify = justify
4130
4131    def text(
4132        self,
4133        txt=None,
4134        s=1,
4135        font="",
4136        hspacing=1.15,
4137        vspacing=2.15,
4138        depth=0,
4139        italic=False,
4140        justify="",
4141        literal=False,
4142    ) -> "Text3D":
4143        """
4144        Update the text and some of its properties.
4145
4146        Check [available fonts here](https://vedo.embl.es/fonts).
4147        """
4148        if txt is None:
4149            return self.txt
4150        if not justify:
4151            justify = self.justify
4152
4153        poly = self._get_text3d_poly(
4154            txt, self.init_scale * s, font, hspacing, vspacing,
4155            depth, italic, justify, literal
4156        )
4157
4158        # apply the current transformation to the new polydata
4159        tf = vtki.new("TransformPolyDataFilter")
4160        tf.SetInputData(poly)
4161        tf.SetTransform(self.transform.T)
4162        tf.Update()
4163        tpoly = tf.GetOutput()
4164
4165        self._update(tpoly)
4166        self.txt = txt
4167        return self
4168
4169    def _get_text3d_poly(
4170        self,
4171        txt,
4172        s=1,
4173        font="",
4174        hspacing=1.15,
4175        vspacing=2.15,
4176        depth=0,
4177        italic=False,
4178        justify="bottom-left",
4179        literal=False,
4180    ) -> vtki.vtkPolyData:
4181        if not font:
4182            font = settings.default_font
4183
4184        txt = str(txt)
4185
4186        if font == "VTK":  #######################################
4187            vtt = vtki.new("VectorText")
4188            vtt.SetText(txt)
4189            vtt.Update()
4190            tpoly = vtt.GetOutput()
4191
4192        else:  ###################################################
4193
4194            stxt = set(txt)  # check here if null or only spaces
4195            if not txt or (len(stxt) == 1 and " " in stxt):
4196                return vtki.vtkPolyData()
4197
4198            if italic is True:
4199                italic = 1
4200
4201            if isinstance(font, int):
4202                lfonts = list(settings.font_parameters.keys())
4203                font = font % len(lfonts)
4204                font = lfonts[font]
4205
4206            if font not in settings.font_parameters.keys():
4207                fpars = settings.font_parameters["Normografo"]
4208            else:
4209                fpars = settings.font_parameters[font]
4210
4211            # ad hoc adjustments
4212            mono = fpars["mono"]
4213            lspacing = fpars["lspacing"]
4214            hspacing *= fpars["hspacing"]
4215            fscale = fpars["fscale"]
4216            dotsep = fpars["dotsep"]
4217
4218            # replacements
4219            if ":" in txt:
4220                for r in _reps:
4221                    txt = txt.replace(r[0], r[1])
4222
4223            if not literal:
4224                reps2 = [
4225                    (r"\_", "┭"),  # trick to protect ~ _ and ^ chars
4226                    (r"\^", "┮"),  #
4227                    (r"\~", "┯"),  #
4228                    ("**", "^"),  # order matters
4229                    ("e+0", dotsep + "10^"),
4230                    ("e-0", dotsep + "10^-"),
4231                    ("E+0", dotsep + "10^"),
4232                    ("E-0", dotsep + "10^-"),
4233                    ("e+", dotsep + "10^"),
4234                    ("e-", dotsep + "10^-"),
4235                    ("E+", dotsep + "10^"),
4236                    ("E-", dotsep + "10^-"),
4237                ]
4238                for r in reps2:
4239                    txt = txt.replace(r[0], r[1])
4240
4241            xmax, ymax, yshift, scale = 0.0, 0.0, 0.0, 1.0
4242            save_xmax = 0.0
4243
4244            notfounds = set()
4245            polyletters = []
4246            ntxt = len(txt)
4247            for i, t in enumerate(txt):
4248                ##########
4249                if t == "┭":
4250                    t = "_"
4251                elif t == "┮":
4252                    t = "^"
4253                elif t == "┯":
4254                    t = "~"
4255                elif t == "^" and not literal:
4256                    if yshift < 0:
4257                        xmax = save_xmax
4258                    yshift = 0.9 * fscale
4259                    scale = 0.5
4260                    continue
4261                elif t == "_" and not literal:
4262                    if yshift > 0:
4263                        xmax = save_xmax
4264                    yshift = -0.3 * fscale
4265                    scale = 0.5
4266                    continue
4267                elif (t in (" ", "\\n")) and yshift:
4268                    yshift = 0.0
4269                    scale = 1.0
4270                    save_xmax = xmax
4271                    if t == " ":
4272                        continue
4273                elif t == "~":
4274                    if i < ntxt - 1 and txt[i + 1] == "_":
4275                        continue
4276                    xmax += hspacing * scale * fscale / 4
4277                    continue
4278
4279                ############
4280                if t == " ":
4281                    xmax += hspacing * scale * fscale
4282
4283                elif t == "\n":
4284                    xmax = 0.0
4285                    save_xmax = 0.0
4286                    ymax -= vspacing
4287
4288                else:
4289                    poly = _get_font_letter(font, t)
4290                    if not poly:
4291                        notfounds.add(t)
4292                        xmax += hspacing * scale * fscale
4293                        continue
4294                    
4295                    if poly.GetNumberOfPoints() == 0:
4296                        continue
4297
4298                    tr = vtki.vtkTransform()
4299                    tr.Translate(xmax, ymax + yshift, 0)
4300                    pscale = scale * fscale / 1000
4301                    tr.Scale(pscale, pscale, pscale)
4302                    if italic:
4303                        tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1])
4304                    tf = vtki.new("TransformPolyDataFilter")
4305                    tf.SetInputData(poly)
4306                    tf.SetTransform(tr)
4307                    tf.Update()
4308                    poly = tf.GetOutput()
4309                    polyletters.append(poly)
4310
4311                    bx = poly.GetBounds()
4312                    if mono:
4313                        xmax += hspacing * scale * fscale
4314                    else:
4315                        xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing
4316                    if yshift == 0:
4317                        save_xmax = xmax
4318
4319            if len(polyletters) == 1:
4320                tpoly = polyletters[0]
4321            else:
4322                polyapp = vtki.new("AppendPolyData")
4323                for polyd in polyletters:
4324                    polyapp.AddInputData(polyd)
4325                polyapp.Update()
4326                tpoly = polyapp.GetOutput()
4327
4328            if notfounds:
4329                wmsg = f"unavailable characters in font name '{font}': {notfounds}."
4330                wmsg += '\nType "vedo -r fonts" for a demo.'
4331                vedo.logger.warning(wmsg)
4332
4333        bb = tpoly.GetBounds()
4334        dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s
4335        shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2
4336        if "bottom" in justify: shift += np.array([  0, dy, 0.])
4337        if "top"    in justify: shift += np.array([  0,-dy, 0.])
4338        if "left"   in justify: shift += np.array([ dx,  0, 0.])
4339        if "right"  in justify: shift += np.array([-dx,  0, 0.])
4340
4341        if tpoly.GetNumberOfPoints():
4342            t = vtki.vtkTransform()
4343            t.PostMultiply()
4344            t.Scale(s, s, s)
4345            t.Translate(shift)
4346            tf = vtki.new("TransformPolyDataFilter")
4347            tf.SetInputData(tpoly)
4348            tf.SetTransform(t)
4349            tf.Update()
4350            tpoly = tf.GetOutput()
4351
4352            if depth:
4353                extrude = vtki.new("LinearExtrusionFilter")
4354                extrude.SetInputData(tpoly)
4355                extrude.SetExtrusionTypeToVectorExtrusion()
4356                extrude.SetVector(0, 0, 1)
4357                extrude.SetScaleFactor(depth * dy)
4358                extrude.Update()
4359                tpoly = extrude.GetOutput()
4360
4361        return tpoly

Generate a 3D polygonal Mesh to represent a text string.

Text3D( txt, pos=(0, 0, 0), s=1.0, font='', hspacing=1.15, vspacing=2.15, depth=0.0, italic=False, justify='bottom-left', literal=False, c=None, alpha=1.0)
4030    def __init__(
4031        self,
4032        txt,
4033        pos=(0, 0, 0),
4034        s=1.0,
4035        font="",
4036        hspacing=1.15,
4037        vspacing=2.15,
4038        depth=0.0,
4039        italic=False,
4040        justify="bottom-left",
4041        literal=False,
4042        c=None,
4043        alpha=1.0,
4044    ) -> None:
4045        """
4046        Generate a 3D polygonal `Mesh` representing a text string.
4047
4048        Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts.
4049        Most Latex symbols are also supported.
4050
4051        Symbols `~ ^ _` are reserved modifiers:
4052        - use ~ to add a short space, 1/4 of the default empty space,
4053        - use ^ and _ to start up/sub scripting, a space terminates their effect.
4054
4055        Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`.
4056
4057        More fonts at: https://vedo.embl.es/fonts/
4058
4059        Arguments:
4060            pos : (list)
4061                position coordinates in 3D space
4062            s : (float)
4063                vertical size of the text (as scaling factor)
4064            depth : (float)
4065                text thickness (along z)
4066            italic : (bool), float
4067                italic font type (can be a signed float too)
4068            justify : (str)
4069                text justification as centering of the bounding box
4070                (bottom-left, bottom-right, top-left, top-right, centered)
4071            font : (str, int)
4072                some of the available 3D-polygonized fonts are:
4073                Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu,
4074                LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK,
4075                Capsmall, Cartoons123, Vega, Justino, Spears, Meson.
4076
4077                Check for more at https://vedo.embl.es/fonts/
4078
4079                Or type in your terminal `vedo --run fonts`.
4080
4081                Default is Normografo, which can be changed using `settings.default_font`.
4082
4083            hspacing : (float)
4084                horizontal spacing of the font
4085            vspacing : (float)
4086                vertical spacing of the font for multiple lines text
4087            literal : (bool)
4088                if set to True will ignore modifiers like _ or ^
4089
4090        Examples:
4091            - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py)
4092            - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py)
4093            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
4094
4095            ![](https://vedo.embl.es/images/pyplot/fonts3d.png)
4096
4097        .. note:: Type `vedo -r fonts` for a demo.
4098        """
4099        if len(pos) == 2:
4100            pos = (pos[0], pos[1], 0)
4101
4102        if c is None:  # automatic black or white
4103            pli = vedo.plotter_instance
4104            if pli and pli.renderer:
4105                c = (0.9, 0.9, 0.9)
4106                if pli.renderer.GetGradientBackground():
4107                    bgcol = pli.renderer.GetBackground2()
4108                else:
4109                    bgcol = pli.renderer.GetBackground()
4110                if np.sum(bgcol) > 1.5:
4111                    c = (0.1, 0.1, 0.1)
4112            else:
4113                c = (0.6, 0.6, 0.6)
4114
4115        tpoly = self._get_text3d_poly(
4116            txt, s, font, hspacing, vspacing, depth, italic, justify, literal
4117        )
4118
4119        super().__init__(tpoly, c, alpha)
4120
4121        self.pos(pos)
4122        self.lighting("off")
4123
4124        self.actor.PickableOff()
4125        self.actor.DragableOff()
4126        self.init_scale = s
4127        self.name = "Text3D"
4128        self.txt = txt
4129        self.justify = justify

Generate a 3D polygonal Mesh representing a text string.

Can render strings like 3.7 10^9 or H_2 O with subscripts and superscripts. Most Latex symbols are also supported.

Symbols ~ ^ _ are reserved modifiers:

  • use ~ to add a short space, 1/4 of the default empty space,
  • use ^ and _ to start up/sub scripting, a space terminates their effect.

Monospaced fonts are: Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino.

More fonts at: https://vedo.embl.es/fonts/

Arguments:
  • pos : (list) position coordinates in 3D space
  • s : (float) vertical size of the text (as scaling factor)
  • depth : (float) text thickness (along z)
  • italic : (bool), float italic font type (can be a signed float too)
  • justify : (str) text justification as centering of the bounding box (bottom-left, bottom-right, top-left, top-right, centered)
  • font : (str, int) some of the available 3D-polygonized fonts are: Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, Capsmall, Cartoons123, Vega, Justino, Spears, Meson.

    Check for more at https://vedo.embl.es/fonts/

    Or type in your terminal vedo --run fonts.

    Default is Normografo, which can be changed using settings.default_font.

  • hspacing : (float) horizontal spacing of the font
  • vspacing : (float) vertical spacing of the font for multiple lines text
  • literal : (bool) if set to True will ignore modifiers like _ or ^
Examples:

Type vedo -r fonts for a demo.
def text( self, txt=None, s=1, font='', hspacing=1.15, vspacing=2.15, depth=0, italic=False, justify='', literal=False) -> Text3D:
4131    def text(
4132        self,
4133        txt=None,
4134        s=1,
4135        font="",
4136        hspacing=1.15,
4137        vspacing=2.15,
4138        depth=0,
4139        italic=False,
4140        justify="",
4141        literal=False,
4142    ) -> "Text3D":
4143        """
4144        Update the text and some of its properties.
4145
4146        Check [available fonts here](https://vedo.embl.es/fonts).
4147        """
4148        if txt is None:
4149            return self.txt
4150        if not justify:
4151            justify = self.justify
4152
4153        poly = self._get_text3d_poly(
4154            txt, self.init_scale * s, font, hspacing, vspacing,
4155            depth, italic, justify, literal
4156        )
4157
4158        # apply the current transformation to the new polydata
4159        tf = vtki.new("TransformPolyDataFilter")
4160        tf.SetInputData(poly)
4161        tf.SetTransform(self.transform.T)
4162        tf.Update()
4163        tpoly = tf.GetOutput()
4164
4165        self._update(tpoly)
4166        self.txt = txt
4167        return self

Update the text and some of its properties.

Check available fonts here.

class Text2D(TextBase, vedo.visual.Actor2D):
4499class Text2D(TextBase, vedo.visual.Actor2D):
4500    """
4501    Create a 2D text object.
4502    """
4503    def __init__(
4504        self,
4505        txt="",
4506        pos="top-left",
4507        s=1.0,
4508        bg=None,
4509        font="",
4510        justify="",
4511        bold=False,
4512        italic=False,
4513        c=None,
4514        alpha=0.5,
4515    ) -> None:
4516        """
4517        Create a 2D text object.
4518
4519        All properties of the text, and the text itself, can be changed after creation
4520        (which is especially useful in loops).
4521
4522        Arguments:
4523            pos : (str)
4524                text is placed in one of the 8 positions:
4525                - bottom-left
4526                - bottom-right
4527                - top-left
4528                - top-right
4529                - bottom-middle
4530                - middle-right
4531                - middle-left
4532                - top-middle
4533
4534                If a pair (x,y) is passed as input the 2D text is place at that
4535                position in the coordinate system of the 2D screen (with the
4536                origin sitting at the bottom left).
4537
4538            s : (float)
4539                size of text
4540            bg : (color)
4541                background color
4542            alpha : (float)
4543                background opacity
4544            justify : (str)
4545                text justification
4546
4547            font : (str)
4548                built-in available fonts are:
4549                - Antares
4550                - Arial
4551                - Bongas
4552                - Calco
4553                - Comae
4554                - ComicMono
4555                - Courier
4556                - Glasgo
4557                - Kanopus
4558                - LogoType
4559                - Normografo
4560                - Quikhand
4561                - SmartCouric
4562                - Theemim
4563                - Times
4564                - VictorMono
4565                - More fonts at: https://vedo.embl.es/fonts/
4566
4567                A path to a `.otf` or `.ttf` font-file can also be supplied as input.
4568
4569        Examples:
4570            - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py)
4571            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
4572            - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py)
4573
4574                ![](https://vedo.embl.es/images/basic/colorcubes.png)
4575        """
4576        super().__init__()
4577        self.name = "Text2D"
4578
4579        self.mapper = vtki.new("TextMapper")
4580        self.SetMapper(self.mapper)
4581
4582        self.properties = self.mapper.GetTextProperty()
4583        self.actor = self
4584        self.actor.retrieve_object = weak_ref_to(self)
4585
4586        self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport()
4587
4588        # automatic black or white
4589        if c is None:
4590            c = (0.1, 0.1, 0.1)
4591            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4592                if vedo.plotter_instance.renderer.GetGradientBackground():
4593                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4594                else:
4595                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4596                c = (0.9, 0.9, 0.9)
4597                if np.sum(bgcol) > 1.5:
4598                    c = (0.1, 0.1, 0.1)
4599
4600        self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic)
4601        self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5)
4602        self.PickableOff()
4603
4604    def pos(self, pos="top-left", justify=""):
4605        """
4606        Set position of the text to draw. Keyword `pos` can be a string
4607        or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4608        """
4609        ajustify = "top-left"  # autojustify
4610        if isinstance(pos, str):  # corners
4611            ajustify = pos
4612            if "top" in pos:
4613                if "left" in pos:
4614                    pos = (0.008, 0.994)
4615                elif "right" in pos:
4616                    pos = (0.994, 0.994)
4617                elif "mid" in pos or "cent" in pos:
4618                    pos = (0.5, 0.994)
4619            elif "bottom" in pos:
4620                if "left" in pos:
4621                    pos = (0.008, 0.008)
4622                elif "right" in pos:
4623                    pos = (0.994, 0.008)
4624                elif "mid" in pos or "cent" in pos:
4625                    pos = (0.5, 0.008)
4626            elif "mid" in pos or "cent" in pos:
4627                if "left" in pos:
4628                    pos = (0.008, 0.5)
4629                elif "right" in pos:
4630                    pos = (0.994, 0.5)
4631                else:
4632                    pos = (0.5, 0.5)
4633
4634            else:
4635                vedo.logger.warning(f"cannot understand text position {pos}")
4636                pos = (0.008, 0.994)
4637                ajustify = "top-left"
4638
4639        elif len(pos) != 2:
4640            vedo.logger.error("pos must be of length 2 or integer value or string")
4641            raise RuntimeError()
4642
4643        if not justify:
4644            justify = ajustify
4645
4646        self.properties.SetJustificationToLeft()
4647        if "top" in justify:
4648            self.properties.SetVerticalJustificationToTop()
4649        if "bottom" in justify:
4650            self.properties.SetVerticalJustificationToBottom()
4651        if "cent" in justify or "mid" in justify:
4652            self.properties.SetJustificationToCentered()
4653        if "left" in justify:
4654            self.properties.SetJustificationToLeft()
4655        if "right" in justify:
4656            self.properties.SetJustificationToRight()
4657
4658        self.SetPosition(pos)
4659        return self
4660
4661    def text(self, txt=None):
4662        """Set/get the input text string."""
4663        if txt is None:
4664            return self.mapper.GetInput()
4665
4666        if ":" in txt:
4667            for r in _reps:
4668                txt = txt.replace(r[0], r[1])
4669        else:
4670            txt = str(txt)
4671
4672        self.mapper.SetInput(txt)
4673        return self
4674
4675    def size(self, s):
4676        """Set the font size."""
4677        self.properties.SetFontSize(int(s * 22.5))
4678        return self

Create a 2D text object.

Text2D( txt='', pos='top-left', s=1.0, bg=None, font='', justify='', bold=False, italic=False, c=None, alpha=0.5)
4503    def __init__(
4504        self,
4505        txt="",
4506        pos="top-left",
4507        s=1.0,
4508        bg=None,
4509        font="",
4510        justify="",
4511        bold=False,
4512        italic=False,
4513        c=None,
4514        alpha=0.5,
4515    ) -> None:
4516        """
4517        Create a 2D text object.
4518
4519        All properties of the text, and the text itself, can be changed after creation
4520        (which is especially useful in loops).
4521
4522        Arguments:
4523            pos : (str)
4524                text is placed in one of the 8 positions:
4525                - bottom-left
4526                - bottom-right
4527                - top-left
4528                - top-right
4529                - bottom-middle
4530                - middle-right
4531                - middle-left
4532                - top-middle
4533
4534                If a pair (x,y) is passed as input the 2D text is place at that
4535                position in the coordinate system of the 2D screen (with the
4536                origin sitting at the bottom left).
4537
4538            s : (float)
4539                size of text
4540            bg : (color)
4541                background color
4542            alpha : (float)
4543                background opacity
4544            justify : (str)
4545                text justification
4546
4547            font : (str)
4548                built-in available fonts are:
4549                - Antares
4550                - Arial
4551                - Bongas
4552                - Calco
4553                - Comae
4554                - ComicMono
4555                - Courier
4556                - Glasgo
4557                - Kanopus
4558                - LogoType
4559                - Normografo
4560                - Quikhand
4561                - SmartCouric
4562                - Theemim
4563                - Times
4564                - VictorMono
4565                - More fonts at: https://vedo.embl.es/fonts/
4566
4567                A path to a `.otf` or `.ttf` font-file can also be supplied as input.
4568
4569        Examples:
4570            - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py)
4571            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
4572            - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py)
4573
4574                ![](https://vedo.embl.es/images/basic/colorcubes.png)
4575        """
4576        super().__init__()
4577        self.name = "Text2D"
4578
4579        self.mapper = vtki.new("TextMapper")
4580        self.SetMapper(self.mapper)
4581
4582        self.properties = self.mapper.GetTextProperty()
4583        self.actor = self
4584        self.actor.retrieve_object = weak_ref_to(self)
4585
4586        self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport()
4587
4588        # automatic black or white
4589        if c is None:
4590            c = (0.1, 0.1, 0.1)
4591            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4592                if vedo.plotter_instance.renderer.GetGradientBackground():
4593                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4594                else:
4595                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4596                c = (0.9, 0.9, 0.9)
4597                if np.sum(bgcol) > 1.5:
4598                    c = (0.1, 0.1, 0.1)
4599
4600        self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic)
4601        self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5)
4602        self.PickableOff()

Create a 2D text object.

All properties of the text, and the text itself, can be changed after creation (which is especially useful in loops).

Arguments:
  • pos : (str) text is placed in one of the 8 positions:

    • bottom-left
    • bottom-right
    • top-left
    • top-right
    • bottom-middle
    • middle-right
    • middle-left
    • top-middle

    If a pair (x,y) is passed as input the 2D text is place at that position in the coordinate system of the 2D screen (with the origin sitting at the bottom left).

  • s : (float) size of text
  • bg : (color) background color
  • alpha : (float) background opacity
  • justify : (str) text justification
  • font : (str) built-in available fonts are:

    • Antares
    • Arial
    • Bongas
    • Calco
    • Comae
    • ComicMono
    • Courier
    • Glasgo
    • Kanopus
    • LogoType
    • Normografo
    • Quikhand
    • SmartCouric
    • Theemim
    • Times
    • VictorMono
    • More fonts at: https://vedo.embl.es/fonts/

    A path to a .otf or .ttf font-file can also be supplied as input.

Examples:
mapper
555    @property
556    def mapper(self):
557        """Get the internal vtkMapper."""
558        return self.GetMapper()

Get the internal vtkMapper.

def pos(self, pos='top-left', justify=''):
4604    def pos(self, pos="top-left", justify=""):
4605        """
4606        Set position of the text to draw. Keyword `pos` can be a string
4607        or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4608        """
4609        ajustify = "top-left"  # autojustify
4610        if isinstance(pos, str):  # corners
4611            ajustify = pos
4612            if "top" in pos:
4613                if "left" in pos:
4614                    pos = (0.008, 0.994)
4615                elif "right" in pos:
4616                    pos = (0.994, 0.994)
4617                elif "mid" in pos or "cent" in pos:
4618                    pos = (0.5, 0.994)
4619            elif "bottom" in pos:
4620                if "left" in pos:
4621                    pos = (0.008, 0.008)
4622                elif "right" in pos:
4623                    pos = (0.994, 0.008)
4624                elif "mid" in pos or "cent" in pos:
4625                    pos = (0.5, 0.008)
4626            elif "mid" in pos or "cent" in pos:
4627                if "left" in pos:
4628                    pos = (0.008, 0.5)
4629                elif "right" in pos:
4630                    pos = (0.994, 0.5)
4631                else:
4632                    pos = (0.5, 0.5)
4633
4634            else:
4635                vedo.logger.warning(f"cannot understand text position {pos}")
4636                pos = (0.008, 0.994)
4637                ajustify = "top-left"
4638
4639        elif len(pos) != 2:
4640            vedo.logger.error("pos must be of length 2 or integer value or string")
4641            raise RuntimeError()
4642
4643        if not justify:
4644            justify = ajustify
4645
4646        self.properties.SetJustificationToLeft()
4647        if "top" in justify:
4648            self.properties.SetVerticalJustificationToTop()
4649        if "bottom" in justify:
4650            self.properties.SetVerticalJustificationToBottom()
4651        if "cent" in justify or "mid" in justify:
4652            self.properties.SetJustificationToCentered()
4653        if "left" in justify:
4654            self.properties.SetJustificationToLeft()
4655        if "right" in justify:
4656            self.properties.SetJustificationToRight()
4657
4658        self.SetPosition(pos)
4659        return self

Set position of the text to draw. Keyword pos can be a string or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.

def text(self, txt=None):
4661    def text(self, txt=None):
4662        """Set/get the input text string."""
4663        if txt is None:
4664            return self.mapper.GetInput()
4665
4666        if ":" in txt:
4667            for r in _reps:
4668                txt = txt.replace(r[0], r[1])
4669        else:
4670            txt = str(txt)
4671
4672        self.mapper.SetInput(txt)
4673        return self

Set/get the input text string.

def size(self, s):
4675    def size(self, s):
4676        """Set the font size."""
4677        self.properties.SetFontSize(int(s * 22.5))
4678        return self

Set the font size.

class CornerAnnotation(TextBase, vtkmodules.vtkRenderingAnnotation.vtkCornerAnnotation):
4681class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation):
4682    # PROBABLY USELESS given that Text2D does pretty much the same ...
4683    """
4684    Annotate the window corner with 2D text.
4685
4686    See `Text2D` description as the basic functionality is very similar.
4687
4688    The added value of this class is the possibility to manage with one single
4689    object the all corner annotations (instead of creating 4 `Text2D` instances).
4690
4691    Examples:
4692        - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
4693    """
4694
4695    def __init__(self, c=None) -> None:
4696
4697        super().__init__()
4698
4699        self.properties = self.GetTextProperty()
4700
4701        # automatic black or white
4702        if c is None:
4703            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4704                c = (0.9, 0.9, 0.9)
4705                if vedo.plotter_instance.renderer.GetGradientBackground():
4706                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4707                else:
4708                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4709                if np.sum(bgcol) > 1.5:
4710                    c = (0.1, 0.1, 0.1)
4711            else:
4712                c = (0.5, 0.5, 0.5)
4713
4714        self.SetNonlinearFontScaleFactor(1 / 2.75)
4715        self.PickableOff()
4716        self.properties.SetColor(get_color(c))
4717        self.properties.SetBold(False)
4718        self.properties.SetItalic(False)
4719
4720    def size(self, s:float, linear=False) -> "CornerAnnotation":
4721        """
4722        The font size is calculated as the largest possible value such that the annotations
4723        for the given viewport do not overlap.
4724
4725        This font size can be scaled non-linearly with the viewport size, to maintain an
4726        acceptable readable size at larger viewport sizes, without being too big.
4727        `f' = linearScale * pow(f,nonlinearScale)`
4728        """
4729        if linear:
4730            self.SetLinearFontScaleFactor(s * 5.5)
4731        else:
4732            self.SetNonlinearFontScaleFactor(s / 2.75)
4733        return self
4734
4735    def text(self, txt: str, pos=2) -> "CornerAnnotation":
4736        """Set text at the assigned position"""
4737
4738        if isinstance(pos, str):  # corners
4739            if "top" in pos:
4740                if "left" in pos: pos = 2
4741                elif "right" in pos: pos = 3
4742                elif "mid" in pos or "cent" in pos: pos = 7
4743            elif "bottom" in pos:
4744                if "left" in pos: pos = 0
4745                elif "right" in pos: pos = 1
4746                elif "mid" in pos or "cent" in pos: pos = 4
4747            else:
4748                if "left" in pos: pos = 6
4749                elif "right" in pos: pos = 5
4750                else: pos = 2
4751
4752        if "\\" in repr(txt):
4753            for r in _reps:
4754                txt = txt.replace(r[0], r[1])
4755        else:
4756            txt = str(txt)
4757
4758        self.SetText(pos, txt)
4759        return self
4760
4761    def clear(self) -> "CornerAnnotation":
4762        """Remove all text from all corners"""
4763        self.ClearAllTexts()
4764        return self

Annotate the window corner with 2D text.

See Text2D description as the basic functionality is very similar.

The added value of this class is the possibility to manage with one single object the all corner annotations (instead of creating 4 Text2D instances).

Examples:
CornerAnnotation(c=None)
4695    def __init__(self, c=None) -> None:
4696
4697        super().__init__()
4698
4699        self.properties = self.GetTextProperty()
4700
4701        # automatic black or white
4702        if c is None:
4703            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4704                c = (0.9, 0.9, 0.9)
4705                if vedo.plotter_instance.renderer.GetGradientBackground():
4706                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4707                else:
4708                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4709                if np.sum(bgcol) > 1.5:
4710                    c = (0.1, 0.1, 0.1)
4711            else:
4712                c = (0.5, 0.5, 0.5)
4713
4714        self.SetNonlinearFontScaleFactor(1 / 2.75)
4715        self.PickableOff()
4716        self.properties.SetColor(get_color(c))
4717        self.properties.SetBold(False)
4718        self.properties.SetItalic(False)

Do not instantiate this base class.

def size(self, s: float, linear=False) -> CornerAnnotation:
4720    def size(self, s:float, linear=False) -> "CornerAnnotation":
4721        """
4722        The font size is calculated as the largest possible value such that the annotations
4723        for the given viewport do not overlap.
4724
4725        This font size can be scaled non-linearly with the viewport size, to maintain an
4726        acceptable readable size at larger viewport sizes, without being too big.
4727        `f' = linearScale * pow(f,nonlinearScale)`
4728        """
4729        if linear:
4730            self.SetLinearFontScaleFactor(s * 5.5)
4731        else:
4732            self.SetNonlinearFontScaleFactor(s / 2.75)
4733        return self

The font size is calculated as the largest possible value such that the annotations for the given viewport do not overlap.

This font size can be scaled non-linearly with the viewport size, to maintain an acceptable readable size at larger viewport sizes, without being too big. f' = linearScale * pow(f,nonlinearScale)

def text(self, txt: str, pos=2) -> CornerAnnotation:
4735    def text(self, txt: str, pos=2) -> "CornerAnnotation":
4736        """Set text at the assigned position"""
4737
4738        if isinstance(pos, str):  # corners
4739            if "top" in pos:
4740                if "left" in pos: pos = 2
4741                elif "right" in pos: pos = 3
4742                elif "mid" in pos or "cent" in pos: pos = 7
4743            elif "bottom" in pos:
4744                if "left" in pos: pos = 0
4745                elif "right" in pos: pos = 1
4746                elif "mid" in pos or "cent" in pos: pos = 4
4747            else:
4748                if "left" in pos: pos = 6
4749                elif "right" in pos: pos = 5
4750                else: pos = 2
4751
4752        if "\\" in repr(txt):
4753            for r in _reps:
4754                txt = txt.replace(r[0], r[1])
4755        else:
4756            txt = str(txt)
4757
4758        self.SetText(pos, txt)
4759        return self

Set text at the assigned position

def clear(self) -> CornerAnnotation:
4761    def clear(self) -> "CornerAnnotation":
4762        """Remove all text from all corners"""
4763        self.ClearAllTexts()
4764        return self

Remove all text from all corners

class Latex(vedo.image.Image):
4767class Latex(Image):
4768    """
4769    Render Latex text and formulas.
4770    """
4771
4772    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4773        """
4774        Render Latex text and formulas.
4775
4776        Arguments:
4777            formula : (str)
4778                latex text string
4779            pos : (list)
4780                position coordinates in space
4781            bg : (color)
4782                background color box
4783            res : (int)
4784                dpi resolution
4785            usetex : (bool)
4786                use latex compiler of matplotlib if available
4787
4788        You can access the latex formula in `Latex.formula`.
4789
4790        Examples:
4791            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4792
4793            ![](https://vedo.embl.es/images/pyplot/latex.png)
4794        """
4795        from tempfile import NamedTemporaryFile
4796        import matplotlib.pyplot as mpltib
4797
4798        def build_img_plt(formula, tfile):
4799
4800            mpltib.rc("text", usetex=usetex)
4801
4802            formula1 = "$" + formula + "$"
4803            mpltib.axis("off")
4804            col = get_color(c)
4805            if bg:
4806                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4807            else:
4808                bx = None
4809            mpltib.text(
4810                0.5,
4811                0.5,
4812                formula1,
4813                size=res,
4814                color=col,
4815                alpha=alpha,
4816                ha="center",
4817                va="center",
4818                bbox=bx,
4819            )
4820            mpltib.savefig(
4821                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4822            )
4823            mpltib.close()
4824
4825        if len(pos) == 2:
4826            pos = (pos[0], pos[1], 0)
4827
4828        tmp_file = NamedTemporaryFile(delete=True)
4829        tmp_file.name = tmp_file.name + ".png"
4830
4831        build_img_plt(formula, tmp_file.name)
4832
4833        super().__init__(tmp_file.name, channels=4)
4834        self.alpha(alpha)
4835        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4836        self.pos(pos)
4837        self.name = "Latex"
4838        self.formula = formula
4839
4840        # except:
4841        #     printc("Error in Latex()\n", formula, c="r")
4842        #     printc(" latex or dvipng not installed?", c="r")
4843        #     printc(" Try: usetex=False", c="r")
4844        #     printc(" Try: sudo apt install dvipng", c="r")

Render Latex text and formulas.

Latex( formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c='k', alpha=1.0)
4772    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4773        """
4774        Render Latex text and formulas.
4775
4776        Arguments:
4777            formula : (str)
4778                latex text string
4779            pos : (list)
4780                position coordinates in space
4781            bg : (color)
4782                background color box
4783            res : (int)
4784                dpi resolution
4785            usetex : (bool)
4786                use latex compiler of matplotlib if available
4787
4788        You can access the latex formula in `Latex.formula`.
4789
4790        Examples:
4791            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4792
4793            ![](https://vedo.embl.es/images/pyplot/latex.png)
4794        """
4795        from tempfile import NamedTemporaryFile
4796        import matplotlib.pyplot as mpltib
4797
4798        def build_img_plt(formula, tfile):
4799
4800            mpltib.rc("text", usetex=usetex)
4801
4802            formula1 = "$" + formula + "$"
4803            mpltib.axis("off")
4804            col = get_color(c)
4805            if bg:
4806                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4807            else:
4808                bx = None
4809            mpltib.text(
4810                0.5,
4811                0.5,
4812                formula1,
4813                size=res,
4814                color=col,
4815                alpha=alpha,
4816                ha="center",
4817                va="center",
4818                bbox=bx,
4819            )
4820            mpltib.savefig(
4821                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4822            )
4823            mpltib.close()
4824
4825        if len(pos) == 2:
4826            pos = (pos[0], pos[1], 0)
4827
4828        tmp_file = NamedTemporaryFile(delete=True)
4829        tmp_file.name = tmp_file.name + ".png"
4830
4831        build_img_plt(formula, tmp_file.name)
4832
4833        super().__init__(tmp_file.name, channels=4)
4834        self.alpha(alpha)
4835        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4836        self.pos(pos)
4837        self.name = "Latex"
4838        self.formula = formula
4839
4840        # except:
4841        #     printc("Error in Latex()\n", formula, c="r")
4842        #     printc(" latex or dvipng not installed?", c="r")
4843        #     printc(" Try: usetex=False", c="r")
4844        #     printc(" Try: sudo apt install dvipng", c="r")

Render Latex text and formulas.

Arguments:
  • formula : (str) latex text string
  • pos : (list) position coordinates in space
  • bg : (color) background color box
  • res : (int) dpi resolution
  • usetex : (bool) use latex compiler of matplotlib if available

You can access the latex formula in Latex.formula.

Examples:

class Glyph(vedo.mesh.Mesh):
153class Glyph(Mesh):
154    """
155    At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with
156    various orientation options and coloring.
157
158    The input can also be a simple list of 2D or 3D coordinates.
159    Color can be specified as a colormap which maps the size of the orientation
160    vectors in `orientation_array`.
161    """
162
163    def __init__(
164        self,
165        mesh,
166        glyph,
167        orientation_array=None,
168        scale_by_scalar=False,
169        scale_by_vector_size=False,
170        scale_by_vector_components=False,
171        color_by_scalar=False,
172        color_by_vector_size=False,
173        c="k8",
174        alpha=1.0,
175    ) -> None:
176        """
177        Arguments:
178            orientation_array: (list, str, vtkArray)
179                list of vectors, `vtkArray` or name of an already existing pointdata array
180            scale_by_scalar : (bool)
181                glyph mesh is scaled by the active scalars
182            scale_by_vector_size : (bool)
183                glyph mesh is scaled by the size of the vectors
184            scale_by_vector_components : (bool)
185                glyph mesh is scaled by the 3 vectors components
186            color_by_scalar : (bool)
187                glyph mesh is colored based on the scalar value
188            color_by_vector_size : (bool)
189                glyph mesh is colored based on the vector size
190
191        Examples:
192            - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py)
193            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
194
195            ![](https://vedo.embl.es/images/basic/glyphs.png)
196        """
197        if utils.is_sequence(mesh):
198            # create a cloud of points
199            poly = utils.buildPolyData(mesh)
200        else:
201            poly = mesh.dataset
202
203        cmap = ""
204        if isinstance(c, str) and c in cmaps_names:
205            cmap = c
206            c = None
207        elif utils.is_sequence(c):  # user passing an array of point colors
208            ucols = vtki.vtkUnsignedCharArray()
209            ucols.SetNumberOfComponents(3)
210            ucols.SetName("GlyphRGB")
211            for col in c:
212                cl = get_color(col)
213                ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255)
214            poly.GetPointData().AddArray(ucols)
215            poly.GetPointData().SetActiveScalars("GlyphRGB")
216            c = None
217
218        gly = vtki.vtkGlyph3D()
219        gly.GeneratePointIdsOn()
220        gly.SetInputData(poly)
221        try:
222            gly.SetSourceData(glyph)
223        except TypeError:
224            gly.SetSourceData(glyph.dataset)
225
226        if scale_by_scalar:
227            gly.SetScaleModeToScaleByScalar()
228        elif scale_by_vector_size:
229            gly.SetScaleModeToScaleByVector()
230        elif scale_by_vector_components:
231            gly.SetScaleModeToScaleByVectorComponents()
232        else:
233            gly.SetScaleModeToDataScalingOff()
234
235        if color_by_vector_size:
236            gly.SetVectorModeToUseVector()
237            gly.SetColorModeToColorByVector()
238        elif color_by_scalar:
239            gly.SetColorModeToColorByScalar()
240        else:
241            gly.SetColorModeToColorByScale()
242
243        if orientation_array is not None:
244            gly.OrientOn()
245            if isinstance(orientation_array, str):
246                if orientation_array.lower() == "normals":
247                    gly.SetVectorModeToUseNormal()
248                else:  # passing a name
249                    poly.GetPointData().SetActiveVectors(orientation_array)
250                    gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array)
251                    gly.SetVectorModeToUseVector()
252            elif utils.is_sequence(orientation_array):  # passing a list
253                varr = vtki.vtkFloatArray()
254                varr.SetNumberOfComponents(3)
255                varr.SetName("glyph_vectors")
256                for v in orientation_array:
257                    varr.InsertNextTuple(v)
258                poly.GetPointData().AddArray(varr)
259                poly.GetPointData().SetActiveVectors("glyph_vectors")
260                gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors")
261                gly.SetVectorModeToUseVector()
262
263        gly.Update()
264
265        super().__init__(gly.GetOutput(), c, alpha)
266        self.flat()
267
268        if cmap:
269            self.cmap(cmap, "VectorMagnitude")
270        elif c is None:
271            self.pointdata.select("GlyphRGB")
272
273        self.name = "Glyph"

At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with various orientation options and coloring.

The input can also be a simple list of 2D or 3D coordinates. Color can be specified as a colormap which maps the size of the orientation vectors in orientation_array.

Glyph( mesh, glyph, orientation_array=None, scale_by_scalar=False, scale_by_vector_size=False, scale_by_vector_components=False, color_by_scalar=False, color_by_vector_size=False, c='k8', alpha=1.0)
163    def __init__(
164        self,
165        mesh,
166        glyph,
167        orientation_array=None,
168        scale_by_scalar=False,
169        scale_by_vector_size=False,
170        scale_by_vector_components=False,
171        color_by_scalar=False,
172        color_by_vector_size=False,
173        c="k8",
174        alpha=1.0,
175    ) -> None:
176        """
177        Arguments:
178            orientation_array: (list, str, vtkArray)
179                list of vectors, `vtkArray` or name of an already existing pointdata array
180            scale_by_scalar : (bool)
181                glyph mesh is scaled by the active scalars
182            scale_by_vector_size : (bool)
183                glyph mesh is scaled by the size of the vectors
184            scale_by_vector_components : (bool)
185                glyph mesh is scaled by the 3 vectors components
186            color_by_scalar : (bool)
187                glyph mesh is colored based on the scalar value
188            color_by_vector_size : (bool)
189                glyph mesh is colored based on the vector size
190
191        Examples:
192            - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py)
193            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
194
195            ![](https://vedo.embl.es/images/basic/glyphs.png)
196        """
197        if utils.is_sequence(mesh):
198            # create a cloud of points
199            poly = utils.buildPolyData(mesh)
200        else:
201            poly = mesh.dataset
202
203        cmap = ""
204        if isinstance(c, str) and c in cmaps_names:
205            cmap = c
206            c = None
207        elif utils.is_sequence(c):  # user passing an array of point colors
208            ucols = vtki.vtkUnsignedCharArray()
209            ucols.SetNumberOfComponents(3)
210            ucols.SetName("GlyphRGB")
211            for col in c:
212                cl = get_color(col)
213                ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255)
214            poly.GetPointData().AddArray(ucols)
215            poly.GetPointData().SetActiveScalars("GlyphRGB")
216            c = None
217
218        gly = vtki.vtkGlyph3D()
219        gly.GeneratePointIdsOn()
220        gly.SetInputData(poly)
221        try:
222            gly.SetSourceData(glyph)
223        except TypeError:
224            gly.SetSourceData(glyph.dataset)
225
226        if scale_by_scalar:
227            gly.SetScaleModeToScaleByScalar()
228        elif scale_by_vector_size:
229            gly.SetScaleModeToScaleByVector()
230        elif scale_by_vector_components:
231            gly.SetScaleModeToScaleByVectorComponents()
232        else:
233            gly.SetScaleModeToDataScalingOff()
234
235        if color_by_vector_size:
236            gly.SetVectorModeToUseVector()
237            gly.SetColorModeToColorByVector()
238        elif color_by_scalar:
239            gly.SetColorModeToColorByScalar()
240        else:
241            gly.SetColorModeToColorByScale()
242
243        if orientation_array is not None:
244            gly.OrientOn()
245            if isinstance(orientation_array, str):
246                if orientation_array.lower() == "normals":
247                    gly.SetVectorModeToUseNormal()
248                else:  # passing a name
249                    poly.GetPointData().SetActiveVectors(orientation_array)
250                    gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array)
251                    gly.SetVectorModeToUseVector()
252            elif utils.is_sequence(orientation_array):  # passing a list
253                varr = vtki.vtkFloatArray()
254                varr.SetNumberOfComponents(3)
255                varr.SetName("glyph_vectors")
256                for v in orientation_array:
257                    varr.InsertNextTuple(v)
258                poly.GetPointData().AddArray(varr)
259                poly.GetPointData().SetActiveVectors("glyph_vectors")
260                gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors")
261                gly.SetVectorModeToUseVector()
262
263        gly.Update()
264
265        super().__init__(gly.GetOutput(), c, alpha)
266        self.flat()
267
268        if cmap:
269            self.cmap(cmap, "VectorMagnitude")
270        elif c is None:
271            self.pointdata.select("GlyphRGB")
272
273        self.name = "Glyph"
Arguments:
  • orientation_array: (list, str, vtkArray) list of vectors, vtkArray or name of an already existing pointdata array
  • scale_by_scalar : (bool) glyph mesh is scaled by the active scalars
  • scale_by_vector_size : (bool) glyph mesh is scaled by the size of the vectors
  • scale_by_vector_components : (bool) glyph mesh is scaled by the 3 vectors components
  • color_by_scalar : (bool) glyph mesh is colored based on the scalar value
  • color_by_vector_size : (bool) glyph mesh is colored based on the vector size
Examples:

class Tensors(vedo.mesh.Mesh):
276class Tensors(Mesh):
277    """
278    Geometric representation of tensors defined on a domain or set of points.
279    Tensors can be scaled and/or rotated according to the source at each input point.
280    Scaling and rotation is controlled by the eigenvalues/eigenvectors of the
281    symmetrical part of the tensor as follows:
282
283    For each tensor, the eigenvalues (and associated eigenvectors) are sorted
284    to determine the major, medium, and minor eigenvalues/eigenvectors.
285    The eigenvalue decomposition only makes sense for symmetric tensors,
286    hence the need to only consider the symmetric part of the tensor,
287    which is `1/2*(T+T.transposed())`.
288    """
289
290    def __init__(
291        self,
292        domain,
293        source="ellipsoid",
294        use_eigenvalues=True,
295        is_symmetric=True,
296        three_axes=False,
297        scale=1.0,
298        max_scale=None,
299        length=None,
300        res=24,
301        c=None,
302        alpha=1.0,
303    ) -> None:
304        """
305        Arguments:
306            source : (str, Mesh)
307                preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object.
308            use_eigenvalues : (bool)
309                color source glyph using the eigenvalues or by scalars
310            three_axes : (bool)
311                if `False` scale the source in the x-direction,
312                the medium in the y-direction, and the minor in the z-direction.
313                Then, the source is rotated so that the glyph's local x-axis lies
314                along the major eigenvector, y-axis along the medium eigenvector,
315                and z-axis along the minor.
316
317                If `True` three sources are produced, each of them oriented along an eigenvector
318                and scaled according to the corresponding eigenvector.
319            is_symmetric : (bool)
320                If `True` each source glyph is mirrored (2 or 6 glyphs will be produced).
321                The x-axis of the source glyph will correspond to the eigenvector on output.
322            length : (float)
323                distance from the origin to the tip of the source glyph along the x-axis
324            scale : (float)
325                scaling factor of the source glyph.
326            max_scale : (float)
327                clamp scaling at this factor.
328
329        Examples:
330            - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py)
331            - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py)
332            - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py)
333
334            ![](https://vedo.embl.es/images/volumetric/tensor_grid.png)
335        """
336        if isinstance(source, Points):
337            src = source.dataset
338        else: # is string
339            if "ellip" in source:
340                src = vtki.new("SphereSource")
341                src.SetPhiResolution(res)
342                src.SetThetaResolution(res*2)
343            elif "cyl" in source:
344                src = vtki.new("CylinderSource")
345                src.SetResolution(res)
346                src.CappingOn()
347            elif source == "cube":
348                src = vtki.new("CubeSource")
349            else:
350                vedo.logger.error(f"Unknown source type {source}")
351                raise ValueError()
352            src.Update()
353            src = src.GetOutput()
354
355        tg = vtki.new("TensorGlyph")
356        if isinstance(domain, vtki.vtkPolyData):
357            tg.SetInputData(domain)
358        else:
359            tg.SetInputData(domain.dataset)
360        tg.SetSourceData(src)
361
362        if c is None:
363            tg.ColorGlyphsOn()
364        else:
365            tg.ColorGlyphsOff()
366
367        tg.SetSymmetric(int(is_symmetric))
368
369        if length is not None:
370            tg.SetLength(length)
371        if use_eigenvalues:
372            tg.ExtractEigenvaluesOn()
373            tg.SetColorModeToEigenvalues()
374        else:
375            tg.SetColorModeToScalars()
376
377        tg.SetThreeGlyphs(three_axes)
378        tg.ScalingOn()
379        tg.SetScaleFactor(scale)
380        if max_scale is None:
381            tg.ClampScalingOn()
382            max_scale = scale * 10
383        tg.SetMaxScaleFactor(max_scale)
384
385        tg.Update()
386        tgn = vtki.new("PolyDataNormals")
387        tgn.ComputeCellNormalsOff()
388        tgn.SetInputData(tg.GetOutput())
389        tgn.Update()
390
391        super().__init__(tgn.GetOutput(), c, alpha)
392        self.name = "Tensors"

Geometric representation of tensors defined on a domain or set of points. Tensors can be scaled and/or rotated according to the source at each input point. Scaling and rotation is controlled by the eigenvalues/eigenvectors of the symmetrical part of the tensor as follows:

For each tensor, the eigenvalues (and associated eigenvectors) are sorted to determine the major, medium, and minor eigenvalues/eigenvectors. The eigenvalue decomposition only makes sense for symmetric tensors, hence the need to only consider the symmetric part of the tensor, which is 1/2*(T+T.transposed()).

Tensors( domain, source='ellipsoid', use_eigenvalues=True, is_symmetric=True, three_axes=False, scale=1.0, max_scale=None, length=None, res=24, c=None, alpha=1.0)
290    def __init__(
291        self,
292        domain,
293        source="ellipsoid",
294        use_eigenvalues=True,
295        is_symmetric=True,
296        three_axes=False,
297        scale=1.0,
298        max_scale=None,
299        length=None,
300        res=24,
301        c=None,
302        alpha=1.0,
303    ) -> None:
304        """
305        Arguments:
306            source : (str, Mesh)
307                preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object.
308            use_eigenvalues : (bool)
309                color source glyph using the eigenvalues or by scalars
310            three_axes : (bool)
311                if `False` scale the source in the x-direction,
312                the medium in the y-direction, and the minor in the z-direction.
313                Then, the source is rotated so that the glyph's local x-axis lies
314                along the major eigenvector, y-axis along the medium eigenvector,
315                and z-axis along the minor.
316
317                If `True` three sources are produced, each of them oriented along an eigenvector
318                and scaled according to the corresponding eigenvector.
319            is_symmetric : (bool)
320                If `True` each source glyph is mirrored (2 or 6 glyphs will be produced).
321                The x-axis of the source glyph will correspond to the eigenvector on output.
322            length : (float)
323                distance from the origin to the tip of the source glyph along the x-axis
324            scale : (float)
325                scaling factor of the source glyph.
326            max_scale : (float)
327                clamp scaling at this factor.
328
329        Examples:
330            - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py)
331            - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py)
332            - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py)
333
334            ![](https://vedo.embl.es/images/volumetric/tensor_grid.png)
335        """
336        if isinstance(source, Points):
337            src = source.dataset
338        else: # is string
339            if "ellip" in source:
340                src = vtki.new("SphereSource")
341                src.SetPhiResolution(res)
342                src.SetThetaResolution(res*2)
343            elif "cyl" in source:
344                src = vtki.new("CylinderSource")
345                src.SetResolution(res)
346                src.CappingOn()
347            elif source == "cube":
348                src = vtki.new("CubeSource")
349            else:
350                vedo.logger.error(f"Unknown source type {source}")
351                raise ValueError()
352            src.Update()
353            src = src.GetOutput()
354
355        tg = vtki.new("TensorGlyph")
356        if isinstance(domain, vtki.vtkPolyData):
357            tg.SetInputData(domain)
358        else:
359            tg.SetInputData(domain.dataset)
360        tg.SetSourceData(src)
361
362        if c is None:
363            tg.ColorGlyphsOn()
364        else:
365            tg.ColorGlyphsOff()
366
367        tg.SetSymmetric(int(is_symmetric))
368
369        if length is not None:
370            tg.SetLength(length)
371        if use_eigenvalues:
372            tg.ExtractEigenvaluesOn()
373            tg.SetColorModeToEigenvalues()
374        else:
375            tg.SetColorModeToScalars()
376
377        tg.SetThreeGlyphs(three_axes)
378        tg.ScalingOn()
379        tg.SetScaleFactor(scale)
380        if max_scale is None:
381            tg.ClampScalingOn()
382            max_scale = scale * 10
383        tg.SetMaxScaleFactor(max_scale)
384
385        tg.Update()
386        tgn = vtki.new("PolyDataNormals")
387        tgn.ComputeCellNormalsOff()
388        tgn.SetInputData(tg.GetOutput())
389        tgn.Update()
390
391        super().__init__(tgn.GetOutput(), c, alpha)
392        self.name = "Tensors"
Arguments:
  • source : (str, Mesh) preset types of source shapes is "ellipsoid", "cylinder", "cube" or a Mesh object.
  • use_eigenvalues : (bool) color source glyph using the eigenvalues or by scalars
  • three_axes : (bool) if False scale the source in the x-direction, the medium in the y-direction, and the minor in the z-direction. Then, the source is rotated so that the glyph's local x-axis lies along the major eigenvector, y-axis along the medium eigenvector, and z-axis along the minor.

    If True three sources are produced, each of them oriented along an eigenvector and scaled according to the corresponding eigenvector.

  • is_symmetric : (bool) If True each source glyph is mirrored (2 or 6 glyphs will be produced). The x-axis of the source glyph will correspond to the eigenvector on output.
  • length : (float) distance from the origin to the tip of the source glyph along the x-axis
  • scale : (float) scaling factor of the source glyph.
  • max_scale : (float) clamp scaling at this factor.
Examples:

class ParametricShape(vedo.mesh.Mesh):
3838class ParametricShape(Mesh):
3839    """
3840    A set of built-in shapes mainly for illustration purposes.
3841    """
3842
3843    def __init__(self, name, res=51, n=25, seed=1):
3844        """
3845        A set of built-in shapes mainly for illustration purposes.
3846
3847        Name can be an integer or a string in this list:
3848            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3849            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3850            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3851            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3852
3853        Example:
3854            ```python
3855            from vedo import *
3856            settings.immediate_rendering = False
3857            plt = Plotter(N=18)
3858            for i in range(18):
3859                ps = ParametricShape(i).color(i)
3860                plt.at(i).show(ps, ps.name)
3861            plt.interactive().close()
3862            ```
3863            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3864        """
3865
3866        shapes = [
3867            "Boy",
3868            "ConicSpiral",
3869            "CrossCap",
3870            "Enneper",
3871            "Figure8Klein",
3872            "Klein",
3873            "Dini",
3874            "Mobius",
3875            "RandomHills",
3876            "Roman",
3877            "SuperEllipsoid",
3878            "BohemianDome",
3879            "Bour",
3880            "CatalanMinimal",
3881            "Henneberg",
3882            "Kuen",
3883            "PluckerConoid",
3884            "Pseudosphere",
3885        ]
3886
3887        if isinstance(name, int):
3888            name = name % len(shapes)
3889            name = shapes[name]
3890
3891        if name == "Boy":
3892            ps = vtki.new("ParametricBoy")
3893        elif name == "ConicSpiral":
3894            ps = vtki.new("ParametricConicSpiral")
3895        elif name == "CrossCap":
3896            ps = vtki.new("ParametricCrossCap")
3897        elif name == "Dini":
3898            ps = vtki.new("ParametricDini")
3899        elif name == "Enneper":
3900            ps = vtki.new("ParametricEnneper")
3901        elif name == "Figure8Klein":
3902            ps = vtki.new("ParametricFigure8Klein")
3903        elif name == "Klein":
3904            ps = vtki.new("ParametricKlein")
3905        elif name == "Mobius":
3906            ps = vtki.new("ParametricMobius")
3907            ps.SetRadius(2.0)
3908            ps.SetMinimumV(-0.5)
3909            ps.SetMaximumV(0.5)
3910        elif name == "RandomHills":
3911            ps = vtki.new("ParametricRandomHills")
3912            ps.AllowRandomGenerationOn()
3913            ps.SetRandomSeed(seed)
3914            ps.SetNumberOfHills(n)
3915        elif name == "Roman":
3916            ps = vtki.new("ParametricRoman")
3917        elif name == "SuperEllipsoid":
3918            ps = vtki.new("ParametricSuperEllipsoid")
3919            ps.SetN1(0.5)
3920            ps.SetN2(0.4)
3921        elif name == "BohemianDome":
3922            ps = vtki.new("ParametricBohemianDome")
3923            ps.SetA(5.0)
3924            ps.SetB(1.0)
3925            ps.SetC(2.0)
3926        elif name == "Bour":
3927            ps = vtki.new("ParametricBour")
3928        elif name == "CatalanMinimal":
3929            ps = vtki.new("ParametricCatalanMinimal")
3930        elif name == "Henneberg":
3931            ps = vtki.new("ParametricHenneberg")
3932        elif name == "Kuen":
3933            ps = vtki.new("ParametricKuen")
3934            ps.SetDeltaV0(0.001)
3935        elif name == "PluckerConoid":
3936            ps = vtki.new("ParametricPluckerConoid")
3937        elif name == "Pseudosphere":
3938            ps = vtki.new("ParametricPseudosphere")
3939        else:
3940            vedo.logger.error(f"unknown ParametricShape {name}")
3941            return
3942
3943        pfs = vtki.new("ParametricFunctionSource")
3944        pfs.SetParametricFunction(ps)
3945        pfs.SetUResolution(res)
3946        pfs.SetVResolution(res)
3947        pfs.SetWResolution(res)
3948        pfs.SetScalarModeToZ()
3949        pfs.Update()
3950
3951        super().__init__(pfs.GetOutput())
3952
3953        if name == "RandomHills": self.shift([0,-10,-2.25])
3954        if name != 'Kuen': self.normalize()
3955        if name == 'Dini': self.scale(0.4)
3956        if name == 'Enneper': self.scale(0.4)
3957        if name == 'ConicSpiral': self.bc('tomato')
3958        self.name = name

A set of built-in shapes mainly for illustration purposes.

ParametricShape(name, res=51, n=25, seed=1)
3843    def __init__(self, name, res=51, n=25, seed=1):
3844        """
3845        A set of built-in shapes mainly for illustration purposes.
3846
3847        Name can be an integer or a string in this list:
3848            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3849            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3850            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3851            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3852
3853        Example:
3854            ```python
3855            from vedo import *
3856            settings.immediate_rendering = False
3857            plt = Plotter(N=18)
3858            for i in range(18):
3859                ps = ParametricShape(i).color(i)
3860                plt.at(i).show(ps, ps.name)
3861            plt.interactive().close()
3862            ```
3863            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3864        """
3865
3866        shapes = [
3867            "Boy",
3868            "ConicSpiral",
3869            "CrossCap",
3870            "Enneper",
3871            "Figure8Klein",
3872            "Klein",
3873            "Dini",
3874            "Mobius",
3875            "RandomHills",
3876            "Roman",
3877            "SuperEllipsoid",
3878            "BohemianDome",
3879            "Bour",
3880            "CatalanMinimal",
3881            "Henneberg",
3882            "Kuen",
3883            "PluckerConoid",
3884            "Pseudosphere",
3885        ]
3886
3887        if isinstance(name, int):
3888            name = name % len(shapes)
3889            name = shapes[name]
3890
3891        if name == "Boy":
3892            ps = vtki.new("ParametricBoy")
3893        elif name == "ConicSpiral":
3894            ps = vtki.new("ParametricConicSpiral")
3895        elif name == "CrossCap":
3896            ps = vtki.new("ParametricCrossCap")
3897        elif name == "Dini":
3898            ps = vtki.new("ParametricDini")
3899        elif name == "Enneper":
3900            ps = vtki.new("ParametricEnneper")
3901        elif name == "Figure8Klein":
3902            ps = vtki.new("ParametricFigure8Klein")
3903        elif name == "Klein":
3904            ps = vtki.new("ParametricKlein")
3905        elif name == "Mobius":
3906            ps = vtki.new("ParametricMobius")
3907            ps.SetRadius(2.0)
3908            ps.SetMinimumV(-0.5)
3909            ps.SetMaximumV(0.5)
3910        elif name == "RandomHills":
3911            ps = vtki.new("ParametricRandomHills")
3912            ps.AllowRandomGenerationOn()
3913            ps.SetRandomSeed(seed)
3914            ps.SetNumberOfHills(n)
3915        elif name == "Roman":
3916            ps = vtki.new("ParametricRoman")
3917        elif name == "SuperEllipsoid":
3918            ps = vtki.new("ParametricSuperEllipsoid")
3919            ps.SetN1(0.5)
3920            ps.SetN2(0.4)
3921        elif name == "BohemianDome":
3922            ps = vtki.new("ParametricBohemianDome")
3923            ps.SetA(5.0)
3924            ps.SetB(1.0)
3925            ps.SetC(2.0)
3926        elif name == "Bour":
3927            ps = vtki.new("ParametricBour")
3928        elif name == "CatalanMinimal":
3929            ps = vtki.new("ParametricCatalanMinimal")
3930        elif name == "Henneberg":
3931            ps = vtki.new("ParametricHenneberg")
3932        elif name == "Kuen":
3933            ps = vtki.new("ParametricKuen")
3934            ps.SetDeltaV0(0.001)
3935        elif name == "PluckerConoid":
3936            ps = vtki.new("ParametricPluckerConoid")
3937        elif name == "Pseudosphere":
3938            ps = vtki.new("ParametricPseudosphere")
3939        else:
3940            vedo.logger.error(f"unknown ParametricShape {name}")
3941            return
3942
3943        pfs = vtki.new("ParametricFunctionSource")
3944        pfs.SetParametricFunction(ps)
3945        pfs.SetUResolution(res)
3946        pfs.SetVResolution(res)
3947        pfs.SetWResolution(res)
3948        pfs.SetScalarModeToZ()
3949        pfs.Update()
3950
3951        super().__init__(pfs.GetOutput())
3952
3953        if name == "RandomHills": self.shift([0,-10,-2.25])
3954        if name != 'Kuen': self.normalize()
3955        if name == 'Dini': self.scale(0.4)
3956        if name == 'Enneper': self.scale(0.4)
3957        if name == 'ConicSpiral': self.bc('tomato')
3958        self.name = name

A set of built-in shapes mainly for illustration purposes.

Name can be an integer or a string in this list:

['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere'].

Example:
from vedo import *
settings.immediate_rendering = False
plt = Plotter(N=18)
for i in range(18):
    ps = ParametricShape(i).color(i)
    plt.at(i).show(ps, ps.name)
plt.interactive().close()

class ConvexHull(vedo.mesh.Mesh):
4847class ConvexHull(Mesh):
4848    """
4849    Create the 2D/3D convex hull from a set of points.
4850    """
4851
4852    def __init__(self, pts) -> None:
4853        """
4854        Create the 2D/3D convex hull from a set of input points or input Mesh.
4855
4856        Examples:
4857            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4858
4859                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4860        """
4861        if utils.is_sequence(pts):
4862            pts = utils.make3d(pts).astype(float)
4863            mesh = Points(pts)
4864        else:
4865            mesh = pts
4866        apoly = mesh.clean().dataset
4867
4868        # Create the convex hull of the pointcloud
4869        z0, z1 = mesh.zbounds()
4870        d = mesh.diagonal_size()
4871        if (z1 - z0) / d > 0.0001:
4872            delaunay = vtki.new("Delaunay3D")
4873            delaunay.SetInputData(apoly)
4874            delaunay.Update()
4875            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4876            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4877            surfaceFilter.Update()
4878            out = surfaceFilter.GetOutput()
4879        else:
4880            delaunay = vtki.new("Delaunay2D")
4881            delaunay.SetInputData(apoly)
4882            delaunay.Update()
4883            fe = vtki.new("FeatureEdges")
4884            fe.SetInputConnection(delaunay.GetOutputPort())
4885            fe.BoundaryEdgesOn()
4886            fe.Update()
4887            out = fe.GetOutput()
4888
4889        super().__init__(out, c=mesh.color(), alpha=0.75)
4890        self.flat()
4891        self.name = "ConvexHull"

Create the 2D/3D convex hull from a set of points.

ConvexHull(pts)
4852    def __init__(self, pts) -> None:
4853        """
4854        Create the 2D/3D convex hull from a set of input points or input Mesh.
4855
4856        Examples:
4857            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4858
4859                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4860        """
4861        if utils.is_sequence(pts):
4862            pts = utils.make3d(pts).astype(float)
4863            mesh = Points(pts)
4864        else:
4865            mesh = pts
4866        apoly = mesh.clean().dataset
4867
4868        # Create the convex hull of the pointcloud
4869        z0, z1 = mesh.zbounds()
4870        d = mesh.diagonal_size()
4871        if (z1 - z0) / d > 0.0001:
4872            delaunay = vtki.new("Delaunay3D")
4873            delaunay.SetInputData(apoly)
4874            delaunay.Update()
4875            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4876            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4877            surfaceFilter.Update()
4878            out = surfaceFilter.GetOutput()
4879        else:
4880            delaunay = vtki.new("Delaunay2D")
4881            delaunay.SetInputData(apoly)
4882            delaunay.Update()
4883            fe = vtki.new("FeatureEdges")
4884            fe.SetInputConnection(delaunay.GetOutputPort())
4885            fe.BoundaryEdgesOn()
4886            fe.Update()
4887            out = fe.GetOutput()
4888
4889        super().__init__(out, c=mesh.color(), alpha=0.75)
4890        self.flat()
4891        self.name = "ConvexHull"

Create the 2D/3D convex hull from a set of input points or input Mesh.

Examples: