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

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

CSpline(points, closed=False, res=None)
1296    def __init__(self, points, closed=False, res=None) -> None:
1297        """
1298        Arguments:
1299            closed : (bool)
1300                join last to first point to produce a closed curve
1301            res : (int)
1302                approximate resolution of the output line.
1303                Default is 20 times the number of input points.
1304
1305        Warning:
1306            This class is not necessarily generating the exact number of points
1307            as requested by `res`. Some points may be concident and removed.
1308
1309        See also: `Spline` and `KSpline`.
1310        """
1311
1312        if isinstance(points, Points):
1313            points = points.vertices
1314
1315        if not res:
1316            res = len(points) * 20
1317
1318        points = utils.make3d(points).astype(float)
1319
1320        vtkCardinalSpline = vtki.get_class("CardinalSpline")
1321        xspline = vtkCardinalSpline()
1322        yspline = vtkCardinalSpline()
1323        zspline = vtkCardinalSpline()
1324        for s in [xspline, yspline, zspline]:
1325            s.SetClosed(closed)
1326
1327        lenp = len(points[0]) > 2
1328
1329        for i, p in enumerate(points):
1330            xspline.AddPoint(i, p[0])
1331            yspline.AddPoint(i, p[1])
1332            if lenp:
1333                zspline.AddPoint(i, p[2])
1334
1335        ln = []
1336        for pos in np.linspace(0, len(points), res):
1337            x = xspline.Evaluate(pos)
1338            y = yspline.Evaluate(pos)
1339            z = 0
1340            if lenp:
1341                z = zspline.Evaluate(pos)
1342            ln.append((x, y, z))
1343
1344        super().__init__(ln, lw=2)
1345        self.clean()
1346        self.lighting("off")
1347        self.name = "CSpline"
1348        self.base = points[0]
1349        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):
1352class Bezier(Line):
1353    """
1354    Generate the Bezier line that links the first to the last point.
1355    """
1356
1357    def __init__(self, points, res=None) -> None:
1358        """
1359        Example:
1360            ```python
1361            from vedo import *
1362            import numpy as np
1363            pts = np.random.randn(25,3)
1364            for i,p in enumerate(pts):
1365                p += [5*i, 15*sin(i/2), i*i*i/200]
1366            show(Points(pts), Bezier(pts), axes=1).close()
1367            ```
1368            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1369        """
1370        N = len(points)
1371        if res is None:
1372            res = 10 * N
1373        t = np.linspace(0, 1, num=res)
1374        bcurve = np.zeros((res, len(points[0])))
1375
1376        def binom(n, k):
1377            b = 1
1378            for t in range(1, min(k, n - k) + 1):
1379                b *= n / t
1380                n -= 1
1381            return b
1382
1383        def bernstein(n, k):
1384            coeff = binom(n, k)
1385
1386            def _bpoly(x):
1387                return coeff * x ** k * (1 - x) ** (n - k)
1388
1389            return _bpoly
1390
1391        for ii in range(N):
1392            b = bernstein(N - 1, ii)(t)
1393            bcurve += np.outer(b, points[ii])
1394        super().__init__(bcurve, lw=2)
1395        self.name = "BezierLine"

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

Bezier(points, res=None)
1357    def __init__(self, points, res=None) -> None:
1358        """
1359        Example:
1360            ```python
1361            from vedo import *
1362            import numpy as np
1363            pts = np.random.randn(25,3)
1364            for i,p in enumerate(pts):
1365                p += [5*i, 15*sin(i/2), i*i*i/200]
1366            show(Points(pts), Bezier(pts), axes=1).close()
1367            ```
1368            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1369        """
1370        N = len(points)
1371        if res is None:
1372            res = 10 * N
1373        t = np.linspace(0, 1, num=res)
1374        bcurve = np.zeros((res, len(points[0])))
1375
1376        def binom(n, k):
1377            b = 1
1378            for t in range(1, min(k, n - k) + 1):
1379                b *= n / t
1380                n -= 1
1381            return b
1382
1383        def bernstein(n, k):
1384            coeff = binom(n, k)
1385
1386            def _bpoly(x):
1387                return coeff * x ** k * (1 - x) ** (n - k)
1388
1389            return _bpoly
1390
1391        for ii in range(N):
1392            b = bernstein(N - 1, ii)(t)
1393            bcurve += np.outer(b, points[ii])
1394        super().__init__(bcurve, lw=2)
1395        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):
3639class Brace(Mesh):
3640    """
3641    Create a brace (bracket) shape.
3642    """
3643
3644    def __init__(
3645        self,
3646        q1,
3647        q2,
3648        style="}",
3649        padding1=0.0,
3650        font="Theemim",
3651        comment="",
3652        justify=None,
3653        angle=0.0,
3654        padding2=0.2,
3655        s=1.0,
3656        italic=0,
3657        c="k1",
3658        alpha=1.0,
3659    ) -> None:
3660        """
3661        Create a brace (bracket) shape which spans from point q1 to point q2.
3662
3663        Arguments:
3664            q1 : (list)
3665                point 1.
3666            q2 : (list)
3667                point 2.
3668            style : (str)
3669                style of the bracket, eg. `{}, [], (), <>`.
3670            padding1 : (float)
3671                padding space in percent form the input points.
3672            font : (str)
3673                font type
3674            comment : (str)
3675                additional text to appear next to the brace symbol.
3676            justify : (str)
3677                specify the anchor point to justify text comment, e.g. "top-left".
3678            italic : float
3679                italicness of the text comment (can be a positive or negative number)
3680            angle : (float)
3681                rotation angle of text. Use `None` to keep it horizontal.
3682            padding2 : (float)
3683                padding space in percent form brace to text comment.
3684            s : (float)
3685                scale factor for the comment
3686
3687        Examples:
3688            - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py)
3689
3690                ![](https://vedo.embl.es/images/pyplot/scatter3.png)
3691        """
3692        if isinstance(q1, vtki.vtkActor):
3693            q1 = q1.GetPosition()
3694        if isinstance(q2, vtki.vtkActor):
3695            q2 = q2.GetPosition()
3696        if len(q1) == 2:
3697            q1 = [q1[0], q1[1], 0.0]
3698        if len(q2) == 2:
3699            q2 = [q2[0], q2[1], 0.0]
3700        q1 = np.array(q1, dtype=float)
3701        q2 = np.array(q2, dtype=float)
3702        mq = (q1 + q2) / 2
3703        q1 = q1 - mq
3704        q2 = q2 - mq
3705        d = np.linalg.norm(q2 - q1)
3706        q2[2] = q1[2]
3707
3708        if style not in "{}[]()<>|I":
3709            vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I")
3710            style = "}"
3711
3712        flip = False
3713        if style in ["{", "[", "(", "<"]:
3714            flip = True
3715            i = ["{", "[", "(", "<"].index(style)
3716            style = ["}", "]", ")", ">"][i]
3717
3718        br = Text3D(style, font="Theemim", justify="center-left")
3719        br.scale([0.4, 1, 1])
3720
3721        angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90
3722        if flip:
3723            angler += 180
3724
3725        _, x1, y0, y1, _, _ = br.bounds()
3726        if comment:
3727            just = "center-top"
3728            if angle is None:
3729                angle = -angler + 90
3730                if not flip:
3731                    angle += 180
3732
3733            if flip:
3734                angle += 180
3735                just = "center-bottom"
3736            if justify is not None:
3737                just = justify
3738            cmt = Text3D(comment, font=font, justify=just, italic=italic)
3739            cx0, cx1 = cmt.xbounds()
3740            cmt.rotate_z(90 + angle)
3741            cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5)
3742            cmt.shift(x1 * (1 + padding2), 0, 0)
3743            poly = merge(br, cmt).dataset
3744
3745        else:
3746            poly = br.dataset
3747
3748        tr = vtki.vtkTransform()
3749        tr.Translate(mq)
3750        tr.RotateZ(angler)
3751        tr.Translate(padding1 * d, 0, 0)
3752        pscale = 1
3753        tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1)
3754
3755        tf = vtki.new("TransformPolyDataFilter")
3756        tf.SetInputData(poly)
3757        tf.SetTransform(tr)
3758        tf.Update()
3759        poly = tf.GetOutput()
3760
3761        super().__init__(poly, c, alpha)
3762
3763        self.base = q1
3764        self.top  = q2
3765        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)
3644    def __init__(
3645        self,
3646        q1,
3647        q2,
3648        style="}",
3649        padding1=0.0,
3650        font="Theemim",
3651        comment="",
3652        justify=None,
3653        angle=0.0,
3654        padding2=0.2,
3655        s=1.0,
3656        italic=0,
3657        c="k1",
3658        alpha=1.0,
3659    ) -> None:
3660        """
3661        Create a brace (bracket) shape which spans from point q1 to point q2.
3662
3663        Arguments:
3664            q1 : (list)
3665                point 1.
3666            q2 : (list)
3667                point 2.
3668            style : (str)
3669                style of the bracket, eg. `{}, [], (), <>`.
3670            padding1 : (float)
3671                padding space in percent form the input points.
3672            font : (str)
3673                font type
3674            comment : (str)
3675                additional text to appear next to the brace symbol.
3676            justify : (str)
3677                specify the anchor point to justify text comment, e.g. "top-left".
3678            italic : float
3679                italicness of the text comment (can be a positive or negative number)
3680            angle : (float)
3681                rotation angle of text. Use `None` to keep it horizontal.
3682            padding2 : (float)
3683                padding space in percent form brace to text comment.
3684            s : (float)
3685                scale factor for the comment
3686
3687        Examples:
3688            - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py)
3689
3690                ![](https://vedo.embl.es/images/pyplot/scatter3.png)
3691        """
3692        if isinstance(q1, vtki.vtkActor):
3693            q1 = q1.GetPosition()
3694        if isinstance(q2, vtki.vtkActor):
3695            q2 = q2.GetPosition()
3696        if len(q1) == 2:
3697            q1 = [q1[0], q1[1], 0.0]
3698        if len(q2) == 2:
3699            q2 = [q2[0], q2[1], 0.0]
3700        q1 = np.array(q1, dtype=float)
3701        q2 = np.array(q2, dtype=float)
3702        mq = (q1 + q2) / 2
3703        q1 = q1 - mq
3704        q2 = q2 - mq
3705        d = np.linalg.norm(q2 - q1)
3706        q2[2] = q1[2]
3707
3708        if style not in "{}[]()<>|I":
3709            vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I")
3710            style = "}"
3711
3712        flip = False
3713        if style in ["{", "[", "(", "<"]:
3714            flip = True
3715            i = ["{", "[", "(", "<"].index(style)
3716            style = ["}", "]", ")", ">"][i]
3717
3718        br = Text3D(style, font="Theemim", justify="center-left")
3719        br.scale([0.4, 1, 1])
3720
3721        angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90
3722        if flip:
3723            angler += 180
3724
3725        _, x1, y0, y1, _, _ = br.bounds()
3726        if comment:
3727            just = "center-top"
3728            if angle is None:
3729                angle = -angler + 90
3730                if not flip:
3731                    angle += 180
3732
3733            if flip:
3734                angle += 180
3735                just = "center-bottom"
3736            if justify is not None:
3737                just = justify
3738            cmt = Text3D(comment, font=font, justify=just, italic=italic)
3739            cx0, cx1 = cmt.xbounds()
3740            cmt.rotate_z(90 + angle)
3741            cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5)
3742            cmt.shift(x1 * (1 + padding2), 0, 0)
3743            poly = merge(br, cmt).dataset
3744
3745        else:
3746            poly = br.dataset
3747
3748        tr = vtki.vtkTransform()
3749        tr.Translate(mq)
3750        tr.RotateZ(angler)
3751        tr.Translate(padding1 * d, 0, 0)
3752        pscale = 1
3753        tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1)
3754
3755        tf = vtki.new("TransformPolyDataFilter")
3756        tf.SetInputData(poly)
3757        tf.SetTransform(tr)
3758        tf.Update()
3759        poly = tf.GetOutput()
3760
3761        super().__init__(poly, c, alpha)
3762
3763        self.base = q1
3764        self.top  = q2
3765        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):
1398class NormalLines(Mesh):
1399    """
1400    Build an `Glyph` to show the normals at cell centers or at mesh vertices.
1401
1402    Arguments:
1403        ratio : (int)
1404            show 1 normal every `ratio` cells.
1405        on : (str)
1406            either "cells" or "points".
1407        scale : (float)
1408            scale factor to control size.
1409    """
1410
1411    def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None:
1412
1413        poly = msh.clone().dataset
1414
1415        if "cell" in on:
1416            centers = vtki.new("CellCenters")
1417            centers.SetInputData(poly)
1418            centers.Update()
1419            poly = centers.GetOutput()
1420
1421        mask_pts = vtki.new("MaskPoints")
1422        mask_pts.SetInputData(poly)
1423        mask_pts.SetOnRatio(ratio)
1424        mask_pts.RandomModeOff()
1425        mask_pts.Update()
1426
1427        ln = vtki.new("LineSource")
1428        ln.SetPoint1(0, 0, 0)
1429        ln.SetPoint2(1, 0, 0)
1430        ln.Update()
1431        glyph = vtki.vtkGlyph3D()
1432        glyph.SetSourceData(ln.GetOutput())
1433        glyph.SetInputData(mask_pts.GetOutput())
1434        glyph.SetVectorModeToUseNormal()
1435
1436        b = poly.GetBounds()
1437        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1438        glyph.SetScaleFactor(f)
1439        glyph.OrientOn()
1440        glyph.Update()
1441
1442        super().__init__(glyph.GetOutput())
1443
1444        self.actor.PickableOff()
1445        prop = vtki.vtkProperty()
1446        prop.DeepCopy(msh.properties)
1447        self.actor.SetProperty(prop)
1448        self.properties = prop
1449        self.properties.LightingOff()
1450        self.mapper.ScalarVisibilityOff()
1451        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)
1411    def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None:
1412
1413        poly = msh.clone().dataset
1414
1415        if "cell" in on:
1416            centers = vtki.new("CellCenters")
1417            centers.SetInputData(poly)
1418            centers.Update()
1419            poly = centers.GetOutput()
1420
1421        mask_pts = vtki.new("MaskPoints")
1422        mask_pts.SetInputData(poly)
1423        mask_pts.SetOnRatio(ratio)
1424        mask_pts.RandomModeOff()
1425        mask_pts.Update()
1426
1427        ln = vtki.new("LineSource")
1428        ln.SetPoint1(0, 0, 0)
1429        ln.SetPoint2(1, 0, 0)
1430        ln.Update()
1431        glyph = vtki.vtkGlyph3D()
1432        glyph.SetSourceData(ln.GetOutput())
1433        glyph.SetInputData(mask_pts.GetOutput())
1434        glyph.SetVectorModeToUseNormal()
1435
1436        b = poly.GetBounds()
1437        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1438        glyph.SetScaleFactor(f)
1439        glyph.OrientOn()
1440        glyph.Update()
1441
1442        super().__init__(glyph.GetOutput())
1443
1444        self.actor.PickableOff()
1445        prop = vtki.vtkProperty()
1446        prop.DeepCopy(msh.properties)
1447        self.actor.SetProperty(prop)
1448        self.properties = prop
1449        self.properties.LightingOff()
1450        self.mapper.ScalarVisibilityOff()
1451        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):
1663class Ribbon(Mesh):
1664    """
1665    Connect two lines to generate the surface inbetween.
1666    Set the mode by which to create the ruled surface.
1667
1668    It also works with a single line in input. In this case the ribbon
1669    is formed by following the local plane of the line in space.
1670    """
1671
1672    def __init__(
1673        self,
1674        line1,
1675        line2=None,
1676        mode=0,
1677        closed=False,
1678        width=None,
1679        res=(200, 5),
1680        c="indigo3",
1681        alpha=1.0,
1682    ) -> None:
1683        """
1684        Arguments:
1685            mode : (int)
1686                If mode=0, resample evenly the input lines (based on length)
1687                and generates triangle strips.
1688
1689                If mode=1, use the existing points and walks around the
1690                polyline using existing points.
1691
1692            closed : (bool)
1693                if True, join the last point with the first to form a closed surface
1694
1695            res : (list)
1696                ribbon resolutions along the line and perpendicularly to it.
1697
1698        Examples:
1699            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1700
1701                ![](https://vedo.embl.es/images/basic/ribbon.png)
1702        """
1703
1704        if isinstance(line1, Points):
1705            line1 = line1.vertices
1706
1707        if isinstance(line2, Points):
1708            line2 = line2.vertices
1709
1710        elif line2 is None:
1711            #############################################
1712            ribbon_filter = vtki.new("RibbonFilter")
1713            aline = Line(line1)
1714            ribbon_filter.SetInputData(aline.dataset)
1715            if width is None:
1716                width = aline.diagonal_size() / 20.0
1717            ribbon_filter.SetWidth(width)
1718            ribbon_filter.Update()
1719            # convert triangle strips to polygons
1720            tris = vtki.new("TriangleFilter")
1721            tris.SetInputData(ribbon_filter.GetOutput())
1722            tris.Update()
1723
1724            super().__init__(tris.GetOutput(), c, alpha)
1725            self.name = "Ribbon"
1726            ##############################################
1727            return  ######################################
1728            ##############################################
1729
1730        line1 = np.asarray(line1)
1731        line2 = np.asarray(line2)
1732
1733        if closed:
1734            line1 = line1.tolist()
1735            line1 += [line1[0]]
1736            line2 = line2.tolist()
1737            line2 += [line2[0]]
1738            line1 = np.array(line1)
1739            line2 = np.array(line2)
1740
1741        if len(line1[0]) == 2:
1742            line1 = np.c_[line1, np.zeros(len(line1))]
1743        if len(line2[0]) == 2:
1744            line2 = np.c_[line2, np.zeros(len(line2))]
1745
1746        ppoints1 = vtki.vtkPoints()  # Generate the polyline1
1747        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1748        lines1 = vtki.vtkCellArray()
1749        lines1.InsertNextCell(len(line1))
1750        for i in range(len(line1)):
1751            lines1.InsertCellPoint(i)
1752        poly1 = vtki.vtkPolyData()
1753        poly1.SetPoints(ppoints1)
1754        poly1.SetLines(lines1)
1755
1756        ppoints2 = vtki.vtkPoints()  # Generate the polyline2
1757        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1758        lines2 = vtki.vtkCellArray()
1759        lines2.InsertNextCell(len(line2))
1760        for i in range(len(line2)):
1761            lines2.InsertCellPoint(i)
1762        poly2 = vtki.vtkPolyData()
1763        poly2.SetPoints(ppoints2)
1764        poly2.SetLines(lines2)
1765
1766        # build the lines
1767        lines1 = vtki.vtkCellArray()
1768        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1769        for i in range(poly1.GetNumberOfPoints()):
1770            lines1.InsertCellPoint(i)
1771
1772        polygon1 = vtki.vtkPolyData()
1773        polygon1.SetPoints(ppoints1)
1774        polygon1.SetLines(lines1)
1775
1776        lines2 = vtki.vtkCellArray()
1777        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1778        for i in range(poly2.GetNumberOfPoints()):
1779            lines2.InsertCellPoint(i)
1780
1781        polygon2 = vtki.vtkPolyData()
1782        polygon2.SetPoints(ppoints2)
1783        polygon2.SetLines(lines2)
1784
1785        merged_pd = vtki.new("AppendPolyData")
1786        merged_pd.AddInputData(polygon1)
1787        merged_pd.AddInputData(polygon2)
1788        merged_pd.Update()
1789
1790        rsf = vtki.new("RuledSurfaceFilter")
1791        rsf.CloseSurfaceOff()
1792        rsf.SetRuledMode(mode)
1793        rsf.SetResolution(res[0], res[1])
1794        rsf.SetInputData(merged_pd.GetOutput())
1795        rsf.Update()
1796        # convert triangle strips to polygons
1797        tris = vtki.new("TriangleFilter")
1798        tris.SetInputData(rsf.GetOutput())
1799        tris.Update()
1800        out = tris.GetOutput()
1801
1802        super().__init__(out, c, alpha)
1803
1804        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)
1672    def __init__(
1673        self,
1674        line1,
1675        line2=None,
1676        mode=0,
1677        closed=False,
1678        width=None,
1679        res=(200, 5),
1680        c="indigo3",
1681        alpha=1.0,
1682    ) -> None:
1683        """
1684        Arguments:
1685            mode : (int)
1686                If mode=0, resample evenly the input lines (based on length)
1687                and generates triangle strips.
1688
1689                If mode=1, use the existing points and walks around the
1690                polyline using existing points.
1691
1692            closed : (bool)
1693                if True, join the last point with the first to form a closed surface
1694
1695            res : (list)
1696                ribbon resolutions along the line and perpendicularly to it.
1697
1698        Examples:
1699            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1700
1701                ![](https://vedo.embl.es/images/basic/ribbon.png)
1702        """
1703
1704        if isinstance(line1, Points):
1705            line1 = line1.vertices
1706
1707        if isinstance(line2, Points):
1708            line2 = line2.vertices
1709
1710        elif line2 is None:
1711            #############################################
1712            ribbon_filter = vtki.new("RibbonFilter")
1713            aline = Line(line1)
1714            ribbon_filter.SetInputData(aline.dataset)
1715            if width is None:
1716                width = aline.diagonal_size() / 20.0
1717            ribbon_filter.SetWidth(width)
1718            ribbon_filter.Update()
1719            # convert triangle strips to polygons
1720            tris = vtki.new("TriangleFilter")
1721            tris.SetInputData(ribbon_filter.GetOutput())
1722            tris.Update()
1723
1724            super().__init__(tris.GetOutput(), c, alpha)
1725            self.name = "Ribbon"
1726            ##############################################
1727            return  ######################################
1728            ##############################################
1729
1730        line1 = np.asarray(line1)
1731        line2 = np.asarray(line2)
1732
1733        if closed:
1734            line1 = line1.tolist()
1735            line1 += [line1[0]]
1736            line2 = line2.tolist()
1737            line2 += [line2[0]]
1738            line1 = np.array(line1)
1739            line2 = np.array(line2)
1740
1741        if len(line1[0]) == 2:
1742            line1 = np.c_[line1, np.zeros(len(line1))]
1743        if len(line2[0]) == 2:
1744            line2 = np.c_[line2, np.zeros(len(line2))]
1745
1746        ppoints1 = vtki.vtkPoints()  # Generate the polyline1
1747        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1748        lines1 = vtki.vtkCellArray()
1749        lines1.InsertNextCell(len(line1))
1750        for i in range(len(line1)):
1751            lines1.InsertCellPoint(i)
1752        poly1 = vtki.vtkPolyData()
1753        poly1.SetPoints(ppoints1)
1754        poly1.SetLines(lines1)
1755
1756        ppoints2 = vtki.vtkPoints()  # Generate the polyline2
1757        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1758        lines2 = vtki.vtkCellArray()
1759        lines2.InsertNextCell(len(line2))
1760        for i in range(len(line2)):
1761            lines2.InsertCellPoint(i)
1762        poly2 = vtki.vtkPolyData()
1763        poly2.SetPoints(ppoints2)
1764        poly2.SetLines(lines2)
1765
1766        # build the lines
1767        lines1 = vtki.vtkCellArray()
1768        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1769        for i in range(poly1.GetNumberOfPoints()):
1770            lines1.InsertCellPoint(i)
1771
1772        polygon1 = vtki.vtkPolyData()
1773        polygon1.SetPoints(ppoints1)
1774        polygon1.SetLines(lines1)
1775
1776        lines2 = vtki.vtkCellArray()
1777        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1778        for i in range(poly2.GetNumberOfPoints()):
1779            lines2.InsertCellPoint(i)
1780
1781        polygon2 = vtki.vtkPolyData()
1782        polygon2.SetPoints(ppoints2)
1783        polygon2.SetLines(lines2)
1784
1785        merged_pd = vtki.new("AppendPolyData")
1786        merged_pd.AddInputData(polygon1)
1787        merged_pd.AddInputData(polygon2)
1788        merged_pd.Update()
1789
1790        rsf = vtki.new("RuledSurfaceFilter")
1791        rsf.CloseSurfaceOff()
1792        rsf.SetRuledMode(mode)
1793        rsf.SetResolution(res[0], res[1])
1794        rsf.SetInputData(merged_pd.GetOutput())
1795        rsf.Update()
1796        # convert triangle strips to polygons
1797        tris = vtki.new("TriangleFilter")
1798        tris.SetInputData(rsf.GetOutput())
1799        tris.Update()
1800        out = tris.GetOutput()
1801
1802        super().__init__(out, c, alpha)
1803
1804        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):
1807class Arrow(Mesh):
1808    """
1809    Build a 3D arrow from `start_pt` to `end_pt` of section size `s`,
1810    expressed as the fraction of the window size.
1811    """
1812
1813    def __init__(
1814        self,
1815        start_pt=(0, 0, 0),
1816        end_pt=(1, 0, 0),
1817        s=None,
1818        shaft_radius=None,
1819        head_radius=None,
1820        head_length=None,
1821        res=12,
1822        c="r4",
1823        alpha=1.0,
1824    ) -> None:
1825        """
1826        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1827        from white to red.
1828
1829        .. note:: If `s=None` the arrow is scaled proportionally to its length
1830
1831        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1832        """
1833        # in case user is passing meshs
1834        if isinstance(start_pt, vtki.vtkActor):
1835            start_pt = start_pt.GetPosition()
1836        if isinstance(end_pt, vtki.vtkActor):
1837            end_pt = end_pt.GetPosition()
1838
1839        axis = np.asarray(end_pt) - np.asarray(start_pt)
1840        length = float(np.linalg.norm(axis))
1841        if length:
1842            axis = axis / length
1843        if len(axis) < 3:  # its 2d
1844            theta = np.pi / 2
1845            start_pt = [start_pt[0], start_pt[1], 0.0]
1846            end_pt = [end_pt[0], end_pt[1], 0.0]
1847        else:
1848            theta = np.arccos(axis[2])
1849        phi = np.arctan2(axis[1], axis[0])
1850        self.source = vtki.new("ArrowSource")
1851        self.source.SetShaftResolution(res)
1852        self.source.SetTipResolution(res)
1853
1854        if s:
1855            sz = 0.02
1856            self.source.SetTipRadius(sz)
1857            self.source.SetShaftRadius(sz / 1.75)
1858            self.source.SetTipLength(sz * 15)
1859
1860        if head_length:
1861            self.source.SetTipLength(head_length)
1862        if head_radius:
1863            self.source.SetTipRadius(head_radius)
1864        if shaft_radius:
1865            self.source.SetShaftRadius(shaft_radius)
1866
1867        self.source.Update()
1868
1869        t = vtki.vtkTransform()
1870        t.Translate(start_pt)
1871        t.RotateZ(np.rad2deg(phi))
1872        t.RotateY(np.rad2deg(theta))
1873        t.RotateY(-90)  # put it along Z
1874        if s:
1875            sz = 800 * s
1876            t.Scale(length, sz, sz)
1877        else:
1878            t.Scale(length, length, length)
1879
1880        tf = vtki.new("TransformPolyDataFilter")
1881        tf.SetInputData(self.source.GetOutput())
1882        tf.SetTransform(t)
1883        tf.Update()
1884
1885        super().__init__(tf.GetOutput(), c, alpha)
1886
1887        self.transform = LinearTransform().translate(start_pt)
1888        # self.pos(start_pt)
1889
1890        self.phong().lighting("plastic")
1891        self.actor.PickableOff()
1892        self.actor.DragableOff()
1893        self.base = np.array(start_pt, dtype=float)  # used by pyplot
1894        self.top  = np.array(end_pt,   dtype=float)  # used by pyplot
1895        self.top_index = None
1896        self.fill = True                    # used by pyplot.__iadd__()
1897        self.s = s if s is not None else 1  # used by pyplot.__iadd__()
1898        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)
1813    def __init__(
1814        self,
1815        start_pt=(0, 0, 0),
1816        end_pt=(1, 0, 0),
1817        s=None,
1818        shaft_radius=None,
1819        head_radius=None,
1820        head_length=None,
1821        res=12,
1822        c="r4",
1823        alpha=1.0,
1824    ) -> None:
1825        """
1826        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1827        from white to red.
1828
1829        .. note:: If `s=None` the arrow is scaled proportionally to its length
1830
1831        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1832        """
1833        # in case user is passing meshs
1834        if isinstance(start_pt, vtki.vtkActor):
1835            start_pt = start_pt.GetPosition()
1836        if isinstance(end_pt, vtki.vtkActor):
1837            end_pt = end_pt.GetPosition()
1838
1839        axis = np.asarray(end_pt) - np.asarray(start_pt)
1840        length = float(np.linalg.norm(axis))
1841        if length:
1842            axis = axis / length
1843        if len(axis) < 3:  # its 2d
1844            theta = np.pi / 2
1845            start_pt = [start_pt[0], start_pt[1], 0.0]
1846            end_pt = [end_pt[0], end_pt[1], 0.0]
1847        else:
1848            theta = np.arccos(axis[2])
1849        phi = np.arctan2(axis[1], axis[0])
1850        self.source = vtki.new("ArrowSource")
1851        self.source.SetShaftResolution(res)
1852        self.source.SetTipResolution(res)
1853
1854        if s:
1855            sz = 0.02
1856            self.source.SetTipRadius(sz)
1857            self.source.SetShaftRadius(sz / 1.75)
1858            self.source.SetTipLength(sz * 15)
1859
1860        if head_length:
1861            self.source.SetTipLength(head_length)
1862        if head_radius:
1863            self.source.SetTipRadius(head_radius)
1864        if shaft_radius:
1865            self.source.SetShaftRadius(shaft_radius)
1866
1867        self.source.Update()
1868
1869        t = vtki.vtkTransform()
1870        t.Translate(start_pt)
1871        t.RotateZ(np.rad2deg(phi))
1872        t.RotateY(np.rad2deg(theta))
1873        t.RotateY(-90)  # put it along Z
1874        if s:
1875            sz = 800 * s
1876            t.Scale(length, sz, sz)
1877        else:
1878            t.Scale(length, length, length)
1879
1880        tf = vtki.new("TransformPolyDataFilter")
1881        tf.SetInputData(self.source.GetOutput())
1882        tf.SetTransform(t)
1883        tf.Update()
1884
1885        super().__init__(tf.GetOutput(), c, alpha)
1886
1887        self.transform = LinearTransform().translate(start_pt)
1888        # self.pos(start_pt)
1889
1890        self.phong().lighting("plastic")
1891        self.actor.PickableOff()
1892        self.actor.DragableOff()
1893        self.base = np.array(start_pt, dtype=float)  # used by pyplot
1894        self.top  = np.array(end_pt,   dtype=float)  # used by pyplot
1895        self.top_index = None
1896        self.fill = True                    # used by pyplot.__iadd__()
1897        self.s = s if s is not None else 1  # used by pyplot.__iadd__()
1898        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):
1901class Arrows(Glyph):
1902    """
1903    Build arrows between two lists of points.
1904    """
1905
1906    def __init__(
1907        self,
1908        start_pts,
1909        end_pts=None,
1910        s=None,
1911        shaft_radius=None,
1912        head_radius=None,
1913        head_length=None,
1914        thickness=1.0,
1915        res=6,
1916        c='k3',
1917        alpha=1.0,
1918    ) -> None:
1919        """
1920        Build arrows between two lists of points `start_pts` and `end_pts`.
1921         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1922
1923        Color can be specified as a colormap which maps the size of the arrows.
1924
1925        Arguments:
1926            s : (float)
1927                fix aspect-ratio of the arrow and scale its cross section
1928            c : (color)
1929                color or color map name
1930            alpha : (float)
1931                set object opacity
1932            res : (int)
1933                set arrow resolution
1934
1935        Examples:
1936            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
1937
1938            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
1939        """
1940        if isinstance(start_pts, Points):
1941            start_pts = start_pts.vertices
1942        if isinstance(end_pts, Points):
1943            end_pts = end_pts.vertices
1944
1945        start_pts = np.asarray(start_pts)
1946        if end_pts is None:
1947            strt = start_pts[:, 0]
1948            end_pts = start_pts[:, 1]
1949            start_pts = strt
1950        else:
1951            end_pts = np.asarray(end_pts)
1952
1953        start_pts = utils.make3d(start_pts)
1954        end_pts = utils.make3d(end_pts)
1955
1956        arr = vtki.new("ArrowSource")
1957        arr.SetShaftResolution(res)
1958        arr.SetTipResolution(res)
1959
1960        if s:
1961            sz = 0.02 * s
1962            arr.SetTipRadius(sz * 2)
1963            arr.SetShaftRadius(sz * thickness)
1964            arr.SetTipLength(sz * 10)
1965
1966        if head_radius:
1967            arr.SetTipRadius(head_radius)
1968        if shaft_radius:
1969            arr.SetShaftRadius(shaft_radius)
1970        if head_length:
1971            arr.SetTipLength(head_length)
1972
1973        arr.Update()
1974        out = arr.GetOutput()
1975
1976        orients = end_pts - start_pts
1977
1978        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
1979
1980        super().__init__(
1981            start_pts,
1982            out,
1983            orientation_array=orients,
1984            scale_by_vector_size=True,
1985            color_by_vector_size=color_by_vector_size,
1986            c=c,
1987            alpha=alpha,
1988        )
1989        self.lighting("off")
1990        if color_by_vector_size:
1991            vals = np.linalg.norm(orients, axis=1)
1992            self.mapper.SetScalarRange(vals.min(), vals.max())
1993        else:
1994            self.c(c)
1995        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)
1906    def __init__(
1907        self,
1908        start_pts,
1909        end_pts=None,
1910        s=None,
1911        shaft_radius=None,
1912        head_radius=None,
1913        head_length=None,
1914        thickness=1.0,
1915        res=6,
1916        c='k3',
1917        alpha=1.0,
1918    ) -> None:
1919        """
1920        Build arrows between two lists of points `start_pts` and `end_pts`.
1921         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1922
1923        Color can be specified as a colormap which maps the size of the arrows.
1924
1925        Arguments:
1926            s : (float)
1927                fix aspect-ratio of the arrow and scale its cross section
1928            c : (color)
1929                color or color map name
1930            alpha : (float)
1931                set object opacity
1932            res : (int)
1933                set arrow resolution
1934
1935        Examples:
1936            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
1937
1938            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
1939        """
1940        if isinstance(start_pts, Points):
1941            start_pts = start_pts.vertices
1942        if isinstance(end_pts, Points):
1943            end_pts = end_pts.vertices
1944
1945        start_pts = np.asarray(start_pts)
1946        if end_pts is None:
1947            strt = start_pts[:, 0]
1948            end_pts = start_pts[:, 1]
1949            start_pts = strt
1950        else:
1951            end_pts = np.asarray(end_pts)
1952
1953        start_pts = utils.make3d(start_pts)
1954        end_pts = utils.make3d(end_pts)
1955
1956        arr = vtki.new("ArrowSource")
1957        arr.SetShaftResolution(res)
1958        arr.SetTipResolution(res)
1959
1960        if s:
1961            sz = 0.02 * s
1962            arr.SetTipRadius(sz * 2)
1963            arr.SetShaftRadius(sz * thickness)
1964            arr.SetTipLength(sz * 10)
1965
1966        if head_radius:
1967            arr.SetTipRadius(head_radius)
1968        if shaft_radius:
1969            arr.SetShaftRadius(shaft_radius)
1970        if head_length:
1971            arr.SetTipLength(head_length)
1972
1973        arr.Update()
1974        out = arr.GetOutput()
1975
1976        orients = end_pts - start_pts
1977
1978        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
1979
1980        super().__init__(
1981            start_pts,
1982            out,
1983            orientation_array=orients,
1984            scale_by_vector_size=True,
1985            color_by_vector_size=color_by_vector_size,
1986            c=c,
1987            alpha=alpha,
1988        )
1989        self.lighting("off")
1990        if color_by_vector_size:
1991            vals = np.linalg.norm(orients, axis=1)
1992            self.mapper.SetScalarRange(vals.min(), vals.max())
1993        else:
1994            self.c(c)
1995        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):
1998class Arrow2D(Mesh):
1999    """
2000    Build a 2D arrow.
2001    """
2002
2003    def __init__(
2004        self,
2005        start_pt=(0, 0, 0),
2006        end_pt=(1, 0, 0),
2007        s=1,
2008        rotation=0.0,
2009        shaft_length=0.85,
2010        shaft_width=0.055,
2011        head_length=0.175,
2012        head_width=0.175,
2013        fill=True,
2014        c="red4",
2015        alpha=1.0,
2016   ) -> None:
2017        """
2018        Build a 2D arrow from `start_pt` to `end_pt`.
2019
2020        Arguments:
2021            s : (float)
2022                a global multiplicative convenience factor controlling the arrow size
2023            shaft_length : (float)
2024                fractional shaft length
2025            shaft_width : (float)
2026                fractional shaft width
2027            head_length : (float)
2028                fractional head length
2029            head_width : (float)
2030                fractional head width
2031            fill : (bool)
2032                if False only generate the outline
2033        """
2034        self.fill = fill  ## needed by pyplot.__iadd()
2035        self.s = s        ## needed by pyplot.__iadd()
2036
2037        if s != 1:
2038            shaft_width *= s
2039            head_width *= np.sqrt(s)
2040
2041        # in case user is passing meshs
2042        if isinstance(start_pt, vtki.vtkActor):
2043            start_pt = start_pt.GetPosition()
2044        if isinstance(end_pt, vtki.vtkActor):
2045            end_pt = end_pt.GetPosition()
2046        if len(start_pt) == 2:
2047            start_pt = [start_pt[0], start_pt[1], 0]
2048        if len(end_pt) == 2:
2049            end_pt = [end_pt[0], end_pt[1], 0]
2050
2051        headBase = 1 - head_length
2052        head_width = max(head_width, shaft_width)
2053        if head_length is None or headBase > shaft_length:
2054            headBase = shaft_length
2055
2056        verts = []
2057        verts.append([0, -shaft_width / 2, 0])
2058        verts.append([shaft_length, -shaft_width / 2, 0])
2059        verts.append([headBase, -head_width / 2, 0])
2060        verts.append([1, 0, 0])
2061        verts.append([headBase, head_width / 2, 0])
2062        verts.append([shaft_length, shaft_width / 2, 0])
2063        verts.append([0, shaft_width / 2, 0])
2064        if fill:
2065            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2066            poly = utils.buildPolyData(verts, faces)
2067        else:
2068            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2069            poly = utils.buildPolyData(verts, [], lines=lines)
2070
2071        axis = np.array(end_pt) - np.array(start_pt)
2072        length = float(np.linalg.norm(axis))
2073        if length:
2074            axis = axis / length
2075        theta = 0
2076        if len(axis) > 2:
2077            theta = np.arccos(axis[2])
2078        phi = np.arctan2(axis[1], axis[0])
2079
2080        t = vtki.vtkTransform()
2081        t.Translate(start_pt)
2082        if phi:
2083            t.RotateZ(np.rad2deg(phi))
2084        if theta:
2085            t.RotateY(np.rad2deg(theta))
2086        t.RotateY(-90)  # put it along Z
2087        if rotation:
2088            t.RotateX(rotation)
2089        t.Scale(length, length, length)
2090
2091        tf = vtki.new("TransformPolyDataFilter")
2092        tf.SetInputData(poly)
2093        tf.SetTransform(t)
2094        tf.Update()
2095
2096        super().__init__(tf.GetOutput(), c, alpha)
2097
2098        self.transform = LinearTransform().translate(start_pt)
2099
2100        self.lighting("off")
2101        self.actor.DragableOff()
2102        self.actor.PickableOff()
2103        self.base = np.array(start_pt, dtype=float) # used by pyplot
2104        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2105        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)
2003    def __init__(
2004        self,
2005        start_pt=(0, 0, 0),
2006        end_pt=(1, 0, 0),
2007        s=1,
2008        rotation=0.0,
2009        shaft_length=0.85,
2010        shaft_width=0.055,
2011        head_length=0.175,
2012        head_width=0.175,
2013        fill=True,
2014        c="red4",
2015        alpha=1.0,
2016   ) -> None:
2017        """
2018        Build a 2D arrow from `start_pt` to `end_pt`.
2019
2020        Arguments:
2021            s : (float)
2022                a global multiplicative convenience factor controlling the arrow size
2023            shaft_length : (float)
2024                fractional shaft length
2025            shaft_width : (float)
2026                fractional shaft width
2027            head_length : (float)
2028                fractional head length
2029            head_width : (float)
2030                fractional head width
2031            fill : (bool)
2032                if False only generate the outline
2033        """
2034        self.fill = fill  ## needed by pyplot.__iadd()
2035        self.s = s        ## needed by pyplot.__iadd()
2036
2037        if s != 1:
2038            shaft_width *= s
2039            head_width *= np.sqrt(s)
2040
2041        # in case user is passing meshs
2042        if isinstance(start_pt, vtki.vtkActor):
2043            start_pt = start_pt.GetPosition()
2044        if isinstance(end_pt, vtki.vtkActor):
2045            end_pt = end_pt.GetPosition()
2046        if len(start_pt) == 2:
2047            start_pt = [start_pt[0], start_pt[1], 0]
2048        if len(end_pt) == 2:
2049            end_pt = [end_pt[0], end_pt[1], 0]
2050
2051        headBase = 1 - head_length
2052        head_width = max(head_width, shaft_width)
2053        if head_length is None or headBase > shaft_length:
2054            headBase = shaft_length
2055
2056        verts = []
2057        verts.append([0, -shaft_width / 2, 0])
2058        verts.append([shaft_length, -shaft_width / 2, 0])
2059        verts.append([headBase, -head_width / 2, 0])
2060        verts.append([1, 0, 0])
2061        verts.append([headBase, head_width / 2, 0])
2062        verts.append([shaft_length, shaft_width / 2, 0])
2063        verts.append([0, shaft_width / 2, 0])
2064        if fill:
2065            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2066            poly = utils.buildPolyData(verts, faces)
2067        else:
2068            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2069            poly = utils.buildPolyData(verts, [], lines=lines)
2070
2071        axis = np.array(end_pt) - np.array(start_pt)
2072        length = float(np.linalg.norm(axis))
2073        if length:
2074            axis = axis / length
2075        theta = 0
2076        if len(axis) > 2:
2077            theta = np.arccos(axis[2])
2078        phi = np.arctan2(axis[1], axis[0])
2079
2080        t = vtki.vtkTransform()
2081        t.Translate(start_pt)
2082        if phi:
2083            t.RotateZ(np.rad2deg(phi))
2084        if theta:
2085            t.RotateY(np.rad2deg(theta))
2086        t.RotateY(-90)  # put it along Z
2087        if rotation:
2088            t.RotateX(rotation)
2089        t.Scale(length, length, length)
2090
2091        tf = vtki.new("TransformPolyDataFilter")
2092        tf.SetInputData(poly)
2093        tf.SetTransform(t)
2094        tf.Update()
2095
2096        super().__init__(tf.GetOutput(), c, alpha)
2097
2098        self.transform = LinearTransform().translate(start_pt)
2099
2100        self.lighting("off")
2101        self.actor.DragableOff()
2102        self.actor.PickableOff()
2103        self.base = np.array(start_pt, dtype=float) # used by pyplot
2104        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2105        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):
2108class Arrows2D(Glyph):
2109    """
2110    Build 2D arrows between two lists of points.
2111    """
2112
2113    def __init__(
2114        self,
2115        start_pts,
2116        end_pts=None,
2117        s=1.0,
2118        rotation=0.0,
2119        shaft_length=0.8,
2120        shaft_width=0.05,
2121        head_length=0.225,
2122        head_width=0.175,
2123        fill=True,
2124        c=None,
2125        alpha=1.0,
2126    ) -> None:
2127        """
2128        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2129        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2130
2131        Color can be specified as a colormap which maps the size of the arrows.
2132
2133        Arguments:
2134            shaft_length : (float)
2135                fractional shaft length
2136            shaft_width : (float)
2137                fractional shaft width
2138            head_length : (float)
2139                fractional head length
2140            head_width : (float)
2141                fractional head width
2142            fill : (bool)
2143                if False only generate the outline
2144        """
2145        if isinstance(start_pts, Points):
2146            start_pts = start_pts.vertices
2147        if isinstance(end_pts, Points):
2148            end_pts = end_pts.vertices
2149
2150        start_pts = np.asarray(start_pts, dtype=float)
2151        if end_pts is None:
2152            strt = start_pts[:, 0]
2153            end_pts = start_pts[:, 1]
2154            start_pts = strt
2155        else:
2156            end_pts = np.asarray(end_pts, dtype=float)
2157
2158        if head_length is None:
2159            head_length = 1 - shaft_length
2160
2161        arr = Arrow2D(
2162            (0, 0, 0),
2163            (1, 0, 0),
2164            s=s,
2165            rotation=rotation,
2166            shaft_length=shaft_length,
2167            shaft_width=shaft_width,
2168            head_length=head_length,
2169            head_width=head_width,
2170            fill=fill,
2171        )
2172
2173        orients = end_pts - start_pts
2174        orients = utils.make3d(orients)
2175
2176        pts = Points(start_pts)
2177        super().__init__(
2178            pts,
2179            arr,
2180            orientation_array=orients,
2181            scale_by_vector_size=True,
2182            c=c,
2183            alpha=alpha,
2184        )
2185        self.flat().lighting("off").pickable(False)
2186        if c is not None:
2187            self.color(c)
2188        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)
2113    def __init__(
2114        self,
2115        start_pts,
2116        end_pts=None,
2117        s=1.0,
2118        rotation=0.0,
2119        shaft_length=0.8,
2120        shaft_width=0.05,
2121        head_length=0.225,
2122        head_width=0.175,
2123        fill=True,
2124        c=None,
2125        alpha=1.0,
2126    ) -> None:
2127        """
2128        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2129        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2130
2131        Color can be specified as a colormap which maps the size of the arrows.
2132
2133        Arguments:
2134            shaft_length : (float)
2135                fractional shaft length
2136            shaft_width : (float)
2137                fractional shaft width
2138            head_length : (float)
2139                fractional head length
2140            head_width : (float)
2141                fractional head width
2142            fill : (bool)
2143                if False only generate the outline
2144        """
2145        if isinstance(start_pts, Points):
2146            start_pts = start_pts.vertices
2147        if isinstance(end_pts, Points):
2148            end_pts = end_pts.vertices
2149
2150        start_pts = np.asarray(start_pts, dtype=float)
2151        if end_pts is None:
2152            strt = start_pts[:, 0]
2153            end_pts = start_pts[:, 1]
2154            start_pts = strt
2155        else:
2156            end_pts = np.asarray(end_pts, dtype=float)
2157
2158        if head_length is None:
2159            head_length = 1 - shaft_length
2160
2161        arr = Arrow2D(
2162            (0, 0, 0),
2163            (1, 0, 0),
2164            s=s,
2165            rotation=rotation,
2166            shaft_length=shaft_length,
2167            shaft_width=shaft_width,
2168            head_length=head_length,
2169            head_width=head_width,
2170            fill=fill,
2171        )
2172
2173        orients = end_pts - start_pts
2174        orients = utils.make3d(orients)
2175
2176        pts = Points(start_pts)
2177        super().__init__(
2178            pts,
2179            arr,
2180            orientation_array=orients,
2181            scale_by_vector_size=True,
2182            c=c,
2183            alpha=alpha,
2184        )
2185        self.flat().lighting("off").pickable(False)
2186        if c is not None:
2187            self.color(c)
2188        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):
2191class FlatArrow(Ribbon):
2192    """
2193    Build a 2D arrow in 3D space by joining two close lines.
2194    """
2195
2196    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2197        """
2198        Build a 2D arrow in 3D space by joining two close lines.
2199
2200        Examples:
2201            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2202
2203                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2204        """
2205        if isinstance(line1, Points):
2206            line1 = line1.vertices
2207        if isinstance(line2, Points):
2208            line2 = line2.vertices
2209
2210        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2211
2212        v = (sm1 - sm2) / 3 * tip_width
2213        p1 = sm1 + v
2214        p2 = sm2 - v
2215        pm1 = (sm1 + sm2) / 2
2216        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2217        pm12 = pm1 - pm2
2218        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2219
2220        line1.append(p1)
2221        line1.append(tip)
2222        line2.append(p2)
2223        line2.append(tip)
2224        resm = max(100, len(line1))
2225
2226        super().__init__(line1, line2, res=(resm, 1))
2227        self.phong().lighting("off")
2228        self.actor.PickableOff()
2229        self.actor.DragableOff()
2230        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)
2196    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2197        """
2198        Build a 2D arrow in 3D space by joining two close lines.
2199
2200        Examples:
2201            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2202
2203                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2204        """
2205        if isinstance(line1, Points):
2206            line1 = line1.vertices
2207        if isinstance(line2, Points):
2208            line2 = line2.vertices
2209
2210        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2211
2212        v = (sm1 - sm2) / 3 * tip_width
2213        p1 = sm1 + v
2214        p2 = sm2 - v
2215        pm1 = (sm1 + sm2) / 2
2216        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2217        pm12 = pm1 - pm2
2218        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2219
2220        line1.append(p1)
2221        line1.append(tip)
2222        line2.append(p2)
2223        line2.append(tip)
2224        resm = max(100, len(line1))
2225
2226        super().__init__(line1, line2, res=(resm, 1))
2227        self.phong().lighting("off")
2228        self.actor.PickableOff()
2229        self.actor.DragableOff()
2230        self.name = "FlatArrow"

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

Examples:
class Polygon(vedo.mesh.Mesh):
2243class Polygon(Mesh):
2244    """
2245    Build a polygon in the `xy` plane.
2246    """
2247
2248    def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None:
2249        """
2250        Build a polygon in the `xy` plane of `nsides` of radius `r`.
2251
2252        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png)
2253        """
2254        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False)
2255        pts = pol2cart(np.ones_like(t) * r, t).T
2256        faces = [list(range(nsides))]
2257        # do not use: vtkRegularPolygonSource
2258        super().__init__([pts, faces], c, alpha)
2259        if len(pos) == 2:
2260            pos = (pos[0], pos[1], 0)
2261        self.pos(pos)
2262        self.properties.LightingOff()
2263        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)
2248    def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None:
2249        """
2250        Build a polygon in the `xy` plane of `nsides` of radius `r`.
2251
2252        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png)
2253        """
2254        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False)
2255        pts = pol2cart(np.ones_like(t) * r, t).T
2256        faces = [list(range(nsides))]
2257        # do not use: vtkRegularPolygonSource
2258        super().__init__([pts, faces], c, alpha)
2259        if len(pos) == 2:
2260            pos = (pos[0], pos[1], 0)
2261        self.pos(pos)
2262        self.properties.LightingOff()
2263        self.name = "Polygon " + str(nsides)

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

class Triangle(vedo.mesh.Mesh):
2233class Triangle(Mesh):
2234    """Create a triangle from 3 points in space."""
2235
2236    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2237        """Create a triangle from 3 points in space."""
2238        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2239        self.properties.LightingOff()
2240        self.name = "Triangle"

Create a triangle from 3 points in space.

Triangle(p1, p2, p3, c='green7', alpha=1.0)
2236    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2237        """Create a triangle from 3 points in space."""
2238        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2239        self.properties.LightingOff()
2240        self.name = "Triangle"

Create a triangle from 3 points in space.

class Rectangle(vedo.mesh.Mesh):
3054class Rectangle(Mesh):
3055    """
3056    Build a rectangle in the xy plane.
3057    """
3058
3059    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3060        """
3061        Build a rectangle in the xy plane identified by any two corner points.
3062
3063        Arguments:
3064            p1 : (list)
3065                bottom-left position of the corner
3066            p2 : (list)
3067                top-right position of the corner
3068            radius : (float, list)
3069                smoothing radius of the corner in world units.
3070                A list can be passed with 4 individual values.
3071        """
3072        if len(p1) == 2:
3073            p1 = np.array([p1[0], p1[1], 0.0])
3074        else:
3075            p1 = np.array(p1, dtype=float)
3076        if len(p2) == 2:
3077            p2 = np.array([p2[0], p2[1], 0.0])
3078        else:
3079            p2 = np.array(p2, dtype=float)
3080
3081        self.corner1 = p1
3082        self.corner2 = p2
3083
3084        color = c
3085        smoothr = False
3086        risseq = False
3087        if utils.is_sequence(radius):
3088            risseq = True
3089            smoothr = True
3090            if max(radius) == 0:
3091                smoothr = False
3092        elif radius:
3093            smoothr = True
3094
3095        if not smoothr:
3096            radius = None
3097        self.radius = radius
3098
3099        if smoothr:
3100            r = radius
3101            if not risseq:
3102                r = [r, r, r, r]
3103            rd, ra, rb, rc = r
3104
3105            if p1[0] > p2[0]:  # flip p1 - p2
3106                p1, p2 = p2, p1
3107            if p1[1] > p2[1]:  # flip p1y - p2y
3108                p1[1], p2[1] = p2[1], p1[1]
3109
3110            px, py, _ = p2 - p1
3111            k = min(px / 2, py / 2)
3112            ra = min(abs(ra), k)
3113            rb = min(abs(rb), k)
3114            rc = min(abs(rc), k)
3115            rd = min(abs(rd), k)
3116            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3117            betas = np.split(beta, 4)
3118            rrx = np.cos(betas)
3119            rry = np.sin(betas)
3120
3121            q1 = (rd, 0)
3122            # q2 = (px-ra, 0)
3123            q3 = (px, ra)
3124            # q4 = (px, py-rb)
3125            q5 = (px - rb, py)
3126            # q6 = (rc, py)
3127            q7 = (0, py - rc)
3128            # q8 = (0, rd)
3129            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3130            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3131            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3132            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3133
3134            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3135            faces = [list(range(len(pts)))]
3136        else:
3137            p1r = np.array([p2[0], p1[1], 0.0])
3138            p2l = np.array([p1[0], p2[1], 0.0])
3139            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3140            faces = [(0, 1, 2, 3)]
3141
3142        super().__init__([pts, faces], color, alpha)
3143        self.pos(p1)
3144        self.properties.LightingOff()
3145        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)
3059    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3060        """
3061        Build a rectangle in the xy plane identified by any two corner points.
3062
3063        Arguments:
3064            p1 : (list)
3065                bottom-left position of the corner
3066            p2 : (list)
3067                top-right position of the corner
3068            radius : (float, list)
3069                smoothing radius of the corner in world units.
3070                A list can be passed with 4 individual values.
3071        """
3072        if len(p1) == 2:
3073            p1 = np.array([p1[0], p1[1], 0.0])
3074        else:
3075            p1 = np.array(p1, dtype=float)
3076        if len(p2) == 2:
3077            p2 = np.array([p2[0], p2[1], 0.0])
3078        else:
3079            p2 = np.array(p2, dtype=float)
3080
3081        self.corner1 = p1
3082        self.corner2 = p2
3083
3084        color = c
3085        smoothr = False
3086        risseq = False
3087        if utils.is_sequence(radius):
3088            risseq = True
3089            smoothr = True
3090            if max(radius) == 0:
3091                smoothr = False
3092        elif radius:
3093            smoothr = True
3094
3095        if not smoothr:
3096            radius = None
3097        self.radius = radius
3098
3099        if smoothr:
3100            r = radius
3101            if not risseq:
3102                r = [r, r, r, r]
3103            rd, ra, rb, rc = r
3104
3105            if p1[0] > p2[0]:  # flip p1 - p2
3106                p1, p2 = p2, p1
3107            if p1[1] > p2[1]:  # flip p1y - p2y
3108                p1[1], p2[1] = p2[1], p1[1]
3109
3110            px, py, _ = p2 - p1
3111            k = min(px / 2, py / 2)
3112            ra = min(abs(ra), k)
3113            rb = min(abs(rb), k)
3114            rc = min(abs(rc), k)
3115            rd = min(abs(rd), k)
3116            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3117            betas = np.split(beta, 4)
3118            rrx = np.cos(betas)
3119            rry = np.sin(betas)
3120
3121            q1 = (rd, 0)
3122            # q2 = (px-ra, 0)
3123            q3 = (px, ra)
3124            # q4 = (px, py-rb)
3125            q5 = (px - rb, py)
3126            # q6 = (rc, py)
3127            q7 = (0, py - rc)
3128            # q8 = (0, rd)
3129            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3130            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3131            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3132            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3133
3134            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3135            faces = [list(range(len(pts)))]
3136        else:
3137            p1r = np.array([p2[0], p1[1], 0.0])
3138            p2l = np.array([p1[0], p2[1], 0.0])
3139            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3140            faces = [(0, 1, 2, 3)]
3141
3142        super().__init__([pts, faces], color, alpha)
3143        self.pos(p1)
3144        self.properties.LightingOff()
3145        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):
2374class Disc(Mesh):
2375    """
2376    Build a 2D disc.
2377    """
2378
2379    def __init__(
2380        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2381    ) -> None:
2382        """
2383        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2384
2385        Set `res` as the resolution in R and Phi (can be a list).
2386
2387        Use `angle_range` to create a disc sector between the 2 specified angles.
2388
2389        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2390        """
2391        if utils.is_sequence(res):
2392            res_r, res_phi = res
2393        else:
2394            res_r, res_phi = res, 12 * res
2395
2396        if len(angle_range) == 0:
2397            ps = vtki.new("DiskSource")
2398        else:
2399            ps = vtki.new("SectorSource")
2400            ps.SetStartAngle(angle_range[0])
2401            ps.SetEndAngle(angle_range[1])
2402
2403        ps.SetInnerRadius(r1)
2404        ps.SetOuterRadius(r2)
2405        ps.SetRadialResolution(res_r)
2406        ps.SetCircumferentialResolution(res_phi)
2407        ps.Update()
2408        super().__init__(ps.GetOutput(), c, alpha)
2409        self.flat()
2410        self.pos(utils.make3d(pos))
2411        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)
2379    def __init__(
2380        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2381    ) -> None:
2382        """
2383        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2384
2385        Set `res` as the resolution in R and Phi (can be a list).
2386
2387        Use `angle_range` to create a disc sector between the 2 specified angles.
2388
2389        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2390        """
2391        if utils.is_sequence(res):
2392            res_r, res_phi = res
2393        else:
2394            res_r, res_phi = res, 12 * res
2395
2396        if len(angle_range) == 0:
2397            ps = vtki.new("DiskSource")
2398        else:
2399            ps = vtki.new("SectorSource")
2400            ps.SetStartAngle(angle_range[0])
2401            ps.SetEndAngle(angle_range[1])
2402
2403        ps.SetInnerRadius(r1)
2404        ps.SetOuterRadius(r2)
2405        ps.SetRadialResolution(res_r)
2406        ps.SetCircumferentialResolution(res_phi)
2407        ps.Update()
2408        super().__init__(ps.GetOutput(), c, alpha)
2409        self.flat()
2410        self.pos(utils.make3d(pos))
2411        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):
2266class Circle(Polygon):
2267    """
2268    Build a Circle of radius `r`.
2269    """
2270
2271    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None:
2272        """
2273        Build a Circle of radius `r`.
2274        """
2275        super().__init__(pos, nsides=res, r=r)
2276
2277        self.nr_of_points = 0
2278        self.va = 0
2279        self.vb = 0
2280        self.axis1: List[float] = []
2281        self.axis2: List[float] = []
2282        self.center: List[float] = []  # filled by pointcloud.pca_ellipse()
2283        self.pvalue = 0.0              # filled by pointcloud.pca_ellipse()
2284        self.alpha(alpha).c(c)
2285        self.name = "Circle"
2286    
2287    def acircularity(self) -> float:
2288        """
2289        Return a measure of how different an ellipse is from a circle.
2290        Values close to zero correspond to a circular object.
2291        """
2292        a, b = self.va, self.vb
2293        value = 0.0
2294        if a+b:
2295            value = ((a-b)/(a+b))**2
2296        return value

Build a Circle of radius r.

Circle(pos=(0, 0, 0), r=1.0, res=120, c='gray5', alpha=1.0)
2271    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None:
2272        """
2273        Build a Circle of radius `r`.
2274        """
2275        super().__init__(pos, nsides=res, r=r)
2276
2277        self.nr_of_points = 0
2278        self.va = 0
2279        self.vb = 0
2280        self.axis1: List[float] = []
2281        self.axis2: List[float] = []
2282        self.center: List[float] = []  # filled by pointcloud.pca_ellipse()
2283        self.pvalue = 0.0              # filled by pointcloud.pca_ellipse()
2284        self.alpha(alpha).c(c)
2285        self.name = "Circle"

Build a Circle of radius r.

def acircularity(self) -> float:
2287    def acircularity(self) -> float:
2288        """
2289        Return a measure of how different an ellipse is from a circle.
2290        Values close to zero correspond to a circular object.
2291        """
2292        a, b = self.va, self.vb
2293        value = 0.0
2294        if a+b:
2295            value = ((a-b)/(a+b))**2
2296        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):
2298class GeoCircle(Polygon):
2299    """
2300    Build a Circle of radius `r`.
2301    """
2302
2303    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None:
2304        """
2305        Build a Circle of radius `r` as projected on a geographic map.
2306        Circles near the poles will look very squashed.
2307
2308        See example:
2309            ```bash
2310            vedo -r earthquake
2311            ```
2312        """
2313        coords = []
2314        sinr, cosr = np.sin(r), np.cos(r)
2315        sinlat, coslat = np.sin(lat), np.cos(lat)
2316        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2317            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2318            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2319            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2320
2321        super().__init__(nsides=res, c=c, alpha=alpha)
2322        self.vertices = coords # warp polygon points to match geo projection
2323        self.name = "Circle"

Build a Circle of radius r.

GeoCircle(lat, lon, r=1.0, res=60, c='red4', alpha=1.0)
2303    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None:
2304        """
2305        Build a Circle of radius `r` as projected on a geographic map.
2306        Circles near the poles will look very squashed.
2307
2308        See example:
2309            ```bash
2310            vedo -r earthquake
2311            ```
2312        """
2313        coords = []
2314        sinr, cosr = np.sin(r), np.cos(r)
2315        sinlat, coslat = np.sin(lat), np.cos(lat)
2316        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2317            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2318            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2319            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2320
2321        super().__init__(nsides=res, c=c, alpha=alpha)
2322        self.vertices = coords # warp polygon points to match geo projection
2323        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
811    @property
812    def vertices(self):
813        """Return the vertices (points) coordinates."""
814        try:
815            # for polydata and unstructured grid
816            varr = self.dataset.GetPoints().GetData()
817        except (AttributeError, TypeError):
818            try:
819                # for RectilinearGrid, StructuredGrid
820                vpts = vtki.vtkPoints()
821                self.dataset.GetPoints(vpts)
822                varr = vpts.GetData()
823            except (AttributeError, TypeError):
824                try:
825                    # for ImageData
826                    v2p = vtki.new("ImageToPoints")
827                    v2p.SetInputData(self.dataset)
828                    v2p.Update()
829                    varr = v2p.GetOutput().GetPoints().GetData()
830                except AttributeError:
831                    return np.array([])
832
833        return utils.vtk2numpy(varr)

Return the vertices (points) coordinates.

class Arc(vedo.mesh.Mesh):
2414class Arc(Mesh):
2415    """
2416    Build a 2D circular arc between 2 points.
2417    """
2418
2419    def __init__(
2420        self,
2421        center,
2422        point1,
2423        point2=None,
2424        normal=None,
2425        angle=None,
2426        invert=False,
2427        res=50,
2428        c="gray4",
2429        alpha=1.0,
2430    ) -> None:
2431        """
2432        Build a 2D circular arc between 2 points `point1` and `point2`.
2433
2434        If `normal` is specified then `center` is ignored, and
2435        normal vector, a starting `point1` (polar vector)
2436        and an angle defining the arc length need to be assigned.
2437
2438        Arc spans the shortest angular sector point1 and point2,
2439        if `invert=True`, then the opposite happens.
2440        """
2441        if len(point1) == 2:
2442            point1 = (point1[0], point1[1], 0)
2443        if point2 is not None and len(point2) == 2:
2444            point2 = (point2[0], point2[1], 0)
2445
2446        ar = vtki.new("ArcSource")
2447        if point2 is not None:
2448            self.top = point2
2449            point2 = point2 - np.asarray(point1)
2450            ar.UseNormalAndAngleOff()
2451            ar.SetPoint1([0, 0, 0])
2452            ar.SetPoint2(point2)
2453            # ar.SetCenter(center)
2454        elif normal is not None and angle is not None:
2455            ar.UseNormalAndAngleOn()
2456            ar.SetAngle(angle)
2457            ar.SetPolarVector(point1)
2458            ar.SetNormal(normal)
2459        else:
2460            vedo.logger.error("incorrect input combination")
2461            return
2462        ar.SetNegative(invert)
2463        ar.SetResolution(res)
2464        ar.Update()
2465
2466        super().__init__(ar.GetOutput(), c, alpha)
2467        self.pos(center)
2468        self.lw(2).lighting("off")
2469        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)
2419    def __init__(
2420        self,
2421        center,
2422        point1,
2423        point2=None,
2424        normal=None,
2425        angle=None,
2426        invert=False,
2427        res=50,
2428        c="gray4",
2429        alpha=1.0,
2430    ) -> None:
2431        """
2432        Build a 2D circular arc between 2 points `point1` and `point2`.
2433
2434        If `normal` is specified then `center` is ignored, and
2435        normal vector, a starting `point1` (polar vector)
2436        and an angle defining the arc length need to be assigned.
2437
2438        Arc spans the shortest angular sector point1 and point2,
2439        if `invert=True`, then the opposite happens.
2440        """
2441        if len(point1) == 2:
2442            point1 = (point1[0], point1[1], 0)
2443        if point2 is not None and len(point2) == 2:
2444            point2 = (point2[0], point2[1], 0)
2445
2446        ar = vtki.new("ArcSource")
2447        if point2 is not None:
2448            self.top = point2
2449            point2 = point2 - np.asarray(point1)
2450            ar.UseNormalAndAngleOff()
2451            ar.SetPoint1([0, 0, 0])
2452            ar.SetPoint2(point2)
2453            # ar.SetCenter(center)
2454        elif normal is not None and angle is not None:
2455            ar.UseNormalAndAngleOn()
2456            ar.SetAngle(angle)
2457            ar.SetPolarVector(point1)
2458            ar.SetNormal(normal)
2459        else:
2460            vedo.logger.error("incorrect input combination")
2461            return
2462        ar.SetNegative(invert)
2463        ar.SetResolution(res)
2464        ar.Update()
2465
2466        super().__init__(ar.GetOutput(), c, alpha)
2467        self.pos(center)
2468        self.lw(2).lighting("off")
2469        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):
2326class Star(Mesh):
2327    """
2328    Build a 2D star shape.
2329    """
2330
2331    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2332        """
2333        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2334
2335        If line is True then only build the outer line (no internal surface meshing).
2336
2337        Example:
2338            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2339
2340                ![](https://vedo.embl.es/images/basic/extrude.png)
2341        """
2342        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2343        x, y = pol2cart(np.ones_like(t) * r2, t)
2344        pts = np.c_[x, y, np.zeros_like(x)]
2345
2346        apts = []
2347        for i, p in enumerate(pts):
2348            apts.append(p)
2349            if i + 1 < n:
2350                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2351        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2352
2353        if line:
2354            apts.append(pts[0])
2355            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2356            super().__init__(poly, c, alpha)
2357            self.lw(2)
2358        else:
2359            apts.append((0, 0, 0))
2360            cells = []
2361            for i in range(2 * n - 1):
2362                cell = [2 * n, i, i + 1]
2363                cells.append(cell)
2364            cells.append([2 * n, i + 1, 0])
2365            super().__init__([apts, cells], c, alpha)
2366
2367        if len(pos) == 2:
2368            pos = (pos[0], pos[1], 0)
2369
2370        self.properties.LightingOff()
2371        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)
2331    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2332        """
2333        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2334
2335        If line is True then only build the outer line (no internal surface meshing).
2336
2337        Example:
2338            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2339
2340                ![](https://vedo.embl.es/images/basic/extrude.png)
2341        """
2342        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2343        x, y = pol2cart(np.ones_like(t) * r2, t)
2344        pts = np.c_[x, y, np.zeros_like(x)]
2345
2346        apts = []
2347        for i, p in enumerate(pts):
2348            apts.append(p)
2349            if i + 1 < n:
2350                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2351        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2352
2353        if line:
2354            apts.append(pts[0])
2355            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2356            super().__init__(poly, c, alpha)
2357            self.lw(2)
2358        else:
2359            apts.append((0, 0, 0))
2360            cells = []
2361            for i in range(2 * n - 1):
2362                cell = [2 * n, i, i + 1]
2363                cells.append(cell)
2364            cells.append([2 * n, i + 1, 0])
2365            super().__init__([apts, cells], c, alpha)
2366
2367        if len(pos) == 2:
2368            pos = (pos[0], pos[1], 0)
2369
2370        self.properties.LightingOff()
2371        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):
3768class Star3D(Mesh):
3769    """
3770    Build a 3D starred shape.
3771    """
3772
3773    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3774        """
3775        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3776        """
3777        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3778               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3779               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3780               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3781        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3782               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3783               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3784               [10,1, 0],[10,11, 9]]
3785
3786        super().__init__([pts, fcs], c, alpha)
3787        self.rotate_x(90)
3788        self.scale(r).lighting("shiny")
3789
3790        if len(pos) == 2:
3791            pos = (pos[0], pos[1], 0)
3792        self.pos(pos)
3793        self.name = "Star3D"

Build a 3D starred shape.

Star3D(pos=(0, 0, 0), r=1.0, thickness=0.1, c='blue4', alpha=1.0)
3773    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3774        """
3775        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3776        """
3777        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3778               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3779               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3780               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3781        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3782               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3783               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3784               [10,1, 0],[10,11, 9]]
3785
3786        super().__init__([pts, fcs], c, alpha)
3787        self.rotate_x(90)
3788        self.scale(r).lighting("shiny")
3789
3790        if len(pos) == 2:
3791            pos = (pos[0], pos[1], 0)
3792        self.pos(pos)
3793        self.name = "Star3D"

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

class Cross3D(vedo.mesh.Mesh):
3796class Cross3D(Mesh):
3797    """
3798    Build a 3D cross shape.
3799    """
3800
3801    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3802        """
3803        Build a 3D cross shape, mainly useful as a 3D marker.
3804        """
3805        if len(pos) == 2:
3806            pos = (pos[0], pos[1], 0)
3807
3808        c1 = Cylinder(r=thickness * s, height=2 * s)
3809        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3810        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3811        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3812        super().__init__(poly, c, alpha)
3813        self.name = "Cross3D"

Build a 3D cross shape.

Cross3D(pos=(0, 0, 0), s=1.0, thickness=0.3, c='b', alpha=1.0)
3801    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3802        """
3803        Build a 3D cross shape, mainly useful as a 3D marker.
3804        """
3805        if len(pos) == 2:
3806            pos = (pos[0], pos[1], 0)
3807
3808        c1 = Cylinder(r=thickness * s, height=2 * s)
3809        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3810        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3811        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3812        super().__init__(poly, c, alpha)
3813        self.name = "Cross3D"

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

class IcoSphere(vedo.mesh.Mesh):
2472class IcoSphere(Mesh):
2473    """
2474    Create a sphere made of a uniform triangle mesh.
2475    """
2476
2477    def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None:
2478        """
2479        Create a sphere made of a uniform triangle mesh
2480        (from recursive subdivision of an icosahedron).
2481
2482        Example:
2483        ```python
2484        from vedo import *
2485        icos = IcoSphere(subdivisions=3)
2486        icos.compute_quality().cmap('coolwarm')
2487        icos.show(axes=1).close()
2488        ```
2489        ![](https://vedo.embl.es/images/basic/icosphere.jpg)
2490        """
2491        subdivisions = int(min(subdivisions, 9))  # to avoid disasters
2492
2493        t = (1.0 + np.sqrt(5.0)) / 2.0
2494        points = np.array(
2495            [
2496                [-1, t, 0],
2497                [1, t, 0],
2498                [-1, -t, 0],
2499                [1, -t, 0],
2500                [0, -1, t],
2501                [0, 1, t],
2502                [0, -1, -t],
2503                [0, 1, -t],
2504                [t, 0, -1],
2505                [t, 0, 1],
2506                [-t, 0, -1],
2507                [-t, 0, 1],
2508            ]
2509        )
2510        faces = [
2511            [0, 11, 5],
2512            [0, 5, 1],
2513            [0, 1, 7],
2514            [0, 7, 10],
2515            [0, 10, 11],
2516            [1, 5, 9],
2517            [5, 11, 4],
2518            [11, 10, 2],
2519            [10, 7, 6],
2520            [7, 1, 8],
2521            [3, 9, 4],
2522            [3, 4, 2],
2523            [3, 2, 6],
2524            [3, 6, 8],
2525            [3, 8, 9],
2526            [4, 9, 5],
2527            [2, 4, 11],
2528            [6, 2, 10],
2529            [8, 6, 7],
2530            [9, 8, 1],
2531        ]
2532        super().__init__([points * r, faces], c=c, alpha=alpha)
2533
2534        for _ in range(subdivisions):
2535            self.subdivide(method=1)
2536            pts = utils.versor(self.vertices) * r
2537            self.vertices = pts
2538
2539        self.pos(pos)
2540        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)
2477    def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None:
2478        """
2479        Create a sphere made of a uniform triangle mesh
2480        (from recursive subdivision of an icosahedron).
2481
2482        Example:
2483        ```python
2484        from vedo import *
2485        icos = IcoSphere(subdivisions=3)
2486        icos.compute_quality().cmap('coolwarm')
2487        icos.show(axes=1).close()
2488        ```
2489        ![](https://vedo.embl.es/images/basic/icosphere.jpg)
2490        """
2491        subdivisions = int(min(subdivisions, 9))  # to avoid disasters
2492
2493        t = (1.0 + np.sqrt(5.0)) / 2.0
2494        points = np.array(
2495            [
2496                [-1, t, 0],
2497                [1, t, 0],
2498                [-1, -t, 0],
2499                [1, -t, 0],
2500                [0, -1, t],
2501                [0, 1, t],
2502                [0, -1, -t],
2503                [0, 1, -t],
2504                [t, 0, -1],
2505                [t, 0, 1],
2506                [-t, 0, -1],
2507                [-t, 0, 1],
2508            ]
2509        )
2510        faces = [
2511            [0, 11, 5],
2512            [0, 5, 1],
2513            [0, 1, 7],
2514            [0, 7, 10],
2515            [0, 10, 11],
2516            [1, 5, 9],
2517            [5, 11, 4],
2518            [11, 10, 2],
2519            [10, 7, 6],
2520            [7, 1, 8],
2521            [3, 9, 4],
2522            [3, 4, 2],
2523            [3, 2, 6],
2524            [3, 6, 8],
2525            [3, 8, 9],
2526            [4, 9, 5],
2527            [2, 4, 11],
2528            [6, 2, 10],
2529            [8, 6, 7],
2530            [9, 8, 1],
2531        ]
2532        super().__init__([points * r, faces], c=c, alpha=alpha)
2533
2534        for _ in range(subdivisions):
2535            self.subdivide(method=1)
2536            pts = utils.versor(self.vertices) * r
2537            self.vertices = pts
2538
2539        self.pos(pos)
2540        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):
2543class Sphere(Mesh):
2544    """
2545    Build a sphere.
2546    """
2547
2548    def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None:
2549        """
2550        Build a sphere at position `pos` of radius `r`.
2551
2552        Arguments:
2553            r : (float)
2554                sphere radius
2555            res : (int, list)
2556                resolution in phi, resolution in theta is by default `2*res`
2557            quads : (bool)
2558                sphere mesh will be made of quads instead of triangles
2559
2560        [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png)
2561        """
2562        if len(pos) == 2:
2563            pos = np.asarray([pos[0], pos[1], 0])
2564
2565        self.radius = r  # used by fitSphere
2566        self.center = pos
2567        self.residue = 0
2568
2569        if quads:
2570            res = max(res, 4)
2571            img = vtki.vtkImageData()
2572            img.SetDimensions(res - 1, res - 1, res - 1)
2573            rs = 1.0 / (res - 2)
2574            img.SetSpacing(rs, rs, rs)
2575            gf = vtki.new("GeometryFilter")
2576            gf.SetInputData(img)
2577            gf.Update()
2578            super().__init__(gf.GetOutput(), c, alpha)
2579            self.lw(0.1)
2580
2581            cgpts = self.vertices - (0.5, 0.5, 0.5)
2582
2583            x, y, z = cgpts.T
2584            x = x * (1 + x * x) / 2
2585            y = y * (1 + y * y) / 2
2586            z = z * (1 + z * z) / 2
2587            _, theta, phi = cart2spher(x, y, z)
2588
2589            pts = spher2cart(np.ones_like(phi) * r, theta, phi).T
2590            self.vertices = pts
2591
2592        else:
2593            if utils.is_sequence(res):
2594                res_t, res_phi = res
2595            else:
2596                res_t, res_phi = 2 * res, res
2597
2598            ss = vtki.new("SphereSource")
2599            ss.SetRadius(r)
2600            ss.SetThetaResolution(res_t)
2601            ss.SetPhiResolution(res_phi)
2602            ss.Update()
2603
2604            super().__init__(ss.GetOutput(), c, alpha)
2605
2606        self.phong()
2607        self.pos(pos)
2608        self.name = "Sphere"

Build a sphere.

Sphere(pos=(0, 0, 0), r=1.0, res=24, quads=False, c='r5', alpha=1.0)
2548    def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None:
2549        """
2550        Build a sphere at position `pos` of radius `r`.
2551
2552        Arguments:
2553            r : (float)
2554                sphere radius
2555            res : (int, list)
2556                resolution in phi, resolution in theta is by default `2*res`
2557            quads : (bool)
2558                sphere mesh will be made of quads instead of triangles
2559
2560        [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png)
2561        """
2562        if len(pos) == 2:
2563            pos = np.asarray([pos[0], pos[1], 0])
2564
2565        self.radius = r  # used by fitSphere
2566        self.center = pos
2567        self.residue = 0
2568
2569        if quads:
2570            res = max(res, 4)
2571            img = vtki.vtkImageData()
2572            img.SetDimensions(res - 1, res - 1, res - 1)
2573            rs = 1.0 / (res - 2)
2574            img.SetSpacing(rs, rs, rs)
2575            gf = vtki.new("GeometryFilter")
2576            gf.SetInputData(img)
2577            gf.Update()
2578            super().__init__(gf.GetOutput(), c, alpha)
2579            self.lw(0.1)
2580
2581            cgpts = self.vertices - (0.5, 0.5, 0.5)
2582
2583            x, y, z = cgpts.T
2584            x = x * (1 + x * x) / 2
2585            y = y * (1 + y * y) / 2
2586            z = z * (1 + z * z) / 2
2587            _, theta, phi = cart2spher(x, y, z)
2588
2589            pts = spher2cart(np.ones_like(phi) * r, theta, phi).T
2590            self.vertices = pts
2591
2592        else:
2593            if utils.is_sequence(res):
2594                res_t, res_phi = res
2595            else:
2596                res_t, res_phi = 2 * res, res
2597
2598            ss = vtki.new("SphereSource")
2599            ss.SetRadius(r)
2600            ss.SetThetaResolution(res_t)
2601            ss.SetPhiResolution(res_phi)
2602            ss.Update()
2603
2604            super().__init__(ss.GetOutput(), c, alpha)
2605
2606        self.phong()
2607        self.pos(pos)
2608        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):
2611class Spheres(Mesh):
2612    """
2613    Build a large set of spheres.
2614    """
2615
2616    def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None:
2617        """
2618        Build a (possibly large) set of spheres at `centers` of radius `r`.
2619
2620        Either `c` or `r` can be a list of RGB colors or radii.
2621
2622        Examples:
2623            - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py)
2624
2625            ![](https://vedo.embl.es/images/basic/manyspheres.jpg)
2626        """
2627
2628        if isinstance(centers, Points):
2629            centers = centers.vertices
2630        centers = np.asarray(centers, dtype=float)
2631        base = centers[0]
2632
2633        cisseq = False
2634        if utils.is_sequence(c):
2635            cisseq = True
2636
2637        if cisseq:
2638            if len(centers) != len(c):
2639                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors")
2640                raise RuntimeError()
2641
2642        risseq = False
2643        if utils.is_sequence(r):
2644            risseq = True
2645
2646        if risseq:
2647            if len(centers) != len(r):
2648                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii")
2649                raise RuntimeError()
2650        if cisseq and risseq:
2651            vedo.logger.error("Limitation: c and r cannot be both sequences.")
2652            raise RuntimeError()
2653
2654        src = vtki.new("SphereSource")
2655        if not risseq:
2656            src.SetRadius(r)
2657        if utils.is_sequence(res):
2658            res_t, res_phi = res
2659        else:
2660            res_t, res_phi = 2 * res, res
2661
2662        src.SetThetaResolution(res_t)
2663        src.SetPhiResolution(res_phi)
2664        src.Update()
2665
2666        psrc = vtki.new("PointSource")
2667        psrc.SetNumberOfPoints(len(centers))
2668        psrc.Update()
2669        pd = psrc.GetOutput()
2670        vpts = pd.GetPoints()
2671
2672        glyph = vtki.vtkGlyph3D()
2673        glyph.SetSourceConnection(src.GetOutputPort())
2674
2675        if cisseq:
2676            glyph.SetColorModeToColorByScalar()
2677            ucols = vtki.vtkUnsignedCharArray()
2678            ucols.SetNumberOfComponents(3)
2679            ucols.SetName("Colors")
2680            for acol in c:
2681                cx, cy, cz = get_color(acol)
2682                ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255)
2683            pd.GetPointData().AddArray(ucols)
2684            pd.GetPointData().SetActiveScalars("Colors")
2685            glyph.ScalingOff()
2686        elif risseq:
2687            glyph.SetScaleModeToScaleByScalar()
2688            urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32)
2689            urads.SetName("Radii")
2690            pd.GetPointData().AddArray(urads)
2691            pd.GetPointData().SetActiveScalars("Radii")
2692
2693        vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32))
2694
2695        glyph.SetInputData(pd)
2696        glyph.Update()
2697
2698        super().__init__(glyph.GetOutput(), alpha=alpha)
2699        self.pos(base)
2700        self.phong()
2701        if cisseq:
2702            self.mapper.ScalarVisibilityOn()
2703        else:
2704            self.mapper.ScalarVisibilityOff()
2705            self.c(c)
2706        self.name = "Spheres"

Build a large set of spheres.

Spheres(centers, r=1.0, res=8, c='red5', alpha=1)
2616    def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None:
2617        """
2618        Build a (possibly large) set of spheres at `centers` of radius `r`.
2619
2620        Either `c` or `r` can be a list of RGB colors or radii.
2621
2622        Examples:
2623            - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py)
2624
2625            ![](https://vedo.embl.es/images/basic/manyspheres.jpg)
2626        """
2627
2628        if isinstance(centers, Points):
2629            centers = centers.vertices
2630        centers = np.asarray(centers, dtype=float)
2631        base = centers[0]
2632
2633        cisseq = False
2634        if utils.is_sequence(c):
2635            cisseq = True
2636
2637        if cisseq:
2638            if len(centers) != len(c):
2639                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors")
2640                raise RuntimeError()
2641
2642        risseq = False
2643        if utils.is_sequence(r):
2644            risseq = True
2645
2646        if risseq:
2647            if len(centers) != len(r):
2648                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii")
2649                raise RuntimeError()
2650        if cisseq and risseq:
2651            vedo.logger.error("Limitation: c and r cannot be both sequences.")
2652            raise RuntimeError()
2653
2654        src = vtki.new("SphereSource")
2655        if not risseq:
2656            src.SetRadius(r)
2657        if utils.is_sequence(res):
2658            res_t, res_phi = res
2659        else:
2660            res_t, res_phi = 2 * res, res
2661
2662        src.SetThetaResolution(res_t)
2663        src.SetPhiResolution(res_phi)
2664        src.Update()
2665
2666        psrc = vtki.new("PointSource")
2667        psrc.SetNumberOfPoints(len(centers))
2668        psrc.Update()
2669        pd = psrc.GetOutput()
2670        vpts = pd.GetPoints()
2671
2672        glyph = vtki.vtkGlyph3D()
2673        glyph.SetSourceConnection(src.GetOutputPort())
2674
2675        if cisseq:
2676            glyph.SetColorModeToColorByScalar()
2677            ucols = vtki.vtkUnsignedCharArray()
2678            ucols.SetNumberOfComponents(3)
2679            ucols.SetName("Colors")
2680            for acol in c:
2681                cx, cy, cz = get_color(acol)
2682                ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255)
2683            pd.GetPointData().AddArray(ucols)
2684            pd.GetPointData().SetActiveScalars("Colors")
2685            glyph.ScalingOff()
2686        elif risseq:
2687            glyph.SetScaleModeToScaleByScalar()
2688            urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32)
2689            urads.SetName("Radii")
2690            pd.GetPointData().AddArray(urads)
2691            pd.GetPointData().SetActiveScalars("Radii")
2692
2693        vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32))
2694
2695        glyph.SetInputData(pd)
2696        glyph.Update()
2697
2698        super().__init__(glyph.GetOutput(), alpha=alpha)
2699        self.pos(base)
2700        self.phong()
2701        if cisseq:
2702            self.mapper.ScalarVisibilityOn()
2703        else:
2704            self.mapper.ScalarVisibilityOff()
2705            self.c(c)
2706        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):
2709class Earth(Mesh):
2710    """
2711    Build a textured mesh representing the Earth.
2712    """
2713
2714    def __init__(self, style=1, r=1.0) -> None:
2715        """
2716        Build a textured mesh representing the Earth.
2717
2718        Example:
2719            - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py)
2720
2721                ![](https://vedo.embl.es/images/advanced/geodesic.png)
2722        """
2723        tss = vtki.new("TexturedSphereSource")
2724        tss.SetRadius(r)
2725        tss.SetThetaResolution(72)
2726        tss.SetPhiResolution(36)
2727        tss.Update()
2728        super().__init__(tss.GetOutput(), c="w")
2729        atext = vtki.vtkTexture()
2730        pnm_reader = vtki.new("JPEGReader")
2731        fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False)
2732        pnm_reader.SetFileName(fn)
2733        atext.SetInputConnection(pnm_reader.GetOutputPort())
2734        atext.InterpolateOn()
2735        self.texture(atext)
2736        self.name = "Earth"

Build a textured mesh representing the Earth.

Earth(style=1, r=1.0)
2714    def __init__(self, style=1, r=1.0) -> None:
2715        """
2716        Build a textured mesh representing the Earth.
2717
2718        Example:
2719            - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py)
2720
2721                ![](https://vedo.embl.es/images/advanced/geodesic.png)
2722        """
2723        tss = vtki.new("TexturedSphereSource")
2724        tss.SetRadius(r)
2725        tss.SetThetaResolution(72)
2726        tss.SetPhiResolution(36)
2727        tss.Update()
2728        super().__init__(tss.GetOutput(), c="w")
2729        atext = vtki.vtkTexture()
2730        pnm_reader = vtki.new("JPEGReader")
2731        fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False)
2732        pnm_reader.SetFileName(fn)
2733        atext.SetInputConnection(pnm_reader.GetOutputPort())
2734        atext.InterpolateOn()
2735        self.texture(atext)
2736        self.name = "Earth"

Build a textured mesh representing the Earth.

Example:
class Ellipsoid(vedo.mesh.Mesh):
2739class Ellipsoid(Mesh):
2740    """Build a 3D ellipsoid."""
2741    def __init__(
2742        self,
2743        pos=(0, 0, 0),
2744        axis1=(0.5, 0, 0),
2745        axis2=(0, 1, 0),
2746        axis3=(0, 0, 1.5),
2747        res=24,
2748        c="cyan4",
2749        alpha=1.0,
2750    ) -> None:
2751        """
2752        Build a 3D ellipsoid centered at position `pos`.
2753
2754        Arguments:
2755            axis1 : (list)
2756                First axis. Length corresponds to semi-axis.
2757            axis2 : (list)
2758                Second axis. Length corresponds to semi-axis.
2759            axis3 : (list)
2760                Third axis. Length corresponds to semi-axis.
2761        """        
2762        self.center = utils.make3d(pos)
2763
2764        self.axis1 = utils.make3d(axis1)
2765        self.axis2 = utils.make3d(axis2)
2766        self.axis3 = utils.make3d(axis3)
2767
2768        self.va = np.linalg.norm(self.axis1)
2769        self.vb = np.linalg.norm(self.axis2)
2770        self.vc = np.linalg.norm(self.axis3)
2771
2772        self.va_error = 0
2773        self.vb_error = 0
2774        self.vc_error = 0
2775
2776        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2777        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2778
2779        if utils.is_sequence(res):
2780            res_t, res_phi = res
2781        else:
2782            res_t, res_phi = 2 * res, res
2783
2784        elli_source = vtki.new("SphereSource")
2785        elli_source.SetRadius(1)
2786        elli_source.SetThetaResolution(res_t)
2787        elli_source.SetPhiResolution(res_phi)
2788        elli_source.Update()
2789
2790        super().__init__(elli_source.GetOutput(), c, alpha)
2791
2792        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2793        lt = LinearTransform(matrix).translate(pos)
2794        self.apply_transform(lt)
2795        self.name = "Ellipsoid"
2796
2797    def asphericity(self) -> float:
2798        """
2799        Return a measure of how different an ellipsoid is from a sphere.
2800        Values close to zero correspond to a spheric object.
2801        """
2802        a, b, c = self.va, self.vb, self.vc
2803        asp = ( ((a-b)/(a+b))**2
2804              + ((a-c)/(a+c))**2
2805              + ((b-c)/(b+c))**2 ) / 3. * 4.
2806        return float(asp)
2807
2808    def asphericity_error(self) -> float:
2809        """
2810        Calculate statistical error on the asphericity value.
2811
2812        Errors on the main axes are stored in
2813        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2814        """
2815        a, b, c = self.va, self.vb, self.vc
2816        sqrtn = np.sqrt(self.nr_of_points)
2817        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2818
2819        # from sympy import *
2820        # init_printing(use_unicode=True)
2821        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2822        # L = (
2823        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2824        #    / 3 * 4)
2825        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2826        # print(dl2)
2827        # exit()
2828
2829        dL2 = (
2830            ea ** 2
2831            * (
2832                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2833                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2834                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2835                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2836            ) ** 2
2837            + eb ** 2
2838            * (
2839                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2840                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2841                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2842                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2843            ) ** 2
2844            + ec ** 2
2845            * (
2846                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2847                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2848                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2849                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2850            ) ** 2
2851        )
2852        err = np.sqrt(dL2)
2853        self.va_error = ea
2854        self.vb_error = eb
2855        self.vc_error = ec
2856        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)
2741    def __init__(
2742        self,
2743        pos=(0, 0, 0),
2744        axis1=(0.5, 0, 0),
2745        axis2=(0, 1, 0),
2746        axis3=(0, 0, 1.5),
2747        res=24,
2748        c="cyan4",
2749        alpha=1.0,
2750    ) -> None:
2751        """
2752        Build a 3D ellipsoid centered at position `pos`.
2753
2754        Arguments:
2755            axis1 : (list)
2756                First axis. Length corresponds to semi-axis.
2757            axis2 : (list)
2758                Second axis. Length corresponds to semi-axis.
2759            axis3 : (list)
2760                Third axis. Length corresponds to semi-axis.
2761        """        
2762        self.center = utils.make3d(pos)
2763
2764        self.axis1 = utils.make3d(axis1)
2765        self.axis2 = utils.make3d(axis2)
2766        self.axis3 = utils.make3d(axis3)
2767
2768        self.va = np.linalg.norm(self.axis1)
2769        self.vb = np.linalg.norm(self.axis2)
2770        self.vc = np.linalg.norm(self.axis3)
2771
2772        self.va_error = 0
2773        self.vb_error = 0
2774        self.vc_error = 0
2775
2776        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2777        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2778
2779        if utils.is_sequence(res):
2780            res_t, res_phi = res
2781        else:
2782            res_t, res_phi = 2 * res, res
2783
2784        elli_source = vtki.new("SphereSource")
2785        elli_source.SetRadius(1)
2786        elli_source.SetThetaResolution(res_t)
2787        elli_source.SetPhiResolution(res_phi)
2788        elli_source.Update()
2789
2790        super().__init__(elli_source.GetOutput(), c, alpha)
2791
2792        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2793        lt = LinearTransform(matrix).translate(pos)
2794        self.apply_transform(lt)
2795        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:
2797    def asphericity(self) -> float:
2798        """
2799        Return a measure of how different an ellipsoid is from a sphere.
2800        Values close to zero correspond to a spheric object.
2801        """
2802        a, b, c = self.va, self.vb, self.vc
2803        asp = ( ((a-b)/(a+b))**2
2804              + ((a-c)/(a+c))**2
2805              + ((b-c)/(b+c))**2 ) / 3. * 4.
2806        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:
2808    def asphericity_error(self) -> float:
2809        """
2810        Calculate statistical error on the asphericity value.
2811
2812        Errors on the main axes are stored in
2813        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2814        """
2815        a, b, c = self.va, self.vb, self.vc
2816        sqrtn = np.sqrt(self.nr_of_points)
2817        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2818
2819        # from sympy import *
2820        # init_printing(use_unicode=True)
2821        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2822        # L = (
2823        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2824        #    / 3 * 4)
2825        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2826        # print(dl2)
2827        # exit()
2828
2829        dL2 = (
2830            ea ** 2
2831            * (
2832                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2833                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2834                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2835                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2836            ) ** 2
2837            + eb ** 2
2838            * (
2839                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2840                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2841                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2842                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2843            ) ** 2
2844            + ec ** 2
2845            * (
2846                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2847                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2848                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2849                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2850            ) ** 2
2851        )
2852        err = np.sqrt(dL2)
2853        self.va_error = ea
2854        self.vb_error = eb
2855        self.vc_error = ec
2856        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):
2859class Grid(Mesh):
2860    """
2861    An even or uneven 2D grid.
2862    """
2863
2864    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2865        """
2866        Create an even or uneven 2D grid.
2867        Can also be created from a `np.mgrid` object (see example).
2868
2869        Arguments:
2870            pos : (list, Points, Mesh)
2871                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2872            s : (float, list)
2873                if a float is provided it is interpreted as the total size along x and y,
2874                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2875                In this case keyword `res` is ignored (see example below).
2876            res : (list)
2877                resolutions along x and y, e.i. the number of subdivisions
2878            lw : (int)
2879                line width
2880
2881        Example:
2882            ```python
2883            from vedo import *
2884            xcoords = np.arange(0, 2, 0.2)
2885            ycoords = np.arange(0, 1, 0.2)
2886            sqrtx = sqrt(xcoords)
2887            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2888            grid.show(axes=8).close()
2889
2890            # Can also create a grid from a np.mgrid:
2891            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2892            vgrid = Grid(s=(X[:,0], Y[0]))
2893            vgrid.show(axes=8).close()
2894            ```
2895            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2896        """
2897        resx, resy = res
2898        sx, sy = s
2899        
2900        try:
2901            bb = pos.bounds()
2902            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2903            sx = bb[1] - bb[0]
2904            sy = bb[3] - bb[2]
2905        except AttributeError:
2906            pass        
2907
2908        if len(pos) == 2:
2909            pos = (pos[0], pos[1], 0)
2910        elif len(pos) in [4,6]: # passing a bounding box
2911            bb = pos
2912            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2913            sx = bb[1] - bb[0]
2914            sy = bb[3] - bb[2]
2915            if len(pos)==6:
2916                pos[2] = bb[4] - bb[5]
2917
2918        if utils.is_sequence(sx) and utils.is_sequence(sy):
2919            verts = []
2920            for y in sy:
2921                for x in sx:
2922                    verts.append([x, y, 0])
2923            faces = []
2924            n = len(sx)
2925            m = len(sy)
2926            for j in range(m - 1):
2927                j1n = (j + 1) * n
2928                for i in range(n - 1):
2929                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2930
2931            super().__init__([verts, faces], c, alpha)
2932
2933        else:
2934            ps = vtki.new("PlaneSource")
2935            ps.SetResolution(resx, resy)
2936            ps.Update()
2937
2938            t = vtki.vtkTransform()
2939            t.Translate(pos)
2940            t.Scale(sx, sy, 1)
2941
2942            tf = vtki.new("TransformPolyDataFilter")
2943            tf.SetInputData(ps.GetOutput())
2944            tf.SetTransform(t)
2945            tf.Update()
2946
2947            super().__init__(tf.GetOutput(), c, alpha)
2948
2949        self.wireframe().lw(lw)
2950        self.properties.LightingOff()
2951        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)
2864    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2865        """
2866        Create an even or uneven 2D grid.
2867        Can also be created from a `np.mgrid` object (see example).
2868
2869        Arguments:
2870            pos : (list, Points, Mesh)
2871                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2872            s : (float, list)
2873                if a float is provided it is interpreted as the total size along x and y,
2874                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2875                In this case keyword `res` is ignored (see example below).
2876            res : (list)
2877                resolutions along x and y, e.i. the number of subdivisions
2878            lw : (int)
2879                line width
2880
2881        Example:
2882            ```python
2883            from vedo import *
2884            xcoords = np.arange(0, 2, 0.2)
2885            ycoords = np.arange(0, 1, 0.2)
2886            sqrtx = sqrt(xcoords)
2887            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2888            grid.show(axes=8).close()
2889
2890            # Can also create a grid from a np.mgrid:
2891            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2892            vgrid = Grid(s=(X[:,0], Y[0]))
2893            vgrid.show(axes=8).close()
2894            ```
2895            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2896        """
2897        resx, resy = res
2898        sx, sy = s
2899        
2900        try:
2901            bb = pos.bounds()
2902            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2903            sx = bb[1] - bb[0]
2904            sy = bb[3] - bb[2]
2905        except AttributeError:
2906            pass        
2907
2908        if len(pos) == 2:
2909            pos = (pos[0], pos[1], 0)
2910        elif len(pos) in [4,6]: # passing a bounding box
2911            bb = pos
2912            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2913            sx = bb[1] - bb[0]
2914            sy = bb[3] - bb[2]
2915            if len(pos)==6:
2916                pos[2] = bb[4] - bb[5]
2917
2918        if utils.is_sequence(sx) and utils.is_sequence(sy):
2919            verts = []
2920            for y in sy:
2921                for x in sx:
2922                    verts.append([x, y, 0])
2923            faces = []
2924            n = len(sx)
2925            m = len(sy)
2926            for j in range(m - 1):
2927                j1n = (j + 1) * n
2928                for i in range(n - 1):
2929                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2930
2931            super().__init__([verts, faces], c, alpha)
2932
2933        else:
2934            ps = vtki.new("PlaneSource")
2935            ps.SetResolution(resx, resy)
2936            ps.Update()
2937
2938            t = vtki.vtkTransform()
2939            t.Translate(pos)
2940            t.Scale(sx, sy, 1)
2941
2942            tf = vtki.new("TransformPolyDataFilter")
2943            tf.SetInputData(ps.GetOutput())
2944            tf.SetTransform(t)
2945            tf.Update()
2946
2947            super().__init__(tf.GetOutput(), c, alpha)
2948
2949        self.wireframe().lw(lw)
2950        self.properties.LightingOff()
2951        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):
3233class TessellatedBox(Mesh):
3234    """
3235    Build a cubic `Mesh` made of quads.
3236    """
3237
3238    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3239        """
3240        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3241
3242        Arguments:
3243            pos : (list)
3244                position of the left bottom corner
3245            n : (int, list)
3246                number of subdivisions along each side
3247            spacing : (float)
3248                size of the side of the single quad in the 3 directions
3249        """
3250        if utils.is_sequence(n):  # slow
3251            img = vtki.vtkImageData()
3252            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3253            img.SetSpacing(spacing)
3254            gf = vtki.new("GeometryFilter")
3255            gf.SetInputData(img)
3256            gf.Update()
3257            poly = gf.GetOutput()
3258        else:  # fast
3259            n -= 1
3260            tbs = vtki.new("TessellatedBoxSource")
3261            tbs.SetLevel(n)
3262            if len(bounds):
3263                tbs.SetBounds(bounds)
3264            else:
3265                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3266            tbs.QuadsOn()
3267            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3268            tbs.Update()
3269            poly = tbs.GetOutput()
3270        super().__init__(poly, c=c, alpha=alpha)
3271        self.pos(pos)
3272        self.lw(1).lighting("off")
3273        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)
3238    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3239        """
3240        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3241
3242        Arguments:
3243            pos : (list)
3244                position of the left bottom corner
3245            n : (int, list)
3246                number of subdivisions along each side
3247            spacing : (float)
3248                size of the side of the single quad in the 3 directions
3249        """
3250        if utils.is_sequence(n):  # slow
3251            img = vtki.vtkImageData()
3252            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3253            img.SetSpacing(spacing)
3254            gf = vtki.new("GeometryFilter")
3255            gf.SetInputData(img)
3256            gf.Update()
3257            poly = gf.GetOutput()
3258        else:  # fast
3259            n -= 1
3260            tbs = vtki.new("TessellatedBoxSource")
3261            tbs.SetLevel(n)
3262            if len(bounds):
3263                tbs.SetBounds(bounds)
3264            else:
3265                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3266            tbs.QuadsOn()
3267            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3268            tbs.Update()
3269            poly = tbs.GetOutput()
3270        super().__init__(poly, c=c, alpha=alpha)
3271        self.pos(pos)
3272        self.lw(1).lighting("off")
3273        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):
2954class Plane(Mesh):
2955    """Create a plane in space."""
2956
2957    def __init__(
2958            self,
2959            pos=(0, 0, 0),
2960            normal=(0, 0, 1),
2961            s=(1, 1),
2962            res=(1, 1),
2963            c="gray5", alpha=1.0,
2964        ) -> None:
2965        """
2966        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
2967        to vector `normal` so that it passes through point `pos`.
2968
2969        Arguments:
2970            pos : (list)
2971                position of the plane center
2972            normal : (list)
2973                normal vector to the plane
2974            s : (list)
2975                size of the plane along x and y
2976            res : (list)
2977                resolution of the plane along x and y
2978        """
2979        if isinstance(pos, vtki.vtkPolyData):
2980            super().__init__(pos, c, alpha)
2981            # self.transform = LinearTransform().translate(pos)
2982
2983        else:
2984            ps = vtki.new("PlaneSource")
2985            ps.SetResolution(res[0], res[1])
2986            tri = vtki.new("TriangleFilter")
2987            tri.SetInputConnection(ps.GetOutputPort())
2988            tri.Update()
2989            
2990            super().__init__(tri.GetOutput(), c, alpha)
2991
2992            pos = utils.make3d(pos)
2993            normal = np.asarray(normal, dtype=float)
2994            axis = normal / np.linalg.norm(normal)
2995            theta = np.arccos(axis[2])
2996            phi = np.arctan2(axis[1], axis[0])
2997
2998            t = LinearTransform()
2999            t.scale([s[0], s[1], 1])
3000            t.rotate_y(np.rad2deg(theta))
3001            t.rotate_z(np.rad2deg(phi))
3002            t.translate(pos)
3003            self.apply_transform(t)
3004
3005        self.lighting("off")
3006        self.name = "Plane"
3007        self.variance = 0
3008
3009    def clone(self, deep=True) -> "Plane":
3010        newplane = Plane()
3011        if deep:
3012            newplane.dataset.DeepCopy(self.dataset)
3013        else:
3014            newplane.dataset.ShallowCopy(self.dataset)
3015        newplane.copy_properties_from(self)
3016        newplane.transform = self.transform.clone()
3017        newplane.variance = 0
3018        return newplane
3019    
3020    @property
3021    def normal(self) -> np.ndarray:
3022        pts = self.vertices
3023        AB = pts[1] - pts[0]
3024        AC = pts[2] - pts[0]
3025        normal = np.cross(AB, AC)
3026        normal = normal / np.linalg.norm(normal)
3027        return normal
3028
3029    @property
3030    def center(self) -> np.ndarray:
3031        pts = self.vertices
3032        return np.mean(pts, axis=0)
3033
3034    def contains(self, points, tol=0) -> np.ndarray:
3035        """
3036        Check if each of the provided point lies on this plane.
3037        `points` is an array of shape (n, 3).
3038        """
3039        points = np.array(points, dtype=float)
3040        bounds = self.vertices
3041
3042        mask = np.isclose(np.dot(points - self.center, self.normal), tol)
3043
3044        for i in [1, 3]:
3045            AB = bounds[i] - bounds[0]
3046            AP = points - bounds[0]
3047            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3048            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3049            mask = np.logical_and(mask, mask_l)
3050            mask = np.logical_and(mask, mask_g)
3051        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)
2957    def __init__(
2958            self,
2959            pos=(0, 0, 0),
2960            normal=(0, 0, 1),
2961            s=(1, 1),
2962            res=(1, 1),
2963            c="gray5", alpha=1.0,
2964        ) -> None:
2965        """
2966        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
2967        to vector `normal` so that it passes through point `pos`.
2968
2969        Arguments:
2970            pos : (list)
2971                position of the plane center
2972            normal : (list)
2973                normal vector to the plane
2974            s : (list)
2975                size of the plane along x and y
2976            res : (list)
2977                resolution of the plane along x and y
2978        """
2979        if isinstance(pos, vtki.vtkPolyData):
2980            super().__init__(pos, c, alpha)
2981            # self.transform = LinearTransform().translate(pos)
2982
2983        else:
2984            ps = vtki.new("PlaneSource")
2985            ps.SetResolution(res[0], res[1])
2986            tri = vtki.new("TriangleFilter")
2987            tri.SetInputConnection(ps.GetOutputPort())
2988            tri.Update()
2989            
2990            super().__init__(tri.GetOutput(), c, alpha)
2991
2992            pos = utils.make3d(pos)
2993            normal = np.asarray(normal, dtype=float)
2994            axis = normal / np.linalg.norm(normal)
2995            theta = np.arccos(axis[2])
2996            phi = np.arctan2(axis[1], axis[0])
2997
2998            t = LinearTransform()
2999            t.scale([s[0], s[1], 1])
3000            t.rotate_y(np.rad2deg(theta))
3001            t.rotate_z(np.rad2deg(phi))
3002            t.translate(pos)
3003            self.apply_transform(t)
3004
3005        self.lighting("off")
3006        self.name = "Plane"
3007        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:
3009    def clone(self, deep=True) -> "Plane":
3010        newplane = Plane()
3011        if deep:
3012            newplane.dataset.DeepCopy(self.dataset)
3013        else:
3014            newplane.dataset.ShallowCopy(self.dataset)
3015        newplane.copy_properties_from(self)
3016        newplane.transform = self.transform.clone()
3017        newplane.variance = 0
3018        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:
3034    def contains(self, points, tol=0) -> np.ndarray:
3035        """
3036        Check if each of the provided point lies on this plane.
3037        `points` is an array of shape (n, 3).
3038        """
3039        points = np.array(points, dtype=float)
3040        bounds = self.vertices
3041
3042        mask = np.isclose(np.dot(points - self.center, self.normal), tol)
3043
3044        for i in [1, 3]:
3045            AB = bounds[i] - bounds[0]
3046            AP = points - bounds[0]
3047            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3048            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3049            mask = np.logical_and(mask, mask_l)
3050            mask = np.logical_and(mask, mask_g)
3051        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):
3148class Box(Mesh):
3149    """
3150    Build a box of specified dimensions.
3151    """
3152
3153    def __init__(
3154            self, pos=(0, 0, 0), 
3155            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3156        """
3157        Build a box of dimensions `x=length, y=width and z=height`.
3158        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3159
3160        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3161        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3162
3163        Examples:
3164            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3165
3166                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3167        """
3168        src = vtki.new("CubeSource")
3169
3170        if len(pos) == 2:
3171            pos = (pos[0], pos[1], 0)
3172
3173        if len(pos) == 6:
3174            src.SetBounds(pos)
3175            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3176        elif len(size) == 3:
3177            length, width, height = size
3178            src.SetXLength(length)
3179            src.SetYLength(width)
3180            src.SetZLength(height)
3181            src.SetCenter(pos)
3182        else:
3183            src.SetXLength(length)
3184            src.SetYLength(width)
3185            src.SetZLength(height)
3186            src.SetCenter(pos)
3187
3188        src.Update()
3189        pd = src.GetOutput()
3190
3191        tc = [
3192            [0.0, 0.0],
3193            [1.0, 0.0],
3194            [0.0, 1.0],
3195            [1.0, 1.0],
3196            [1.0, 0.0],
3197            [0.0, 0.0],
3198            [1.0, 1.0],
3199            [0.0, 1.0],
3200            [1.0, 1.0],
3201            [1.0, 0.0],
3202            [0.0, 1.0],
3203            [0.0, 0.0],
3204            [0.0, 1.0],
3205            [0.0, 0.0],
3206            [1.0, 1.0],
3207            [1.0, 0.0],
3208            [1.0, 0.0],
3209            [0.0, 0.0],
3210            [1.0, 1.0],
3211            [0.0, 1.0],
3212            [0.0, 0.0],
3213            [1.0, 0.0],
3214            [0.0, 1.0],
3215            [1.0, 1.0],
3216        ]
3217        vtc = utils.numpy2vtk(tc)
3218        pd.GetPointData().SetTCoords(vtc)
3219        super().__init__(pd, c, alpha)
3220        self.transform = LinearTransform().translate(pos)
3221        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)
3153    def __init__(
3154            self, pos=(0, 0, 0), 
3155            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3156        """
3157        Build a box of dimensions `x=length, y=width and z=height`.
3158        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3159
3160        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3161        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3162
3163        Examples:
3164            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3165
3166                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3167        """
3168        src = vtki.new("CubeSource")
3169
3170        if len(pos) == 2:
3171            pos = (pos[0], pos[1], 0)
3172
3173        if len(pos) == 6:
3174            src.SetBounds(pos)
3175            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3176        elif len(size) == 3:
3177            length, width, height = size
3178            src.SetXLength(length)
3179            src.SetYLength(width)
3180            src.SetZLength(height)
3181            src.SetCenter(pos)
3182        else:
3183            src.SetXLength(length)
3184            src.SetYLength(width)
3185            src.SetZLength(height)
3186            src.SetCenter(pos)
3187
3188        src.Update()
3189        pd = src.GetOutput()
3190
3191        tc = [
3192            [0.0, 0.0],
3193            [1.0, 0.0],
3194            [0.0, 1.0],
3195            [1.0, 1.0],
3196            [1.0, 0.0],
3197            [0.0, 0.0],
3198            [1.0, 1.0],
3199            [0.0, 1.0],
3200            [1.0, 1.0],
3201            [1.0, 0.0],
3202            [0.0, 1.0],
3203            [0.0, 0.0],
3204            [0.0, 1.0],
3205            [0.0, 0.0],
3206            [1.0, 1.0],
3207            [1.0, 0.0],
3208            [1.0, 0.0],
3209            [0.0, 0.0],
3210            [1.0, 1.0],
3211            [0.0, 1.0],
3212            [0.0, 0.0],
3213            [1.0, 0.0],
3214            [0.0, 1.0],
3215            [1.0, 1.0],
3216        ]
3217        vtc = utils.numpy2vtk(tc)
3218        pd.GetPointData().SetTCoords(vtc)
3219        super().__init__(pd, c, alpha)
3220        self.transform = LinearTransform().translate(pos)
3221        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):
3224class Cube(Box):
3225    """Build a cube."""
3226
3227    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3228        """Build a cube of size `side`."""
3229        super().__init__(pos, side, side, side, (), c, alpha)
3230        self.name = "Cube"

Build a cube.

Cube(pos=(0, 0, 0), side=1.0, c='g4', alpha=1.0)
3227    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3228        """Build a cube of size `side`."""
3229        super().__init__(pos, side, side, side, (), c, alpha)
3230        self.name = "Cube"

Build a cube of size side.

class Spring(vedo.mesh.Mesh):
3276class Spring(Mesh):
3277    """
3278    Build a spring model.
3279    """
3280
3281    def __init__(
3282        self,
3283        start_pt=(0, 0, 0),
3284        end_pt=(1, 0, 0),
3285        coils=20,
3286        r1=0.1,
3287        r2=None,
3288        thickness=None,
3289        c="gray5",
3290        alpha=1.0,
3291    ) -> None:
3292        """
3293        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3294
3295        Arguments:
3296            coils : (int)
3297                number of coils
3298            r1 : (float)
3299                radius at start point
3300            r2 : (float)
3301                radius at end point
3302            thickness : (float)
3303                thickness of the coil section
3304        """
3305        start_pt = utils.make3d(start_pt)
3306        end_pt = utils.make3d(end_pt)
3307
3308        diff = end_pt - start_pt
3309        length = np.linalg.norm(diff)
3310        if not length:
3311            return
3312        if not r1:
3313            r1 = length / 20
3314        trange = np.linspace(0, length, num=50 * coils)
3315        om = 6.283 * (coils - 0.5) / length
3316        if not r2:
3317            r2 = r1
3318        pts = []
3319        for t in trange:
3320            f = (length - t) / length
3321            rd = r1 * f + r2 * (1 - f)
3322            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3323
3324        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3325        diff = diff / length
3326        theta = np.arccos(diff[2])
3327        phi = np.arctan2(diff[1], diff[0])
3328        sp = Line(pts)
3329        
3330        t = vtki.vtkTransform()
3331        t.Translate(start_pt)
3332        t.RotateZ(np.rad2deg(phi))
3333        t.RotateY(np.rad2deg(theta))
3334
3335        tf = vtki.new("TransformPolyDataFilter")
3336        tf.SetInputData(sp.dataset)
3337        tf.SetTransform(t)
3338        tf.Update()
3339
3340        tuf = vtki.new("TubeFilter")
3341        tuf.SetNumberOfSides(12)
3342        tuf.CappingOn()
3343        tuf.SetInputData(tf.GetOutput())
3344        if not thickness:
3345            thickness = r1 / 10
3346        tuf.SetRadius(thickness)
3347        tuf.Update()
3348
3349        super().__init__(tuf.GetOutput(), c, alpha)
3350
3351        self.phong()
3352        self.base = np.array(start_pt, dtype=float)
3353        self.top  = np.array(end_pt, dtype=float)
3354        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)
3281    def __init__(
3282        self,
3283        start_pt=(0, 0, 0),
3284        end_pt=(1, 0, 0),
3285        coils=20,
3286        r1=0.1,
3287        r2=None,
3288        thickness=None,
3289        c="gray5",
3290        alpha=1.0,
3291    ) -> None:
3292        """
3293        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3294
3295        Arguments:
3296            coils : (int)
3297                number of coils
3298            r1 : (float)
3299                radius at start point
3300            r2 : (float)
3301                radius at end point
3302            thickness : (float)
3303                thickness of the coil section
3304        """
3305        start_pt = utils.make3d(start_pt)
3306        end_pt = utils.make3d(end_pt)
3307
3308        diff = end_pt - start_pt
3309        length = np.linalg.norm(diff)
3310        if not length:
3311            return
3312        if not r1:
3313            r1 = length / 20
3314        trange = np.linspace(0, length, num=50 * coils)
3315        om = 6.283 * (coils - 0.5) / length
3316        if not r2:
3317            r2 = r1
3318        pts = []
3319        for t in trange:
3320            f = (length - t) / length
3321            rd = r1 * f + r2 * (1 - f)
3322            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3323
3324        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3325        diff = diff / length
3326        theta = np.arccos(diff[2])
3327        phi = np.arctan2(diff[1], diff[0])
3328        sp = Line(pts)
3329        
3330        t = vtki.vtkTransform()
3331        t.Translate(start_pt)
3332        t.RotateZ(np.rad2deg(phi))
3333        t.RotateY(np.rad2deg(theta))
3334
3335        tf = vtki.new("TransformPolyDataFilter")
3336        tf.SetInputData(sp.dataset)
3337        tf.SetTransform(t)
3338        tf.Update()
3339
3340        tuf = vtki.new("TubeFilter")
3341        tuf.SetNumberOfSides(12)
3342        tuf.CappingOn()
3343        tuf.SetInputData(tf.GetOutput())
3344        if not thickness:
3345            thickness = r1 / 10
3346        tuf.SetRadius(thickness)
3347        tuf.Update()
3348
3349        super().__init__(tuf.GetOutput(), c, alpha)
3350
3351        self.phong()
3352        self.base = np.array(start_pt, dtype=float)
3353        self.top  = np.array(end_pt, dtype=float)
3354        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):
3357class Cylinder(Mesh):
3358    """
3359    Build a cylinder of specified height and radius.
3360    """
3361
3362    def __init__(
3363        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3364        cap=True, res=24, c="teal3", alpha=1.0
3365    ) -> None:
3366        """
3367        Build a cylinder of specified height and radius `r`, centered at `pos`.
3368
3369        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3370        centered at `v1` and top at `v2`.
3371
3372        Arguments:
3373            cap : (bool)
3374                enable/disable the caps of the cylinder
3375            res : (int)
3376                resolution of the cylinder sides
3377
3378        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3379        """
3380        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3381            base = np.array(pos[0], dtype=float)
3382            top = np.array(pos[1], dtype=float)
3383            pos = (base + top) / 2
3384            height = np.linalg.norm(top - base)
3385            axis = top - base
3386            axis = utils.versor(axis)
3387        else:
3388            axis = utils.versor(axis)
3389            base = pos - axis * height / 2
3390            top = pos + axis * height / 2
3391
3392        cyl = vtki.new("CylinderSource")
3393        cyl.SetResolution(res)
3394        cyl.SetRadius(r)
3395        cyl.SetHeight(height)
3396        cyl.SetCapping(cap)
3397        cyl.Update()
3398
3399        theta = np.arccos(axis[2])
3400        phi = np.arctan2(axis[1], axis[0])
3401        t = vtki.vtkTransform()
3402        t.PostMultiply()
3403        t.RotateX(90)  # put it along Z
3404        t.RotateY(np.rad2deg(theta))
3405        t.RotateZ(np.rad2deg(phi))
3406        t.Translate(pos)
3407
3408        tf = vtki.new("TransformPolyDataFilter")
3409        tf.SetInputData(cyl.GetOutput())
3410        tf.SetTransform(t)
3411        tf.Update()
3412
3413        super().__init__(tf.GetOutput(), c, alpha)
3414
3415        self.phong()
3416        self.base = base
3417        self.top  = top
3418        self.transform = LinearTransform().translate(pos)
3419        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)
3362    def __init__(
3363        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3364        cap=True, res=24, c="teal3", alpha=1.0
3365    ) -> None:
3366        """
3367        Build a cylinder of specified height and radius `r`, centered at `pos`.
3368
3369        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3370        centered at `v1` and top at `v2`.
3371
3372        Arguments:
3373            cap : (bool)
3374                enable/disable the caps of the cylinder
3375            res : (int)
3376                resolution of the cylinder sides
3377
3378        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3379        """
3380        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3381            base = np.array(pos[0], dtype=float)
3382            top = np.array(pos[1], dtype=float)
3383            pos = (base + top) / 2
3384            height = np.linalg.norm(top - base)
3385            axis = top - base
3386            axis = utils.versor(axis)
3387        else:
3388            axis = utils.versor(axis)
3389            base = pos - axis * height / 2
3390            top = pos + axis * height / 2
3391
3392        cyl = vtki.new("CylinderSource")
3393        cyl.SetResolution(res)
3394        cyl.SetRadius(r)
3395        cyl.SetHeight(height)
3396        cyl.SetCapping(cap)
3397        cyl.Update()
3398
3399        theta = np.arccos(axis[2])
3400        phi = np.arctan2(axis[1], axis[0])
3401        t = vtki.vtkTransform()
3402        t.PostMultiply()
3403        t.RotateX(90)  # put it along Z
3404        t.RotateY(np.rad2deg(theta))
3405        t.RotateZ(np.rad2deg(phi))
3406        t.Translate(pos)
3407
3408        tf = vtki.new("TransformPolyDataFilter")
3409        tf.SetInputData(cyl.GetOutput())
3410        tf.SetTransform(t)
3411        tf.Update()
3412
3413        super().__init__(tf.GetOutput(), c, alpha)
3414
3415        self.phong()
3416        self.base = base
3417        self.top  = top
3418        self.transform = LinearTransform().translate(pos)
3419        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):
3422class Cone(Mesh):
3423    """Build a cone of specified radius and height."""
3424
3425    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3426                 res=48, c="green3", alpha=1.0) -> None:
3427        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3428        con = vtki.new("ConeSource")
3429        con.SetResolution(res)
3430        con.SetRadius(r)
3431        con.SetHeight(height)
3432        con.SetDirection(axis)
3433        con.Update()
3434        super().__init__(con.GetOutput(), c, alpha)
3435        self.phong()
3436        if len(pos) == 2:
3437            pos = (pos[0], pos[1], 0)
3438        self.pos(pos)
3439        v = utils.versor(axis) * height / 2
3440        self.base = pos - v
3441        self.top  = pos + v
3442        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)
3425    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3426                 res=48, c="green3", alpha=1.0) -> None:
3427        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3428        con = vtki.new("ConeSource")
3429        con.SetResolution(res)
3430        con.SetRadius(r)
3431        con.SetHeight(height)
3432        con.SetDirection(axis)
3433        con.Update()
3434        super().__init__(con.GetOutput(), c, alpha)
3435        self.phong()
3436        if len(pos) == 2:
3437            pos = (pos[0], pos[1], 0)
3438        self.pos(pos)
3439        v = utils.versor(axis) * height / 2
3440        self.base = pos - v
3441        self.top  = pos + v
3442        self.name = "Cone"

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

class Pyramid(Cone):
3445class Pyramid(Cone):
3446    """Build a pyramidal shape."""
3447
3448    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3449                 c="green3", alpha=1) -> None:
3450        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3451        super().__init__(pos, s, height, axis, 4, c, alpha)
3452        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)
3448    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3449                 c="green3", alpha=1) -> None:
3450        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3451        super().__init__(pos, s, height, axis, 4, c, alpha)
3452        self.name = "Pyramid"

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

class Torus(vedo.mesh.Mesh):
3455class Torus(Mesh):
3456    """
3457    Build a toroidal shape.
3458    """
3459
3460    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3461        """
3462        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3463        If `quad=True` a quad-mesh is generated.
3464        """
3465        if utils.is_sequence(res):
3466            res_u, res_v = res
3467        else:
3468            res_u, res_v = 3 * res, res
3469
3470        if quads:
3471            # https://github.com/marcomusy/vedo/issues/710
3472
3473            n = res_v
3474            m = res_u
3475
3476            theta = np.linspace(0, 2.0 * np.pi, n)
3477            phi = np.linspace(0, 2.0 * np.pi, m)
3478            theta, phi = np.meshgrid(theta, phi)
3479            t = r1 + r2 * np.cos(theta)
3480            x = t * np.cos(phi)
3481            y = t * np.sin(phi)
3482            z = r2 * np.sin(theta)
3483            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3484
3485            faces = []
3486            for j in range(m - 1):
3487                j1n = (j + 1) * n
3488                for i in range(n - 1):
3489                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3490
3491            super().__init__([pts, faces], c, alpha)
3492
3493        else:
3494            rs = vtki.new("ParametricTorus")
3495            rs.SetRingRadius(r1)
3496            rs.SetCrossSectionRadius(r2)
3497            pfs = vtki.new("ParametricFunctionSource")
3498            pfs.SetParametricFunction(rs)
3499            pfs.SetUResolution(res_u)
3500            pfs.SetVResolution(res_v)
3501            pfs.Update()
3502
3503            super().__init__(pfs.GetOutput(), c, alpha)
3504
3505        self.phong()
3506        if len(pos) == 2:
3507            pos = (pos[0], pos[1], 0)
3508        self.pos(pos)
3509        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)
3460    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3461        """
3462        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3463        If `quad=True` a quad-mesh is generated.
3464        """
3465        if utils.is_sequence(res):
3466            res_u, res_v = res
3467        else:
3468            res_u, res_v = 3 * res, res
3469
3470        if quads:
3471            # https://github.com/marcomusy/vedo/issues/710
3472
3473            n = res_v
3474            m = res_u
3475
3476            theta = np.linspace(0, 2.0 * np.pi, n)
3477            phi = np.linspace(0, 2.0 * np.pi, m)
3478            theta, phi = np.meshgrid(theta, phi)
3479            t = r1 + r2 * np.cos(theta)
3480            x = t * np.cos(phi)
3481            y = t * np.sin(phi)
3482            z = r2 * np.sin(theta)
3483            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3484
3485            faces = []
3486            for j in range(m - 1):
3487                j1n = (j + 1) * n
3488                for i in range(n - 1):
3489                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3490
3491            super().__init__([pts, faces], c, alpha)
3492
3493        else:
3494            rs = vtki.new("ParametricTorus")
3495            rs.SetRingRadius(r1)
3496            rs.SetCrossSectionRadius(r2)
3497            pfs = vtki.new("ParametricFunctionSource")
3498            pfs.SetParametricFunction(rs)
3499            pfs.SetUResolution(res_u)
3500            pfs.SetVResolution(res_v)
3501            pfs.Update()
3502
3503            super().__init__(pfs.GetOutput(), c, alpha)
3504
3505        self.phong()
3506        if len(pos) == 2:
3507            pos = (pos[0], pos[1], 0)
3508        self.pos(pos)
3509        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):
3512class Paraboloid(Mesh):
3513    """
3514    Build a paraboloid.
3515    """
3516
3517    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3518        """
3519        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3520
3521        Full volumetric expression is:
3522            `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`
3523
3524        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3525        """
3526        quadric = vtki.new("Quadric")
3527        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3528        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3529        #         + a3*x*y + a4*y*z + a5*x*z
3530        #         + a6*x   + a7*y   + a8*z  +a9
3531        sample = vtki.new("SampleFunction")
3532        sample.SetSampleDimensions(res, res, res)
3533        sample.SetImplicitFunction(quadric)
3534
3535        contours = vtki.new("ContourFilter")
3536        contours.SetInputConnection(sample.GetOutputPort())
3537        contours.GenerateValues(1, 0.01, 0.01)
3538        contours.Update()
3539
3540        super().__init__(contours.GetOutput(), c, alpha)
3541        self.compute_normals().phong()
3542        self.mapper.ScalarVisibilityOff()
3543        self.pos(pos)
3544        self.name = "Paraboloid"

Build a paraboloid.

Paraboloid(pos=(0, 0, 0), height=1.0, res=50, c='cyan5', alpha=1.0)
3517    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3518        """
3519        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3520
3521        Full volumetric expression is:
3522            `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`
3523
3524        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3525        """
3526        quadric = vtki.new("Quadric")
3527        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3528        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3529        #         + a3*x*y + a4*y*z + a5*x*z
3530        #         + a6*x   + a7*y   + a8*z  +a9
3531        sample = vtki.new("SampleFunction")
3532        sample.SetSampleDimensions(res, res, res)
3533        sample.SetImplicitFunction(quadric)
3534
3535        contours = vtki.new("ContourFilter")
3536        contours.SetInputConnection(sample.GetOutputPort())
3537        contours.GenerateValues(1, 0.01, 0.01)
3538        contours.Update()
3539
3540        super().__init__(contours.GetOutput(), c, alpha)
3541        self.compute_normals().phong()
3542        self.mapper.ScalarVisibilityOff()
3543        self.pos(pos)
3544        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):
3547class Hyperboloid(Mesh):
3548    """
3549    Build a hyperboloid.
3550    """
3551
3552    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3553        """
3554        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3555
3556        Full volumetric expression is:
3557            `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`
3558        """
3559        q = vtki.new("Quadric")
3560        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3561        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3562        #         + a3*x*y + a4*y*z + a5*x*z
3563        #         + a6*x   + a7*y   + a8*z  +a9
3564        sample = vtki.new("SampleFunction")
3565        sample.SetSampleDimensions(res, res, res)
3566        sample.SetImplicitFunction(q)
3567
3568        contours = vtki.new("ContourFilter")
3569        contours.SetInputConnection(sample.GetOutputPort())
3570        contours.GenerateValues(1, value, value)
3571        contours.Update()
3572
3573        super().__init__(contours.GetOutput(), c, alpha)
3574        self.compute_normals().phong()
3575        self.mapper.ScalarVisibilityOff()
3576        self.pos(pos)
3577        self.name = "Hyperboloid"

Build a hyperboloid.

Hyperboloid(pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c='pink4', alpha=1.0)
3552    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3553        """
3554        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3555
3556        Full volumetric expression is:
3557            `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`
3558        """
3559        q = vtki.new("Quadric")
3560        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3561        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3562        #         + a3*x*y + a4*y*z + a5*x*z
3563        #         + a6*x   + a7*y   + a8*z  +a9
3564        sample = vtki.new("SampleFunction")
3565        sample.SetSampleDimensions(res, res, res)
3566        sample.SetImplicitFunction(q)
3567
3568        contours = vtki.new("ContourFilter")
3569        contours.SetInputConnection(sample.GetOutputPort())
3570        contours.GenerateValues(1, value, value)
3571        contours.Update()
3572
3573        super().__init__(contours.GetOutput(), c, alpha)
3574        self.compute_normals().phong()
3575        self.mapper.ScalarVisibilityOff()
3576        self.pos(pos)
3577        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:
4342class TextBase:
4343    "Base class."
4344
4345    def __init__(self):
4346        "Do not instantiate this base class."
4347
4348        self.rendered_at = set()
4349        self.properties = None
4350
4351        self.name = "Text"
4352        self.filename = ""
4353        self.time = 0
4354        self.info = {}
4355
4356        if isinstance(settings.default_font, int):
4357            lfonts = list(settings.font_parameters.keys())
4358            font = settings.default_font % len(lfonts)
4359            self.fontname = lfonts[font]
4360        else:
4361            self.fontname = settings.default_font
4362
4363    def angle(self, value: float):
4364        """Orientation angle in degrees"""
4365        self.properties.SetOrientation(value)
4366        return self
4367
4368    def line_spacing(self, value: float):
4369        """Set the extra spacing between lines
4370        expressed as a text height multiplicative factor."""
4371        self.properties.SetLineSpacing(value)
4372        return self
4373
4374    def line_offset(self, value: float):
4375        """Set/Get the vertical offset (measured in pixels)."""
4376        self.properties.SetLineOffset(value)
4377        return self
4378
4379    def bold(self, value=True):
4380        """Set bold face"""
4381        self.properties.SetBold(value)
4382        return self
4383
4384    def italic(self, value=True):
4385        """Set italic face"""
4386        self.properties.SetItalic(value)
4387        return self
4388
4389    def shadow(self, offset=(1, -1)):
4390        """Text shadowing. Set to `None` to disable it."""
4391        if offset is None:
4392            self.properties.ShadowOff()
4393        else:
4394            self.properties.ShadowOn()
4395            self.properties.SetShadowOffset(offset)
4396        return self
4397
4398    def color(self, c=None):
4399        """Set the text color"""
4400        if c is None:
4401            return get_color(self.properties.GetColor())
4402        self.properties.SetColor(get_color(c))
4403        return self
4404
4405    def c(self, color=None):
4406        """Set the text color"""
4407        if color is None:
4408            return get_color(self.properties.GetColor())
4409        return self.color(color)
4410
4411    def alpha(self, value: float):
4412        """Set the text opacity"""
4413        self.properties.SetBackgroundOpacity(value)
4414        return self
4415
4416    def background(self, color="k9", alpha=1.0):
4417        """Text background. Set to `None` to disable it."""
4418        bg = get_color(color)
4419        if color is None:
4420            self.properties.SetBackgroundOpacity(0)
4421        else:
4422            self.properties.SetBackgroundColor(bg)
4423            if alpha:
4424                self.properties.SetBackgroundOpacity(alpha)
4425        return self
4426
4427    def frame(self, color="k1", lw=2):
4428        """Border color and width"""
4429        if color is None:
4430            self.properties.FrameOff()
4431        else:
4432            c = get_color(color)
4433            self.properties.FrameOn()
4434            self.properties.SetFrameColor(c)
4435            self.properties.SetFrameWidth(lw)
4436        return self
4437
4438    def font(self, font: str):
4439        """Text font face"""
4440        if isinstance(font, int):
4441            lfonts = list(settings.font_parameters.keys())
4442            n = font % len(lfonts)
4443            font = lfonts[n]
4444            self.fontname = font
4445
4446        if not font:  # use default font
4447            font = self.fontname
4448            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4449        elif font.startswith("https"):  # user passed URL link, make it a path
4450            fpath = vedo.file_io.download(font, verbose=False, force=False)
4451        elif font.endswith(".ttf"):  # user passing a local path to font file
4452            fpath = font
4453        else:  # user passing name of preset font
4454            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4455
4456        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4457        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4458        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4459        else:
4460            fpath = utils.get_font_path(font)
4461            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4462            self.properties.SetFontFile(fpath)
4463        self.fontname = font  # io.tonumpy() uses it
4464
4465        return self
4466
4467    def on(self):
4468        """Make text visible"""
4469        self.actor.SetVisibility(True)
4470        return self
4471
4472    def off(self):
4473        """Make text invisible"""
4474        self.actor.SetVisibility(False)
4475        return self

Base class.

TextBase()
4345    def __init__(self):
4346        "Do not instantiate this base class."
4347
4348        self.rendered_at = set()
4349        self.properties = None
4350
4351        self.name = "Text"
4352        self.filename = ""
4353        self.time = 0
4354        self.info = {}
4355
4356        if isinstance(settings.default_font, int):
4357            lfonts = list(settings.font_parameters.keys())
4358            font = settings.default_font % len(lfonts)
4359            self.fontname = lfonts[font]
4360        else:
4361            self.fontname = settings.default_font

Do not instantiate this base class.

def angle(self, value: float):
4363    def angle(self, value: float):
4364        """Orientation angle in degrees"""
4365        self.properties.SetOrientation(value)
4366        return self

Orientation angle in degrees

def line_spacing(self, value: float):
4368    def line_spacing(self, value: float):
4369        """Set the extra spacing between lines
4370        expressed as a text height multiplicative factor."""
4371        self.properties.SetLineSpacing(value)
4372        return self

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

def line_offset(self, value: float):
4374    def line_offset(self, value: float):
4375        """Set/Get the vertical offset (measured in pixels)."""
4376        self.properties.SetLineOffset(value)
4377        return self

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

def bold(self, value=True):
4379    def bold(self, value=True):
4380        """Set bold face"""
4381        self.properties.SetBold(value)
4382        return self

Set bold face

def italic(self, value=True):
4384    def italic(self, value=True):
4385        """Set italic face"""
4386        self.properties.SetItalic(value)
4387        return self

Set italic face

def shadow(self, offset=(1, -1)):
4389    def shadow(self, offset=(1, -1)):
4390        """Text shadowing. Set to `None` to disable it."""
4391        if offset is None:
4392            self.properties.ShadowOff()
4393        else:
4394            self.properties.ShadowOn()
4395            self.properties.SetShadowOffset(offset)
4396        return self

Text shadowing. Set to None to disable it.

def color(self, c=None):
4398    def color(self, c=None):
4399        """Set the text color"""
4400        if c is None:
4401            return get_color(self.properties.GetColor())
4402        self.properties.SetColor(get_color(c))
4403        return self

Set the text color

def c(self, color=None):
4405    def c(self, color=None):
4406        """Set the text color"""
4407        if color is None:
4408            return get_color(self.properties.GetColor())
4409        return self.color(color)

Set the text color

def alpha(self, value: float):
4411    def alpha(self, value: float):
4412        """Set the text opacity"""
4413        self.properties.SetBackgroundOpacity(value)
4414        return self

Set the text opacity

def background(self, color='k9', alpha=1.0):
4416    def background(self, color="k9", alpha=1.0):
4417        """Text background. Set to `None` to disable it."""
4418        bg = get_color(color)
4419        if color is None:
4420            self.properties.SetBackgroundOpacity(0)
4421        else:
4422            self.properties.SetBackgroundColor(bg)
4423            if alpha:
4424                self.properties.SetBackgroundOpacity(alpha)
4425        return self

Text background. Set to None to disable it.

def frame(self, color='k1', lw=2):
4427    def frame(self, color="k1", lw=2):
4428        """Border color and width"""
4429        if color is None:
4430            self.properties.FrameOff()
4431        else:
4432            c = get_color(color)
4433            self.properties.FrameOn()
4434            self.properties.SetFrameColor(c)
4435            self.properties.SetFrameWidth(lw)
4436        return self

Border color and width

def font(self, font: str):
4438    def font(self, font: str):
4439        """Text font face"""
4440        if isinstance(font, int):
4441            lfonts = list(settings.font_parameters.keys())
4442            n = font % len(lfonts)
4443            font = lfonts[n]
4444            self.fontname = font
4445
4446        if not font:  # use default font
4447            font = self.fontname
4448            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4449        elif font.startswith("https"):  # user passed URL link, make it a path
4450            fpath = vedo.file_io.download(font, verbose=False, force=False)
4451        elif font.endswith(".ttf"):  # user passing a local path to font file
4452            fpath = font
4453        else:  # user passing name of preset font
4454            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4455
4456        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4457        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4458        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4459        else:
4460            fpath = utils.get_font_path(font)
4461            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4462            self.properties.SetFontFile(fpath)
4463        self.fontname = font  # io.tonumpy() uses it
4464
4465        return self

Text font face

def on(self):
4467    def on(self):
4468        """Make text visible"""
4469        self.actor.SetVisibility(True)
4470        return self

Make text visible

def off(self):
4472    def off(self):
4473        """Make text invisible"""
4474        self.actor.SetVisibility(False)
4475        return self

Make text invisible

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

Update the text and some of its properties.

Check available fonts here.

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

Get the internal vtkMapper.

def pos(self, pos='top-left', justify=''):
4582    def pos(self, pos="top-left", justify=""):
4583        """
4584        Set position of the text to draw. Keyword `pos` can be a string
4585        or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4586        """
4587        ajustify = "top-left"  # autojustify
4588        if isinstance(pos, str):  # corners
4589            ajustify = pos
4590            if "top" in pos:
4591                if "left" in pos:
4592                    pos = (0.008, 0.994)
4593                elif "right" in pos:
4594                    pos = (0.994, 0.994)
4595                elif "mid" in pos or "cent" in pos:
4596                    pos = (0.5, 0.994)
4597            elif "bottom" in pos:
4598                if "left" in pos:
4599                    pos = (0.008, 0.008)
4600                elif "right" in pos:
4601                    pos = (0.994, 0.008)
4602                elif "mid" in pos or "cent" in pos:
4603                    pos = (0.5, 0.008)
4604            elif "mid" in pos or "cent" in pos:
4605                if "left" in pos:
4606                    pos = (0.008, 0.5)
4607                elif "right" in pos:
4608                    pos = (0.994, 0.5)
4609                else:
4610                    pos = (0.5, 0.5)
4611
4612            else:
4613                vedo.logger.warning(f"cannot understand text position {pos}")
4614                pos = (0.008, 0.994)
4615                ajustify = "top-left"
4616
4617        elif len(pos) != 2:
4618            vedo.logger.error("pos must be of length 2 or integer value or string")
4619            raise RuntimeError()
4620
4621        if not justify:
4622            justify = ajustify
4623
4624        self.properties.SetJustificationToLeft()
4625        if "top" in justify:
4626            self.properties.SetVerticalJustificationToTop()
4627        if "bottom" in justify:
4628            self.properties.SetVerticalJustificationToBottom()
4629        if "cent" in justify or "mid" in justify:
4630            self.properties.SetJustificationToCentered()
4631        if "left" in justify:
4632            self.properties.SetJustificationToLeft()
4633        if "right" in justify:
4634            self.properties.SetJustificationToRight()
4635
4636        self.SetPosition(pos)
4637        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):
4639    def text(self, txt=None):
4640        """Set/get the input text string."""
4641        if txt is None:
4642            return self.mapper.GetInput()
4643
4644        if ":" in txt:
4645            for r in _reps:
4646                txt = txt.replace(r[0], r[1])
4647        else:
4648            txt = str(txt)
4649
4650        self.mapper.SetInput(txt)
4651        return self

Set/get the input text string.

def size(self, s):
4653    def size(self, s):
4654        """Set the font size."""
4655        self.properties.SetFontSize(int(s * 22.5))
4656        return self

Set the font size.

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

Do not instantiate this base class.

def size(self, s: float, linear=False) -> CornerAnnotation:
4698    def size(self, s:float, linear=False) -> "CornerAnnotation":
4699        """
4700        The font size is calculated as the largest possible value such that the annotations
4701        for the given viewport do not overlap.
4702
4703        This font size can be scaled non-linearly with the viewport size, to maintain an
4704        acceptable readable size at larger viewport sizes, without being too big.
4705        `f' = linearScale * pow(f,nonlinearScale)`
4706        """
4707        if linear:
4708            self.SetLinearFontScaleFactor(s * 5.5)
4709        else:
4710            self.SetNonlinearFontScaleFactor(s / 2.75)
4711        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:
4713    def text(self, txt: str, pos=2) -> "CornerAnnotation":
4714        """Set text at the assigned position"""
4715
4716        if isinstance(pos, str):  # corners
4717            if "top" in pos:
4718                if "left" in pos: pos = 2
4719                elif "right" in pos: pos = 3
4720                elif "mid" in pos or "cent" in pos: pos = 7
4721            elif "bottom" in pos:
4722                if "left" in pos: pos = 0
4723                elif "right" in pos: pos = 1
4724                elif "mid" in pos or "cent" in pos: pos = 4
4725            else:
4726                if "left" in pos: pos = 6
4727                elif "right" in pos: pos = 5
4728                else: pos = 2
4729
4730        if "\\" in repr(txt):
4731            for r in _reps:
4732                txt = txt.replace(r[0], r[1])
4733        else:
4734            txt = str(txt)
4735
4736        self.SetText(pos, txt)
4737        return self

Set text at the assigned position

def clear(self) -> CornerAnnotation:
4739    def clear(self) -> "CornerAnnotation":
4740        """Remove all text from all corners"""
4741        self.ClearAllTexts()
4742        return self

Remove all text from all corners

class Latex(vedo.image.Image):
4745class Latex(Image):
4746    """
4747    Render Latex text and formulas.
4748    """
4749
4750    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4751        """
4752        Render Latex text and formulas.
4753
4754        Arguments:
4755            formula : (str)
4756                latex text string
4757            pos : (list)
4758                position coordinates in space
4759            bg : (color)
4760                background color box
4761            res : (int)
4762                dpi resolution
4763            usetex : (bool)
4764                use latex compiler of matplotlib if available
4765
4766        You can access the latex formula in `Latex.formula`.
4767
4768        Examples:
4769            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4770
4771            ![](https://vedo.embl.es/images/pyplot/latex.png)
4772        """
4773        from tempfile import NamedTemporaryFile
4774        import matplotlib.pyplot as mpltib
4775
4776        def build_img_plt(formula, tfile):
4777
4778            mpltib.rc("text", usetex=usetex)
4779
4780            formula1 = "$" + formula + "$"
4781            mpltib.axis("off")
4782            col = get_color(c)
4783            if bg:
4784                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4785            else:
4786                bx = None
4787            mpltib.text(
4788                0.5,
4789                0.5,
4790                formula1,
4791                size=res,
4792                color=col,
4793                alpha=alpha,
4794                ha="center",
4795                va="center",
4796                bbox=bx,
4797            )
4798            mpltib.savefig(
4799                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4800            )
4801            mpltib.close()
4802
4803        if len(pos) == 2:
4804            pos = (pos[0], pos[1], 0)
4805
4806        tmp_file = NamedTemporaryFile(delete=True)
4807        tmp_file.name = tmp_file.name + ".png"
4808
4809        build_img_plt(formula, tmp_file.name)
4810
4811        super().__init__(tmp_file.name, channels=4)
4812        self.alpha(alpha)
4813        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4814        self.pos(pos)
4815        self.name = "Latex"
4816        self.formula = formula
4817
4818        # except:
4819        #     printc("Error in Latex()\n", formula, c="r")
4820        #     printc(" latex or dvipng not installed?", c="r")
4821        #     printc(" Try: usetex=False", c="r")
4822        #     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)
4750    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4751        """
4752        Render Latex text and formulas.
4753
4754        Arguments:
4755            formula : (str)
4756                latex text string
4757            pos : (list)
4758                position coordinates in space
4759            bg : (color)
4760                background color box
4761            res : (int)
4762                dpi resolution
4763            usetex : (bool)
4764                use latex compiler of matplotlib if available
4765
4766        You can access the latex formula in `Latex.formula`.
4767
4768        Examples:
4769            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4770
4771            ![](https://vedo.embl.es/images/pyplot/latex.png)
4772        """
4773        from tempfile import NamedTemporaryFile
4774        import matplotlib.pyplot as mpltib
4775
4776        def build_img_plt(formula, tfile):
4777
4778            mpltib.rc("text", usetex=usetex)
4779
4780            formula1 = "$" + formula + "$"
4781            mpltib.axis("off")
4782            col = get_color(c)
4783            if bg:
4784                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4785            else:
4786                bx = None
4787            mpltib.text(
4788                0.5,
4789                0.5,
4790                formula1,
4791                size=res,
4792                color=col,
4793                alpha=alpha,
4794                ha="center",
4795                va="center",
4796                bbox=bx,
4797            )
4798            mpltib.savefig(
4799                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4800            )
4801            mpltib.close()
4802
4803        if len(pos) == 2:
4804            pos = (pos[0], pos[1], 0)
4805
4806        tmp_file = NamedTemporaryFile(delete=True)
4807        tmp_file.name = tmp_file.name + ".png"
4808
4809        build_img_plt(formula, tmp_file.name)
4810
4811        super().__init__(tmp_file.name, channels=4)
4812        self.alpha(alpha)
4813        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4814        self.pos(pos)
4815        self.name = "Latex"
4816        self.formula = formula
4817
4818        # except:
4819        #     printc("Error in Latex()\n", formula, c="r")
4820        #     printc(" latex or dvipng not installed?", c="r")
4821        #     printc(" Try: usetex=False", c="r")
4822        #     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):
3816class ParametricShape(Mesh):
3817    """
3818    A set of built-in shapes mainly for illustration purposes.
3819    """
3820
3821    def __init__(self, name, res=51, n=25, seed=1):
3822        """
3823        A set of built-in shapes mainly for illustration purposes.
3824
3825        Name can be an integer or a string in this list:
3826            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3827            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3828            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3829            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3830
3831        Example:
3832            ```python
3833            from vedo import *
3834            settings.immediate_rendering = False
3835            plt = Plotter(N=18)
3836            for i in range(18):
3837                ps = ParametricShape(i).color(i)
3838                plt.at(i).show(ps, ps.name)
3839            plt.interactive().close()
3840            ```
3841            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3842        """
3843
3844        shapes = [
3845            "Boy",
3846            "ConicSpiral",
3847            "CrossCap",
3848            "Enneper",
3849            "Figure8Klein",
3850            "Klein",
3851            "Dini",
3852            "Mobius",
3853            "RandomHills",
3854            "Roman",
3855            "SuperEllipsoid",
3856            "BohemianDome",
3857            "Bour",
3858            "CatalanMinimal",
3859            "Henneberg",
3860            "Kuen",
3861            "PluckerConoid",
3862            "Pseudosphere",
3863        ]
3864
3865        if isinstance(name, int):
3866            name = name % len(shapes)
3867            name = shapes[name]
3868
3869        if name == "Boy":
3870            ps = vtki.new("ParametricBoy")
3871        elif name == "ConicSpiral":
3872            ps = vtki.new("ParametricConicSpiral")
3873        elif name == "CrossCap":
3874            ps = vtki.new("ParametricCrossCap")
3875        elif name == "Dini":
3876            ps = vtki.new("ParametricDini")
3877        elif name == "Enneper":
3878            ps = vtki.new("ParametricEnneper")
3879        elif name == "Figure8Klein":
3880            ps = vtki.new("ParametricFigure8Klein")
3881        elif name == "Klein":
3882            ps = vtki.new("ParametricKlein")
3883        elif name == "Mobius":
3884            ps = vtki.new("ParametricMobius")
3885            ps.SetRadius(2.0)
3886            ps.SetMinimumV(-0.5)
3887            ps.SetMaximumV(0.5)
3888        elif name == "RandomHills":
3889            ps = vtki.new("ParametricRandomHills")
3890            ps.AllowRandomGenerationOn()
3891            ps.SetRandomSeed(seed)
3892            ps.SetNumberOfHills(n)
3893        elif name == "Roman":
3894            ps = vtki.new("ParametricRoman")
3895        elif name == "SuperEllipsoid":
3896            ps = vtki.new("ParametricSuperEllipsoid")
3897            ps.SetN1(0.5)
3898            ps.SetN2(0.4)
3899        elif name == "BohemianDome":
3900            ps = vtki.new("ParametricBohemianDome")
3901            ps.SetA(5.0)
3902            ps.SetB(1.0)
3903            ps.SetC(2.0)
3904        elif name == "Bour":
3905            ps = vtki.new("ParametricBour")
3906        elif name == "CatalanMinimal":
3907            ps = vtki.new("ParametricCatalanMinimal")
3908        elif name == "Henneberg":
3909            ps = vtki.new("ParametricHenneberg")
3910        elif name == "Kuen":
3911            ps = vtki.new("ParametricKuen")
3912            ps.SetDeltaV0(0.001)
3913        elif name == "PluckerConoid":
3914            ps = vtki.new("ParametricPluckerConoid")
3915        elif name == "Pseudosphere":
3916            ps = vtki.new("ParametricPseudosphere")
3917        else:
3918            vedo.logger.error(f"unknown ParametricShape {name}")
3919            return
3920
3921        pfs = vtki.new("ParametricFunctionSource")
3922        pfs.SetParametricFunction(ps)
3923        pfs.SetUResolution(res)
3924        pfs.SetVResolution(res)
3925        pfs.SetWResolution(res)
3926        pfs.SetScalarModeToZ()
3927        pfs.Update()
3928
3929        super().__init__(pfs.GetOutput())
3930
3931        if name == "RandomHills": self.shift([0,-10,-2.25])
3932        if name != 'Kuen': self.normalize()
3933        if name == 'Dini': self.scale(0.4)
3934        if name == 'Enneper': self.scale(0.4)
3935        if name == 'ConicSpiral': self.bc('tomato')
3936        self.name = name

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

ParametricShape(name, res=51, n=25, seed=1)
3821    def __init__(self, name, res=51, n=25, seed=1):
3822        """
3823        A set of built-in shapes mainly for illustration purposes.
3824
3825        Name can be an integer or a string in this list:
3826            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3827            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3828            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3829            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3830
3831        Example:
3832            ```python
3833            from vedo import *
3834            settings.immediate_rendering = False
3835            plt = Plotter(N=18)
3836            for i in range(18):
3837                ps = ParametricShape(i).color(i)
3838                plt.at(i).show(ps, ps.name)
3839            plt.interactive().close()
3840            ```
3841            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3842        """
3843
3844        shapes = [
3845            "Boy",
3846            "ConicSpiral",
3847            "CrossCap",
3848            "Enneper",
3849            "Figure8Klein",
3850            "Klein",
3851            "Dini",
3852            "Mobius",
3853            "RandomHills",
3854            "Roman",
3855            "SuperEllipsoid",
3856            "BohemianDome",
3857            "Bour",
3858            "CatalanMinimal",
3859            "Henneberg",
3860            "Kuen",
3861            "PluckerConoid",
3862            "Pseudosphere",
3863        ]
3864
3865        if isinstance(name, int):
3866            name = name % len(shapes)
3867            name = shapes[name]
3868
3869        if name == "Boy":
3870            ps = vtki.new("ParametricBoy")
3871        elif name == "ConicSpiral":
3872            ps = vtki.new("ParametricConicSpiral")
3873        elif name == "CrossCap":
3874            ps = vtki.new("ParametricCrossCap")
3875        elif name == "Dini":
3876            ps = vtki.new("ParametricDini")
3877        elif name == "Enneper":
3878            ps = vtki.new("ParametricEnneper")
3879        elif name == "Figure8Klein":
3880            ps = vtki.new("ParametricFigure8Klein")
3881        elif name == "Klein":
3882            ps = vtki.new("ParametricKlein")
3883        elif name == "Mobius":
3884            ps = vtki.new("ParametricMobius")
3885            ps.SetRadius(2.0)
3886            ps.SetMinimumV(-0.5)
3887            ps.SetMaximumV(0.5)
3888        elif name == "RandomHills":
3889            ps = vtki.new("ParametricRandomHills")
3890            ps.AllowRandomGenerationOn()
3891            ps.SetRandomSeed(seed)
3892            ps.SetNumberOfHills(n)
3893        elif name == "Roman":
3894            ps = vtki.new("ParametricRoman")
3895        elif name == "SuperEllipsoid":
3896            ps = vtki.new("ParametricSuperEllipsoid")
3897            ps.SetN1(0.5)
3898            ps.SetN2(0.4)
3899        elif name == "BohemianDome":
3900            ps = vtki.new("ParametricBohemianDome")
3901            ps.SetA(5.0)
3902            ps.SetB(1.0)
3903            ps.SetC(2.0)
3904        elif name == "Bour":
3905            ps = vtki.new("ParametricBour")
3906        elif name == "CatalanMinimal":
3907            ps = vtki.new("ParametricCatalanMinimal")
3908        elif name == "Henneberg":
3909            ps = vtki.new("ParametricHenneberg")
3910        elif name == "Kuen":
3911            ps = vtki.new("ParametricKuen")
3912            ps.SetDeltaV0(0.001)
3913        elif name == "PluckerConoid":
3914            ps = vtki.new("ParametricPluckerConoid")
3915        elif name == "Pseudosphere":
3916            ps = vtki.new("ParametricPseudosphere")
3917        else:
3918            vedo.logger.error(f"unknown ParametricShape {name}")
3919            return
3920
3921        pfs = vtki.new("ParametricFunctionSource")
3922        pfs.SetParametricFunction(ps)
3923        pfs.SetUResolution(res)
3924        pfs.SetVResolution(res)
3925        pfs.SetWResolution(res)
3926        pfs.SetScalarModeToZ()
3927        pfs.Update()
3928
3929        super().__init__(pfs.GetOutput())
3930
3931        if name == "RandomHills": self.shift([0,-10,-2.25])
3932        if name != 'Kuen': self.normalize()
3933        if name == 'Dini': self.scale(0.4)
3934        if name == 'Enneper': self.scale(0.4)
3935        if name == 'ConicSpiral': self.bc('tomato')
3936        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):
4825class ConvexHull(Mesh):
4826    """
4827    Create the 2D/3D convex hull from a set of points.
4828    """
4829
4830    def __init__(self, pts) -> None:
4831        """
4832        Create the 2D/3D convex hull from a set of input points or input Mesh.
4833
4834        Examples:
4835            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4836
4837                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4838        """
4839        if utils.is_sequence(pts):
4840            pts = utils.make3d(pts).astype(float)
4841            mesh = Points(pts)
4842        else:
4843            mesh = pts
4844        apoly = mesh.clean().dataset
4845
4846        # Create the convex hull of the pointcloud
4847        z0, z1 = mesh.zbounds()
4848        d = mesh.diagonal_size()
4849        if (z1 - z0) / d > 0.0001:
4850            delaunay = vtki.new("Delaunay3D")
4851            delaunay.SetInputData(apoly)
4852            delaunay.Update()
4853            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4854            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4855            surfaceFilter.Update()
4856            out = surfaceFilter.GetOutput()
4857        else:
4858            delaunay = vtki.new("Delaunay2D")
4859            delaunay.SetInputData(apoly)
4860            delaunay.Update()
4861            fe = vtki.new("FeatureEdges")
4862            fe.SetInputConnection(delaunay.GetOutputPort())
4863            fe.BoundaryEdgesOn()
4864            fe.Update()
4865            out = fe.GetOutput()
4866
4867        super().__init__(out, c=mesh.color(), alpha=0.75)
4868        self.flat()
4869        self.name = "ConvexHull"

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

ConvexHull(pts)
4830    def __init__(self, pts) -> None:
4831        """
4832        Create the 2D/3D convex hull from a set of input points or input Mesh.
4833
4834        Examples:
4835            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4836
4837                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4838        """
4839        if utils.is_sequence(pts):
4840            pts = utils.make3d(pts).astype(float)
4841            mesh = Points(pts)
4842        else:
4843            mesh = pts
4844        apoly = mesh.clean().dataset
4845
4846        # Create the convex hull of the pointcloud
4847        z0, z1 = mesh.zbounds()
4848        d = mesh.diagonal_size()
4849        if (z1 - z0) / d > 0.0001:
4850            delaunay = vtki.new("Delaunay3D")
4851            delaunay.SetInputData(apoly)
4852            delaunay.Update()
4853            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4854            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4855            surfaceFilter.Update()
4856            out = surfaceFilter.GetOutput()
4857        else:
4858            delaunay = vtki.new("Delaunay2D")
4859            delaunay.SetInputData(apoly)
4860            delaunay.Update()
4861            fe = vtki.new("FeatureEdges")
4862            fe.SetInputConnection(delaunay.GetOutputPort())
4863            fe.BoundaryEdgesOn()
4864            fe.Update()
4865            out = fe.GetOutput()
4866
4867        super().__init__(out, c=mesh.color(), alpha=0.75)
4868        self.flat()
4869        self.name = "ConvexHull"

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

Examples: