vedo.shapes

Submodule to generate simple and complex geometric shapes

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

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

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

Build the line segment between point p0 and point p1.

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

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

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

Return a copy of the Line object.

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

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

Assign a color to the line

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

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

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

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

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

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

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

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

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

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

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

Calculate length of the line.

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

Compute the tangents of a line in space.

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

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

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

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

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

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

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

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

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

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

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

Sweep the Line along the specified vector direction.

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

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

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

Reverse the points sequence order.

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

Consider using Line.pattern() instead.

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

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

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

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

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

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

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

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

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

Build tubes around a Lines object.

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

Wrap tubes around the input Lines object.

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

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

Example:

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

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

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

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

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

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

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

See also: CSpline and KSpline.

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

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

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

Warning:

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

See also: Spline and CSpline.

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

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

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

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

See also: Spline and KSpline.

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

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

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

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

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

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

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

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

Initialize a Mesh object.

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

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

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

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

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

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

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

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

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

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

def top_point(self):
1921    def top_point(self):
1922        """Return the current coordinates of the tip of the Arrow."""
1923        return self.transform.transform_point(self.top)

Return the current coordinates of the tip of the Arrow.

def base_point(self):
1925    def base_point(self):
1926        """Return the current coordinates of the base of the Arrow."""
1927        return self.transform.transform_point(self.base)

Return the current coordinates of the base of the Arrow.

class Arrows(Glyph):
1929class Arrows(Glyph):
1930    """
1931    Build arrows between two lists of points.
1932    """
1933
1934    def __init__(
1935        self,
1936        start_pts,
1937        end_pts=None,
1938        s=None,
1939        shaft_radius=None,
1940        head_radius=None,
1941        head_length=None,
1942        thickness=1.0,
1943        res=6,
1944        c='k3',
1945        alpha=1.0,
1946    ) -> None:
1947        """
1948        Build arrows between two lists of points `start_pts` and `end_pts`.
1949         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1950
1951        Color can be specified as a colormap which maps the size of the arrows.
1952
1953        Arguments:
1954            s : (float)
1955                fix aspect-ratio of the arrow and scale its cross section
1956            c : (color)
1957                color or color map name
1958            alpha : (float)
1959                set object opacity
1960            res : (int)
1961                set arrow resolution
1962
1963        Examples:
1964            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
1965
1966            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
1967        """
1968        if isinstance(start_pts, Points):
1969            start_pts = start_pts.vertices
1970        if isinstance(end_pts, Points):
1971            end_pts = end_pts.vertices
1972
1973        start_pts = np.asarray(start_pts)
1974        if end_pts is None:
1975            strt = start_pts[:, 0]
1976            end_pts = start_pts[:, 1]
1977            start_pts = strt
1978        else:
1979            end_pts = np.asarray(end_pts)
1980
1981        start_pts = utils.make3d(start_pts)
1982        end_pts = utils.make3d(end_pts)
1983
1984        arr = vtki.new("ArrowSource")
1985        arr.SetShaftResolution(res)
1986        arr.SetTipResolution(res)
1987
1988        if s:
1989            sz = 0.02 * s
1990            arr.SetTipRadius(sz * 2)
1991            arr.SetShaftRadius(sz * thickness)
1992            arr.SetTipLength(sz * 10)
1993
1994        if head_radius:
1995            arr.SetTipRadius(head_radius)
1996        if shaft_radius:
1997            arr.SetShaftRadius(shaft_radius)
1998        if head_length:
1999            arr.SetTipLength(head_length)
2000
2001        arr.Update()
2002        out = arr.GetOutput()
2003
2004        orients = end_pts - start_pts
2005
2006        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
2007
2008        super().__init__(
2009            start_pts,
2010            out,
2011            orientation_array=orients,
2012            scale_by_vector_size=True,
2013            color_by_vector_size=color_by_vector_size,
2014            c=c,
2015            alpha=alpha,
2016        )
2017        self.lighting("off")
2018        if color_by_vector_size:
2019            vals = np.linalg.norm(orients, axis=1)
2020            self.mapper.SetScalarRange(vals.min(), vals.max())
2021        else:
2022            self.c(c)
2023        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)
1934    def __init__(
1935        self,
1936        start_pts,
1937        end_pts=None,
1938        s=None,
1939        shaft_radius=None,
1940        head_radius=None,
1941        head_length=None,
1942        thickness=1.0,
1943        res=6,
1944        c='k3',
1945        alpha=1.0,
1946    ) -> None:
1947        """
1948        Build arrows between two lists of points `start_pts` and `end_pts`.
1949         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
1950
1951        Color can be specified as a colormap which maps the size of the arrows.
1952
1953        Arguments:
1954            s : (float)
1955                fix aspect-ratio of the arrow and scale its cross section
1956            c : (color)
1957                color or color map name
1958            alpha : (float)
1959                set object opacity
1960            res : (int)
1961                set arrow resolution
1962
1963        Examples:
1964            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
1965
1966            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
1967        """
1968        if isinstance(start_pts, Points):
1969            start_pts = start_pts.vertices
1970        if isinstance(end_pts, Points):
1971            end_pts = end_pts.vertices
1972
1973        start_pts = np.asarray(start_pts)
1974        if end_pts is None:
1975            strt = start_pts[:, 0]
1976            end_pts = start_pts[:, 1]
1977            start_pts = strt
1978        else:
1979            end_pts = np.asarray(end_pts)
1980
1981        start_pts = utils.make3d(start_pts)
1982        end_pts = utils.make3d(end_pts)
1983
1984        arr = vtki.new("ArrowSource")
1985        arr.SetShaftResolution(res)
1986        arr.SetTipResolution(res)
1987
1988        if s:
1989            sz = 0.02 * s
1990            arr.SetTipRadius(sz * 2)
1991            arr.SetShaftRadius(sz * thickness)
1992            arr.SetTipLength(sz * 10)
1993
1994        if head_radius:
1995            arr.SetTipRadius(head_radius)
1996        if shaft_radius:
1997            arr.SetShaftRadius(shaft_radius)
1998        if head_length:
1999            arr.SetTipLength(head_length)
2000
2001        arr.Update()
2002        out = arr.GetOutput()
2003
2004        orients = end_pts - start_pts
2005
2006        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
2007
2008        super().__init__(
2009            start_pts,
2010            out,
2011            orientation_array=orients,
2012            scale_by_vector_size=True,
2013            color_by_vector_size=color_by_vector_size,
2014            c=c,
2015            alpha=alpha,
2016        )
2017        self.lighting("off")
2018        if color_by_vector_size:
2019            vals = np.linalg.norm(orients, axis=1)
2020            self.mapper.SetScalarRange(vals.min(), vals.max())
2021        else:
2022            self.c(c)
2023        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):
2026class Arrow2D(Mesh):
2027    """
2028    Build a 2D arrow.
2029    """
2030
2031    def __init__(
2032        self,
2033        start_pt=(0, 0, 0),
2034        end_pt=(1, 0, 0),
2035        s=1,
2036        rotation=0.0,
2037        shaft_length=0.85,
2038        shaft_width=0.055,
2039        head_length=0.175,
2040        head_width=0.175,
2041        fill=True,
2042        c="red4",
2043        alpha=1.0,
2044   ) -> None:
2045        """
2046        Build a 2D arrow from `start_pt` to `end_pt`.
2047
2048        Arguments:
2049            s : (float)
2050                a global multiplicative convenience factor controlling the arrow size
2051            shaft_length : (float)
2052                fractional shaft length
2053            shaft_width : (float)
2054                fractional shaft width
2055            head_length : (float)
2056                fractional head length
2057            head_width : (float)
2058                fractional head width
2059            fill : (bool)
2060                if False only generate the outline
2061        """
2062        self.fill = fill  ## needed by pyplot.__iadd()
2063        self.s = s        ## needed by pyplot.__iadd()
2064
2065        if s != 1:
2066            shaft_width *= s
2067            head_width *= np.sqrt(s)
2068
2069        # in case user is passing meshs
2070        if isinstance(start_pt, vtki.vtkActor):
2071            start_pt = start_pt.GetPosition()
2072        if isinstance(end_pt, vtki.vtkActor):
2073            end_pt = end_pt.GetPosition()
2074        if len(start_pt) == 2:
2075            start_pt = [start_pt[0], start_pt[1], 0]
2076        if len(end_pt) == 2:
2077            end_pt = [end_pt[0], end_pt[1], 0]
2078
2079        headBase = 1 - head_length
2080        head_width = max(head_width, shaft_width)
2081        if head_length is None or headBase > shaft_length:
2082            headBase = shaft_length
2083
2084        verts = []
2085        verts.append([0, -shaft_width / 2, 0])
2086        verts.append([shaft_length, -shaft_width / 2, 0])
2087        verts.append([headBase, -head_width / 2, 0])
2088        verts.append([1, 0, 0])
2089        verts.append([headBase, head_width / 2, 0])
2090        verts.append([shaft_length, shaft_width / 2, 0])
2091        verts.append([0, shaft_width / 2, 0])
2092        if fill:
2093            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2094            poly = utils.buildPolyData(verts, faces)
2095        else:
2096            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2097            poly = utils.buildPolyData(verts, [], lines=lines)
2098
2099        axis = np.array(end_pt) - np.array(start_pt)
2100        length = float(np.linalg.norm(axis))
2101        if length:
2102            axis = axis / length
2103        theta = 0
2104        if len(axis) > 2:
2105            theta = np.arccos(axis[2])
2106        phi = np.arctan2(axis[1], axis[0])
2107
2108        t = vtki.vtkTransform()
2109        t.Translate(start_pt)
2110        if phi:
2111            t.RotateZ(np.rad2deg(phi))
2112        if theta:
2113            t.RotateY(np.rad2deg(theta))
2114        t.RotateY(-90)  # put it along Z
2115        if rotation:
2116            t.RotateX(rotation)
2117        t.Scale(length, length, length)
2118
2119        tf = vtki.new("TransformPolyDataFilter")
2120        tf.SetInputData(poly)
2121        tf.SetTransform(t)
2122        tf.Update()
2123
2124        super().__init__(tf.GetOutput(), c, alpha)
2125
2126        self.transform = LinearTransform().translate(start_pt)
2127
2128        self.lighting("off")
2129        self.actor.DragableOff()
2130        self.actor.PickableOff()
2131        self.base = np.array(start_pt, dtype=float) # used by pyplot
2132        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2133        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)
2031    def __init__(
2032        self,
2033        start_pt=(0, 0, 0),
2034        end_pt=(1, 0, 0),
2035        s=1,
2036        rotation=0.0,
2037        shaft_length=0.85,
2038        shaft_width=0.055,
2039        head_length=0.175,
2040        head_width=0.175,
2041        fill=True,
2042        c="red4",
2043        alpha=1.0,
2044   ) -> None:
2045        """
2046        Build a 2D arrow from `start_pt` to `end_pt`.
2047
2048        Arguments:
2049            s : (float)
2050                a global multiplicative convenience factor controlling the arrow size
2051            shaft_length : (float)
2052                fractional shaft length
2053            shaft_width : (float)
2054                fractional shaft width
2055            head_length : (float)
2056                fractional head length
2057            head_width : (float)
2058                fractional head width
2059            fill : (bool)
2060                if False only generate the outline
2061        """
2062        self.fill = fill  ## needed by pyplot.__iadd()
2063        self.s = s        ## needed by pyplot.__iadd()
2064
2065        if s != 1:
2066            shaft_width *= s
2067            head_width *= np.sqrt(s)
2068
2069        # in case user is passing meshs
2070        if isinstance(start_pt, vtki.vtkActor):
2071            start_pt = start_pt.GetPosition()
2072        if isinstance(end_pt, vtki.vtkActor):
2073            end_pt = end_pt.GetPosition()
2074        if len(start_pt) == 2:
2075            start_pt = [start_pt[0], start_pt[1], 0]
2076        if len(end_pt) == 2:
2077            end_pt = [end_pt[0], end_pt[1], 0]
2078
2079        headBase = 1 - head_length
2080        head_width = max(head_width, shaft_width)
2081        if head_length is None or headBase > shaft_length:
2082            headBase = shaft_length
2083
2084        verts = []
2085        verts.append([0, -shaft_width / 2, 0])
2086        verts.append([shaft_length, -shaft_width / 2, 0])
2087        verts.append([headBase, -head_width / 2, 0])
2088        verts.append([1, 0, 0])
2089        verts.append([headBase, head_width / 2, 0])
2090        verts.append([shaft_length, shaft_width / 2, 0])
2091        verts.append([0, shaft_width / 2, 0])
2092        if fill:
2093            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2094            poly = utils.buildPolyData(verts, faces)
2095        else:
2096            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2097            poly = utils.buildPolyData(verts, [], lines=lines)
2098
2099        axis = np.array(end_pt) - np.array(start_pt)
2100        length = float(np.linalg.norm(axis))
2101        if length:
2102            axis = axis / length
2103        theta = 0
2104        if len(axis) > 2:
2105            theta = np.arccos(axis[2])
2106        phi = np.arctan2(axis[1], axis[0])
2107
2108        t = vtki.vtkTransform()
2109        t.Translate(start_pt)
2110        if phi:
2111            t.RotateZ(np.rad2deg(phi))
2112        if theta:
2113            t.RotateY(np.rad2deg(theta))
2114        t.RotateY(-90)  # put it along Z
2115        if rotation:
2116            t.RotateX(rotation)
2117        t.Scale(length, length, length)
2118
2119        tf = vtki.new("TransformPolyDataFilter")
2120        tf.SetInputData(poly)
2121        tf.SetTransform(t)
2122        tf.Update()
2123
2124        super().__init__(tf.GetOutput(), c, alpha)
2125
2126        self.transform = LinearTransform().translate(start_pt)
2127
2128        self.lighting("off")
2129        self.actor.DragableOff()
2130        self.actor.PickableOff()
2131        self.base = np.array(start_pt, dtype=float) # used by pyplot
2132        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2133        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):
2136class Arrows2D(Glyph):
2137    """
2138    Build 2D arrows between two lists of points.
2139    """
2140
2141    def __init__(
2142        self,
2143        start_pts,
2144        end_pts=None,
2145        s=1.0,
2146        rotation=0.0,
2147        shaft_length=0.8,
2148        shaft_width=0.05,
2149        head_length=0.225,
2150        head_width=0.175,
2151        fill=True,
2152        c=None,
2153        alpha=1.0,
2154    ) -> None:
2155        """
2156        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2157        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2158
2159        Color can be specified as a colormap which maps the size of the arrows.
2160
2161        Arguments:
2162            shaft_length : (float)
2163                fractional shaft length
2164            shaft_width : (float)
2165                fractional shaft width
2166            head_length : (float)
2167                fractional head length
2168            head_width : (float)
2169                fractional head width
2170            fill : (bool)
2171                if False only generate the outline
2172        """
2173        if isinstance(start_pts, Points):
2174            start_pts = start_pts.vertices
2175        if isinstance(end_pts, Points):
2176            end_pts = end_pts.vertices
2177
2178        start_pts = np.asarray(start_pts, dtype=float)
2179        if end_pts is None:
2180            strt = start_pts[:, 0]
2181            end_pts = start_pts[:, 1]
2182            start_pts = strt
2183        else:
2184            end_pts = np.asarray(end_pts, dtype=float)
2185
2186        if head_length is None:
2187            head_length = 1 - shaft_length
2188
2189        arr = Arrow2D(
2190            (0, 0, 0),
2191            (1, 0, 0),
2192            s=s,
2193            rotation=rotation,
2194            shaft_length=shaft_length,
2195            shaft_width=shaft_width,
2196            head_length=head_length,
2197            head_width=head_width,
2198            fill=fill,
2199        )
2200
2201        orients = end_pts - start_pts
2202        orients = utils.make3d(orients)
2203
2204        pts = Points(start_pts)
2205        super().__init__(
2206            pts,
2207            arr,
2208            orientation_array=orients,
2209            scale_by_vector_size=True,
2210            c=c,
2211            alpha=alpha,
2212        )
2213        self.flat().lighting("off").pickable(False)
2214        if c is not None:
2215            self.color(c)
2216        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)
2141    def __init__(
2142        self,
2143        start_pts,
2144        end_pts=None,
2145        s=1.0,
2146        rotation=0.0,
2147        shaft_length=0.8,
2148        shaft_width=0.05,
2149        head_length=0.225,
2150        head_width=0.175,
2151        fill=True,
2152        c=None,
2153        alpha=1.0,
2154    ) -> None:
2155        """
2156        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2157        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2158
2159        Color can be specified as a colormap which maps the size of the arrows.
2160
2161        Arguments:
2162            shaft_length : (float)
2163                fractional shaft length
2164            shaft_width : (float)
2165                fractional shaft width
2166            head_length : (float)
2167                fractional head length
2168            head_width : (float)
2169                fractional head width
2170            fill : (bool)
2171                if False only generate the outline
2172        """
2173        if isinstance(start_pts, Points):
2174            start_pts = start_pts.vertices
2175        if isinstance(end_pts, Points):
2176            end_pts = end_pts.vertices
2177
2178        start_pts = np.asarray(start_pts, dtype=float)
2179        if end_pts is None:
2180            strt = start_pts[:, 0]
2181            end_pts = start_pts[:, 1]
2182            start_pts = strt
2183        else:
2184            end_pts = np.asarray(end_pts, dtype=float)
2185
2186        if head_length is None:
2187            head_length = 1 - shaft_length
2188
2189        arr = Arrow2D(
2190            (0, 0, 0),
2191            (1, 0, 0),
2192            s=s,
2193            rotation=rotation,
2194            shaft_length=shaft_length,
2195            shaft_width=shaft_width,
2196            head_length=head_length,
2197            head_width=head_width,
2198            fill=fill,
2199        )
2200
2201        orients = end_pts - start_pts
2202        orients = utils.make3d(orients)
2203
2204        pts = Points(start_pts)
2205        super().__init__(
2206            pts,
2207            arr,
2208            orientation_array=orients,
2209            scale_by_vector_size=True,
2210            c=c,
2211            alpha=alpha,
2212        )
2213        self.flat().lighting("off").pickable(False)
2214        if c is not None:
2215            self.color(c)
2216        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):
2219class FlatArrow(Ribbon):
2220    """
2221    Build a 2D arrow in 3D space by joining two close lines.
2222    """
2223
2224    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2225        """
2226        Build a 2D arrow in 3D space by joining two close lines.
2227
2228        Examples:
2229            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2230
2231                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2232        """
2233        if isinstance(line1, Points):
2234            line1 = line1.vertices
2235        if isinstance(line2, Points):
2236            line2 = line2.vertices
2237
2238        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2239
2240        v = (sm1 - sm2) / 3 * tip_width
2241        p1 = sm1 + v
2242        p2 = sm2 - v
2243        pm1 = (sm1 + sm2) / 2
2244        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2245        pm12 = pm1 - pm2
2246        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2247
2248        line1.append(p1)
2249        line1.append(tip)
2250        line2.append(p2)
2251        line2.append(tip)
2252        resm = max(100, len(line1))
2253
2254        super().__init__(line1, line2, res=(resm, 1))
2255        self.phong().lighting("off")
2256        self.actor.PickableOff()
2257        self.actor.DragableOff()
2258        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)
2224    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2225        """
2226        Build a 2D arrow in 3D space by joining two close lines.
2227
2228        Examples:
2229            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2230
2231                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2232        """
2233        if isinstance(line1, Points):
2234            line1 = line1.vertices
2235        if isinstance(line2, Points):
2236            line2 = line2.vertices
2237
2238        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2239
2240        v = (sm1 - sm2) / 3 * tip_width
2241        p1 = sm1 + v
2242        p2 = sm2 - v
2243        pm1 = (sm1 + sm2) / 2
2244        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2245        pm12 = pm1 - pm2
2246        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2247
2248        line1.append(p1)
2249        line1.append(tip)
2250        line2.append(p2)
2251        line2.append(tip)
2252        resm = max(100, len(line1))
2253
2254        super().__init__(line1, line2, res=(resm, 1))
2255        self.phong().lighting("off")
2256        self.actor.PickableOff()
2257        self.actor.DragableOff()
2258        self.name = "FlatArrow"

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

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

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

class Triangle(vedo.mesh.Mesh):
2261class Triangle(Mesh):
2262    """Create a triangle from 3 points in space."""
2263
2264    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2265        """Create a triangle from 3 points in space."""
2266        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2267        self.properties.LightingOff()
2268        self.name = "Triangle"

Create a triangle from 3 points in space.

Triangle(p1, p2, p3, c='green7', alpha=1.0)
2264    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2265        """Create a triangle from 3 points in space."""
2266        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2267        self.properties.LightingOff()
2268        self.name = "Triangle"

Create a triangle from 3 points in space.

class Rectangle(vedo.mesh.Mesh):
3095class Rectangle(Mesh):
3096    """
3097    Build a rectangle in the xy plane.
3098    """
3099
3100    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3101        """
3102        Build a rectangle in the xy plane identified by any two corner points.
3103
3104        Arguments:
3105            p1 : (list)
3106                bottom-left position of the corner
3107            p2 : (list)
3108                top-right position of the corner
3109            radius : (float, list)
3110                smoothing radius of the corner in world units.
3111                A list can be passed with 4 individual values.
3112        """
3113        if len(p1) == 2:
3114            p1 = np.array([p1[0], p1[1], 0.0])
3115        else:
3116            p1 = np.array(p1, dtype=float)
3117        if len(p2) == 2:
3118            p2 = np.array([p2[0], p2[1], 0.0])
3119        else:
3120            p2 = np.array(p2, dtype=float)
3121
3122        self.corner1 = p1
3123        self.corner2 = p2
3124
3125        color = c
3126        smoothr = False
3127        risseq = False
3128        if utils.is_sequence(radius):
3129            risseq = True
3130            smoothr = True
3131            if max(radius) == 0:
3132                smoothr = False
3133        elif radius:
3134            smoothr = True
3135
3136        if not smoothr:
3137            radius = None
3138        self.radius = radius
3139
3140        if smoothr:
3141            r = radius
3142            if not risseq:
3143                r = [r, r, r, r]
3144            rd, ra, rb, rc = r
3145
3146            if p1[0] > p2[0]:  # flip p1 - p2
3147                p1, p2 = p2, p1
3148            if p1[1] > p2[1]:  # flip p1y - p2y
3149                p1[1], p2[1] = p2[1], p1[1]
3150
3151            px, py, _ = p2 - p1
3152            k = min(px / 2, py / 2)
3153            ra = min(abs(ra), k)
3154            rb = min(abs(rb), k)
3155            rc = min(abs(rc), k)
3156            rd = min(abs(rd), k)
3157            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3158            betas = np.split(beta, 4)
3159            rrx = np.cos(betas)
3160            rry = np.sin(betas)
3161
3162            q1 = (rd, 0)
3163            # q2 = (px-ra, 0)
3164            q3 = (px, ra)
3165            # q4 = (px, py-rb)
3166            q5 = (px - rb, py)
3167            # q6 = (rc, py)
3168            q7 = (0, py - rc)
3169            # q8 = (0, rd)
3170            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3171            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3172            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3173            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3174
3175            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3176            faces = [list(range(len(pts)))]
3177        else:
3178            p1r = np.array([p2[0], p1[1], 0.0])
3179            p2l = np.array([p1[0], p2[1], 0.0])
3180            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3181            faces = [(0, 1, 2, 3)]
3182
3183        super().__init__([pts, faces], color, alpha)
3184        self.pos(p1)
3185        self.properties.LightingOff()
3186        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)
3100    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3101        """
3102        Build a rectangle in the xy plane identified by any two corner points.
3103
3104        Arguments:
3105            p1 : (list)
3106                bottom-left position of the corner
3107            p2 : (list)
3108                top-right position of the corner
3109            radius : (float, list)
3110                smoothing radius of the corner in world units.
3111                A list can be passed with 4 individual values.
3112        """
3113        if len(p1) == 2:
3114            p1 = np.array([p1[0], p1[1], 0.0])
3115        else:
3116            p1 = np.array(p1, dtype=float)
3117        if len(p2) == 2:
3118            p2 = np.array([p2[0], p2[1], 0.0])
3119        else:
3120            p2 = np.array(p2, dtype=float)
3121
3122        self.corner1 = p1
3123        self.corner2 = p2
3124
3125        color = c
3126        smoothr = False
3127        risseq = False
3128        if utils.is_sequence(radius):
3129            risseq = True
3130            smoothr = True
3131            if max(radius) == 0:
3132                smoothr = False
3133        elif radius:
3134            smoothr = True
3135
3136        if not smoothr:
3137            radius = None
3138        self.radius = radius
3139
3140        if smoothr:
3141            r = radius
3142            if not risseq:
3143                r = [r, r, r, r]
3144            rd, ra, rb, rc = r
3145
3146            if p1[0] > p2[0]:  # flip p1 - p2
3147                p1, p2 = p2, p1
3148            if p1[1] > p2[1]:  # flip p1y - p2y
3149                p1[1], p2[1] = p2[1], p1[1]
3150
3151            px, py, _ = p2 - p1
3152            k = min(px / 2, py / 2)
3153            ra = min(abs(ra), k)
3154            rb = min(abs(rb), k)
3155            rc = min(abs(rc), k)
3156            rd = min(abs(rd), k)
3157            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3158            betas = np.split(beta, 4)
3159            rrx = np.cos(betas)
3160            rry = np.sin(betas)
3161
3162            q1 = (rd, 0)
3163            # q2 = (px-ra, 0)
3164            q3 = (px, ra)
3165            # q4 = (px, py-rb)
3166            q5 = (px - rb, py)
3167            # q6 = (rc, py)
3168            q7 = (0, py - rc)
3169            # q8 = (0, rd)
3170            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3171            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3172            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3173            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3174
3175            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3176            faces = [list(range(len(pts)))]
3177        else:
3178            p1r = np.array([p2[0], p1[1], 0.0])
3179            p2l = np.array([p1[0], p2[1], 0.0])
3180            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3181            faces = [(0, 1, 2, 3)]
3182
3183        super().__init__([pts, faces], color, alpha)
3184        self.pos(p1)
3185        self.properties.LightingOff()
3186        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):
2402class Disc(Mesh):
2403    """
2404    Build a 2D disc.
2405    """
2406
2407    def __init__(
2408        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2409    ) -> None:
2410        """
2411        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2412
2413        Set `res` as the resolution in R and Phi (can be a list).
2414
2415        Use `angle_range` to create a disc sector between the 2 specified angles.
2416
2417        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2418        """
2419        if utils.is_sequence(res):
2420            res_r, res_phi = res
2421        else:
2422            res_r, res_phi = res, 12 * res
2423
2424        if len(angle_range) == 0:
2425            ps = vtki.new("DiskSource")
2426        else:
2427            ps = vtki.new("SectorSource")
2428            ps.SetStartAngle(angle_range[0])
2429            ps.SetEndAngle(angle_range[1])
2430
2431        ps.SetInnerRadius(r1)
2432        ps.SetOuterRadius(r2)
2433        ps.SetRadialResolution(res_r)
2434        ps.SetCircumferentialResolution(res_phi)
2435        ps.Update()
2436        super().__init__(ps.GetOutput(), c, alpha)
2437        self.flat()
2438        self.pos(utils.make3d(pos))
2439        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)
2407    def __init__(
2408        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2409    ) -> None:
2410        """
2411        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2412
2413        Set `res` as the resolution in R and Phi (can be a list).
2414
2415        Use `angle_range` to create a disc sector between the 2 specified angles.
2416
2417        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2418        """
2419        if utils.is_sequence(res):
2420            res_r, res_phi = res
2421        else:
2422            res_r, res_phi = res, 12 * res
2423
2424        if len(angle_range) == 0:
2425            ps = vtki.new("DiskSource")
2426        else:
2427            ps = vtki.new("SectorSource")
2428            ps.SetStartAngle(angle_range[0])
2429            ps.SetEndAngle(angle_range[1])
2430
2431        ps.SetInnerRadius(r1)
2432        ps.SetOuterRadius(r2)
2433        ps.SetRadialResolution(res_r)
2434        ps.SetCircumferentialResolution(res_phi)
2435        ps.Update()
2436        super().__init__(ps.GetOutput(), c, alpha)
2437        self.flat()
2438        self.pos(utils.make3d(pos))
2439        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):
2294class Circle(Polygon):
2295    """
2296    Build a Circle of radius `r`.
2297    """
2298
2299    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None:
2300        """
2301        Build a Circle of radius `r`.
2302        """
2303        super().__init__(pos, nsides=res, r=r)
2304
2305        self.nr_of_points = 0
2306        self.va = 0
2307        self.vb = 0
2308        self.axis1: List[float] = []
2309        self.axis2: List[float] = []
2310        self.center: List[float] = []  # filled by pointcloud.pca_ellipse()
2311        self.pvalue = 0.0              # filled by pointcloud.pca_ellipse()
2312        self.alpha(alpha).c(c)
2313        self.name = "Circle"
2314    
2315    def acircularity(self) -> float:
2316        """
2317        Return a measure of how different an ellipse is from a circle.
2318        Values close to zero correspond to a circular object.
2319        """
2320        a, b = self.va, self.vb
2321        value = 0.0
2322        if a+b:
2323            value = ((a-b)/(a+b))**2
2324        return value

Build a Circle of radius r.

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

Build a Circle of radius r.

def acircularity(self) -> float:
2315    def acircularity(self) -> float:
2316        """
2317        Return a measure of how different an ellipse is from a circle.
2318        Values close to zero correspond to a circular object.
2319        """
2320        a, b = self.va, self.vb
2321        value = 0.0
2322        if a+b:
2323            value = ((a-b)/(a+b))**2
2324        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):
2326class GeoCircle(Polygon):
2327    """
2328    Build a Circle of radius `r`.
2329    """
2330
2331    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None:
2332        """
2333        Build a Circle of radius `r` as projected on a geographic map.
2334        Circles near the poles will look very squashed.
2335
2336        See example:
2337            ```bash
2338            vedo -r earthquake
2339            ```
2340        """
2341        coords = []
2342        sinr, cosr = np.sin(r), np.cos(r)
2343        sinlat, coslat = np.sin(lat), np.cos(lat)
2344        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2345            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2346            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2347            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2348
2349        super().__init__(nsides=res, c=c, alpha=alpha)
2350        self.vertices = coords # warp polygon points to match geo projection
2351        self.name = "Circle"

Build a Circle of radius r.

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

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

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

Return the vertices (points) coordinates.

class Arc(vedo.mesh.Mesh):
2442class Arc(Mesh):
2443    """
2444    Build a 2D circular arc between 2 points.
2445    """
2446
2447    def __init__(
2448        self,
2449        center,
2450        point1,
2451        point2=None,
2452        normal=None,
2453        angle=None,
2454        invert=False,
2455        res=50,
2456        c="gray4",
2457        alpha=1.0,
2458    ) -> None:
2459        """
2460        Build a 2D circular arc between 2 points `point1` and `point2`.
2461
2462        If `normal` is specified then `center` is ignored, and
2463        normal vector, a starting `point1` (polar vector)
2464        and an angle defining the arc length need to be assigned.
2465
2466        Arc spans the shortest angular sector point1 and point2,
2467        if `invert=True`, then the opposite happens.
2468        """
2469        if len(point1) == 2:
2470            point1 = (point1[0], point1[1], 0)
2471        if point2 is not None and len(point2) == 2:
2472            point2 = (point2[0], point2[1], 0)
2473
2474        ar = vtki.new("ArcSource")
2475        if point2 is not None:
2476            self.top = point2
2477            point2 = point2 - np.asarray(point1)
2478            ar.UseNormalAndAngleOff()
2479            ar.SetPoint1([0, 0, 0])
2480            ar.SetPoint2(point2)
2481            # ar.SetCenter(center)
2482        elif normal is not None and angle is not None:
2483            ar.UseNormalAndAngleOn()
2484            ar.SetAngle(angle)
2485            ar.SetPolarVector(point1)
2486            ar.SetNormal(normal)
2487        else:
2488            vedo.logger.error("incorrect input combination")
2489            return
2490        ar.SetNegative(invert)
2491        ar.SetResolution(res)
2492        ar.Update()
2493
2494        super().__init__(ar.GetOutput(), c, alpha)
2495        self.pos(center)
2496        self.lw(2).lighting("off")
2497        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)
2447    def __init__(
2448        self,
2449        center,
2450        point1,
2451        point2=None,
2452        normal=None,
2453        angle=None,
2454        invert=False,
2455        res=50,
2456        c="gray4",
2457        alpha=1.0,
2458    ) -> None:
2459        """
2460        Build a 2D circular arc between 2 points `point1` and `point2`.
2461
2462        If `normal` is specified then `center` is ignored, and
2463        normal vector, a starting `point1` (polar vector)
2464        and an angle defining the arc length need to be assigned.
2465
2466        Arc spans the shortest angular sector point1 and point2,
2467        if `invert=True`, then the opposite happens.
2468        """
2469        if len(point1) == 2:
2470            point1 = (point1[0], point1[1], 0)
2471        if point2 is not None and len(point2) == 2:
2472            point2 = (point2[0], point2[1], 0)
2473
2474        ar = vtki.new("ArcSource")
2475        if point2 is not None:
2476            self.top = point2
2477            point2 = point2 - np.asarray(point1)
2478            ar.UseNormalAndAngleOff()
2479            ar.SetPoint1([0, 0, 0])
2480            ar.SetPoint2(point2)
2481            # ar.SetCenter(center)
2482        elif normal is not None and angle is not None:
2483            ar.UseNormalAndAngleOn()
2484            ar.SetAngle(angle)
2485            ar.SetPolarVector(point1)
2486            ar.SetNormal(normal)
2487        else:
2488            vedo.logger.error("incorrect input combination")
2489            return
2490        ar.SetNegative(invert)
2491        ar.SetResolution(res)
2492        ar.Update()
2493
2494        super().__init__(ar.GetOutput(), c, alpha)
2495        self.pos(center)
2496        self.lw(2).lighting("off")
2497        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):
2354class Star(Mesh):
2355    """
2356    Build a 2D star shape.
2357    """
2358
2359    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2360        """
2361        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2362
2363        If line is True then only build the outer line (no internal surface meshing).
2364
2365        Example:
2366            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2367
2368                ![](https://vedo.embl.es/images/basic/extrude.png)
2369        """
2370        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2371        x, y = pol2cart(np.ones_like(t) * r2, t)
2372        pts = np.c_[x, y, np.zeros_like(x)]
2373
2374        apts = []
2375        for i, p in enumerate(pts):
2376            apts.append(p)
2377            if i + 1 < n:
2378                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2379        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2380
2381        if line:
2382            apts.append(pts[0])
2383            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2384            super().__init__(poly, c, alpha)
2385            self.lw(2)
2386        else:
2387            apts.append((0, 0, 0))
2388            cells = []
2389            for i in range(2 * n - 1):
2390                cell = [2 * n, i, i + 1]
2391                cells.append(cell)
2392            cells.append([2 * n, i + 1, 0])
2393            super().__init__([apts, cells], c, alpha)
2394
2395        if len(pos) == 2:
2396            pos = (pos[0], pos[1], 0)
2397
2398        self.properties.LightingOff()
2399        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)
2359    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2360        """
2361        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2362
2363        If line is True then only build the outer line (no internal surface meshing).
2364
2365        Example:
2366            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2367
2368                ![](https://vedo.embl.es/images/basic/extrude.png)
2369        """
2370        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2371        x, y = pol2cart(np.ones_like(t) * r2, t)
2372        pts = np.c_[x, y, np.zeros_like(x)]
2373
2374        apts = []
2375        for i, p in enumerate(pts):
2376            apts.append(p)
2377            if i + 1 < n:
2378                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2379        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2380
2381        if line:
2382            apts.append(pts[0])
2383            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2384            super().__init__(poly, c, alpha)
2385            self.lw(2)
2386        else:
2387            apts.append((0, 0, 0))
2388            cells = []
2389            for i in range(2 * n - 1):
2390                cell = [2 * n, i, i + 1]
2391                cells.append(cell)
2392            cells.append([2 * n, i + 1, 0])
2393            super().__init__([apts, cells], c, alpha)
2394
2395        if len(pos) == 2:
2396            pos = (pos[0], pos[1], 0)
2397
2398        self.properties.LightingOff()
2399        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):
3819class Star3D(Mesh):
3820    """
3821    Build a 3D starred shape.
3822    """
3823
3824    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3825        """
3826        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3827        """
3828        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3829               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3830               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3831               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3832        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3833               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3834               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3835               [10,1, 0],[10,11, 9]]
3836
3837        super().__init__([pts, fcs], c, alpha)
3838        self.rotate_x(90)
3839        self.scale(r).lighting("shiny")
3840
3841        if len(pos) == 2:
3842            pos = (pos[0], pos[1], 0)
3843        self.pos(pos)
3844        self.name = "Star3D"

Build a 3D starred shape.

Star3D(pos=(0, 0, 0), r=1.0, thickness=0.1, c='blue4', alpha=1.0)
3824    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3825        """
3826        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3827        """
3828        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3829               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3830               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3831               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3832        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3833               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3834               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3835               [10,1, 0],[10,11, 9]]
3836
3837        super().__init__([pts, fcs], c, alpha)
3838        self.rotate_x(90)
3839        self.scale(r).lighting("shiny")
3840
3841        if len(pos) == 2:
3842            pos = (pos[0], pos[1], 0)
3843        self.pos(pos)
3844        self.name = "Star3D"

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

class Cross3D(vedo.mesh.Mesh):
3847class Cross3D(Mesh):
3848    """
3849    Build a 3D cross shape.
3850    """
3851
3852    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3853        """
3854        Build a 3D cross shape, mainly useful as a 3D marker.
3855        """
3856        if len(pos) == 2:
3857            pos = (pos[0], pos[1], 0)
3858
3859        c1 = Cylinder(r=thickness * s, height=2 * s)
3860        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3861        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3862        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3863        super().__init__(poly, c, alpha)
3864        self.name = "Cross3D"

Build a 3D cross shape.

Cross3D(pos=(0, 0, 0), s=1.0, thickness=0.3, c='b', alpha=1.0)
3852    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3853        """
3854        Build a 3D cross shape, mainly useful as a 3D marker.
3855        """
3856        if len(pos) == 2:
3857            pos = (pos[0], pos[1], 0)
3858
3859        c1 = Cylinder(r=thickness * s, height=2 * s)
3860        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3861        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3862        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3863        super().__init__(poly, c, alpha)
3864        self.name = "Cross3D"

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

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

Build a sphere.

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

Build a large set of spheres.

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

Build a textured mesh representing the Earth.

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

Build a textured mesh representing the Earth.

Example:
class Ellipsoid(vedo.mesh.Mesh):
2767class Ellipsoid(Mesh):
2768    """Build a 3D ellipsoid."""
2769    def __init__(
2770        self,
2771        pos=(0, 0, 0),
2772        axis1=(0.5, 0, 0),
2773        axis2=(0, 1, 0),
2774        axis3=(0, 0, 1.5),
2775        res=24,
2776        c="cyan4",
2777        alpha=1.0,
2778    ) -> None:
2779        """
2780        Build a 3D ellipsoid centered at position `pos`.
2781
2782        Arguments:
2783            axis1 : (list)
2784                First axis. Length corresponds to semi-axis.
2785            axis2 : (list)
2786                Second axis. Length corresponds to semi-axis.
2787            axis3 : (list)
2788                Third axis. Length corresponds to semi-axis.
2789        """        
2790        self.center = utils.make3d(pos)
2791
2792        self.axis1 = utils.make3d(axis1)
2793        self.axis2 = utils.make3d(axis2)
2794        self.axis3 = utils.make3d(axis3)
2795
2796        self.va = np.linalg.norm(self.axis1)
2797        self.vb = np.linalg.norm(self.axis2)
2798        self.vc = np.linalg.norm(self.axis3)
2799
2800        self.va_error = 0
2801        self.vb_error = 0
2802        self.vc_error = 0
2803
2804        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2805        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2806
2807        if utils.is_sequence(res):
2808            res_t, res_phi = res
2809        else:
2810            res_t, res_phi = 2 * res, res
2811
2812        elli_source = vtki.new("SphereSource")
2813        elli_source.SetRadius(1)
2814        elli_source.SetThetaResolution(res_t)
2815        elli_source.SetPhiResolution(res_phi)
2816        elli_source.Update()
2817
2818        super().__init__(elli_source.GetOutput(), c, alpha)
2819
2820        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2821        lt = LinearTransform(matrix).translate(pos)
2822        self.apply_transform(lt)
2823        self.name = "Ellipsoid"
2824
2825    def asphericity(self) -> float:
2826        """
2827        Return a measure of how different an ellipsoid is from a sphere.
2828        Values close to zero correspond to a spheric object.
2829        """
2830        a, b, c = self.va, self.vb, self.vc
2831        asp = ( ((a-b)/(a+b))**2
2832              + ((a-c)/(a+c))**2
2833              + ((b-c)/(b+c))**2 ) / 3. * 4.
2834        return float(asp)
2835
2836    def asphericity_error(self) -> float:
2837        """
2838        Calculate statistical error on the asphericity value.
2839
2840        Errors on the main axes are stored in
2841        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2842        """
2843        a, b, c = self.va, self.vb, self.vc
2844        sqrtn = np.sqrt(self.nr_of_points)
2845        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2846
2847        # from sympy import *
2848        # init_printing(use_unicode=True)
2849        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2850        # L = (
2851        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2852        #    / 3 * 4)
2853        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2854        # print(dl2)
2855        # exit()
2856
2857        dL2 = (
2858            ea ** 2
2859            * (
2860                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2861                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2862                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2863                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2864            ) ** 2
2865            + eb ** 2
2866            * (
2867                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2868                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2869                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2870                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2871            ) ** 2
2872            + ec ** 2
2873            * (
2874                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2875                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2876                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2877                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2878            ) ** 2
2879        )
2880        err = np.sqrt(dL2)
2881        self.va_error = ea
2882        self.vb_error = eb
2883        self.vc_error = ec
2884        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)
2769    def __init__(
2770        self,
2771        pos=(0, 0, 0),
2772        axis1=(0.5, 0, 0),
2773        axis2=(0, 1, 0),
2774        axis3=(0, 0, 1.5),
2775        res=24,
2776        c="cyan4",
2777        alpha=1.0,
2778    ) -> None:
2779        """
2780        Build a 3D ellipsoid centered at position `pos`.
2781
2782        Arguments:
2783            axis1 : (list)
2784                First axis. Length corresponds to semi-axis.
2785            axis2 : (list)
2786                Second axis. Length corresponds to semi-axis.
2787            axis3 : (list)
2788                Third axis. Length corresponds to semi-axis.
2789        """        
2790        self.center = utils.make3d(pos)
2791
2792        self.axis1 = utils.make3d(axis1)
2793        self.axis2 = utils.make3d(axis2)
2794        self.axis3 = utils.make3d(axis3)
2795
2796        self.va = np.linalg.norm(self.axis1)
2797        self.vb = np.linalg.norm(self.axis2)
2798        self.vc = np.linalg.norm(self.axis3)
2799
2800        self.va_error = 0
2801        self.vb_error = 0
2802        self.vc_error = 0
2803
2804        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2805        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2806
2807        if utils.is_sequence(res):
2808            res_t, res_phi = res
2809        else:
2810            res_t, res_phi = 2 * res, res
2811
2812        elli_source = vtki.new("SphereSource")
2813        elli_source.SetRadius(1)
2814        elli_source.SetThetaResolution(res_t)
2815        elli_source.SetPhiResolution(res_phi)
2816        elli_source.Update()
2817
2818        super().__init__(elli_source.GetOutput(), c, alpha)
2819
2820        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2821        lt = LinearTransform(matrix).translate(pos)
2822        self.apply_transform(lt)
2823        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:
2825    def asphericity(self) -> float:
2826        """
2827        Return a measure of how different an ellipsoid is from a sphere.
2828        Values close to zero correspond to a spheric object.
2829        """
2830        a, b, c = self.va, self.vb, self.vc
2831        asp = ( ((a-b)/(a+b))**2
2832              + ((a-c)/(a+c))**2
2833              + ((b-c)/(b+c))**2 ) / 3. * 4.
2834        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:
2836    def asphericity_error(self) -> float:
2837        """
2838        Calculate statistical error on the asphericity value.
2839
2840        Errors on the main axes are stored in
2841        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2842        """
2843        a, b, c = self.va, self.vb, self.vc
2844        sqrtn = np.sqrt(self.nr_of_points)
2845        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2846
2847        # from sympy import *
2848        # init_printing(use_unicode=True)
2849        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2850        # L = (
2851        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2852        #    / 3 * 4)
2853        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2854        # print(dl2)
2855        # exit()
2856
2857        dL2 = (
2858            ea ** 2
2859            * (
2860                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2861                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2862                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2863                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2864            ) ** 2
2865            + eb ** 2
2866            * (
2867                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2868                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2869                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2870                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2871            ) ** 2
2872            + ec ** 2
2873            * (
2874                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2875                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2876                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2877                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2878            ) ** 2
2879        )
2880        err = np.sqrt(dL2)
2881        self.va_error = ea
2882        self.vb_error = eb
2883        self.vc_error = ec
2884        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):
2887class Grid(Mesh):
2888    """
2889    An even or uneven 2D grid.
2890    """
2891
2892    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2893        """
2894        Create an even or uneven 2D grid.
2895        Can also be created from a `np.mgrid` object (see example).
2896
2897        Arguments:
2898            pos : (list, Points, Mesh)
2899                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2900            s : (float, list)
2901                if a float is provided it is interpreted as the total size along x and y,
2902                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2903                In this case keyword `res` is ignored (see example below).
2904            res : (list)
2905                resolutions along x and y, e.i. the number of subdivisions
2906            lw : (int)
2907                line width
2908
2909        Example:
2910            ```python
2911            from vedo import *
2912            xcoords = np.arange(0, 2, 0.2)
2913            ycoords = np.arange(0, 1, 0.2)
2914            sqrtx = sqrt(xcoords)
2915            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2916            grid.show(axes=8).close()
2917
2918            # Can also create a grid from a np.mgrid:
2919            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2920            vgrid = Grid(s=(X[:,0], Y[0]))
2921            vgrid.show(axes=8).close()
2922            ```
2923            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2924        """
2925        resx, resy = res
2926        sx, sy = s
2927        
2928        try:
2929            bb = pos.bounds()
2930            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2931            sx = bb[1] - bb[0]
2932            sy = bb[3] - bb[2]
2933        except AttributeError:
2934            pass        
2935
2936        if len(pos) == 2:
2937            pos = (pos[0], pos[1], 0)
2938        elif len(pos) in [4,6]: # passing a bounding box
2939            bb = pos
2940            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2941            sx = bb[1] - bb[0]
2942            sy = bb[3] - bb[2]
2943            if len(pos)==6:
2944                pos[2] = bb[4] - bb[5]
2945
2946        if utils.is_sequence(sx) and utils.is_sequence(sy):
2947            verts = []
2948            for y in sy:
2949                for x in sx:
2950                    verts.append([x, y, 0])
2951            faces = []
2952            n = len(sx)
2953            m = len(sy)
2954            for j in range(m - 1):
2955                j1n = (j + 1) * n
2956                for i in range(n - 1):
2957                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2958
2959            super().__init__([verts, faces], c, alpha)
2960
2961        else:
2962            ps = vtki.new("PlaneSource")
2963            ps.SetResolution(resx, resy)
2964            ps.Update()
2965
2966            t = vtki.vtkTransform()
2967            t.Translate(pos)
2968            t.Scale(sx, sy, 1)
2969
2970            tf = vtki.new("TransformPolyDataFilter")
2971            tf.SetInputData(ps.GetOutput())
2972            tf.SetTransform(t)
2973            tf.Update()
2974
2975            super().__init__(tf.GetOutput(), c, alpha)
2976
2977        self.wireframe().lw(lw)
2978        self.properties.LightingOff()
2979        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)
2892    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2893        """
2894        Create an even or uneven 2D grid.
2895        Can also be created from a `np.mgrid` object (see example).
2896
2897        Arguments:
2898            pos : (list, Points, Mesh)
2899                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2900            s : (float, list)
2901                if a float is provided it is interpreted as the total size along x and y,
2902                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2903                In this case keyword `res` is ignored (see example below).
2904            res : (list)
2905                resolutions along x and y, e.i. the number of subdivisions
2906            lw : (int)
2907                line width
2908
2909        Example:
2910            ```python
2911            from vedo import *
2912            xcoords = np.arange(0, 2, 0.2)
2913            ycoords = np.arange(0, 1, 0.2)
2914            sqrtx = sqrt(xcoords)
2915            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2916            grid.show(axes=8).close()
2917
2918            # Can also create a grid from a np.mgrid:
2919            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2920            vgrid = Grid(s=(X[:,0], Y[0]))
2921            vgrid.show(axes=8).close()
2922            ```
2923            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2924        """
2925        resx, resy = res
2926        sx, sy = s
2927        
2928        try:
2929            bb = pos.bounds()
2930            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2931            sx = bb[1] - bb[0]
2932            sy = bb[3] - bb[2]
2933        except AttributeError:
2934            pass        
2935
2936        if len(pos) == 2:
2937            pos = (pos[0], pos[1], 0)
2938        elif len(pos) in [4,6]: # passing a bounding box
2939            bb = pos
2940            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2941            sx = bb[1] - bb[0]
2942            sy = bb[3] - bb[2]
2943            if len(pos)==6:
2944                pos[2] = bb[4] - bb[5]
2945
2946        if utils.is_sequence(sx) and utils.is_sequence(sy):
2947            verts = []
2948            for y in sy:
2949                for x in sx:
2950                    verts.append([x, y, 0])
2951            faces = []
2952            n = len(sx)
2953            m = len(sy)
2954            for j in range(m - 1):
2955                j1n = (j + 1) * n
2956                for i in range(n - 1):
2957                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2958
2959            super().__init__([verts, faces], c, alpha)
2960
2961        else:
2962            ps = vtki.new("PlaneSource")
2963            ps.SetResolution(resx, resy)
2964            ps.Update()
2965
2966            t = vtki.vtkTransform()
2967            t.Translate(pos)
2968            t.Scale(sx, sy, 1)
2969
2970            tf = vtki.new("TransformPolyDataFilter")
2971            tf.SetInputData(ps.GetOutput())
2972            tf.SetTransform(t)
2973            tf.Update()
2974
2975            super().__init__(tf.GetOutput(), c, alpha)
2976
2977        self.wireframe().lw(lw)
2978        self.properties.LightingOff()
2979        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):
3284class TessellatedBox(Mesh):
3285    """
3286    Build a cubic `Mesh` made of quads.
3287    """
3288
3289    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3290        """
3291        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3292
3293        Arguments:
3294            pos : (list)
3295                position of the left bottom corner
3296            n : (int, list)
3297                number of subdivisions along each side
3298            spacing : (float)
3299                size of the side of the single quad in the 3 directions
3300        """
3301        if utils.is_sequence(n):  # slow
3302            img = vtki.vtkImageData()
3303            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3304            img.SetSpacing(spacing)
3305            gf = vtki.new("GeometryFilter")
3306            gf.SetInputData(img)
3307            gf.Update()
3308            poly = gf.GetOutput()
3309        else:  # fast
3310            n -= 1
3311            tbs = vtki.new("TessellatedBoxSource")
3312            tbs.SetLevel(n)
3313            if len(bounds):
3314                tbs.SetBounds(bounds)
3315            else:
3316                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3317            tbs.QuadsOn()
3318            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3319            tbs.Update()
3320            poly = tbs.GetOutput()
3321        super().__init__(poly, c=c, alpha=alpha)
3322        self.pos(pos)
3323        self.lw(1).lighting("off")
3324        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)
3289    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3290        """
3291        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3292
3293        Arguments:
3294            pos : (list)
3295                position of the left bottom corner
3296            n : (int, list)
3297                number of subdivisions along each side
3298            spacing : (float)
3299                size of the side of the single quad in the 3 directions
3300        """
3301        if utils.is_sequence(n):  # slow
3302            img = vtki.vtkImageData()
3303            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3304            img.SetSpacing(spacing)
3305            gf = vtki.new("GeometryFilter")
3306            gf.SetInputData(img)
3307            gf.Update()
3308            poly = gf.GetOutput()
3309        else:  # fast
3310            n -= 1
3311            tbs = vtki.new("TessellatedBoxSource")
3312            tbs.SetLevel(n)
3313            if len(bounds):
3314                tbs.SetBounds(bounds)
3315            else:
3316                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3317            tbs.QuadsOn()
3318            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3319            tbs.Update()
3320            poly = tbs.GetOutput()
3321        super().__init__(poly, c=c, alpha=alpha)
3322        self.pos(pos)
3323        self.lw(1).lighting("off")
3324        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):
2982class Plane(Mesh):
2983    """Create a plane in space."""
2984
2985    def __init__(
2986            self,
2987            pos=(0, 0, 0),
2988            normal=(0, 0, 1),
2989            s=(1, 1),
2990            res=(1, 1),
2991            c="gray5", alpha=1.0,
2992        ) -> None:
2993        """
2994        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
2995        to vector `normal` so that it passes through point `pos`.
2996
2997        Arguments:
2998            pos : (list)
2999                position of the plane center
3000            normal : (list)
3001                normal vector to the plane
3002            s : (list)
3003                size of the plane along x and y
3004            res : (list)
3005                resolution of the plane along x and y
3006        """
3007        if isinstance(pos, vtki.vtkPolyData):
3008            super().__init__(pos, c, alpha)
3009            # self.transform = LinearTransform().translate(pos)
3010
3011        else:
3012            ps = vtki.new("PlaneSource")
3013            ps.SetResolution(res[0], res[1])
3014            tri = vtki.new("TriangleFilter")
3015            tri.SetInputConnection(ps.GetOutputPort())
3016            tri.Update()
3017            
3018            super().__init__(tri.GetOutput(), c, alpha)
3019
3020            pos = utils.make3d(pos)
3021            normal = np.asarray(normal, dtype=float)
3022            axis = normal / np.linalg.norm(normal)
3023            theta = np.arccos(axis[2])
3024            phi = np.arctan2(axis[1], axis[0])
3025
3026            t = LinearTransform()
3027            t.scale([s[0], s[1], 1])
3028            t.rotate_y(np.rad2deg(theta))
3029            t.rotate_z(np.rad2deg(phi))
3030            t.translate(pos)
3031            self.apply_transform(t)
3032
3033        self.lighting("off")
3034        self.name = "Plane"
3035        self.variance = 0
3036
3037    def clone(self, deep=True) -> "Plane":
3038        newplane = Plane()
3039        if deep:
3040            newplane.dataset.DeepCopy(self.dataset)
3041        else:
3042            newplane.dataset.ShallowCopy(self.dataset)
3043        newplane.copy_properties_from(self)
3044        newplane.transform = self.transform.clone()
3045        newplane.variance = 0
3046        return newplane
3047    
3048    @property
3049    def normal(self) -> np.ndarray:
3050        pts = self.vertices
3051        # this is necessary because plane can have high resolution
3052        # p0, p1 = pts[0], pts[1]
3053        # AB = p1 - p0
3054        # AB /= np.linalg.norm(AB)
3055        # for pt in pts[2:]:
3056        #     AC = pt - p0
3057        #     AC /= np.linalg.norm(AC)
3058        #     cosine_angle = np.dot(AB, AC)
3059        #     if abs(cosine_angle) < 0.99:
3060        #         normal = np.cross(AB, AC)
3061        #         return normal / np.linalg.norm(normal)
3062        p0, p1, p2 = pts[0], pts[1], pts[int(len(pts)/2 +0.5)]
3063        AB = p1 - p0
3064        AB /= np.linalg.norm(AB)
3065        AC = p2 - p0
3066        AC /= np.linalg.norm(AC)
3067        normal = np.cross(AB, AC)
3068        return normal / np.linalg.norm(normal)
3069
3070    @property
3071    def center(self) -> np.ndarray:
3072        pts = self.vertices
3073        return np.mean(pts, axis=0)
3074
3075    def contains(self, points, tol=0) -> np.ndarray:
3076        """
3077        Check if each of the provided point lies on this plane.
3078        `points` is an array of shape (n, 3).
3079        """
3080        points = np.array(points, dtype=float)
3081        bounds = self.vertices
3082
3083        mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol)
3084
3085        for i in [1, 3]:
3086            AB = bounds[i] - bounds[0]
3087            AP = points - bounds[0]
3088            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3089            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3090            mask = np.logical_and(mask, mask_l)
3091            mask = np.logical_and(mask, mask_g)
3092        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)
2985    def __init__(
2986            self,
2987            pos=(0, 0, 0),
2988            normal=(0, 0, 1),
2989            s=(1, 1),
2990            res=(1, 1),
2991            c="gray5", alpha=1.0,
2992        ) -> None:
2993        """
2994        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
2995        to vector `normal` so that it passes through point `pos`.
2996
2997        Arguments:
2998            pos : (list)
2999                position of the plane center
3000            normal : (list)
3001                normal vector to the plane
3002            s : (list)
3003                size of the plane along x and y
3004            res : (list)
3005                resolution of the plane along x and y
3006        """
3007        if isinstance(pos, vtki.vtkPolyData):
3008            super().__init__(pos, c, alpha)
3009            # self.transform = LinearTransform().translate(pos)
3010
3011        else:
3012            ps = vtki.new("PlaneSource")
3013            ps.SetResolution(res[0], res[1])
3014            tri = vtki.new("TriangleFilter")
3015            tri.SetInputConnection(ps.GetOutputPort())
3016            tri.Update()
3017            
3018            super().__init__(tri.GetOutput(), c, alpha)
3019
3020            pos = utils.make3d(pos)
3021            normal = np.asarray(normal, dtype=float)
3022            axis = normal / np.linalg.norm(normal)
3023            theta = np.arccos(axis[2])
3024            phi = np.arctan2(axis[1], axis[0])
3025
3026            t = LinearTransform()
3027            t.scale([s[0], s[1], 1])
3028            t.rotate_y(np.rad2deg(theta))
3029            t.rotate_z(np.rad2deg(phi))
3030            t.translate(pos)
3031            self.apply_transform(t)
3032
3033        self.lighting("off")
3034        self.name = "Plane"
3035        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:
3037    def clone(self, deep=True) -> "Plane":
3038        newplane = Plane()
3039        if deep:
3040            newplane.dataset.DeepCopy(self.dataset)
3041        else:
3042            newplane.dataset.ShallowCopy(self.dataset)
3043        newplane.copy_properties_from(self)
3044        newplane.transform = self.transform.clone()
3045        newplane.variance = 0
3046        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:
3075    def contains(self, points, tol=0) -> np.ndarray:
3076        """
3077        Check if each of the provided point lies on this plane.
3078        `points` is an array of shape (n, 3).
3079        """
3080        points = np.array(points, dtype=float)
3081        bounds = self.vertices
3082
3083        mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol)
3084
3085        for i in [1, 3]:
3086            AB = bounds[i] - bounds[0]
3087            AP = points - bounds[0]
3088            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3089            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3090            mask = np.logical_and(mask, mask_l)
3091            mask = np.logical_and(mask, mask_g)
3092        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):
3189class Box(Mesh):
3190    """
3191    Build a box of specified dimensions.
3192    """
3193
3194    def __init__(
3195            self, pos=(0, 0, 0), 
3196            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3197        """
3198        Build a box of dimensions `x=length, y=width and z=height`.
3199        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3200
3201        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3202        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3203
3204        Note that the shape polygonal data contains duplicated vertices. This is to allow
3205        each face to have its own normal, which is essential for some operations.
3206        Use the `clean()` method to remove duplicate points.
3207
3208        Examples:
3209            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3210
3211                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3212        """
3213        src = vtki.new("CubeSource")
3214
3215        if len(pos) == 2:
3216            pos = (pos[0], pos[1], 0)
3217
3218        if len(pos) == 6:
3219            src.SetBounds(pos)
3220            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3221        elif len(size) == 3:
3222            length, width, height = size
3223            src.SetXLength(length)
3224            src.SetYLength(width)
3225            src.SetZLength(height)
3226            src.SetCenter(pos)
3227        else:
3228            src.SetXLength(length)
3229            src.SetYLength(width)
3230            src.SetZLength(height)
3231            src.SetCenter(pos)
3232
3233        src.Update()
3234        pd = src.GetOutput()
3235
3236        tc = [
3237            [0.0, 0.0],
3238            [1.0, 0.0],
3239            [0.0, 1.0],
3240            [1.0, 1.0],
3241            [1.0, 0.0],
3242            [0.0, 0.0],
3243            [1.0, 1.0],
3244            [0.0, 1.0],
3245            [1.0, 1.0],
3246            [1.0, 0.0],
3247            [0.0, 1.0],
3248            [0.0, 0.0],
3249            [0.0, 1.0],
3250            [0.0, 0.0],
3251            [1.0, 1.0],
3252            [1.0, 0.0],
3253            [1.0, 0.0],
3254            [0.0, 0.0],
3255            [1.0, 1.0],
3256            [0.0, 1.0],
3257            [0.0, 0.0],
3258            [1.0, 0.0],
3259            [0.0, 1.0],
3260            [1.0, 1.0],
3261        ]
3262        vtc = utils.numpy2vtk(tc)
3263        pd.GetPointData().SetTCoords(vtc)
3264        super().__init__(pd, c, alpha)
3265        self.transform = LinearTransform().translate(pos)
3266        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)
3194    def __init__(
3195            self, pos=(0, 0, 0), 
3196            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3197        """
3198        Build a box of dimensions `x=length, y=width and z=height`.
3199        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3200
3201        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3202        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3203
3204        Note that the shape polygonal data contains duplicated vertices. This is to allow
3205        each face to have its own normal, which is essential for some operations.
3206        Use the `clean()` method to remove duplicate points.
3207
3208        Examples:
3209            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3210
3211                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3212        """
3213        src = vtki.new("CubeSource")
3214
3215        if len(pos) == 2:
3216            pos = (pos[0], pos[1], 0)
3217
3218        if len(pos) == 6:
3219            src.SetBounds(pos)
3220            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3221        elif len(size) == 3:
3222            length, width, height = size
3223            src.SetXLength(length)
3224            src.SetYLength(width)
3225            src.SetZLength(height)
3226            src.SetCenter(pos)
3227        else:
3228            src.SetXLength(length)
3229            src.SetYLength(width)
3230            src.SetZLength(height)
3231            src.SetCenter(pos)
3232
3233        src.Update()
3234        pd = src.GetOutput()
3235
3236        tc = [
3237            [0.0, 0.0],
3238            [1.0, 0.0],
3239            [0.0, 1.0],
3240            [1.0, 1.0],
3241            [1.0, 0.0],
3242            [0.0, 0.0],
3243            [1.0, 1.0],
3244            [0.0, 1.0],
3245            [1.0, 1.0],
3246            [1.0, 0.0],
3247            [0.0, 1.0],
3248            [0.0, 0.0],
3249            [0.0, 1.0],
3250            [0.0, 0.0],
3251            [1.0, 1.0],
3252            [1.0, 0.0],
3253            [1.0, 0.0],
3254            [0.0, 0.0],
3255            [1.0, 1.0],
3256            [0.0, 1.0],
3257            [0.0, 0.0],
3258            [1.0, 0.0],
3259            [0.0, 1.0],
3260            [1.0, 1.0],
3261        ]
3262        vtc = utils.numpy2vtk(tc)
3263        pd.GetPointData().SetTCoords(vtc)
3264        super().__init__(pd, c, alpha)
3265        self.transform = LinearTransform().translate(pos)
3266        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]

Note that the shape polygonal data contains duplicated vertices. This is to allow each face to have its own normal, which is essential for some operations. Use the clean() method to remove duplicate points.

Examples:
class Cube(Box):
3269class Cube(Box):
3270    """
3271    Build a cube shape.
3272    
3273    Note that the shape polygonal data contains duplicated vertices. This is to allow
3274    each face to have its own normal, which is essential for some operations.
3275    Use the `clean()` method to remove duplicate points.
3276    """
3277
3278    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3279        """Build a cube of size `side`."""
3280        super().__init__(pos, side, side, side, (), c, alpha)
3281        self.name = "Cube"

Build a cube shape.

Note that the shape polygonal data contains duplicated vertices. This is to allow each face to have its own normal, which is essential for some operations. Use the clean() method to remove duplicate points.

Cube(pos=(0, 0, 0), side=1.0, c='g4', alpha=1.0)
3278    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3279        """Build a cube of size `side`."""
3280        super().__init__(pos, side, side, side, (), c, alpha)
3281        self.name = "Cube"

Build a cube of size side.

class Spring(vedo.mesh.Mesh):
3327class Spring(Mesh):
3328    """
3329    Build a spring model.
3330    """
3331
3332    def __init__(
3333        self,
3334        start_pt=(0, 0, 0),
3335        end_pt=(1, 0, 0),
3336        coils=20,
3337        r1=0.1,
3338        r2=None,
3339        thickness=None,
3340        c="gray5",
3341        alpha=1.0,
3342    ) -> None:
3343        """
3344        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3345
3346        Arguments:
3347            coils : (int)
3348                number of coils
3349            r1 : (float)
3350                radius at start point
3351            r2 : (float)
3352                radius at end point
3353            thickness : (float)
3354                thickness of the coil section
3355        """
3356        start_pt = utils.make3d(start_pt)
3357        end_pt = utils.make3d(end_pt)
3358
3359        diff = end_pt - start_pt
3360        length = np.linalg.norm(diff)
3361        if not length:
3362            return
3363        if not r1:
3364            r1 = length / 20
3365        trange = np.linspace(0, length, num=50 * coils)
3366        om = 6.283 * (coils - 0.5) / length
3367        if not r2:
3368            r2 = r1
3369        pts = []
3370        for t in trange:
3371            f = (length - t) / length
3372            rd = r1 * f + r2 * (1 - f)
3373            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3374
3375        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3376        diff = diff / length
3377        theta = np.arccos(diff[2])
3378        phi = np.arctan2(diff[1], diff[0])
3379        sp = Line(pts)
3380        
3381        t = vtki.vtkTransform()
3382        t.Translate(start_pt)
3383        t.RotateZ(np.rad2deg(phi))
3384        t.RotateY(np.rad2deg(theta))
3385
3386        tf = vtki.new("TransformPolyDataFilter")
3387        tf.SetInputData(sp.dataset)
3388        tf.SetTransform(t)
3389        tf.Update()
3390
3391        tuf = vtki.new("TubeFilter")
3392        tuf.SetNumberOfSides(12)
3393        tuf.CappingOn()
3394        tuf.SetInputData(tf.GetOutput())
3395        if not thickness:
3396            thickness = r1 / 10
3397        tuf.SetRadius(thickness)
3398        tuf.Update()
3399
3400        super().__init__(tuf.GetOutput(), c, alpha)
3401
3402        self.phong()
3403        self.base = np.array(start_pt, dtype=float)
3404        self.top  = np.array(end_pt, dtype=float)
3405        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)
3332    def __init__(
3333        self,
3334        start_pt=(0, 0, 0),
3335        end_pt=(1, 0, 0),
3336        coils=20,
3337        r1=0.1,
3338        r2=None,
3339        thickness=None,
3340        c="gray5",
3341        alpha=1.0,
3342    ) -> None:
3343        """
3344        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3345
3346        Arguments:
3347            coils : (int)
3348                number of coils
3349            r1 : (float)
3350                radius at start point
3351            r2 : (float)
3352                radius at end point
3353            thickness : (float)
3354                thickness of the coil section
3355        """
3356        start_pt = utils.make3d(start_pt)
3357        end_pt = utils.make3d(end_pt)
3358
3359        diff = end_pt - start_pt
3360        length = np.linalg.norm(diff)
3361        if not length:
3362            return
3363        if not r1:
3364            r1 = length / 20
3365        trange = np.linspace(0, length, num=50 * coils)
3366        om = 6.283 * (coils - 0.5) / length
3367        if not r2:
3368            r2 = r1
3369        pts = []
3370        for t in trange:
3371            f = (length - t) / length
3372            rd = r1 * f + r2 * (1 - f)
3373            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3374
3375        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3376        diff = diff / length
3377        theta = np.arccos(diff[2])
3378        phi = np.arctan2(diff[1], diff[0])
3379        sp = Line(pts)
3380        
3381        t = vtki.vtkTransform()
3382        t.Translate(start_pt)
3383        t.RotateZ(np.rad2deg(phi))
3384        t.RotateY(np.rad2deg(theta))
3385
3386        tf = vtki.new("TransformPolyDataFilter")
3387        tf.SetInputData(sp.dataset)
3388        tf.SetTransform(t)
3389        tf.Update()
3390
3391        tuf = vtki.new("TubeFilter")
3392        tuf.SetNumberOfSides(12)
3393        tuf.CappingOn()
3394        tuf.SetInputData(tf.GetOutput())
3395        if not thickness:
3396            thickness = r1 / 10
3397        tuf.SetRadius(thickness)
3398        tuf.Update()
3399
3400        super().__init__(tuf.GetOutput(), c, alpha)
3401
3402        self.phong()
3403        self.base = np.array(start_pt, dtype=float)
3404        self.top  = np.array(end_pt, dtype=float)
3405        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):
3408class Cylinder(Mesh):
3409    """
3410    Build a cylinder of specified height and radius.
3411    """
3412
3413    def __init__(
3414        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3415        cap=True, res=24, c="teal3", alpha=1.0
3416    ) -> None:
3417        """
3418        Build a cylinder of specified height and radius `r`, centered at `pos`.
3419
3420        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3421        centered at `v1` and top at `v2`.
3422
3423        Arguments:
3424            cap : (bool)
3425                enable/disable the caps of the cylinder
3426            res : (int)
3427                resolution of the cylinder sides
3428
3429        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3430        """
3431        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3432            base = np.array(pos[0], dtype=float)
3433            top = np.array(pos[1], dtype=float)
3434            pos = (base + top) / 2
3435            height = np.linalg.norm(top - base)
3436            axis = top - base
3437            axis = utils.versor(axis)
3438        else:
3439            axis = utils.versor(axis)
3440            base = pos - axis * height / 2
3441            top = pos + axis * height / 2
3442
3443        cyl = vtki.new("CylinderSource")
3444        cyl.SetResolution(res)
3445        cyl.SetRadius(r)
3446        cyl.SetHeight(height)
3447        cyl.SetCapping(cap)
3448        cyl.Update()
3449
3450        theta = np.arccos(axis[2])
3451        phi = np.arctan2(axis[1], axis[0])
3452        t = vtki.vtkTransform()
3453        t.PostMultiply()
3454        t.RotateX(90)  # put it along Z
3455        t.RotateY(np.rad2deg(theta))
3456        t.RotateZ(np.rad2deg(phi))
3457        t.Translate(pos)
3458
3459        tf = vtki.new("TransformPolyDataFilter")
3460        tf.SetInputData(cyl.GetOutput())
3461        tf.SetTransform(t)
3462        tf.Update()
3463
3464        super().__init__(tf.GetOutput(), c, alpha)
3465
3466        self.phong()
3467        self.base = base
3468        self.top  = top
3469        self.transform = LinearTransform().translate(pos)
3470        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)
3413    def __init__(
3414        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3415        cap=True, res=24, c="teal3", alpha=1.0
3416    ) -> None:
3417        """
3418        Build a cylinder of specified height and radius `r`, centered at `pos`.
3419
3420        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3421        centered at `v1` and top at `v2`.
3422
3423        Arguments:
3424            cap : (bool)
3425                enable/disable the caps of the cylinder
3426            res : (int)
3427                resolution of the cylinder sides
3428
3429        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3430        """
3431        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3432            base = np.array(pos[0], dtype=float)
3433            top = np.array(pos[1], dtype=float)
3434            pos = (base + top) / 2
3435            height = np.linalg.norm(top - base)
3436            axis = top - base
3437            axis = utils.versor(axis)
3438        else:
3439            axis = utils.versor(axis)
3440            base = pos - axis * height / 2
3441            top = pos + axis * height / 2
3442
3443        cyl = vtki.new("CylinderSource")
3444        cyl.SetResolution(res)
3445        cyl.SetRadius(r)
3446        cyl.SetHeight(height)
3447        cyl.SetCapping(cap)
3448        cyl.Update()
3449
3450        theta = np.arccos(axis[2])
3451        phi = np.arctan2(axis[1], axis[0])
3452        t = vtki.vtkTransform()
3453        t.PostMultiply()
3454        t.RotateX(90)  # put it along Z
3455        t.RotateY(np.rad2deg(theta))
3456        t.RotateZ(np.rad2deg(phi))
3457        t.Translate(pos)
3458
3459        tf = vtki.new("TransformPolyDataFilter")
3460        tf.SetInputData(cyl.GetOutput())
3461        tf.SetTransform(t)
3462        tf.Update()
3463
3464        super().__init__(tf.GetOutput(), c, alpha)
3465
3466        self.phong()
3467        self.base = base
3468        self.top  = top
3469        self.transform = LinearTransform().translate(pos)
3470        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):
3473class Cone(Mesh):
3474    """Build a cone of specified radius and height."""
3475
3476    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3477                 res=48, c="green3", alpha=1.0) -> None:
3478        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3479        con = vtki.new("ConeSource")
3480        con.SetResolution(res)
3481        con.SetRadius(r)
3482        con.SetHeight(height)
3483        con.SetDirection(axis)
3484        con.Update()
3485        super().__init__(con.GetOutput(), c, alpha)
3486        self.phong()
3487        if len(pos) == 2:
3488            pos = (pos[0], pos[1], 0)
3489        self.pos(pos)
3490        v = utils.versor(axis) * height / 2
3491        self.base = pos - v
3492        self.top  = pos + v
3493        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)
3476    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3477                 res=48, c="green3", alpha=1.0) -> None:
3478        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3479        con = vtki.new("ConeSource")
3480        con.SetResolution(res)
3481        con.SetRadius(r)
3482        con.SetHeight(height)
3483        con.SetDirection(axis)
3484        con.Update()
3485        super().__init__(con.GetOutput(), c, alpha)
3486        self.phong()
3487        if len(pos) == 2:
3488            pos = (pos[0], pos[1], 0)
3489        self.pos(pos)
3490        v = utils.versor(axis) * height / 2
3491        self.base = pos - v
3492        self.top  = pos + v
3493        self.name = "Cone"

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

class Pyramid(Cone):
3496class Pyramid(Cone):
3497    """Build a pyramidal shape."""
3498
3499    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3500                 c="green3", alpha=1) -> None:
3501        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3502        super().__init__(pos, s, height, axis, 4, c, alpha)
3503        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)
3499    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3500                 c="green3", alpha=1) -> None:
3501        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3502        super().__init__(pos, s, height, axis, 4, c, alpha)
3503        self.name = "Pyramid"

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

class Torus(vedo.mesh.Mesh):
3506class Torus(Mesh):
3507    """
3508    Build a toroidal shape.
3509    """
3510
3511    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3512        """
3513        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3514        If `quad=True` a quad-mesh is generated.
3515        """
3516        if utils.is_sequence(res):
3517            res_u, res_v = res
3518        else:
3519            res_u, res_v = 3 * res, res
3520
3521        if quads:
3522            # https://github.com/marcomusy/vedo/issues/710
3523
3524            n = res_v
3525            m = res_u
3526
3527            theta = np.linspace(0, 2.0 * np.pi, n)
3528            phi = np.linspace(0, 2.0 * np.pi, m)
3529            theta, phi = np.meshgrid(theta, phi)
3530            t = r1 + r2 * np.cos(theta)
3531            x = t * np.cos(phi)
3532            y = t * np.sin(phi)
3533            z = r2 * np.sin(theta)
3534            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3535
3536            faces = []
3537            for j in range(m - 1):
3538                j1n = (j + 1) * n
3539                for i in range(n - 1):
3540                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3541
3542            super().__init__([pts, faces], c, alpha)
3543
3544        else:
3545            rs = vtki.new("ParametricTorus")
3546            rs.SetRingRadius(r1)
3547            rs.SetCrossSectionRadius(r2)
3548            pfs = vtki.new("ParametricFunctionSource")
3549            pfs.SetParametricFunction(rs)
3550            pfs.SetUResolution(res_u)
3551            pfs.SetVResolution(res_v)
3552            pfs.Update()
3553
3554            super().__init__(pfs.GetOutput(), c, alpha)
3555
3556        self.phong()
3557        if len(pos) == 2:
3558            pos = (pos[0], pos[1], 0)
3559        self.pos(pos)
3560        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)
3511    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3512        """
3513        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3514        If `quad=True` a quad-mesh is generated.
3515        """
3516        if utils.is_sequence(res):
3517            res_u, res_v = res
3518        else:
3519            res_u, res_v = 3 * res, res
3520
3521        if quads:
3522            # https://github.com/marcomusy/vedo/issues/710
3523
3524            n = res_v
3525            m = res_u
3526
3527            theta = np.linspace(0, 2.0 * np.pi, n)
3528            phi = np.linspace(0, 2.0 * np.pi, m)
3529            theta, phi = np.meshgrid(theta, phi)
3530            t = r1 + r2 * np.cos(theta)
3531            x = t * np.cos(phi)
3532            y = t * np.sin(phi)
3533            z = r2 * np.sin(theta)
3534            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3535
3536            faces = []
3537            for j in range(m - 1):
3538                j1n = (j + 1) * n
3539                for i in range(n - 1):
3540                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3541
3542            super().__init__([pts, faces], c, alpha)
3543
3544        else:
3545            rs = vtki.new("ParametricTorus")
3546            rs.SetRingRadius(r1)
3547            rs.SetCrossSectionRadius(r2)
3548            pfs = vtki.new("ParametricFunctionSource")
3549            pfs.SetParametricFunction(rs)
3550            pfs.SetUResolution(res_u)
3551            pfs.SetVResolution(res_v)
3552            pfs.Update()
3553
3554            super().__init__(pfs.GetOutput(), c, alpha)
3555
3556        self.phong()
3557        if len(pos) == 2:
3558            pos = (pos[0], pos[1], 0)
3559        self.pos(pos)
3560        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):
3563class Paraboloid(Mesh):
3564    """
3565    Build a paraboloid.
3566    """
3567
3568    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3569        """
3570        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3571
3572        Full volumetric expression is:
3573            `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`
3574
3575        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3576        """
3577        quadric = vtki.new("Quadric")
3578        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3579        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3580        #         + a3*x*y + a4*y*z + a5*x*z
3581        #         + a6*x   + a7*y   + a8*z  +a9
3582        sample = vtki.new("SampleFunction")
3583        sample.SetSampleDimensions(res, res, res)
3584        sample.SetImplicitFunction(quadric)
3585
3586        contours = vtki.new("ContourFilter")
3587        contours.SetInputConnection(sample.GetOutputPort())
3588        contours.GenerateValues(1, 0.01, 0.01)
3589        contours.Update()
3590
3591        super().__init__(contours.GetOutput(), c, alpha)
3592        self.compute_normals().phong()
3593        self.mapper.ScalarVisibilityOff()
3594        self.pos(pos)
3595        self.name = "Paraboloid"

Build a paraboloid.

Paraboloid(pos=(0, 0, 0), height=1.0, res=50, c='cyan5', alpha=1.0)
3568    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3569        """
3570        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3571
3572        Full volumetric expression is:
3573            `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`
3574
3575        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3576        """
3577        quadric = vtki.new("Quadric")
3578        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3579        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3580        #         + a3*x*y + a4*y*z + a5*x*z
3581        #         + a6*x   + a7*y   + a8*z  +a9
3582        sample = vtki.new("SampleFunction")
3583        sample.SetSampleDimensions(res, res, res)
3584        sample.SetImplicitFunction(quadric)
3585
3586        contours = vtki.new("ContourFilter")
3587        contours.SetInputConnection(sample.GetOutputPort())
3588        contours.GenerateValues(1, 0.01, 0.01)
3589        contours.Update()
3590
3591        super().__init__(contours.GetOutput(), c, alpha)
3592        self.compute_normals().phong()
3593        self.mapper.ScalarVisibilityOff()
3594        self.pos(pos)
3595        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):
3598class Hyperboloid(Mesh):
3599    """
3600    Build a hyperboloid.
3601    """
3602
3603    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3604        """
3605        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3606
3607        Full volumetric expression is:
3608            `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`
3609        """
3610        q = vtki.new("Quadric")
3611        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3612        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3613        #         + a3*x*y + a4*y*z + a5*x*z
3614        #         + a6*x   + a7*y   + a8*z  +a9
3615        sample = vtki.new("SampleFunction")
3616        sample.SetSampleDimensions(res, res, res)
3617        sample.SetImplicitFunction(q)
3618
3619        contours = vtki.new("ContourFilter")
3620        contours.SetInputConnection(sample.GetOutputPort())
3621        contours.GenerateValues(1, value, value)
3622        contours.Update()
3623
3624        super().__init__(contours.GetOutput(), c, alpha)
3625        self.compute_normals().phong()
3626        self.mapper.ScalarVisibilityOff()
3627        self.pos(pos)
3628        self.name = "Hyperboloid"

Build a hyperboloid.

Hyperboloid(pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c='pink4', alpha=1.0)
3603    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3604        """
3605        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3606
3607        Full volumetric expression is:
3608            `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`
3609        """
3610        q = vtki.new("Quadric")
3611        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3612        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3613        #         + a3*x*y + a4*y*z + a5*x*z
3614        #         + a6*x   + a7*y   + a8*z  +a9
3615        sample = vtki.new("SampleFunction")
3616        sample.SetSampleDimensions(res, res, res)
3617        sample.SetImplicitFunction(q)
3618
3619        contours = vtki.new("ContourFilter")
3620        contours.SetInputConnection(sample.GetOutputPort())
3621        contours.GenerateValues(1, value, value)
3622        contours.Update()
3623
3624        super().__init__(contours.GetOutput(), c, alpha)
3625        self.compute_normals().phong()
3626        self.mapper.ScalarVisibilityOff()
3627        self.pos(pos)
3628        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:
4393class TextBase:
4394    "Base class."
4395
4396    def __init__(self):
4397        "Do not instantiate this base class."
4398
4399        self.rendered_at = set()
4400        self.properties = None
4401
4402        self.name = "Text"
4403        self.filename = ""
4404        self.time = 0
4405        self.info = {}
4406
4407        if isinstance(settings.default_font, int):
4408            lfonts = list(settings.font_parameters.keys())
4409            font = settings.default_font % len(lfonts)
4410            self.fontname = lfonts[font]
4411        else:
4412            self.fontname = settings.default_font
4413
4414    def angle(self, value: float):
4415        """Orientation angle in degrees"""
4416        self.properties.SetOrientation(value)
4417        return self
4418
4419    def line_spacing(self, value: float):
4420        """Set the extra spacing between lines
4421        expressed as a text height multiplicative factor."""
4422        self.properties.SetLineSpacing(value)
4423        return self
4424
4425    def line_offset(self, value: float):
4426        """Set/Get the vertical offset (measured in pixels)."""
4427        self.properties.SetLineOffset(value)
4428        return self
4429
4430    def bold(self, value=True):
4431        """Set bold face"""
4432        self.properties.SetBold(value)
4433        return self
4434
4435    def italic(self, value=True):
4436        """Set italic face"""
4437        self.properties.SetItalic(value)
4438        return self
4439
4440    def shadow(self, offset=(1, -1)):
4441        """Text shadowing. Set to `None` to disable it."""
4442        if offset is None:
4443            self.properties.ShadowOff()
4444        else:
4445            self.properties.ShadowOn()
4446            self.properties.SetShadowOffset(offset)
4447        return self
4448
4449    def color(self, c=None):
4450        """Set the text color"""
4451        if c is None:
4452            return get_color(self.properties.GetColor())
4453        self.properties.SetColor(get_color(c))
4454        return self
4455
4456    def c(self, color=None):
4457        """Set the text color"""
4458        if color is None:
4459            return get_color(self.properties.GetColor())
4460        return self.color(color)
4461
4462    def alpha(self, value: float):
4463        """Set the text opacity"""
4464        self.properties.SetBackgroundOpacity(value)
4465        return self
4466
4467    def background(self, color="k9", alpha=1.0):
4468        """Text background. Set to `None` to disable it."""
4469        bg = get_color(color)
4470        if color is None:
4471            self.properties.SetBackgroundOpacity(0)
4472        else:
4473            self.properties.SetBackgroundColor(bg)
4474            if alpha:
4475                self.properties.SetBackgroundOpacity(alpha)
4476        return self
4477
4478    def frame(self, color="k1", lw=2):
4479        """Border color and width"""
4480        if color is None:
4481            self.properties.FrameOff()
4482        else:
4483            c = get_color(color)
4484            self.properties.FrameOn()
4485            self.properties.SetFrameColor(c)
4486            self.properties.SetFrameWidth(lw)
4487        return self
4488
4489    def font(self, font: str):
4490        """Text font face"""
4491        if isinstance(font, int):
4492            lfonts = list(settings.font_parameters.keys())
4493            n = font % len(lfonts)
4494            font = lfonts[n]
4495            self.fontname = font
4496
4497        if not font:  # use default font
4498            font = self.fontname
4499            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4500        elif font.startswith("https"):  # user passed URL link, make it a path
4501            fpath = vedo.file_io.download(font, verbose=False, force=False)
4502        elif font.endswith(".ttf"):  # user passing a local path to font file
4503            fpath = font
4504        else:  # user passing name of preset font
4505            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4506
4507        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4508        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4509        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4510        else:
4511            fpath = utils.get_font_path(font)
4512            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4513            self.properties.SetFontFile(fpath)
4514        self.fontname = font  # io.tonumpy() uses it
4515
4516        return self
4517
4518    def on(self):
4519        """Make text visible"""
4520        self.actor.SetVisibility(True)
4521        return self
4522
4523    def off(self):
4524        """Make text invisible"""
4525        self.actor.SetVisibility(False)
4526        return self

Base class.

TextBase()
4396    def __init__(self):
4397        "Do not instantiate this base class."
4398
4399        self.rendered_at = set()
4400        self.properties = None
4401
4402        self.name = "Text"
4403        self.filename = ""
4404        self.time = 0
4405        self.info = {}
4406
4407        if isinstance(settings.default_font, int):
4408            lfonts = list(settings.font_parameters.keys())
4409            font = settings.default_font % len(lfonts)
4410            self.fontname = lfonts[font]
4411        else:
4412            self.fontname = settings.default_font

Do not instantiate this base class.

def angle(self, value: float):
4414    def angle(self, value: float):
4415        """Orientation angle in degrees"""
4416        self.properties.SetOrientation(value)
4417        return self

Orientation angle in degrees

def line_spacing(self, value: float):
4419    def line_spacing(self, value: float):
4420        """Set the extra spacing between lines
4421        expressed as a text height multiplicative factor."""
4422        self.properties.SetLineSpacing(value)
4423        return self

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

def line_offset(self, value: float):
4425    def line_offset(self, value: float):
4426        """Set/Get the vertical offset (measured in pixels)."""
4427        self.properties.SetLineOffset(value)
4428        return self

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

def bold(self, value=True):
4430    def bold(self, value=True):
4431        """Set bold face"""
4432        self.properties.SetBold(value)
4433        return self

Set bold face

def italic(self, value=True):
4435    def italic(self, value=True):
4436        """Set italic face"""
4437        self.properties.SetItalic(value)
4438        return self

Set italic face

def shadow(self, offset=(1, -1)):
4440    def shadow(self, offset=(1, -1)):
4441        """Text shadowing. Set to `None` to disable it."""
4442        if offset is None:
4443            self.properties.ShadowOff()
4444        else:
4445            self.properties.ShadowOn()
4446            self.properties.SetShadowOffset(offset)
4447        return self

Text shadowing. Set to None to disable it.

def color(self, c=None):
4449    def color(self, c=None):
4450        """Set the text color"""
4451        if c is None:
4452            return get_color(self.properties.GetColor())
4453        self.properties.SetColor(get_color(c))
4454        return self

Set the text color

def c(self, color=None):
4456    def c(self, color=None):
4457        """Set the text color"""
4458        if color is None:
4459            return get_color(self.properties.GetColor())
4460        return self.color(color)

Set the text color

def alpha(self, value: float):
4462    def alpha(self, value: float):
4463        """Set the text opacity"""
4464        self.properties.SetBackgroundOpacity(value)
4465        return self

Set the text opacity

def background(self, color='k9', alpha=1.0):
4467    def background(self, color="k9", alpha=1.0):
4468        """Text background. Set to `None` to disable it."""
4469        bg = get_color(color)
4470        if color is None:
4471            self.properties.SetBackgroundOpacity(0)
4472        else:
4473            self.properties.SetBackgroundColor(bg)
4474            if alpha:
4475                self.properties.SetBackgroundOpacity(alpha)
4476        return self

Text background. Set to None to disable it.

def frame(self, color='k1', lw=2):
4478    def frame(self, color="k1", lw=2):
4479        """Border color and width"""
4480        if color is None:
4481            self.properties.FrameOff()
4482        else:
4483            c = get_color(color)
4484            self.properties.FrameOn()
4485            self.properties.SetFrameColor(c)
4486            self.properties.SetFrameWidth(lw)
4487        return self

Border color and width

def font(self, font: str):
4489    def font(self, font: str):
4490        """Text font face"""
4491        if isinstance(font, int):
4492            lfonts = list(settings.font_parameters.keys())
4493            n = font % len(lfonts)
4494            font = lfonts[n]
4495            self.fontname = font
4496
4497        if not font:  # use default font
4498            font = self.fontname
4499            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4500        elif font.startswith("https"):  # user passed URL link, make it a path
4501            fpath = vedo.file_io.download(font, verbose=False, force=False)
4502        elif font.endswith(".ttf"):  # user passing a local path to font file
4503            fpath = font
4504        else:  # user passing name of preset font
4505            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4506
4507        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4508        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4509        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4510        else:
4511            fpath = utils.get_font_path(font)
4512            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4513            self.properties.SetFontFile(fpath)
4514        self.fontname = font  # io.tonumpy() uses it
4515
4516        return self

Text font face

def on(self):
4518    def on(self):
4519        """Make text visible"""
4520        self.actor.SetVisibility(True)
4521        return self

Make text visible

def off(self):
4523    def off(self):
4524        """Make text invisible"""
4525        self.actor.SetVisibility(False)
4526        return self

Make text invisible

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

Update the text and some of its properties.

Check available fonts here.

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

Get the internal vtkMapper.

def pos(self, pos='top-left', justify=''):
4633    def pos(self, pos="top-left", justify=""):
4634        """
4635        Set position of the text to draw. Keyword `pos` can be a string
4636        or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4637        """
4638        ajustify = "top-left"  # autojustify
4639        if isinstance(pos, str):  # corners
4640            ajustify = pos
4641            if "top" in pos:
4642                if "left" in pos:
4643                    pos = (0.008, 0.994)
4644                elif "right" in pos:
4645                    pos = (0.994, 0.994)
4646                elif "mid" in pos or "cent" in pos:
4647                    pos = (0.5, 0.994)
4648            elif "bottom" in pos:
4649                if "left" in pos:
4650                    pos = (0.008, 0.008)
4651                elif "right" in pos:
4652                    pos = (0.994, 0.008)
4653                elif "mid" in pos or "cent" in pos:
4654                    pos = (0.5, 0.008)
4655            elif "mid" in pos or "cent" in pos:
4656                if "left" in pos:
4657                    pos = (0.008, 0.5)
4658                elif "right" in pos:
4659                    pos = (0.994, 0.5)
4660                else:
4661                    pos = (0.5, 0.5)
4662
4663            else:
4664                vedo.logger.warning(f"cannot understand text position {pos}")
4665                pos = (0.008, 0.994)
4666                ajustify = "top-left"
4667
4668        elif len(pos) != 2:
4669            vedo.logger.error("pos must be of length 2 or integer value or string")
4670            raise RuntimeError()
4671
4672        if not justify:
4673            justify = ajustify
4674
4675        self.properties.SetJustificationToLeft()
4676        if "top" in justify:
4677            self.properties.SetVerticalJustificationToTop()
4678        if "bottom" in justify:
4679            self.properties.SetVerticalJustificationToBottom()
4680        if "cent" in justify or "mid" in justify:
4681            self.properties.SetJustificationToCentered()
4682        if "left" in justify:
4683            self.properties.SetJustificationToLeft()
4684        if "right" in justify:
4685            self.properties.SetJustificationToRight()
4686
4687        self.SetPosition(pos)
4688        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):
4690    def text(self, txt=None):
4691        """Set/get the input text string."""
4692        if txt is None:
4693            return self.mapper.GetInput()
4694
4695        if ":" in txt:
4696            for r in _reps:
4697                txt = txt.replace(r[0], r[1])
4698        else:
4699            txt = str(txt)
4700
4701        self.mapper.SetInput(txt)
4702        return self

Set/get the input text string.

def size(self, s):
4704    def size(self, s):
4705        """Set the font size."""
4706        self.properties.SetFontSize(int(s * 22.5))
4707        return self

Set the font size.

class CornerAnnotation(TextBase, vtkmodules.vtkRenderingAnnotation.vtkCornerAnnotation):
4710class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation):
4711    # PROBABLY USELESS given that Text2D does pretty much the same ...
4712    """
4713    Annotate the window corner with 2D text.
4714
4715    See `Text2D` description as the basic functionality is very similar.
4716
4717    The added value of this class is the possibility to manage with one single
4718    object the all corner annotations (instead of creating 4 `Text2D` instances).
4719
4720    Examples:
4721        - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
4722    """
4723
4724    def __init__(self, c=None) -> None:
4725
4726        super().__init__()
4727
4728        self.properties = self.GetTextProperty()
4729
4730        # automatic black or white
4731        if c is None:
4732            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4733                c = (0.9, 0.9, 0.9)
4734                if vedo.plotter_instance.renderer.GetGradientBackground():
4735                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4736                else:
4737                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4738                if np.sum(bgcol) > 1.5:
4739                    c = (0.1, 0.1, 0.1)
4740            else:
4741                c = (0.5, 0.5, 0.5)
4742
4743        self.SetNonlinearFontScaleFactor(1 / 2.75)
4744        self.PickableOff()
4745        self.properties.SetColor(get_color(c))
4746        self.properties.SetBold(False)
4747        self.properties.SetItalic(False)
4748
4749    def size(self, s:float, linear=False) -> "CornerAnnotation":
4750        """
4751        The font size is calculated as the largest possible value such that the annotations
4752        for the given viewport do not overlap.
4753
4754        This font size can be scaled non-linearly with the viewport size, to maintain an
4755        acceptable readable size at larger viewport sizes, without being too big.
4756        `f' = linearScale * pow(f,nonlinearScale)`
4757        """
4758        if linear:
4759            self.SetLinearFontScaleFactor(s * 5.5)
4760        else:
4761            self.SetNonlinearFontScaleFactor(s / 2.75)
4762        return self
4763
4764    def text(self, txt: str, pos=2) -> "CornerAnnotation":
4765        """Set text at the assigned position"""
4766
4767        if isinstance(pos, str):  # corners
4768            if "top" in pos:
4769                if "left" in pos: pos = 2
4770                elif "right" in pos: pos = 3
4771                elif "mid" in pos or "cent" in pos: pos = 7
4772            elif "bottom" in pos:
4773                if "left" in pos: pos = 0
4774                elif "right" in pos: pos = 1
4775                elif "mid" in pos or "cent" in pos: pos = 4
4776            else:
4777                if "left" in pos: pos = 6
4778                elif "right" in pos: pos = 5
4779                else: pos = 2
4780
4781        if "\\" in repr(txt):
4782            for r in _reps:
4783                txt = txt.replace(r[0], r[1])
4784        else:
4785            txt = str(txt)
4786
4787        self.SetText(pos, txt)
4788        return self
4789
4790    def clear(self) -> "CornerAnnotation":
4791        """Remove all text from all corners"""
4792        self.ClearAllTexts()
4793        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)
4724    def __init__(self, c=None) -> None:
4725
4726        super().__init__()
4727
4728        self.properties = self.GetTextProperty()
4729
4730        # automatic black or white
4731        if c is None:
4732            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4733                c = (0.9, 0.9, 0.9)
4734                if vedo.plotter_instance.renderer.GetGradientBackground():
4735                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4736                else:
4737                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4738                if np.sum(bgcol) > 1.5:
4739                    c = (0.1, 0.1, 0.1)
4740            else:
4741                c = (0.5, 0.5, 0.5)
4742
4743        self.SetNonlinearFontScaleFactor(1 / 2.75)
4744        self.PickableOff()
4745        self.properties.SetColor(get_color(c))
4746        self.properties.SetBold(False)
4747        self.properties.SetItalic(False)

Do not instantiate this base class.

def size(self, s: float, linear=False) -> CornerAnnotation:
4749    def size(self, s:float, linear=False) -> "CornerAnnotation":
4750        """
4751        The font size is calculated as the largest possible value such that the annotations
4752        for the given viewport do not overlap.
4753
4754        This font size can be scaled non-linearly with the viewport size, to maintain an
4755        acceptable readable size at larger viewport sizes, without being too big.
4756        `f' = linearScale * pow(f,nonlinearScale)`
4757        """
4758        if linear:
4759            self.SetLinearFontScaleFactor(s * 5.5)
4760        else:
4761            self.SetNonlinearFontScaleFactor(s / 2.75)
4762        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:
4764    def text(self, txt: str, pos=2) -> "CornerAnnotation":
4765        """Set text at the assigned position"""
4766
4767        if isinstance(pos, str):  # corners
4768            if "top" in pos:
4769                if "left" in pos: pos = 2
4770                elif "right" in pos: pos = 3
4771                elif "mid" in pos or "cent" in pos: pos = 7
4772            elif "bottom" in pos:
4773                if "left" in pos: pos = 0
4774                elif "right" in pos: pos = 1
4775                elif "mid" in pos or "cent" in pos: pos = 4
4776            else:
4777                if "left" in pos: pos = 6
4778                elif "right" in pos: pos = 5
4779                else: pos = 2
4780
4781        if "\\" in repr(txt):
4782            for r in _reps:
4783                txt = txt.replace(r[0], r[1])
4784        else:
4785            txt = str(txt)
4786
4787        self.SetText(pos, txt)
4788        return self

Set text at the assigned position

def clear(self) -> CornerAnnotation:
4790    def clear(self) -> "CornerAnnotation":
4791        """Remove all text from all corners"""
4792        self.ClearAllTexts()
4793        return self

Remove all text from all corners

class Latex(vedo.image.Image):
4796class Latex(Image):
4797    """
4798    Render Latex text and formulas.
4799    """
4800
4801    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4802        """
4803        Render Latex text and formulas.
4804
4805        Arguments:
4806            formula : (str)
4807                latex text string
4808            pos : (list)
4809                position coordinates in space
4810            bg : (color)
4811                background color box
4812            res : (int)
4813                dpi resolution
4814            usetex : (bool)
4815                use latex compiler of matplotlib if available
4816
4817        You can access the latex formula in `Latex.formula`.
4818
4819        Examples:
4820            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4821
4822            ![](https://vedo.embl.es/images/pyplot/latex.png)
4823        """
4824        from tempfile import NamedTemporaryFile
4825        import matplotlib.pyplot as mpltib
4826
4827        def build_img_plt(formula, tfile):
4828
4829            mpltib.rc("text", usetex=usetex)
4830
4831            formula1 = "$" + formula + "$"
4832            mpltib.axis("off")
4833            col = get_color(c)
4834            if bg:
4835                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4836            else:
4837                bx = None
4838            mpltib.text(
4839                0.5,
4840                0.5,
4841                formula1,
4842                size=res,
4843                color=col,
4844                alpha=alpha,
4845                ha="center",
4846                va="center",
4847                bbox=bx,
4848            )
4849            mpltib.savefig(
4850                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4851            )
4852            mpltib.close()
4853
4854        if len(pos) == 2:
4855            pos = (pos[0], pos[1], 0)
4856
4857        tmp_file = NamedTemporaryFile(delete=True)
4858        tmp_file.name = tmp_file.name + ".png"
4859
4860        build_img_plt(formula, tmp_file.name)
4861
4862        super().__init__(tmp_file.name, channels=4)
4863        self.alpha(alpha)
4864        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4865        self.pos(pos)
4866        self.name = "Latex"
4867        self.formula = formula
4868
4869        # except:
4870        #     printc("Error in Latex()\n", formula, c="r")
4871        #     printc(" latex or dvipng not installed?", c="r")
4872        #     printc(" Try: usetex=False", c="r")
4873        #     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)
4801    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4802        """
4803        Render Latex text and formulas.
4804
4805        Arguments:
4806            formula : (str)
4807                latex text string
4808            pos : (list)
4809                position coordinates in space
4810            bg : (color)
4811                background color box
4812            res : (int)
4813                dpi resolution
4814            usetex : (bool)
4815                use latex compiler of matplotlib if available
4816
4817        You can access the latex formula in `Latex.formula`.
4818
4819        Examples:
4820            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4821
4822            ![](https://vedo.embl.es/images/pyplot/latex.png)
4823        """
4824        from tempfile import NamedTemporaryFile
4825        import matplotlib.pyplot as mpltib
4826
4827        def build_img_plt(formula, tfile):
4828
4829            mpltib.rc("text", usetex=usetex)
4830
4831            formula1 = "$" + formula + "$"
4832            mpltib.axis("off")
4833            col = get_color(c)
4834            if bg:
4835                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4836            else:
4837                bx = None
4838            mpltib.text(
4839                0.5,
4840                0.5,
4841                formula1,
4842                size=res,
4843                color=col,
4844                alpha=alpha,
4845                ha="center",
4846                va="center",
4847                bbox=bx,
4848            )
4849            mpltib.savefig(
4850                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4851            )
4852            mpltib.close()
4853
4854        if len(pos) == 2:
4855            pos = (pos[0], pos[1], 0)
4856
4857        tmp_file = NamedTemporaryFile(delete=True)
4858        tmp_file.name = tmp_file.name + ".png"
4859
4860        build_img_plt(formula, tmp_file.name)
4861
4862        super().__init__(tmp_file.name, channels=4)
4863        self.alpha(alpha)
4864        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4865        self.pos(pos)
4866        self.name = "Latex"
4867        self.formula = formula
4868
4869        # except:
4870        #     printc("Error in Latex()\n", formula, c="r")
4871        #     printc(" latex or dvipng not installed?", c="r")
4872        #     printc(" Try: usetex=False", c="r")
4873        #     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):
3867class ParametricShape(Mesh):
3868    """
3869    A set of built-in shapes mainly for illustration purposes.
3870    """
3871
3872    def __init__(self, name, res=51, n=25, seed=1):
3873        """
3874        A set of built-in shapes mainly for illustration purposes.
3875
3876        Name can be an integer or a string in this list:
3877            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3878            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3879            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3880            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3881
3882        Example:
3883            ```python
3884            from vedo import *
3885            settings.immediate_rendering = False
3886            plt = Plotter(N=18)
3887            for i in range(18):
3888                ps = ParametricShape(i).color(i)
3889                plt.at(i).show(ps, ps.name)
3890            plt.interactive().close()
3891            ```
3892            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3893        """
3894
3895        shapes = [
3896            "Boy",
3897            "ConicSpiral",
3898            "CrossCap",
3899            "Enneper",
3900            "Figure8Klein",
3901            "Klein",
3902            "Dini",
3903            "Mobius",
3904            "RandomHills",
3905            "Roman",
3906            "SuperEllipsoid",
3907            "BohemianDome",
3908            "Bour",
3909            "CatalanMinimal",
3910            "Henneberg",
3911            "Kuen",
3912            "PluckerConoid",
3913            "Pseudosphere",
3914        ]
3915
3916        if isinstance(name, int):
3917            name = name % len(shapes)
3918            name = shapes[name]
3919
3920        if name == "Boy":
3921            ps = vtki.new("ParametricBoy")
3922        elif name == "ConicSpiral":
3923            ps = vtki.new("ParametricConicSpiral")
3924        elif name == "CrossCap":
3925            ps = vtki.new("ParametricCrossCap")
3926        elif name == "Dini":
3927            ps = vtki.new("ParametricDini")
3928        elif name == "Enneper":
3929            ps = vtki.new("ParametricEnneper")
3930        elif name == "Figure8Klein":
3931            ps = vtki.new("ParametricFigure8Klein")
3932        elif name == "Klein":
3933            ps = vtki.new("ParametricKlein")
3934        elif name == "Mobius":
3935            ps = vtki.new("ParametricMobius")
3936            ps.SetRadius(2.0)
3937            ps.SetMinimumV(-0.5)
3938            ps.SetMaximumV(0.5)
3939        elif name == "RandomHills":
3940            ps = vtki.new("ParametricRandomHills")
3941            ps.AllowRandomGenerationOn()
3942            ps.SetRandomSeed(seed)
3943            ps.SetNumberOfHills(n)
3944        elif name == "Roman":
3945            ps = vtki.new("ParametricRoman")
3946        elif name == "SuperEllipsoid":
3947            ps = vtki.new("ParametricSuperEllipsoid")
3948            ps.SetN1(0.5)
3949            ps.SetN2(0.4)
3950        elif name == "BohemianDome":
3951            ps = vtki.new("ParametricBohemianDome")
3952            ps.SetA(5.0)
3953            ps.SetB(1.0)
3954            ps.SetC(2.0)
3955        elif name == "Bour":
3956            ps = vtki.new("ParametricBour")
3957        elif name == "CatalanMinimal":
3958            ps = vtki.new("ParametricCatalanMinimal")
3959        elif name == "Henneberg":
3960            ps = vtki.new("ParametricHenneberg")
3961        elif name == "Kuen":
3962            ps = vtki.new("ParametricKuen")
3963            ps.SetDeltaV0(0.001)
3964        elif name == "PluckerConoid":
3965            ps = vtki.new("ParametricPluckerConoid")
3966        elif name == "Pseudosphere":
3967            ps = vtki.new("ParametricPseudosphere")
3968        else:
3969            vedo.logger.error(f"unknown ParametricShape {name}")
3970            return
3971
3972        pfs = vtki.new("ParametricFunctionSource")
3973        pfs.SetParametricFunction(ps)
3974        pfs.SetUResolution(res)
3975        pfs.SetVResolution(res)
3976        pfs.SetWResolution(res)
3977        pfs.SetScalarModeToZ()
3978        pfs.Update()
3979
3980        super().__init__(pfs.GetOutput())
3981
3982        if name == "RandomHills": self.shift([0,-10,-2.25])
3983        if name != 'Kuen': self.normalize()
3984        if name == 'Dini': self.scale(0.4)
3985        if name == 'Enneper': self.scale(0.4)
3986        if name == 'ConicSpiral': self.bc('tomato')
3987        self.name = name

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

ParametricShape(name, res=51, n=25, seed=1)
3872    def __init__(self, name, res=51, n=25, seed=1):
3873        """
3874        A set of built-in shapes mainly for illustration purposes.
3875
3876        Name can be an integer or a string in this list:
3877            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3878            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3879            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3880            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3881
3882        Example:
3883            ```python
3884            from vedo import *
3885            settings.immediate_rendering = False
3886            plt = Plotter(N=18)
3887            for i in range(18):
3888                ps = ParametricShape(i).color(i)
3889                plt.at(i).show(ps, ps.name)
3890            plt.interactive().close()
3891            ```
3892            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3893        """
3894
3895        shapes = [
3896            "Boy",
3897            "ConicSpiral",
3898            "CrossCap",
3899            "Enneper",
3900            "Figure8Klein",
3901            "Klein",
3902            "Dini",
3903            "Mobius",
3904            "RandomHills",
3905            "Roman",
3906            "SuperEllipsoid",
3907            "BohemianDome",
3908            "Bour",
3909            "CatalanMinimal",
3910            "Henneberg",
3911            "Kuen",
3912            "PluckerConoid",
3913            "Pseudosphere",
3914        ]
3915
3916        if isinstance(name, int):
3917            name = name % len(shapes)
3918            name = shapes[name]
3919
3920        if name == "Boy":
3921            ps = vtki.new("ParametricBoy")
3922        elif name == "ConicSpiral":
3923            ps = vtki.new("ParametricConicSpiral")
3924        elif name == "CrossCap":
3925            ps = vtki.new("ParametricCrossCap")
3926        elif name == "Dini":
3927            ps = vtki.new("ParametricDini")
3928        elif name == "Enneper":
3929            ps = vtki.new("ParametricEnneper")
3930        elif name == "Figure8Klein":
3931            ps = vtki.new("ParametricFigure8Klein")
3932        elif name == "Klein":
3933            ps = vtki.new("ParametricKlein")
3934        elif name == "Mobius":
3935            ps = vtki.new("ParametricMobius")
3936            ps.SetRadius(2.0)
3937            ps.SetMinimumV(-0.5)
3938            ps.SetMaximumV(0.5)
3939        elif name == "RandomHills":
3940            ps = vtki.new("ParametricRandomHills")
3941            ps.AllowRandomGenerationOn()
3942            ps.SetRandomSeed(seed)
3943            ps.SetNumberOfHills(n)
3944        elif name == "Roman":
3945            ps = vtki.new("ParametricRoman")
3946        elif name == "SuperEllipsoid":
3947            ps = vtki.new("ParametricSuperEllipsoid")
3948            ps.SetN1(0.5)
3949            ps.SetN2(0.4)
3950        elif name == "BohemianDome":
3951            ps = vtki.new("ParametricBohemianDome")
3952            ps.SetA(5.0)
3953            ps.SetB(1.0)
3954            ps.SetC(2.0)
3955        elif name == "Bour":
3956            ps = vtki.new("ParametricBour")
3957        elif name == "CatalanMinimal":
3958            ps = vtki.new("ParametricCatalanMinimal")
3959        elif name == "Henneberg":
3960            ps = vtki.new("ParametricHenneberg")
3961        elif name == "Kuen":
3962            ps = vtki.new("ParametricKuen")
3963            ps.SetDeltaV0(0.001)
3964        elif name == "PluckerConoid":
3965            ps = vtki.new("ParametricPluckerConoid")
3966        elif name == "Pseudosphere":
3967            ps = vtki.new("ParametricPseudosphere")
3968        else:
3969            vedo.logger.error(f"unknown ParametricShape {name}")
3970            return
3971
3972        pfs = vtki.new("ParametricFunctionSource")
3973        pfs.SetParametricFunction(ps)
3974        pfs.SetUResolution(res)
3975        pfs.SetVResolution(res)
3976        pfs.SetWResolution(res)
3977        pfs.SetScalarModeToZ()
3978        pfs.Update()
3979
3980        super().__init__(pfs.GetOutput())
3981
3982        if name == "RandomHills": self.shift([0,-10,-2.25])
3983        if name != 'Kuen': self.normalize()
3984        if name == 'Dini': self.scale(0.4)
3985        if name == 'Enneper': self.scale(0.4)
3986        if name == 'ConicSpiral': self.bc('tomato')
3987        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):
4876class ConvexHull(Mesh):
4877    """
4878    Create the 2D/3D convex hull from a set of points.
4879    """
4880
4881    def __init__(self, pts) -> None:
4882        """
4883        Create the 2D/3D convex hull from a set of input points or input Mesh.
4884
4885        Examples:
4886            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4887
4888                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4889        """
4890        if utils.is_sequence(pts):
4891            pts = utils.make3d(pts).astype(float)
4892            mesh = Points(pts)
4893        else:
4894            mesh = pts
4895        apoly = mesh.clean().dataset
4896
4897        # Create the convex hull of the pointcloud
4898        z0, z1 = mesh.zbounds()
4899        d = mesh.diagonal_size()
4900        if (z1 - z0) / d > 0.0001:
4901            delaunay = vtki.new("Delaunay3D")
4902            delaunay.SetInputData(apoly)
4903            delaunay.Update()
4904            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4905            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4906            surfaceFilter.Update()
4907            out = surfaceFilter.GetOutput()
4908        else:
4909            delaunay = vtki.new("Delaunay2D")
4910            delaunay.SetInputData(apoly)
4911            delaunay.Update()
4912            fe = vtki.new("FeatureEdges")
4913            fe.SetInputConnection(delaunay.GetOutputPort())
4914            fe.BoundaryEdgesOn()
4915            fe.Update()
4916            out = fe.GetOutput()
4917
4918        super().__init__(out, c=mesh.color(), alpha=0.75)
4919        self.flat()
4920        self.name = "ConvexHull"

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

ConvexHull(pts)
4881    def __init__(self, pts) -> None:
4882        """
4883        Create the 2D/3D convex hull from a set of input points or input Mesh.
4884
4885        Examples:
4886            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4887
4888                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4889        """
4890        if utils.is_sequence(pts):
4891            pts = utils.make3d(pts).astype(float)
4892            mesh = Points(pts)
4893        else:
4894            mesh = pts
4895        apoly = mesh.clean().dataset
4896
4897        # Create the convex hull of the pointcloud
4898        z0, z1 = mesh.zbounds()
4899        d = mesh.diagonal_size()
4900        if (z1 - z0) / d > 0.0001:
4901            delaunay = vtki.new("Delaunay3D")
4902            delaunay.SetInputData(apoly)
4903            delaunay.Update()
4904            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4905            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4906            surfaceFilter.Update()
4907            out = surfaceFilter.GetOutput()
4908        else:
4909            delaunay = vtki.new("Delaunay2D")
4910            delaunay.SetInputData(apoly)
4911            delaunay.Update()
4912            fe = vtki.new("FeatureEdges")
4913            fe.SetInputConnection(delaunay.GetOutputPort())
4914            fe.BoundaryEdgesOn()
4915            fe.Update()
4916            out = fe.GetOutput()
4917
4918        super().__init__(out, c=mesh.color(), alpha=0.75)
4919        self.flat()
4920        self.name = "ConvexHull"

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

Examples: