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

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

CSpline(points, closed=False, res=None)
1385    def __init__(self, points, closed=False, res=None) -> None:
1386        """
1387        Arguments:
1388            closed : (bool)
1389                join last to first point to produce a closed curve
1390            res : (int)
1391                approximate resolution of the output line.
1392                Default is 20 times the number of input points.
1393
1394        Warning:
1395            This class is not necessarily generating the exact number of points
1396            as requested by `res`. Some points may be concident and removed.
1397
1398        See also: `Spline` and `KSpline`.
1399        """
1400
1401        if isinstance(points, Points):
1402            points = points.coordinates
1403
1404        if not res:
1405            res = len(points) * 20
1406
1407        points = utils.make3d(points).astype(float)
1408
1409        vtkCardinalSpline = vtki.get_class("CardinalSpline")
1410        xspline = vtkCardinalSpline()
1411        yspline = vtkCardinalSpline()
1412        zspline = vtkCardinalSpline()
1413        for s in [xspline, yspline, zspline]:
1414            s.SetClosed(closed)
1415
1416        lenp = len(points[0]) > 2
1417
1418        for i, p in enumerate(points):
1419            xspline.AddPoint(i, p[0])
1420            yspline.AddPoint(i, p[1])
1421            if lenp:
1422                zspline.AddPoint(i, p[2])
1423
1424        ln = []
1425        for pos in np.linspace(0, len(points), res):
1426            x = xspline.Evaluate(pos)
1427            y = yspline.Evaluate(pos)
1428            z = 0
1429            if lenp:
1430                z = zspline.Evaluate(pos)
1431            ln.append((x, y, z))
1432
1433        super().__init__(ln, lw=2)
1434        self.clean()
1435        self.lighting("off")
1436        self.name = "CSpline"
1437        self.base = points[0]
1438        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):
1441class Bezier(Line):
1442    """
1443    Generate the Bezier line that links the first to the last point.
1444    """
1445
1446    def __init__(self, points, res=None) -> None:
1447        """
1448        Example:
1449            ```python
1450            from vedo import *
1451            import numpy as np
1452            pts = np.random.randn(25,3)
1453            for i,p in enumerate(pts):
1454                p += [5*i, 15*sin(i/2), i*i*i/200]
1455            show(Points(pts), Bezier(pts), axes=1).close()
1456            ```
1457            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1458        """
1459        N = len(points)
1460        if res is None:
1461            res = 10 * N
1462        t = np.linspace(0, 1, num=res)
1463        bcurve = np.zeros((res, len(points[0])))
1464
1465        def binom(n, k):
1466            b = 1
1467            for t in range(1, min(k, n - k) + 1):
1468                b *= n / t
1469                n -= 1
1470            return b
1471
1472        def bernstein(n, k):
1473            coeff = binom(n, k)
1474
1475            def _bpoly(x):
1476                return coeff * x ** k * (1 - x) ** (n - k)
1477
1478            return _bpoly
1479
1480        for ii in range(N):
1481            b = bernstein(N - 1, ii)(t)
1482            bcurve += np.outer(b, points[ii])
1483        super().__init__(bcurve, lw=2)
1484        self.name = "BezierLine"

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

Bezier(points, res=None)
1446    def __init__(self, points, res=None) -> None:
1447        """
1448        Example:
1449            ```python
1450            from vedo import *
1451            import numpy as np
1452            pts = np.random.randn(25,3)
1453            for i,p in enumerate(pts):
1454                p += [5*i, 15*sin(i/2), i*i*i/200]
1455            show(Points(pts), Bezier(pts), axes=1).close()
1456            ```
1457            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1458        """
1459        N = len(points)
1460        if res is None:
1461            res = 10 * N
1462        t = np.linspace(0, 1, num=res)
1463        bcurve = np.zeros((res, len(points[0])))
1464
1465        def binom(n, k):
1466            b = 1
1467            for t in range(1, min(k, n - k) + 1):
1468                b *= n / t
1469                n -= 1
1470            return b
1471
1472        def bernstein(n, k):
1473            coeff = binom(n, k)
1474
1475            def _bpoly(x):
1476                return coeff * x ** k * (1 - x) ** (n - k)
1477
1478            return _bpoly
1479
1480        for ii in range(N):
1481            b = bernstein(N - 1, ii)(t)
1482            bcurve += np.outer(b, points[ii])
1483        super().__init__(bcurve, lw=2)
1484        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):
3735class Brace(Mesh):
3736    """
3737    Create a brace (bracket) shape.
3738    """
3739
3740    def __init__(
3741        self,
3742        q1,
3743        q2,
3744        style="}",
3745        padding1=0.0,
3746        font="Theemim",
3747        comment="",
3748        justify=None,
3749        angle=0.0,
3750        padding2=0.2,
3751        s=1.0,
3752        italic=0,
3753        c="k1",
3754        alpha=1.0,
3755    ) -> None:
3756        """
3757        Create a brace (bracket) shape which spans from point q1 to point q2.
3758
3759        Arguments:
3760            q1 : (list)
3761                point 1.
3762            q2 : (list)
3763                point 2.
3764            style : (str)
3765                style of the bracket, eg. `{}, [], (), <>`.
3766            padding1 : (float)
3767                padding space in percent form the input points.
3768            font : (str)
3769                font type
3770            comment : (str)
3771                additional text to appear next to the brace symbol.
3772            justify : (str)
3773                specify the anchor point to justify text comment, e.g. "top-left".
3774            italic : float
3775                italicness of the text comment (can be a positive or negative number)
3776            angle : (float)
3777                rotation angle of text. Use `None` to keep it horizontal.
3778            padding2 : (float)
3779                padding space in percent form brace to text comment.
3780            s : (float)
3781                scale factor for the comment
3782
3783        Examples:
3784            - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py)
3785
3786                ![](https://vedo.embl.es/images/pyplot/scatter3.png)
3787        """
3788        if isinstance(q1, vtki.vtkActor):
3789            q1 = q1.GetPosition()
3790        if isinstance(q2, vtki.vtkActor):
3791            q2 = q2.GetPosition()
3792        if len(q1) == 2:
3793            q1 = [q1[0], q1[1], 0.0]
3794        if len(q2) == 2:
3795            q2 = [q2[0], q2[1], 0.0]
3796        q1 = np.array(q1, dtype=float)
3797        q2 = np.array(q2, dtype=float)
3798        mq = (q1 + q2) / 2
3799        q1 = q1 - mq
3800        q2 = q2 - mq
3801        d = np.linalg.norm(q2 - q1)
3802        q2[2] = q1[2]
3803
3804        if style not in "{}[]()<>|I":
3805            vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I")
3806            style = "}"
3807
3808        flip = False
3809        if style in ["{", "[", "(", "<"]:
3810            flip = True
3811            i = ["{", "[", "(", "<"].index(style)
3812            style = ["}", "]", ")", ">"][i]
3813
3814        br = Text3D(style, font="Theemim", justify="center-left")
3815        br.scale([0.4, 1, 1])
3816
3817        angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90
3818        if flip:
3819            angler += 180
3820
3821        _, x1, y0, y1, _, _ = br.bounds()
3822        if comment:
3823            just = "center-top"
3824            if angle is None:
3825                angle = -angler + 90
3826                if not flip:
3827                    angle += 180
3828
3829            if flip:
3830                angle += 180
3831                just = "center-bottom"
3832            if justify is not None:
3833                just = justify
3834            cmt = Text3D(comment, font=font, justify=just, italic=italic)
3835            cx0, cx1 = cmt.xbounds()
3836            cmt.rotate_z(90 + angle)
3837            cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5)
3838            cmt.shift(x1 * (1 + padding2), 0, 0)
3839            poly = merge(br, cmt).dataset
3840
3841        else:
3842            poly = br.dataset
3843
3844        tr = vtki.vtkTransform()
3845        tr.Translate(mq)
3846        tr.RotateZ(angler)
3847        tr.Translate(padding1 * d, 0, 0)
3848        pscale = 1
3849        tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1)
3850
3851        tf = vtki.new("TransformPolyDataFilter")
3852        tf.SetInputData(poly)
3853        tf.SetTransform(tr)
3854        tf.Update()
3855        poly = tf.GetOutput()
3856
3857        super().__init__(poly, c, alpha)
3858
3859        self.base = q1
3860        self.top  = q2
3861        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)
3740    def __init__(
3741        self,
3742        q1,
3743        q2,
3744        style="}",
3745        padding1=0.0,
3746        font="Theemim",
3747        comment="",
3748        justify=None,
3749        angle=0.0,
3750        padding2=0.2,
3751        s=1.0,
3752        italic=0,
3753        c="k1",
3754        alpha=1.0,
3755    ) -> None:
3756        """
3757        Create a brace (bracket) shape which spans from point q1 to point q2.
3758
3759        Arguments:
3760            q1 : (list)
3761                point 1.
3762            q2 : (list)
3763                point 2.
3764            style : (str)
3765                style of the bracket, eg. `{}, [], (), <>`.
3766            padding1 : (float)
3767                padding space in percent form the input points.
3768            font : (str)
3769                font type
3770            comment : (str)
3771                additional text to appear next to the brace symbol.
3772            justify : (str)
3773                specify the anchor point to justify text comment, e.g. "top-left".
3774            italic : float
3775                italicness of the text comment (can be a positive or negative number)
3776            angle : (float)
3777                rotation angle of text. Use `None` to keep it horizontal.
3778            padding2 : (float)
3779                padding space in percent form brace to text comment.
3780            s : (float)
3781                scale factor for the comment
3782
3783        Examples:
3784            - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py)
3785
3786                ![](https://vedo.embl.es/images/pyplot/scatter3.png)
3787        """
3788        if isinstance(q1, vtki.vtkActor):
3789            q1 = q1.GetPosition()
3790        if isinstance(q2, vtki.vtkActor):
3791            q2 = q2.GetPosition()
3792        if len(q1) == 2:
3793            q1 = [q1[0], q1[1], 0.0]
3794        if len(q2) == 2:
3795            q2 = [q2[0], q2[1], 0.0]
3796        q1 = np.array(q1, dtype=float)
3797        q2 = np.array(q2, dtype=float)
3798        mq = (q1 + q2) / 2
3799        q1 = q1 - mq
3800        q2 = q2 - mq
3801        d = np.linalg.norm(q2 - q1)
3802        q2[2] = q1[2]
3803
3804        if style not in "{}[]()<>|I":
3805            vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I")
3806            style = "}"
3807
3808        flip = False
3809        if style in ["{", "[", "(", "<"]:
3810            flip = True
3811            i = ["{", "[", "(", "<"].index(style)
3812            style = ["}", "]", ")", ">"][i]
3813
3814        br = Text3D(style, font="Theemim", justify="center-left")
3815        br.scale([0.4, 1, 1])
3816
3817        angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90
3818        if flip:
3819            angler += 180
3820
3821        _, x1, y0, y1, _, _ = br.bounds()
3822        if comment:
3823            just = "center-top"
3824            if angle is None:
3825                angle = -angler + 90
3826                if not flip:
3827                    angle += 180
3828
3829            if flip:
3830                angle += 180
3831                just = "center-bottom"
3832            if justify is not None:
3833                just = justify
3834            cmt = Text3D(comment, font=font, justify=just, italic=italic)
3835            cx0, cx1 = cmt.xbounds()
3836            cmt.rotate_z(90 + angle)
3837            cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5)
3838            cmt.shift(x1 * (1 + padding2), 0, 0)
3839            poly = merge(br, cmt).dataset
3840
3841        else:
3842            poly = br.dataset
3843
3844        tr = vtki.vtkTransform()
3845        tr.Translate(mq)
3846        tr.RotateZ(angler)
3847        tr.Translate(padding1 * d, 0, 0)
3848        pscale = 1
3849        tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1)
3850
3851        tf = vtki.new("TransformPolyDataFilter")
3852        tf.SetInputData(poly)
3853        tf.SetTransform(tr)
3854        tf.Update()
3855        poly = tf.GetOutput()
3856
3857        super().__init__(poly, c, alpha)
3858
3859        self.base = q1
3860        self.top  = q2
3861        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):
1487class NormalLines(Mesh):
1488    """
1489    Build an `Glyph` to show the normals at cell centers or at mesh vertices.
1490
1491    Arguments:
1492        ratio : (int)
1493            show 1 normal every `ratio` cells.
1494        on : (str)
1495            either "cells" or "points".
1496        scale : (float)
1497            scale factor to control size.
1498    """
1499
1500    def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None:
1501
1502        poly = msh.clone().dataset
1503
1504        if "cell" in on:
1505            centers = vtki.new("CellCenters")
1506            centers.SetInputData(poly)
1507            centers.Update()
1508            poly = centers.GetOutput()
1509
1510        mask_pts = vtki.new("MaskPoints")
1511        mask_pts.SetInputData(poly)
1512        mask_pts.SetOnRatio(ratio)
1513        mask_pts.RandomModeOff()
1514        mask_pts.Update()
1515
1516        ln = vtki.new("LineSource")
1517        ln.SetPoint1(0, 0, 0)
1518        ln.SetPoint2(1, 0, 0)
1519        ln.Update()
1520        glyph = vtki.vtkGlyph3D()
1521        glyph.SetSourceData(ln.GetOutput())
1522        glyph.SetInputData(mask_pts.GetOutput())
1523        glyph.SetVectorModeToUseNormal()
1524
1525        b = poly.GetBounds()
1526        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1527        glyph.SetScaleFactor(f)
1528        glyph.OrientOn()
1529        glyph.Update()
1530
1531        super().__init__(glyph.GetOutput())
1532
1533        self.actor.PickableOff()
1534        prop = vtki.vtkProperty()
1535        prop.DeepCopy(msh.properties)
1536        self.actor.SetProperty(prop)
1537        self.properties = prop
1538        self.properties.LightingOff()
1539        self.mapper.ScalarVisibilityOff()
1540        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)
1500    def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None:
1501
1502        poly = msh.clone().dataset
1503
1504        if "cell" in on:
1505            centers = vtki.new("CellCenters")
1506            centers.SetInputData(poly)
1507            centers.Update()
1508            poly = centers.GetOutput()
1509
1510        mask_pts = vtki.new("MaskPoints")
1511        mask_pts.SetInputData(poly)
1512        mask_pts.SetOnRatio(ratio)
1513        mask_pts.RandomModeOff()
1514        mask_pts.Update()
1515
1516        ln = vtki.new("LineSource")
1517        ln.SetPoint1(0, 0, 0)
1518        ln.SetPoint2(1, 0, 0)
1519        ln.Update()
1520        glyph = vtki.vtkGlyph3D()
1521        glyph.SetSourceData(ln.GetOutput())
1522        glyph.SetInputData(mask_pts.GetOutput())
1523        glyph.SetVectorModeToUseNormal()
1524
1525        b = poly.GetBounds()
1526        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1527        glyph.SetScaleFactor(f)
1528        glyph.OrientOn()
1529        glyph.Update()
1530
1531        super().__init__(glyph.GetOutput())
1532
1533        self.actor.PickableOff()
1534        prop = vtki.vtkProperty()
1535        prop.DeepCopy(msh.properties)
1536        self.actor.SetProperty(prop)
1537        self.properties = prop
1538        self.properties.LightingOff()
1539        self.mapper.ScalarVisibilityOff()
1540        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):
1765class Ribbon(Mesh):
1766    """
1767    Connect two lines to generate the surface inbetween.
1768    Set the mode by which to create the ruled surface.
1769
1770    It also works with a single line in input. In this case the ribbon
1771    is formed by following the local plane of the line in space.
1772    """
1773
1774    def __init__(
1775        self,
1776        line1,
1777        line2=None,
1778        mode=0,
1779        closed=False,
1780        width=None,
1781        res=(200, 5),
1782        c="indigo3",
1783        alpha=1.0,
1784    ) -> None:
1785        """
1786        Arguments:
1787            mode : (int)
1788                If mode=0, resample evenly the input lines (based on length)
1789                and generates triangle strips.
1790
1791                If mode=1, use the existing points and walks around the
1792                polyline using existing points.
1793
1794            closed : (bool)
1795                if True, join the last point with the first to form a closed surface
1796
1797            res : (list)
1798                ribbon resolutions along the line and perpendicularly to it.
1799
1800        Examples:
1801            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1802
1803                ![](https://vedo.embl.es/images/basic/ribbon.png)
1804        """
1805
1806        if isinstance(line1, Points):
1807            line1 = line1.coordinates
1808
1809        if isinstance(line2, Points):
1810            line2 = line2.coordinates
1811
1812        elif line2 is None:
1813            #############################################
1814            ribbon_filter = vtki.new("RibbonFilter")
1815            aline = Line(line1)
1816            ribbon_filter.SetInputData(aline.dataset)
1817            if width is None:
1818                width = aline.diagonal_size() / 20.0
1819            ribbon_filter.SetWidth(width)
1820            ribbon_filter.Update()
1821            # convert triangle strips to polygons
1822            tris = vtki.new("TriangleFilter")
1823            tris.SetInputData(ribbon_filter.GetOutput())
1824            tris.Update()
1825
1826            super().__init__(tris.GetOutput(), c, alpha)
1827            self.name = "Ribbon"
1828            ##############################################
1829            return  ######################################
1830            ##############################################
1831
1832        line1 = np.asarray(line1)
1833        line2 = np.asarray(line2)
1834
1835        if closed:
1836            line1 = line1.tolist()
1837            line1 += [line1[0]]
1838            line2 = line2.tolist()
1839            line2 += [line2[0]]
1840            line1 = np.array(line1)
1841            line2 = np.array(line2)
1842
1843        if len(line1[0]) == 2:
1844            line1 = np.c_[line1, np.zeros(len(line1))]
1845        if len(line2[0]) == 2:
1846            line2 = np.c_[line2, np.zeros(len(line2))]
1847
1848        ppoints1 = vtki.vtkPoints()  # Generate the polyline1
1849        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1850        lines1 = vtki.vtkCellArray()
1851        lines1.InsertNextCell(len(line1))
1852        for i in range(len(line1)):
1853            lines1.InsertCellPoint(i)
1854        poly1 = vtki.vtkPolyData()
1855        poly1.SetPoints(ppoints1)
1856        poly1.SetLines(lines1)
1857
1858        ppoints2 = vtki.vtkPoints()  # Generate the polyline2
1859        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1860        lines2 = vtki.vtkCellArray()
1861        lines2.InsertNextCell(len(line2))
1862        for i in range(len(line2)):
1863            lines2.InsertCellPoint(i)
1864        poly2 = vtki.vtkPolyData()
1865        poly2.SetPoints(ppoints2)
1866        poly2.SetLines(lines2)
1867
1868        # build the lines
1869        lines1 = vtki.vtkCellArray()
1870        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1871        for i in range(poly1.GetNumberOfPoints()):
1872            lines1.InsertCellPoint(i)
1873
1874        polygon1 = vtki.vtkPolyData()
1875        polygon1.SetPoints(ppoints1)
1876        polygon1.SetLines(lines1)
1877
1878        lines2 = vtki.vtkCellArray()
1879        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1880        for i in range(poly2.GetNumberOfPoints()):
1881            lines2.InsertCellPoint(i)
1882
1883        polygon2 = vtki.vtkPolyData()
1884        polygon2.SetPoints(ppoints2)
1885        polygon2.SetLines(lines2)
1886
1887        merged_pd = vtki.new("AppendPolyData")
1888        merged_pd.AddInputData(polygon1)
1889        merged_pd.AddInputData(polygon2)
1890        merged_pd.Update()
1891
1892        rsf = vtki.new("RuledSurfaceFilter")
1893        rsf.CloseSurfaceOff()
1894        rsf.SetRuledMode(mode)
1895        rsf.SetResolution(res[0], res[1])
1896        rsf.SetInputData(merged_pd.GetOutput())
1897        rsf.Update()
1898        # convert triangle strips to polygons
1899        tris = vtki.new("TriangleFilter")
1900        tris.SetInputData(rsf.GetOutput())
1901        tris.Update()
1902        out = tris.GetOutput()
1903
1904        super().__init__(out, c, alpha)
1905
1906        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)
1774    def __init__(
1775        self,
1776        line1,
1777        line2=None,
1778        mode=0,
1779        closed=False,
1780        width=None,
1781        res=(200, 5),
1782        c="indigo3",
1783        alpha=1.0,
1784    ) -> None:
1785        """
1786        Arguments:
1787            mode : (int)
1788                If mode=0, resample evenly the input lines (based on length)
1789                and generates triangle strips.
1790
1791                If mode=1, use the existing points and walks around the
1792                polyline using existing points.
1793
1794            closed : (bool)
1795                if True, join the last point with the first to form a closed surface
1796
1797            res : (list)
1798                ribbon resolutions along the line and perpendicularly to it.
1799
1800        Examples:
1801            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1802
1803                ![](https://vedo.embl.es/images/basic/ribbon.png)
1804        """
1805
1806        if isinstance(line1, Points):
1807            line1 = line1.coordinates
1808
1809        if isinstance(line2, Points):
1810            line2 = line2.coordinates
1811
1812        elif line2 is None:
1813            #############################################
1814            ribbon_filter = vtki.new("RibbonFilter")
1815            aline = Line(line1)
1816            ribbon_filter.SetInputData(aline.dataset)
1817            if width is None:
1818                width = aline.diagonal_size() / 20.0
1819            ribbon_filter.SetWidth(width)
1820            ribbon_filter.Update()
1821            # convert triangle strips to polygons
1822            tris = vtki.new("TriangleFilter")
1823            tris.SetInputData(ribbon_filter.GetOutput())
1824            tris.Update()
1825
1826            super().__init__(tris.GetOutput(), c, alpha)
1827            self.name = "Ribbon"
1828            ##############################################
1829            return  ######################################
1830            ##############################################
1831
1832        line1 = np.asarray(line1)
1833        line2 = np.asarray(line2)
1834
1835        if closed:
1836            line1 = line1.tolist()
1837            line1 += [line1[0]]
1838            line2 = line2.tolist()
1839            line2 += [line2[0]]
1840            line1 = np.array(line1)
1841            line2 = np.array(line2)
1842
1843        if len(line1[0]) == 2:
1844            line1 = np.c_[line1, np.zeros(len(line1))]
1845        if len(line2[0]) == 2:
1846            line2 = np.c_[line2, np.zeros(len(line2))]
1847
1848        ppoints1 = vtki.vtkPoints()  # Generate the polyline1
1849        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1850        lines1 = vtki.vtkCellArray()
1851        lines1.InsertNextCell(len(line1))
1852        for i in range(len(line1)):
1853            lines1.InsertCellPoint(i)
1854        poly1 = vtki.vtkPolyData()
1855        poly1.SetPoints(ppoints1)
1856        poly1.SetLines(lines1)
1857
1858        ppoints2 = vtki.vtkPoints()  # Generate the polyline2
1859        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1860        lines2 = vtki.vtkCellArray()
1861        lines2.InsertNextCell(len(line2))
1862        for i in range(len(line2)):
1863            lines2.InsertCellPoint(i)
1864        poly2 = vtki.vtkPolyData()
1865        poly2.SetPoints(ppoints2)
1866        poly2.SetLines(lines2)
1867
1868        # build the lines
1869        lines1 = vtki.vtkCellArray()
1870        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1871        for i in range(poly1.GetNumberOfPoints()):
1872            lines1.InsertCellPoint(i)
1873
1874        polygon1 = vtki.vtkPolyData()
1875        polygon1.SetPoints(ppoints1)
1876        polygon1.SetLines(lines1)
1877
1878        lines2 = vtki.vtkCellArray()
1879        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1880        for i in range(poly2.GetNumberOfPoints()):
1881            lines2.InsertCellPoint(i)
1882
1883        polygon2 = vtki.vtkPolyData()
1884        polygon2.SetPoints(ppoints2)
1885        polygon2.SetLines(lines2)
1886
1887        merged_pd = vtki.new("AppendPolyData")
1888        merged_pd.AddInputData(polygon1)
1889        merged_pd.AddInputData(polygon2)
1890        merged_pd.Update()
1891
1892        rsf = vtki.new("RuledSurfaceFilter")
1893        rsf.CloseSurfaceOff()
1894        rsf.SetRuledMode(mode)
1895        rsf.SetResolution(res[0], res[1])
1896        rsf.SetInputData(merged_pd.GetOutput())
1897        rsf.Update()
1898        # convert triangle strips to polygons
1899        tris = vtki.new("TriangleFilter")
1900        tris.SetInputData(rsf.GetOutput())
1901        tris.Update()
1902        out = tris.GetOutput()
1903
1904        super().__init__(out, c, alpha)
1905
1906        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):
1909class Arrow(Mesh):
1910    """
1911    Build a 3D arrow from `start_pt` to `end_pt` of section size `s`,
1912    expressed as the fraction of the window size.
1913    """
1914
1915    def __init__(
1916        self,
1917        start_pt=(0, 0, 0),
1918        end_pt=(1, 0, 0),
1919        s=None,
1920        shaft_radius=None,
1921        head_radius=None,
1922        head_length=None,
1923        res=12,
1924        c="r4",
1925        alpha=1.0,
1926    ) -> None:
1927        """
1928        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1929        from white to red.
1930
1931        .. note:: If `s=None` the arrow is scaled proportionally to its length
1932
1933        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1934        """
1935        # in case user is passing meshs
1936        if isinstance(start_pt, vtki.vtkActor):
1937            start_pt = start_pt.GetPosition()
1938        if isinstance(end_pt, vtki.vtkActor):
1939            end_pt = end_pt.GetPosition()
1940
1941        axis = np.asarray(end_pt) - np.asarray(start_pt)
1942        length = float(np.linalg.norm(axis))
1943        if length:
1944            axis = axis / length
1945        if len(axis) < 3:  # its 2d
1946            theta = np.pi / 2
1947            start_pt = [start_pt[0], start_pt[1], 0.0]
1948            end_pt = [end_pt[0], end_pt[1], 0.0]
1949        else:
1950            theta = np.arccos(axis[2])
1951        phi = np.arctan2(axis[1], axis[0])
1952        self.source = vtki.new("ArrowSource")
1953        self.source.SetShaftResolution(res)
1954        self.source.SetTipResolution(res)
1955
1956        if s:
1957            sz = 0.02
1958            self.source.SetTipRadius(sz)
1959            self.source.SetShaftRadius(sz / 1.75)
1960            self.source.SetTipLength(sz * 15)
1961
1962        if head_length:
1963            self.source.SetTipLength(head_length)
1964        if head_radius:
1965            self.source.SetTipRadius(head_radius)
1966        if shaft_radius:
1967            self.source.SetShaftRadius(shaft_radius)
1968
1969        self.source.Update()
1970
1971        t = vtki.vtkTransform()
1972        t.Translate(start_pt)
1973        t.RotateZ(np.rad2deg(phi))
1974        t.RotateY(np.rad2deg(theta))
1975        t.RotateY(-90)  # put it along Z
1976        if s:
1977            sz = 800 * s
1978            t.Scale(length, sz, sz)
1979        else:
1980            t.Scale(length, length, length)
1981
1982        tf = vtki.new("TransformPolyDataFilter")
1983        tf.SetInputData(self.source.GetOutput())
1984        tf.SetTransform(t)
1985        tf.Update()
1986
1987        super().__init__(tf.GetOutput(), c, alpha)
1988
1989        self.transform = LinearTransform().translate(start_pt)
1990
1991        self.phong().lighting("plastic")
1992        self.actor.PickableOff()
1993        self.actor.DragableOff()
1994        self.base = np.array(start_pt, dtype=float)  # used by pyplot
1995        self.top  = np.array(end_pt,   dtype=float)  # used by pyplot
1996        self.top_index = self.source.GetTipResolution() * 4
1997        self.fill = True                    # used by pyplot.__iadd__()
1998        self.s = s if s is not None else 1  # used by pyplot.__iadd__()
1999        self.name = "Arrow"
2000    
2001    def top_point(self):
2002        """Return the current coordinates of the tip of the Arrow."""
2003        return self.transform.transform_point(self.top)
2004
2005    def base_point(self):
2006        """Return the current coordinates of the base of the Arrow."""
2007        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)
1915    def __init__(
1916        self,
1917        start_pt=(0, 0, 0),
1918        end_pt=(1, 0, 0),
1919        s=None,
1920        shaft_radius=None,
1921        head_radius=None,
1922        head_length=None,
1923        res=12,
1924        c="r4",
1925        alpha=1.0,
1926    ) -> None:
1927        """
1928        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1929        from white to red.
1930
1931        .. note:: If `s=None` the arrow is scaled proportionally to its length
1932
1933        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1934        """
1935        # in case user is passing meshs
1936        if isinstance(start_pt, vtki.vtkActor):
1937            start_pt = start_pt.GetPosition()
1938        if isinstance(end_pt, vtki.vtkActor):
1939            end_pt = end_pt.GetPosition()
1940
1941        axis = np.asarray(end_pt) - np.asarray(start_pt)
1942        length = float(np.linalg.norm(axis))
1943        if length:
1944            axis = axis / length
1945        if len(axis) < 3:  # its 2d
1946            theta = np.pi / 2
1947            start_pt = [start_pt[0], start_pt[1], 0.0]
1948            end_pt = [end_pt[0], end_pt[1], 0.0]
1949        else:
1950            theta = np.arccos(axis[2])
1951        phi = np.arctan2(axis[1], axis[0])
1952        self.source = vtki.new("ArrowSource")
1953        self.source.SetShaftResolution(res)
1954        self.source.SetTipResolution(res)
1955
1956        if s:
1957            sz = 0.02
1958            self.source.SetTipRadius(sz)
1959            self.source.SetShaftRadius(sz / 1.75)
1960            self.source.SetTipLength(sz * 15)
1961
1962        if head_length:
1963            self.source.SetTipLength(head_length)
1964        if head_radius:
1965            self.source.SetTipRadius(head_radius)
1966        if shaft_radius:
1967            self.source.SetShaftRadius(shaft_radius)
1968
1969        self.source.Update()
1970
1971        t = vtki.vtkTransform()
1972        t.Translate(start_pt)
1973        t.RotateZ(np.rad2deg(phi))
1974        t.RotateY(np.rad2deg(theta))
1975        t.RotateY(-90)  # put it along Z
1976        if s:
1977            sz = 800 * s
1978            t.Scale(length, sz, sz)
1979        else:
1980            t.Scale(length, length, length)
1981
1982        tf = vtki.new("TransformPolyDataFilter")
1983        tf.SetInputData(self.source.GetOutput())
1984        tf.SetTransform(t)
1985        tf.Update()
1986
1987        super().__init__(tf.GetOutput(), c, alpha)
1988
1989        self.transform = LinearTransform().translate(start_pt)
1990
1991        self.phong().lighting("plastic")
1992        self.actor.PickableOff()
1993        self.actor.DragableOff()
1994        self.base = np.array(start_pt, dtype=float)  # used by pyplot
1995        self.top  = np.array(end_pt,   dtype=float)  # used by pyplot
1996        self.top_index = self.source.GetTipResolution() * 4
1997        self.fill = True                    # used by pyplot.__iadd__()
1998        self.s = s if s is not None else 1  # used by pyplot.__iadd__()
1999        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):
2001    def top_point(self):
2002        """Return the current coordinates of the tip of the Arrow."""
2003        return self.transform.transform_point(self.top)

Return the current coordinates of the tip of the Arrow.

def base_point(self):
2005    def base_point(self):
2006        """Return the current coordinates of the base of the Arrow."""
2007        return self.transform.transform_point(self.base)

Return the current coordinates of the base of the Arrow.

class Arrows(Glyph):
2009class Arrows(Glyph):
2010    """
2011    Build arrows between two lists of points.
2012    """
2013
2014    def __init__(
2015        self,
2016        start_pts,
2017        end_pts=None,
2018        s=None,
2019        shaft_radius=None,
2020        head_radius=None,
2021        head_length=None,
2022        thickness=1.0,
2023        res=6,
2024        c='k3',
2025        alpha=1.0,
2026    ) -> None:
2027        """
2028        Build arrows between two lists of points `start_pts` and `end_pts`.
2029         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2030
2031        Color can be specified as a colormap which maps the size of the arrows.
2032
2033        Arguments:
2034            s : (float)
2035                fix aspect-ratio of the arrow and scale its cross section
2036            c : (color)
2037                color or color map name
2038            alpha : (float)
2039                set object opacity
2040            res : (int)
2041                set arrow resolution
2042
2043        Examples:
2044            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
2045
2046            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
2047        """
2048        if isinstance(start_pts, Points):
2049            start_pts = start_pts.coordinates
2050        if isinstance(end_pts, Points):
2051            end_pts = end_pts.coordinates
2052
2053        start_pts = np.asarray(start_pts)
2054        if end_pts is None:
2055            strt = start_pts[:, 0]
2056            end_pts = start_pts[:, 1]
2057            start_pts = strt
2058        else:
2059            end_pts = np.asarray(end_pts)
2060
2061        start_pts = utils.make3d(start_pts)
2062        end_pts = utils.make3d(end_pts)
2063
2064        arr = vtki.new("ArrowSource")
2065        arr.SetShaftResolution(res)
2066        arr.SetTipResolution(res)
2067
2068        if s:
2069            sz = 0.02 * s
2070            arr.SetTipRadius(sz * 2)
2071            arr.SetShaftRadius(sz * thickness)
2072            arr.SetTipLength(sz * 10)
2073
2074        if head_radius:
2075            arr.SetTipRadius(head_radius)
2076        if shaft_radius:
2077            arr.SetShaftRadius(shaft_radius)
2078        if head_length:
2079            arr.SetTipLength(head_length)
2080
2081        arr.Update()
2082        out = arr.GetOutput()
2083
2084        orients = end_pts - start_pts
2085
2086        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
2087
2088        super().__init__(
2089            start_pts,
2090            out,
2091            orientation_array=orients,
2092            scale_by_vector_size=True,
2093            color_by_vector_size=color_by_vector_size,
2094            c=c,
2095            alpha=alpha,
2096        )
2097        self.lighting("off")
2098        if color_by_vector_size:
2099            vals = np.linalg.norm(orients, axis=1)
2100            self.mapper.SetScalarRange(vals.min(), vals.max())
2101        else:
2102            self.c(c)
2103        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)
2014    def __init__(
2015        self,
2016        start_pts,
2017        end_pts=None,
2018        s=None,
2019        shaft_radius=None,
2020        head_radius=None,
2021        head_length=None,
2022        thickness=1.0,
2023        res=6,
2024        c='k3',
2025        alpha=1.0,
2026    ) -> None:
2027        """
2028        Build arrows between two lists of points `start_pts` and `end_pts`.
2029         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2030
2031        Color can be specified as a colormap which maps the size of the arrows.
2032
2033        Arguments:
2034            s : (float)
2035                fix aspect-ratio of the arrow and scale its cross section
2036            c : (color)
2037                color or color map name
2038            alpha : (float)
2039                set object opacity
2040            res : (int)
2041                set arrow resolution
2042
2043        Examples:
2044            - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py)
2045
2046            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
2047        """
2048        if isinstance(start_pts, Points):
2049            start_pts = start_pts.coordinates
2050        if isinstance(end_pts, Points):
2051            end_pts = end_pts.coordinates
2052
2053        start_pts = np.asarray(start_pts)
2054        if end_pts is None:
2055            strt = start_pts[:, 0]
2056            end_pts = start_pts[:, 1]
2057            start_pts = strt
2058        else:
2059            end_pts = np.asarray(end_pts)
2060
2061        start_pts = utils.make3d(start_pts)
2062        end_pts = utils.make3d(end_pts)
2063
2064        arr = vtki.new("ArrowSource")
2065        arr.SetShaftResolution(res)
2066        arr.SetTipResolution(res)
2067
2068        if s:
2069            sz = 0.02 * s
2070            arr.SetTipRadius(sz * 2)
2071            arr.SetShaftRadius(sz * thickness)
2072            arr.SetTipLength(sz * 10)
2073
2074        if head_radius:
2075            arr.SetTipRadius(head_radius)
2076        if shaft_radius:
2077            arr.SetShaftRadius(shaft_radius)
2078        if head_length:
2079            arr.SetTipLength(head_length)
2080
2081        arr.Update()
2082        out = arr.GetOutput()
2083
2084        orients = end_pts - start_pts
2085
2086        color_by_vector_size = utils.is_sequence(c) or c in cmaps_names
2087
2088        super().__init__(
2089            start_pts,
2090            out,
2091            orientation_array=orients,
2092            scale_by_vector_size=True,
2093            color_by_vector_size=color_by_vector_size,
2094            c=c,
2095            alpha=alpha,
2096        )
2097        self.lighting("off")
2098        if color_by_vector_size:
2099            vals = np.linalg.norm(orients, axis=1)
2100            self.mapper.SetScalarRange(vals.min(), vals.max())
2101        else:
2102            self.c(c)
2103        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):
2106class Arrow2D(Mesh):
2107    """
2108    Build a 2D arrow.
2109    """
2110
2111    def __init__(
2112        self,
2113        start_pt=(0, 0, 0),
2114        end_pt=(1, 0, 0),
2115        s=1,
2116        rotation=0.0,
2117        shaft_length=0.85,
2118        shaft_width=0.055,
2119        head_length=0.175,
2120        head_width=0.175,
2121        fill=True,
2122        c="red4",
2123        alpha=1.0,
2124   ) -> None:
2125        """
2126        Build a 2D arrow from `start_pt` to `end_pt`.
2127
2128        Arguments:
2129            s : (float)
2130                a global multiplicative convenience factor controlling the arrow size
2131            shaft_length : (float)
2132                fractional shaft length
2133            shaft_width : (float)
2134                fractional shaft width
2135            head_length : (float)
2136                fractional head length
2137            head_width : (float)
2138                fractional head width
2139            fill : (bool)
2140                if False only generate the outline
2141        """
2142        self.fill = fill  ## needed by pyplot.__iadd()
2143        self.s = s        ## needed by pyplot.__iadd()
2144
2145        if s != 1:
2146            shaft_width *= s
2147            head_width *= np.sqrt(s)
2148
2149        # in case user is passing meshs
2150        if isinstance(start_pt, vtki.vtkActor):
2151            start_pt = start_pt.GetPosition()
2152        if isinstance(end_pt, vtki.vtkActor):
2153            end_pt = end_pt.GetPosition()
2154        if len(start_pt) == 2:
2155            start_pt = [start_pt[0], start_pt[1], 0]
2156        if len(end_pt) == 2:
2157            end_pt = [end_pt[0], end_pt[1], 0]
2158
2159        headBase = 1 - head_length
2160        head_width = max(head_width, shaft_width)
2161        if head_length is None or headBase > shaft_length:
2162            headBase = shaft_length
2163
2164        verts = []
2165        verts.append([0, -shaft_width / 2, 0])
2166        verts.append([shaft_length, -shaft_width / 2, 0])
2167        verts.append([headBase, -head_width / 2, 0])
2168        verts.append([1, 0, 0])
2169        verts.append([headBase, head_width / 2, 0])
2170        verts.append([shaft_length, shaft_width / 2, 0])
2171        verts.append([0, shaft_width / 2, 0])
2172        if fill:
2173            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2174            poly = utils.buildPolyData(verts, faces)
2175        else:
2176            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2177            poly = utils.buildPolyData(verts, [], lines=lines)
2178
2179        axis = np.array(end_pt) - np.array(start_pt)
2180        length = float(np.linalg.norm(axis))
2181        if length:
2182            axis = axis / length
2183        theta = 0
2184        if len(axis) > 2:
2185            theta = np.arccos(axis[2])
2186        phi = np.arctan2(axis[1], axis[0])
2187
2188        t = vtki.vtkTransform()
2189        t.Translate(start_pt)
2190        if phi:
2191            t.RotateZ(np.rad2deg(phi))
2192        if theta:
2193            t.RotateY(np.rad2deg(theta))
2194        t.RotateY(-90)  # put it along Z
2195        if rotation:
2196            t.RotateX(rotation)
2197        t.Scale(length, length, length)
2198
2199        tf = vtki.new("TransformPolyDataFilter")
2200        tf.SetInputData(poly)
2201        tf.SetTransform(t)
2202        tf.Update()
2203
2204        super().__init__(tf.GetOutput(), c, alpha)
2205
2206        self.transform = LinearTransform().translate(start_pt)
2207
2208        self.lighting("off")
2209        self.actor.DragableOff()
2210        self.actor.PickableOff()
2211        self.base = np.array(start_pt, dtype=float) # used by pyplot
2212        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2213        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)
2111    def __init__(
2112        self,
2113        start_pt=(0, 0, 0),
2114        end_pt=(1, 0, 0),
2115        s=1,
2116        rotation=0.0,
2117        shaft_length=0.85,
2118        shaft_width=0.055,
2119        head_length=0.175,
2120        head_width=0.175,
2121        fill=True,
2122        c="red4",
2123        alpha=1.0,
2124   ) -> None:
2125        """
2126        Build a 2D arrow from `start_pt` to `end_pt`.
2127
2128        Arguments:
2129            s : (float)
2130                a global multiplicative convenience factor controlling the arrow size
2131            shaft_length : (float)
2132                fractional shaft length
2133            shaft_width : (float)
2134                fractional shaft width
2135            head_length : (float)
2136                fractional head length
2137            head_width : (float)
2138                fractional head width
2139            fill : (bool)
2140                if False only generate the outline
2141        """
2142        self.fill = fill  ## needed by pyplot.__iadd()
2143        self.s = s        ## needed by pyplot.__iadd()
2144
2145        if s != 1:
2146            shaft_width *= s
2147            head_width *= np.sqrt(s)
2148
2149        # in case user is passing meshs
2150        if isinstance(start_pt, vtki.vtkActor):
2151            start_pt = start_pt.GetPosition()
2152        if isinstance(end_pt, vtki.vtkActor):
2153            end_pt = end_pt.GetPosition()
2154        if len(start_pt) == 2:
2155            start_pt = [start_pt[0], start_pt[1], 0]
2156        if len(end_pt) == 2:
2157            end_pt = [end_pt[0], end_pt[1], 0]
2158
2159        headBase = 1 - head_length
2160        head_width = max(head_width, shaft_width)
2161        if head_length is None or headBase > shaft_length:
2162            headBase = shaft_length
2163
2164        verts = []
2165        verts.append([0, -shaft_width / 2, 0])
2166        verts.append([shaft_length, -shaft_width / 2, 0])
2167        verts.append([headBase, -head_width / 2, 0])
2168        verts.append([1, 0, 0])
2169        verts.append([headBase, head_width / 2, 0])
2170        verts.append([shaft_length, shaft_width / 2, 0])
2171        verts.append([0, shaft_width / 2, 0])
2172        if fill:
2173            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2174            poly = utils.buildPolyData(verts, faces)
2175        else:
2176            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2177            poly = utils.buildPolyData(verts, [], lines=lines)
2178
2179        axis = np.array(end_pt) - np.array(start_pt)
2180        length = float(np.linalg.norm(axis))
2181        if length:
2182            axis = axis / length
2183        theta = 0
2184        if len(axis) > 2:
2185            theta = np.arccos(axis[2])
2186        phi = np.arctan2(axis[1], axis[0])
2187
2188        t = vtki.vtkTransform()
2189        t.Translate(start_pt)
2190        if phi:
2191            t.RotateZ(np.rad2deg(phi))
2192        if theta:
2193            t.RotateY(np.rad2deg(theta))
2194        t.RotateY(-90)  # put it along Z
2195        if rotation:
2196            t.RotateX(rotation)
2197        t.Scale(length, length, length)
2198
2199        tf = vtki.new("TransformPolyDataFilter")
2200        tf.SetInputData(poly)
2201        tf.SetTransform(t)
2202        tf.Update()
2203
2204        super().__init__(tf.GetOutput(), c, alpha)
2205
2206        self.transform = LinearTransform().translate(start_pt)
2207
2208        self.lighting("off")
2209        self.actor.DragableOff()
2210        self.actor.PickableOff()
2211        self.base = np.array(start_pt, dtype=float) # used by pyplot
2212        self.top  = np.array(end_pt,   dtype=float) # used by pyplot
2213        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):
2216class Arrows2D(Glyph):
2217    """
2218    Build 2D arrows between two lists of points.
2219    """
2220
2221    def __init__(
2222        self,
2223        start_pts,
2224        end_pts=None,
2225        s=1.0,
2226        rotation=0.0,
2227        shaft_length=0.8,
2228        shaft_width=0.05,
2229        head_length=0.225,
2230        head_width=0.175,
2231        fill=True,
2232        c=None,
2233        alpha=1.0,
2234    ) -> None:
2235        """
2236        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2237        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2238
2239        Color can be specified as a colormap which maps the size of the arrows.
2240
2241        Arguments:
2242            shaft_length : (float)
2243                fractional shaft length
2244            shaft_width : (float)
2245                fractional shaft width
2246            head_length : (float)
2247                fractional head length
2248            head_width : (float)
2249                fractional head width
2250            fill : (bool)
2251                if False only generate the outline
2252        """
2253        if isinstance(start_pts, Points):
2254            start_pts = start_pts.coordinates
2255        if isinstance(end_pts, Points):
2256            end_pts = end_pts.coordinates
2257
2258        start_pts = np.asarray(start_pts, dtype=float)
2259        if end_pts is None:
2260            strt = start_pts[:, 0]
2261            end_pts = start_pts[:, 1]
2262            start_pts = strt
2263        else:
2264            end_pts = np.asarray(end_pts, dtype=float)
2265
2266        if head_length is None:
2267            head_length = 1 - shaft_length
2268
2269        arr = Arrow2D(
2270            (0, 0, 0),
2271            (1, 0, 0),
2272            s=s,
2273            rotation=rotation,
2274            shaft_length=shaft_length,
2275            shaft_width=shaft_width,
2276            head_length=head_length,
2277            head_width=head_width,
2278            fill=fill,
2279        )
2280
2281        orients = end_pts - start_pts
2282        orients = utils.make3d(orients)
2283
2284        pts = Points(start_pts)
2285        super().__init__(
2286            pts,
2287            arr,
2288            orientation_array=orients,
2289            scale_by_vector_size=True,
2290            c=c,
2291            alpha=alpha,
2292        )
2293        self.flat().lighting("off").pickable(False)
2294        if c is not None:
2295            self.color(c)
2296        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)
2221    def __init__(
2222        self,
2223        start_pts,
2224        end_pts=None,
2225        s=1.0,
2226        rotation=0.0,
2227        shaft_length=0.8,
2228        shaft_width=0.05,
2229        head_length=0.225,
2230        head_width=0.175,
2231        fill=True,
2232        c=None,
2233        alpha=1.0,
2234    ) -> None:
2235        """
2236        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2237        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2238
2239        Color can be specified as a colormap which maps the size of the arrows.
2240
2241        Arguments:
2242            shaft_length : (float)
2243                fractional shaft length
2244            shaft_width : (float)
2245                fractional shaft width
2246            head_length : (float)
2247                fractional head length
2248            head_width : (float)
2249                fractional head width
2250            fill : (bool)
2251                if False only generate the outline
2252        """
2253        if isinstance(start_pts, Points):
2254            start_pts = start_pts.coordinates
2255        if isinstance(end_pts, Points):
2256            end_pts = end_pts.coordinates
2257
2258        start_pts = np.asarray(start_pts, dtype=float)
2259        if end_pts is None:
2260            strt = start_pts[:, 0]
2261            end_pts = start_pts[:, 1]
2262            start_pts = strt
2263        else:
2264            end_pts = np.asarray(end_pts, dtype=float)
2265
2266        if head_length is None:
2267            head_length = 1 - shaft_length
2268
2269        arr = Arrow2D(
2270            (0, 0, 0),
2271            (1, 0, 0),
2272            s=s,
2273            rotation=rotation,
2274            shaft_length=shaft_length,
2275            shaft_width=shaft_width,
2276            head_length=head_length,
2277            head_width=head_width,
2278            fill=fill,
2279        )
2280
2281        orients = end_pts - start_pts
2282        orients = utils.make3d(orients)
2283
2284        pts = Points(start_pts)
2285        super().__init__(
2286            pts,
2287            arr,
2288            orientation_array=orients,
2289            scale_by_vector_size=True,
2290            c=c,
2291            alpha=alpha,
2292        )
2293        self.flat().lighting("off").pickable(False)
2294        if c is not None:
2295            self.color(c)
2296        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):
2299class FlatArrow(Ribbon):
2300    """
2301    Build a 2D arrow in 3D space by joining two close lines.
2302    """
2303
2304    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2305        """
2306        Build a 2D arrow in 3D space by joining two close lines.
2307
2308        Examples:
2309            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2310
2311                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2312        """
2313        if isinstance(line1, Points):
2314            line1 = line1.coordinates
2315        if isinstance(line2, Points):
2316            line2 = line2.coordinates
2317
2318        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2319
2320        v = (sm1 - sm2) / 3 * tip_width
2321        p1 = sm1 + v
2322        p2 = sm2 - v
2323        pm1 = (sm1 + sm2) / 2
2324        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2325        pm12 = pm1 - pm2
2326        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2327
2328        line1.append(p1)
2329        line1.append(tip)
2330        line2.append(p2)
2331        line2.append(tip)
2332        resm = max(100, len(line1))
2333
2334        super().__init__(line1, line2, res=(resm, 1))
2335        self.phong().lighting("off")
2336        self.actor.PickableOff()
2337        self.actor.DragableOff()
2338        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)
2304    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None:
2305        """
2306        Build a 2D arrow in 3D space by joining two close lines.
2307
2308        Examples:
2309            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2310
2311                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2312        """
2313        if isinstance(line1, Points):
2314            line1 = line1.coordinates
2315        if isinstance(line2, Points):
2316            line2 = line2.coordinates
2317
2318        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2319
2320        v = (sm1 - sm2) / 3 * tip_width
2321        p1 = sm1 + v
2322        p2 = sm2 - v
2323        pm1 = (sm1 + sm2) / 2
2324        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2325        pm12 = pm1 - pm2
2326        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2327
2328        line1.append(p1)
2329        line1.append(tip)
2330        line2.append(p2)
2331        line2.append(tip)
2332        resm = max(100, len(line1))
2333
2334        super().__init__(line1, line2, res=(resm, 1))
2335        self.phong().lighting("off")
2336        self.actor.PickableOff()
2337        self.actor.DragableOff()
2338        self.name = "FlatArrow"

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

Examples:
class Polygon(vedo.mesh.Mesh):
2351class Polygon(Mesh):
2352    """
2353    Build a polygon in the `xy` plane.
2354    """
2355
2356    def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None:
2357        """
2358        Build a polygon in the `xy` plane of `nsides` of radius `r`.
2359
2360        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png)
2361        """
2362        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False)
2363        pts = pol2cart(np.ones_like(t) * r, t).T
2364        faces = [list(range(nsides))]
2365        # do not use: vtkRegularPolygonSource
2366        super().__init__([pts, faces], c, alpha)
2367        if len(pos) == 2:
2368            pos = (pos[0], pos[1], 0)
2369        self.pos(pos)
2370        self.properties.LightingOff()
2371        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)
2356    def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None:
2357        """
2358        Build a polygon in the `xy` plane of `nsides` of radius `r`.
2359
2360        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png)
2361        """
2362        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False)
2363        pts = pol2cart(np.ones_like(t) * r, t).T
2364        faces = [list(range(nsides))]
2365        # do not use: vtkRegularPolygonSource
2366        super().__init__([pts, faces], c, alpha)
2367        if len(pos) == 2:
2368            pos = (pos[0], pos[1], 0)
2369        self.pos(pos)
2370        self.properties.LightingOff()
2371        self.name = "Polygon " + str(nsides)

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

class Triangle(vedo.mesh.Mesh):
2341class Triangle(Mesh):
2342    """Create a triangle from 3 points in space."""
2343
2344    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2345        """Create a triangle from 3 points in space."""
2346        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2347        self.properties.LightingOff()
2348        self.name = "Triangle"

Create a triangle from 3 points in space.

Triangle(p1, p2, p3, c='green7', alpha=1.0)
2344    def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None:
2345        """Create a triangle from 3 points in space."""
2346        super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2347        self.properties.LightingOff()
2348        self.name = "Triangle"

Create a triangle from 3 points in space.

class Rectangle(vedo.mesh.Mesh):
3140class Rectangle(Mesh):
3141    """
3142    Build a rectangle in the xy plane.
3143    """
3144
3145    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3146        """
3147        Build a rectangle in the xy plane identified by any two corner points.
3148
3149        Arguments:
3150            p1 : (list)
3151                bottom-left position of the corner
3152            p2 : (list)
3153                top-right position of the corner
3154            radius : (float, list)
3155                smoothing radius of the corner in world units.
3156                A list can be passed with 4 individual values.
3157        """
3158        if len(p1) == 2:
3159            p1 = np.array([p1[0], p1[1], 0.0])
3160        else:
3161            p1 = np.array(p1, dtype=float)
3162        if len(p2) == 2:
3163            p2 = np.array([p2[0], p2[1], 0.0])
3164        else:
3165            p2 = np.array(p2, dtype=float)
3166
3167        self.corner1 = p1
3168        self.corner2 = p2
3169
3170        color = c
3171        smoothr = False
3172        risseq = False
3173        if utils.is_sequence(radius):
3174            risseq = True
3175            smoothr = True
3176            if max(radius) == 0:
3177                smoothr = False
3178        elif radius:
3179            smoothr = True
3180
3181        if not smoothr:
3182            radius = None
3183        self.radius = radius
3184
3185        if smoothr:
3186            r = radius
3187            if not risseq:
3188                r = [r, r, r, r]
3189            rd, ra, rb, rc = r
3190
3191            if p1[0] > p2[0]:  # flip p1 - p2
3192                p1, p2 = p2, p1
3193            if p1[1] > p2[1]:  # flip p1y - p2y
3194                p1[1], p2[1] = p2[1], p1[1]
3195
3196            px, py, _ = p2 - p1
3197            k = min(px / 2, py / 2)
3198            ra = min(abs(ra), k)
3199            rb = min(abs(rb), k)
3200            rc = min(abs(rc), k)
3201            rd = min(abs(rd), k)
3202            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3203            betas = np.split(beta, 4)
3204            rrx = np.cos(betas)
3205            rry = np.sin(betas)
3206
3207            q1 = (rd, 0)
3208            # q2 = (px-ra, 0)
3209            q3 = (px, ra)
3210            # q4 = (px, py-rb)
3211            q5 = (px - rb, py)
3212            # q6 = (rc, py)
3213            q7 = (0, py - rc)
3214            # q8 = (0, rd)
3215            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3216            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3217            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3218            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3219
3220            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3221            faces = [list(range(len(pts)))]
3222        else:
3223            p1r = np.array([p2[0], p1[1], 0.0])
3224            p2l = np.array([p1[0], p2[1], 0.0])
3225            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3226            faces = [(0, 1, 2, 3)]
3227
3228        super().__init__([pts, faces], color, alpha)
3229        self.pos(p1)
3230        self.properties.LightingOff()
3231        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)
3145    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None:
3146        """
3147        Build a rectangle in the xy plane identified by any two corner points.
3148
3149        Arguments:
3150            p1 : (list)
3151                bottom-left position of the corner
3152            p2 : (list)
3153                top-right position of the corner
3154            radius : (float, list)
3155                smoothing radius of the corner in world units.
3156                A list can be passed with 4 individual values.
3157        """
3158        if len(p1) == 2:
3159            p1 = np.array([p1[0], p1[1], 0.0])
3160        else:
3161            p1 = np.array(p1, dtype=float)
3162        if len(p2) == 2:
3163            p2 = np.array([p2[0], p2[1], 0.0])
3164        else:
3165            p2 = np.array(p2, dtype=float)
3166
3167        self.corner1 = p1
3168        self.corner2 = p2
3169
3170        color = c
3171        smoothr = False
3172        risseq = False
3173        if utils.is_sequence(radius):
3174            risseq = True
3175            smoothr = True
3176            if max(radius) == 0:
3177                smoothr = False
3178        elif radius:
3179            smoothr = True
3180
3181        if not smoothr:
3182            radius = None
3183        self.radius = radius
3184
3185        if smoothr:
3186            r = radius
3187            if not risseq:
3188                r = [r, r, r, r]
3189            rd, ra, rb, rc = r
3190
3191            if p1[0] > p2[0]:  # flip p1 - p2
3192                p1, p2 = p2, p1
3193            if p1[1] > p2[1]:  # flip p1y - p2y
3194                p1[1], p2[1] = p2[1], p1[1]
3195
3196            px, py, _ = p2 - p1
3197            k = min(px / 2, py / 2)
3198            ra = min(abs(ra), k)
3199            rb = min(abs(rb), k)
3200            rc = min(abs(rc), k)
3201            rd = min(abs(rd), k)
3202            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3203            betas = np.split(beta, 4)
3204            rrx = np.cos(betas)
3205            rry = np.sin(betas)
3206
3207            q1 = (rd, 0)
3208            # q2 = (px-ra, 0)
3209            q3 = (px, ra)
3210            # q4 = (px, py-rb)
3211            q5 = (px - rb, py)
3212            # q6 = (rc, py)
3213            q7 = (0, py - rc)
3214            # q8 = (0, rd)
3215            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3216            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3217            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3218            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3219
3220            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3221            faces = [list(range(len(pts)))]
3222        else:
3223            p1r = np.array([p2[0], p1[1], 0.0])
3224            p2l = np.array([p1[0], p2[1], 0.0])
3225            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3226            faces = [(0, 1, 2, 3)]
3227
3228        super().__init__([pts, faces], color, alpha)
3229        self.pos(p1)
3230        self.properties.LightingOff()
3231        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):
2482class Disc(Mesh):
2483    """
2484    Build a 2D disc.
2485    """
2486
2487    def __init__(
2488        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2489    ) -> None:
2490        """
2491        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2492
2493        Set `res` as the resolution in R and Phi (can be a list).
2494
2495        Use `angle_range` to create a disc sector between the 2 specified angles.
2496
2497        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2498        """
2499        if utils.is_sequence(res):
2500            res_r, res_phi = res
2501        else:
2502            res_r, res_phi = res, 12 * res
2503
2504        if len(angle_range) == 0:
2505            ps = vtki.new("DiskSource")
2506        else:
2507            ps = vtki.new("SectorSource")
2508            ps.SetStartAngle(angle_range[0])
2509            ps.SetEndAngle(angle_range[1])
2510
2511        ps.SetInnerRadius(r1)
2512        ps.SetOuterRadius(r2)
2513        ps.SetRadialResolution(res_r)
2514        ps.SetCircumferentialResolution(res_phi)
2515        ps.Update()
2516        super().__init__(ps.GetOutput(), c, alpha)
2517        self.flat()
2518        self.pos(utils.make3d(pos))
2519        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)
2487    def __init__(
2488        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2489    ) -> None:
2490        """
2491        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2492
2493        Set `res` as the resolution in R and Phi (can be a list).
2494
2495        Use `angle_range` to create a disc sector between the 2 specified angles.
2496
2497        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2498        """
2499        if utils.is_sequence(res):
2500            res_r, res_phi = res
2501        else:
2502            res_r, res_phi = res, 12 * res
2503
2504        if len(angle_range) == 0:
2505            ps = vtki.new("DiskSource")
2506        else:
2507            ps = vtki.new("SectorSource")
2508            ps.SetStartAngle(angle_range[0])
2509            ps.SetEndAngle(angle_range[1])
2510
2511        ps.SetInnerRadius(r1)
2512        ps.SetOuterRadius(r2)
2513        ps.SetRadialResolution(res_r)
2514        ps.SetCircumferentialResolution(res_phi)
2515        ps.Update()
2516        super().__init__(ps.GetOutput(), c, alpha)
2517        self.flat()
2518        self.pos(utils.make3d(pos))
2519        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):
2374class Circle(Polygon):
2375    """
2376    Build a Circle of radius `r`.
2377    """
2378
2379    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None:
2380        """
2381        Build a Circle of radius `r`.
2382        """
2383        super().__init__(pos, nsides=res, r=r)
2384
2385        self.nr_of_points = 0
2386        self.va = 0
2387        self.vb = 0
2388        self.axis1: List[float] = []
2389        self.axis2: List[float] = []
2390        self.center: List[float] = []  # filled by pointcloud.pca_ellipse()
2391        self.pvalue = 0.0              # filled by pointcloud.pca_ellipse()
2392        self.alpha(alpha).c(c)
2393        self.name = "Circle"
2394    
2395    def acircularity(self) -> float:
2396        """
2397        Return a measure of how different an ellipse is from a circle.
2398        Values close to zero correspond to a circular object.
2399        """
2400        a, b = self.va, self.vb
2401        value = 0.0
2402        if a+b:
2403            value = ((a-b)/(a+b))**2
2404        return value

Build a Circle of radius r.

Circle(pos=(0, 0, 0), r=1.0, res=120, c='gray5', alpha=1.0)
2379    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None:
2380        """
2381        Build a Circle of radius `r`.
2382        """
2383        super().__init__(pos, nsides=res, r=r)
2384
2385        self.nr_of_points = 0
2386        self.va = 0
2387        self.vb = 0
2388        self.axis1: List[float] = []
2389        self.axis2: List[float] = []
2390        self.center: List[float] = []  # filled by pointcloud.pca_ellipse()
2391        self.pvalue = 0.0              # filled by pointcloud.pca_ellipse()
2392        self.alpha(alpha).c(c)
2393        self.name = "Circle"

Build a Circle of radius r.

def acircularity(self) -> float:
2395    def acircularity(self) -> float:
2396        """
2397        Return a measure of how different an ellipse is from a circle.
2398        Values close to zero correspond to a circular object.
2399        """
2400        a, b = self.va, self.vb
2401        value = 0.0
2402        if a+b:
2403            value = ((a-b)/(a+b))**2
2404        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):
2406class GeoCircle(Polygon):
2407    """
2408    Build a Circle of radius `r`.
2409    """
2410
2411    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None:
2412        """
2413        Build a Circle of radius `r` as projected on a geographic map.
2414        Circles near the poles will look very squashed.
2415
2416        See example:
2417            ```bash
2418            vedo -r earthquake
2419            ```
2420        """
2421        coords = []
2422        sinr, cosr = np.sin(r), np.cos(r)
2423        sinlat, coslat = np.sin(lat), np.cos(lat)
2424        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2425            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2426            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2427            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2428
2429        super().__init__(nsides=res, c=c, alpha=alpha)
2430        self.coordinates = coords # warp polygon points to match geo projection
2431        self.name = "Circle"

Build a Circle of radius r.

GeoCircle(lat, lon, r=1.0, res=60, c='red4', alpha=1.0)
2411    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None:
2412        """
2413        Build a Circle of radius `r` as projected on a geographic map.
2414        Circles near the poles will look very squashed.
2415
2416        See example:
2417            ```bash
2418            vedo -r earthquake
2419            ```
2420        """
2421        coords = []
2422        sinr, cosr = np.sin(r), np.cos(r)
2423        sinlat, coslat = np.sin(lat), np.cos(lat)
2424        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2425            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2426            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2427            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2428
2429        super().__init__(nsides=res, c=c, alpha=alpha)
2430        self.coordinates = coords # warp polygon points to match geo projection
2431        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
coordinates
830    @property
831    def coordinates(self):
832        """Return the vertices (points) coordinates. Same as `vertices`."""
833        return self.vertices

Return the vertices (points) coordinates. Same as vertices.

class Arc(Line):
1135class Arc(Line):
1136    """
1137    Build a 2D circular arc between 2 points.
1138    """
1139
1140    def __init__(
1141        self,
1142        center=None,
1143        point1=None,
1144        point2=None,
1145        normal=None,
1146        angle=None,
1147        invert=False,
1148        res=60,
1149        c="k3",
1150        alpha=1.0,
1151    ) -> None:
1152        """
1153        Build a 2D circular arc between 2 points.
1154        Two modes are available:
1155            1. [center, point1, point2] are specified
1156
1157            2. [point1, normal, angle] are specified.
1158
1159        In the first case it creates an arc defined by two endpoints and a center.
1160        In the second the arc spans the shortest angular sector defined by
1161        a starting point, a normal and a spanning angle.
1162        if `invert=True`, then the opposite happens.
1163
1164        Example 1:
1165        ```python
1166        from vedo import *
1167        center = [0,1,0]
1168        p1 = [1,2,0.4]
1169        p2 = [0.5,3,-1]
1170        arc = Arc(center, p1, p2).lw(5).c("purple5")
1171        line2 = Line(center, p2)
1172        pts = Points([center, p1,p2], r=9, c='r')
1173        show(pts, line2, arc, f"length={arc.length()}", axes=1).close()
1174        ```
1175
1176        Example 2:
1177        ```python
1178        from vedo import *
1179        arc = Arc(point1=[0,1,0], normal=[0,0,1], angle=270)
1180        arc.lw(5).c("purple5")
1181        origin = Point([0,0,0], r=9, c='r')
1182        show(origin, arc, arc.labels2d(), axes=1).close()
1183        ```
1184        """
1185        ar = vtki.new("ArcSource")
1186        if point2 is not None:
1187            center = utils.make3d(center)
1188            point1 = utils.make3d(point1)
1189            point2 = utils.make3d(point2)
1190            ar.UseNormalAndAngleOff()
1191            ar.SetPoint1(point1-center)
1192            ar.SetPoint2(point2-center)
1193        elif normal is not None and angle and point1 is not None:
1194            normal = utils.make3d(normal)
1195            point1 = utils.make3d(point1)
1196            ar.UseNormalAndAngleOn()
1197            ar.SetAngle(angle)
1198            ar.SetPolarVector(point1)
1199            ar.SetNormal(normal)
1200            self.top = normal
1201        else:
1202            vedo.logger.error("in Arc(), incorrect input combination.")
1203            raise TypeError
1204        ar.SetNegative(invert)
1205        ar.SetResolution(res)
1206        ar.Update()
1207
1208        super().__init__(ar.GetOutput(), c, alpha)
1209        self.lw(2).lighting("off")
1210        if point2 is not None: # nb: not center
1211            self.pos(center)
1212        self.name = "Arc"

Build a 2D circular arc between 2 points.

Arc( center=None, point1=None, point2=None, normal=None, angle=None, invert=False, res=60, c='k3', alpha=1.0)
1140    def __init__(
1141        self,
1142        center=None,
1143        point1=None,
1144        point2=None,
1145        normal=None,
1146        angle=None,
1147        invert=False,
1148        res=60,
1149        c="k3",
1150        alpha=1.0,
1151    ) -> None:
1152        """
1153        Build a 2D circular arc between 2 points.
1154        Two modes are available:
1155            1. [center, point1, point2] are specified
1156
1157            2. [point1, normal, angle] are specified.
1158
1159        In the first case it creates an arc defined by two endpoints and a center.
1160        In the second the arc spans the shortest angular sector defined by
1161        a starting point, a normal and a spanning angle.
1162        if `invert=True`, then the opposite happens.
1163
1164        Example 1:
1165        ```python
1166        from vedo import *
1167        center = [0,1,0]
1168        p1 = [1,2,0.4]
1169        p2 = [0.5,3,-1]
1170        arc = Arc(center, p1, p2).lw(5).c("purple5")
1171        line2 = Line(center, p2)
1172        pts = Points([center, p1,p2], r=9, c='r')
1173        show(pts, line2, arc, f"length={arc.length()}", axes=1).close()
1174        ```
1175
1176        Example 2:
1177        ```python
1178        from vedo import *
1179        arc = Arc(point1=[0,1,0], normal=[0,0,1], angle=270)
1180        arc.lw(5).c("purple5")
1181        origin = Point([0,0,0], r=9, c='r')
1182        show(origin, arc, arc.labels2d(), axes=1).close()
1183        ```
1184        """
1185        ar = vtki.new("ArcSource")
1186        if point2 is not None:
1187            center = utils.make3d(center)
1188            point1 = utils.make3d(point1)
1189            point2 = utils.make3d(point2)
1190            ar.UseNormalAndAngleOff()
1191            ar.SetPoint1(point1-center)
1192            ar.SetPoint2(point2-center)
1193        elif normal is not None and angle and point1 is not None:
1194            normal = utils.make3d(normal)
1195            point1 = utils.make3d(point1)
1196            ar.UseNormalAndAngleOn()
1197            ar.SetAngle(angle)
1198            ar.SetPolarVector(point1)
1199            ar.SetNormal(normal)
1200            self.top = normal
1201        else:
1202            vedo.logger.error("in Arc(), incorrect input combination.")
1203            raise TypeError
1204        ar.SetNegative(invert)
1205        ar.SetResolution(res)
1206        ar.Update()
1207
1208        super().__init__(ar.GetOutput(), c, alpha)
1209        self.lw(2).lighting("off")
1210        if point2 is not None: # nb: not center
1211            self.pos(center)
1212        self.name = "Arc"

Build a 2D circular arc between 2 points.

Two modes are available:
  1. [center, point1, point2] are specified

  2. [point1, normal, angle] are specified.

In the first case it creates an arc defined by two endpoints and a center. In the second the arc spans the shortest angular sector defined by a starting point, a normal and a spanning angle. if invert=True, then the opposite happens.

Example 1:

from vedo import *
center = [0,1,0]
p1 = [1,2,0.4]
p2 = [0.5,3,-1]
arc = Arc(center, p1, p2).lw(5).c("purple5")
line2 = Line(center, p2)
pts = Points([center, p1,p2], r=9, c='r')
show(pts, line2, arc, f"length={arc.length()}", axes=1).close()

Example 2:

from vedo import *
arc = Arc(point1=[0,1,0], normal=[0,0,1], angle=270)
arc.lw(5).c("purple5")
origin = Point([0,0,0], r=9, c='r')
show(origin, arc, arc.labels2d(), axes=1).close()
class Star(vedo.mesh.Mesh):
2434class Star(Mesh):
2435    """
2436    Build a 2D star shape.
2437    """
2438
2439    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2440        """
2441        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2442
2443        If line is True then only build the outer line (no internal surface meshing).
2444
2445        Example:
2446            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2447
2448                ![](https://vedo.embl.es/images/basic/extrude.png)
2449        """
2450        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2451        x, y = pol2cart(np.ones_like(t) * r2, t)
2452        pts = np.c_[x, y, np.zeros_like(x)]
2453
2454        apts = []
2455        for i, p in enumerate(pts):
2456            apts.append(p)
2457            if i + 1 < n:
2458                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2459        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2460
2461        if line:
2462            apts.append(pts[0])
2463            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2464            super().__init__(poly, c, alpha)
2465            self.lw(2)
2466        else:
2467            apts.append((0, 0, 0))
2468            cells = []
2469            for i in range(2 * n - 1):
2470                cell = [2 * n, i, i + 1]
2471                cells.append(cell)
2472            cells.append([2 * n, i + 1, 0])
2473            super().__init__([apts, cells], c, alpha)
2474
2475        if len(pos) == 2:
2476            pos = (pos[0], pos[1], 0)
2477
2478        self.properties.LightingOff()
2479        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)
2439    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None:
2440        """
2441        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2442
2443        If line is True then only build the outer line (no internal surface meshing).
2444
2445        Example:
2446            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2447
2448                ![](https://vedo.embl.es/images/basic/extrude.png)
2449        """
2450        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2451        x, y = pol2cart(np.ones_like(t) * r2, t)
2452        pts = np.c_[x, y, np.zeros_like(x)]
2453
2454        apts = []
2455        for i, p in enumerate(pts):
2456            apts.append(p)
2457            if i + 1 < n:
2458                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2459        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2460
2461        if line:
2462            apts.append(pts[0])
2463            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2464            super().__init__(poly, c, alpha)
2465            self.lw(2)
2466        else:
2467            apts.append((0, 0, 0))
2468            cells = []
2469            for i in range(2 * n - 1):
2470                cell = [2 * n, i, i + 1]
2471                cells.append(cell)
2472            cells.append([2 * n, i + 1, 0])
2473            super().__init__([apts, cells], c, alpha)
2474
2475        if len(pos) == 2:
2476            pos = (pos[0], pos[1], 0)
2477
2478        self.properties.LightingOff()
2479        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):
3864class Star3D(Mesh):
3865    """
3866    Build a 3D starred shape.
3867    """
3868
3869    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3870        """
3871        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3872        """
3873        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3874               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3875               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3876               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3877        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3878               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3879               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3880               [10,1, 0],[10,11, 9]]
3881
3882        super().__init__([pts, fcs], c, alpha)
3883        self.rotate_x(90)
3884        self.scale(r).lighting("shiny")
3885
3886        if len(pos) == 2:
3887            pos = (pos[0], pos[1], 0)
3888        self.pos(pos)
3889        self.name = "Star3D"

Build a 3D starred shape.

Star3D(pos=(0, 0, 0), r=1.0, thickness=0.1, c='blue4', alpha=1.0)
3869    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None:
3870        """
3871        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3872        """
3873        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3874               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3875               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3876               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3877        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3878               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3879               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3880               [10,1, 0],[10,11, 9]]
3881
3882        super().__init__([pts, fcs], c, alpha)
3883        self.rotate_x(90)
3884        self.scale(r).lighting("shiny")
3885
3886        if len(pos) == 2:
3887            pos = (pos[0], pos[1], 0)
3888        self.pos(pos)
3889        self.name = "Star3D"

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

class Cross3D(vedo.mesh.Mesh):
3892class Cross3D(Mesh):
3893    """
3894    Build a 3D cross shape.
3895    """
3896
3897    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3898        """
3899        Build a 3D cross shape, mainly useful as a 3D marker.
3900        """
3901        if len(pos) == 2:
3902            pos = (pos[0], pos[1], 0)
3903
3904        c1 = Cylinder(r=thickness * s, height=2 * s)
3905        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3906        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3907        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3908        super().__init__(poly, c, alpha)
3909        self.name = "Cross3D"

Build a 3D cross shape.

Cross3D(pos=(0, 0, 0), s=1.0, thickness=0.3, c='b', alpha=1.0)
3897    def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None:
3898        """
3899        Build a 3D cross shape, mainly useful as a 3D marker.
3900        """
3901        if len(pos) == 2:
3902            pos = (pos[0], pos[1], 0)
3903
3904        c1 = Cylinder(r=thickness * s, height=2 * s)
3905        c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90)
3906        c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90)
3907        poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset
3908        super().__init__(poly, c, alpha)
3909        self.name = "Cross3D"

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

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

Build a sphere.

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

Build a large set of spheres.

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

Build a textured mesh representing the Earth.

Earth(style=1, r=1.0)
2763    def __init__(self, style=1, r=1.0) -> None:
2764        """
2765        Build a textured mesh representing the Earth.
2766
2767        Example:
2768            - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py)
2769
2770                ![](https://vedo.embl.es/images/advanced/geodesic.png)
2771        """
2772        tss = vtki.new("TexturedSphereSource")
2773        tss.SetRadius(r)
2774        tss.SetThetaResolution(72)
2775        tss.SetPhiResolution(36)
2776        tss.Update()
2777        super().__init__(tss.GetOutput(), c="w")
2778        atext = vtki.vtkTexture()
2779        pnm_reader = vtki.new("JPEGReader")
2780        fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False)
2781        pnm_reader.SetFileName(fn)
2782        atext.SetInputConnection(pnm_reader.GetOutputPort())
2783        atext.InterpolateOn()
2784        self.texture(atext)
2785        self.name = "Earth"

Build a textured mesh representing the Earth.

Example:
class Ellipsoid(vedo.mesh.Mesh):
2788class Ellipsoid(Mesh):
2789    """Build a 3D ellipsoid."""
2790    def __init__(
2791        self,
2792        pos=(0, 0, 0),
2793        axis1=(0.5, 0, 0),
2794        axis2=(0, 1, 0),
2795        axis3=(0, 0, 1.5),
2796        res=24,
2797        c="cyan4",
2798        alpha=1.0,
2799    ) -> None:
2800        """
2801        Build a 3D ellipsoid centered at position `pos`.
2802
2803        Arguments:
2804            axis1 : (list)
2805                First axis. Length corresponds to semi-axis.
2806            axis2 : (list)
2807                Second axis. Length corresponds to semi-axis.
2808            axis3 : (list)
2809                Third axis. Length corresponds to semi-axis.
2810        """        
2811        self.center = utils.make3d(pos)
2812
2813        self.axis1 = utils.make3d(axis1)
2814        self.axis2 = utils.make3d(axis2)
2815        self.axis3 = utils.make3d(axis3)
2816
2817        self.va = np.linalg.norm(self.axis1)
2818        self.vb = np.linalg.norm(self.axis2)
2819        self.vc = np.linalg.norm(self.axis3)
2820
2821        self.va_error = 0
2822        self.vb_error = 0
2823        self.vc_error = 0
2824
2825        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2826        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2827
2828        if utils.is_sequence(res):
2829            res_t, res_phi = res
2830        else:
2831            res_t, res_phi = 2 * res, res
2832
2833        elli_source = vtki.new("SphereSource")
2834        elli_source.SetRadius(1)
2835        elli_source.SetThetaResolution(res_t)
2836        elli_source.SetPhiResolution(res_phi)
2837        elli_source.Update()
2838
2839        super().__init__(elli_source.GetOutput(), c, alpha)
2840
2841        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2842        lt = LinearTransform(matrix).translate(pos)
2843        self.apply_transform(lt)
2844        self.name = "Ellipsoid"
2845
2846    def asphericity(self) -> float:
2847        """
2848        Return a measure of how different an ellipsoid is from a sphere.
2849        Values close to zero correspond to a spheric object.
2850        """
2851        a, b, c = self.va, self.vb, self.vc
2852        asp = ( ((a-b)/(a+b))**2
2853              + ((a-c)/(a+c))**2
2854              + ((b-c)/(b+c))**2 ) / 3. * 4.
2855        return float(asp)
2856
2857    def asphericity_error(self) -> float:
2858        """
2859        Calculate statistical error on the asphericity value.
2860
2861        Errors on the main axes are stored in
2862        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2863        """
2864        a, b, c = self.va, self.vb, self.vc
2865        sqrtn = np.sqrt(self.nr_of_points)
2866        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2867
2868        # from sympy import *
2869        # init_printing(use_unicode=True)
2870        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2871        # L = (
2872        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2873        #    / 3 * 4)
2874        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2875        # print(dl2)
2876        # exit()
2877
2878        dL2 = (
2879            ea ** 2
2880            * (
2881                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2882                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2883                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2884                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2885            ) ** 2
2886            + eb ** 2
2887            * (
2888                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2889                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2890                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2891                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2892            ) ** 2
2893            + ec ** 2
2894            * (
2895                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2896                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2897                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2898                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2899            ) ** 2
2900        )
2901        err = np.sqrt(dL2)
2902        self.va_error = ea
2903        self.vb_error = eb
2904        self.vc_error = ec
2905        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)
2790    def __init__(
2791        self,
2792        pos=(0, 0, 0),
2793        axis1=(0.5, 0, 0),
2794        axis2=(0, 1, 0),
2795        axis3=(0, 0, 1.5),
2796        res=24,
2797        c="cyan4",
2798        alpha=1.0,
2799    ) -> None:
2800        """
2801        Build a 3D ellipsoid centered at position `pos`.
2802
2803        Arguments:
2804            axis1 : (list)
2805                First axis. Length corresponds to semi-axis.
2806            axis2 : (list)
2807                Second axis. Length corresponds to semi-axis.
2808            axis3 : (list)
2809                Third axis. Length corresponds to semi-axis.
2810        """        
2811        self.center = utils.make3d(pos)
2812
2813        self.axis1 = utils.make3d(axis1)
2814        self.axis2 = utils.make3d(axis2)
2815        self.axis3 = utils.make3d(axis3)
2816
2817        self.va = np.linalg.norm(self.axis1)
2818        self.vb = np.linalg.norm(self.axis2)
2819        self.vc = np.linalg.norm(self.axis3)
2820
2821        self.va_error = 0
2822        self.vb_error = 0
2823        self.vc_error = 0
2824
2825        self.nr_of_points = 1  # used by pointcloud.pca_ellipsoid()
2826        self.pvalue = 0        # used by pointcloud.pca_ellipsoid()
2827
2828        if utils.is_sequence(res):
2829            res_t, res_phi = res
2830        else:
2831            res_t, res_phi = 2 * res, res
2832
2833        elli_source = vtki.new("SphereSource")
2834        elli_source.SetRadius(1)
2835        elli_source.SetThetaResolution(res_t)
2836        elli_source.SetPhiResolution(res_phi)
2837        elli_source.Update()
2838
2839        super().__init__(elli_source.GetOutput(), c, alpha)
2840
2841        matrix = np.c_[self.axis1, self.axis2, self.axis3]
2842        lt = LinearTransform(matrix).translate(pos)
2843        self.apply_transform(lt)
2844        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:
2846    def asphericity(self) -> float:
2847        """
2848        Return a measure of how different an ellipsoid is from a sphere.
2849        Values close to zero correspond to a spheric object.
2850        """
2851        a, b, c = self.va, self.vb, self.vc
2852        asp = ( ((a-b)/(a+b))**2
2853              + ((a-c)/(a+c))**2
2854              + ((b-c)/(b+c))**2 ) / 3. * 4.
2855        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:
2857    def asphericity_error(self) -> float:
2858        """
2859        Calculate statistical error on the asphericity value.
2860
2861        Errors on the main axes are stored in
2862        `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`.
2863        """
2864        a, b, c = self.va, self.vb, self.vc
2865        sqrtn = np.sqrt(self.nr_of_points)
2866        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2867
2868        # from sympy import *
2869        # init_printing(use_unicode=True)
2870        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2871        # L = (
2872        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2873        #    / 3 * 4)
2874        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2875        # print(dl2)
2876        # exit()
2877
2878        dL2 = (
2879            ea ** 2
2880            * (
2881                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2882                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2883                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2884                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2885            ) ** 2
2886            + eb ** 2
2887            * (
2888                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2889                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2890                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2891                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2892            ) ** 2
2893            + ec ** 2
2894            * (
2895                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2896                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2897                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2898                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2899            ) ** 2
2900        )
2901        err = np.sqrt(dL2)
2902        self.va_error = ea
2903        self.vb_error = eb
2904        self.vc_error = ec
2905        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):
2908class Grid(Mesh):
2909    """
2910    An even or uneven 2D grid.
2911    """
2912
2913    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2914        """
2915        Create an even or uneven 2D grid.
2916        Can also be created from a `np.mgrid` object (see example).
2917
2918        Arguments:
2919            pos : (list, Points, Mesh)
2920                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2921            s : (float, list)
2922                if a float is provided it is interpreted as the total size along x and y,
2923                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2924                In this case keyword `res` is ignored (see example below).
2925            res : (list)
2926                resolutions along x and y, e.i. the number of subdivisions
2927            lw : (int)
2928                line width
2929
2930        Example:
2931            ```python
2932            from vedo import *
2933            xcoords = np.arange(0, 2, 0.2)
2934            ycoords = np.arange(0, 1, 0.2)
2935            sqrtx = sqrt(xcoords)
2936            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2937            grid.show(axes=8).close()
2938
2939            # Can also create a grid from a np.mgrid:
2940            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2941            vgrid = Grid(s=(X[:,0], Y[0]))
2942            vgrid.show(axes=8).close()
2943            ```
2944            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2945        """
2946        resx, resy = res
2947        sx, sy = s
2948        
2949        try:
2950            bb = pos.bounds()
2951            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2952            sx = bb[1] - bb[0]
2953            sy = bb[3] - bb[2]
2954        except AttributeError:
2955            pass        
2956
2957        if len(pos) == 2:
2958            pos = (pos[0], pos[1], 0)
2959        elif len(pos) in [4,6]: # passing a bounding box
2960            bb = pos
2961            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2962            sx = bb[1] - bb[0]
2963            sy = bb[3] - bb[2]
2964            if len(pos)==6:
2965                pos[2] = bb[4] - bb[5]
2966
2967        if utils.is_sequence(sx) and utils.is_sequence(sy):
2968            verts = []
2969            for y in sy:
2970                for x in sx:
2971                    verts.append([x, y, 0])
2972            faces = []
2973            n = len(sx)
2974            m = len(sy)
2975            for j in range(m - 1):
2976                j1n = (j + 1) * n
2977                for i in range(n - 1):
2978                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2979
2980            super().__init__([verts, faces], c, alpha)
2981
2982        else:
2983            ps = vtki.new("PlaneSource")
2984            ps.SetResolution(resx, resy)
2985            ps.Update()
2986
2987            t = vtki.vtkTransform()
2988            t.Translate(pos)
2989            t.Scale(sx, sy, 1)
2990
2991            tf = vtki.new("TransformPolyDataFilter")
2992            tf.SetInputData(ps.GetOutput())
2993            tf.SetTransform(t)
2994            tf.Update()
2995
2996            super().__init__(tf.GetOutput(), c, alpha)
2997
2998        self.wireframe().lw(lw)
2999        self.properties.LightingOff()
3000        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)
2913    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None:
2914        """
2915        Create an even or uneven 2D grid.
2916        Can also be created from a `np.mgrid` object (see example).
2917
2918        Arguments:
2919            pos : (list, Points, Mesh)
2920                position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
2921            s : (float, list)
2922                if a float is provided it is interpreted as the total size along x and y,
2923                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2924                In this case keyword `res` is ignored (see example below).
2925            res : (list)
2926                resolutions along x and y, e.i. the number of subdivisions
2927            lw : (int)
2928                line width
2929
2930        Example:
2931            ```python
2932            from vedo import *
2933            xcoords = np.arange(0, 2, 0.2)
2934            ycoords = np.arange(0, 1, 0.2)
2935            sqrtx = sqrt(xcoords)
2936            grid = Grid(s=(sqrtx, ycoords)).lw(2)
2937            grid.show(axes=8).close()
2938
2939            # Can also create a grid from a np.mgrid:
2940            X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j]
2941            vgrid = Grid(s=(X[:,0], Y[0]))
2942            vgrid.show(axes=8).close()
2943            ```
2944            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
2945        """
2946        resx, resy = res
2947        sx, sy = s
2948        
2949        try:
2950            bb = pos.bounds()
2951            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2]
2952            sx = bb[1] - bb[0]
2953            sy = bb[3] - bb[2]
2954        except AttributeError:
2955            pass        
2956
2957        if len(pos) == 2:
2958            pos = (pos[0], pos[1], 0)
2959        elif len(pos) in [4,6]: # passing a bounding box
2960            bb = pos
2961            pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0]
2962            sx = bb[1] - bb[0]
2963            sy = bb[3] - bb[2]
2964            if len(pos)==6:
2965                pos[2] = bb[4] - bb[5]
2966
2967        if utils.is_sequence(sx) and utils.is_sequence(sy):
2968            verts = []
2969            for y in sy:
2970                for x in sx:
2971                    verts.append([x, y, 0])
2972            faces = []
2973            n = len(sx)
2974            m = len(sy)
2975            for j in range(m - 1):
2976                j1n = (j + 1) * n
2977                for i in range(n - 1):
2978                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
2979
2980            super().__init__([verts, faces], c, alpha)
2981
2982        else:
2983            ps = vtki.new("PlaneSource")
2984            ps.SetResolution(resx, resy)
2985            ps.Update()
2986
2987            t = vtki.vtkTransform()
2988            t.Translate(pos)
2989            t.Scale(sx, sy, 1)
2990
2991            tf = vtki.new("TransformPolyDataFilter")
2992            tf.SetInputData(ps.GetOutput())
2993            tf.SetTransform(t)
2994            tf.Update()
2995
2996            super().__init__(tf.GetOutput(), c, alpha)
2997
2998        self.wireframe().lw(lw)
2999        self.properties.LightingOff()
3000        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):
3329class TessellatedBox(Mesh):
3330    """
3331    Build a cubic `Mesh` made of quads.
3332    """
3333
3334    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3335        """
3336        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3337
3338        Arguments:
3339            pos : (list)
3340                position of the left bottom corner
3341            n : (int, list)
3342                number of subdivisions along each side
3343            spacing : (float)
3344                size of the side of the single quad in the 3 directions
3345        """
3346        if utils.is_sequence(n):  # slow
3347            img = vtki.vtkImageData()
3348            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3349            img.SetSpacing(spacing)
3350            gf = vtki.new("GeometryFilter")
3351            gf.SetInputData(img)
3352            gf.Update()
3353            poly = gf.GetOutput()
3354        else:  # fast
3355            n -= 1
3356            tbs = vtki.new("TessellatedBoxSource")
3357            tbs.SetLevel(n)
3358            if len(bounds):
3359                tbs.SetBounds(bounds)
3360            else:
3361                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3362            tbs.QuadsOn()
3363            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3364            tbs.Update()
3365            poly = tbs.GetOutput()
3366        super().__init__(poly, c=c, alpha=alpha)
3367        self.pos(pos)
3368        self.lw(1).lighting("off")
3369        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)
3334    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None:
3335        """
3336        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3337
3338        Arguments:
3339            pos : (list)
3340                position of the left bottom corner
3341            n : (int, list)
3342                number of subdivisions along each side
3343            spacing : (float)
3344                size of the side of the single quad in the 3 directions
3345        """
3346        if utils.is_sequence(n):  # slow
3347            img = vtki.vtkImageData()
3348            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3349            img.SetSpacing(spacing)
3350            gf = vtki.new("GeometryFilter")
3351            gf.SetInputData(img)
3352            gf.Update()
3353            poly = gf.GetOutput()
3354        else:  # fast
3355            n -= 1
3356            tbs = vtki.new("TessellatedBoxSource")
3357            tbs.SetLevel(n)
3358            if len(bounds):
3359                tbs.SetBounds(bounds)
3360            else:
3361                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3362            tbs.QuadsOn()
3363            #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION)
3364            tbs.Update()
3365            poly = tbs.GetOutput()
3366        super().__init__(poly, c=c, alpha=alpha)
3367        self.pos(pos)
3368        self.lw(1).lighting("off")
3369        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):
3003class Plane(Mesh):
3004    """Create a plane in space."""
3005    def __init__(
3006        self,
3007        pos=(0, 0, 0),
3008        normal=(0, 0, 1),
3009        s=(1, 1),
3010        res=(1, 1),
3011        edge_direction=(),
3012        c="gray5",
3013        alpha=1.0,
3014    ) -> None:
3015        """
3016        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
3017        to vector `normal` so that it passes through point `pos`, optionally
3018        aligning an edge with `direction`.
3019
3020        Arguments:
3021            pos : (list)
3022                position of the plane center
3023            normal : (list)
3024                normal vector to the plane
3025            s : (list)
3026                size of the plane along x and y
3027            res : (list)
3028                resolution of the plane along x and y
3029            edge_direction : (list)
3030                direction vector to align one edge of the plane
3031        """
3032        if isinstance(pos, vtki.vtkPolyData):
3033            super().__init__(pos, c, alpha)
3034
3035        else:
3036            ps = vtki.new("PlaneSource")
3037            ps.SetResolution(res[0], res[1])
3038            tri = vtki.new("TriangleFilter")
3039            tri.SetInputConnection(ps.GetOutputPort())
3040            tri.Update()
3041            super().__init__(tri.GetOutput(), c, alpha)
3042
3043            pos = utils.make3d(pos)
3044            normal = np.asarray(normal, dtype=float)
3045            axis = normal / np.linalg.norm(normal)
3046
3047            # Calculate orientation using normal
3048            theta = np.arccos(axis[2])
3049            phi = np.arctan2(axis[1], axis[0])
3050
3051            t = LinearTransform()
3052            t.scale([s[0], s[1], 1])
3053
3054            # Rotate to align normal
3055            t.rotate_y(np.rad2deg(theta))
3056            t.rotate_z(np.rad2deg(phi))
3057
3058            # Additional direction alignment
3059            if len(edge_direction) >= 2:
3060                direction = utils.make3d(edge_direction).astype(float)
3061                direction /= np.linalg.norm(direction)
3062
3063                if s[0] <= s[1]:
3064                    current_direction = np.asarray([0,1,0])
3065                else:
3066                    current_direction = np.asarray([1,0,0])
3067
3068                transformed_current_direction = t.transform_point(current_direction)
3069                n = transformed_current_direction / np.linalg.norm(transformed_current_direction)
3070
3071                if np.linalg.norm(transformed_current_direction) >= 1e-6:
3072                    angle = np.arccos(np.dot(n, direction))
3073                    t.rotate(axis=axis, angle=np.rad2deg(angle))
3074
3075            t.translate(pos)
3076            self.apply_transform(t)
3077
3078        self.lighting("off")
3079        self.name = "Plane"
3080        self.variance = 0 # used by pointcloud.fit_plane()
3081
3082    def clone(self, deep=True) -> "Plane":
3083        newplane = Plane()
3084        if deep:
3085            newplane.dataset.DeepCopy(self.dataset)
3086        else:
3087            newplane.dataset.ShallowCopy(self.dataset)
3088        newplane.copy_properties_from(self)
3089        newplane.transform = self.transform.clone()
3090        newplane.variance = 0
3091        return newplane
3092    
3093    @property
3094    def normal(self) -> np.ndarray:
3095        pts = self.coordinates
3096        # this is necessary because plane can have high resolution
3097        # p0, p1 = pts[0], pts[1]
3098        # AB = p1 - p0
3099        # AB /= np.linalg.norm(AB)
3100        # for pt in pts[2:]:
3101        #     AC = pt - p0
3102        #     AC /= np.linalg.norm(AC)
3103        #     cosine_angle = np.dot(AB, AC)
3104        #     if abs(cosine_angle) < 0.99:
3105        #         normal = np.cross(AB, AC)
3106        #         return normal / np.linalg.norm(normal)
3107        p0, p1, p2 = pts[0], pts[1], pts[int(len(pts)/2 +0.5)]
3108        AB = p1 - p0
3109        AB /= np.linalg.norm(AB)
3110        AC = p2 - p0
3111        AC /= np.linalg.norm(AC)
3112        normal = np.cross(AB, AC)
3113        return normal / np.linalg.norm(normal)
3114
3115    @property
3116    def center(self) -> np.ndarray:
3117        pts = self.coordinates
3118        return np.mean(pts, axis=0)
3119
3120    def contains(self, points, tol=0) -> np.ndarray:
3121        """
3122        Check if each of the provided point lies on this plane.
3123        `points` is an array of shape (n, 3).
3124        """
3125        points = np.array(points, dtype=float)
3126        bounds = self.coordinates
3127
3128        mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol)
3129
3130        for i in [1, 3]:
3131            AB = bounds[i] - bounds[0]
3132            AP = points - bounds[0]
3133            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3134            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3135            mask = np.logical_and(mask, mask_l)
3136            mask = np.logical_and(mask, mask_g)
3137        return mask

Create a plane in space.

Plane( pos=(0, 0, 0), normal=(0, 0, 1), s=(1, 1), res=(1, 1), edge_direction=(), c='gray5', alpha=1.0)
3005    def __init__(
3006        self,
3007        pos=(0, 0, 0),
3008        normal=(0, 0, 1),
3009        s=(1, 1),
3010        res=(1, 1),
3011        edge_direction=(),
3012        c="gray5",
3013        alpha=1.0,
3014    ) -> None:
3015        """
3016        Create a plane of size `s=(xsize, ysize)` oriented perpendicular
3017        to vector `normal` so that it passes through point `pos`, optionally
3018        aligning an edge with `direction`.
3019
3020        Arguments:
3021            pos : (list)
3022                position of the plane center
3023            normal : (list)
3024                normal vector to the plane
3025            s : (list)
3026                size of the plane along x and y
3027            res : (list)
3028                resolution of the plane along x and y
3029            edge_direction : (list)
3030                direction vector to align one edge of the plane
3031        """
3032        if isinstance(pos, vtki.vtkPolyData):
3033            super().__init__(pos, c, alpha)
3034
3035        else:
3036            ps = vtki.new("PlaneSource")
3037            ps.SetResolution(res[0], res[1])
3038            tri = vtki.new("TriangleFilter")
3039            tri.SetInputConnection(ps.GetOutputPort())
3040            tri.Update()
3041            super().__init__(tri.GetOutput(), c, alpha)
3042
3043            pos = utils.make3d(pos)
3044            normal = np.asarray(normal, dtype=float)
3045            axis = normal / np.linalg.norm(normal)
3046
3047            # Calculate orientation using normal
3048            theta = np.arccos(axis[2])
3049            phi = np.arctan2(axis[1], axis[0])
3050
3051            t = LinearTransform()
3052            t.scale([s[0], s[1], 1])
3053
3054            # Rotate to align normal
3055            t.rotate_y(np.rad2deg(theta))
3056            t.rotate_z(np.rad2deg(phi))
3057
3058            # Additional direction alignment
3059            if len(edge_direction) >= 2:
3060                direction = utils.make3d(edge_direction).astype(float)
3061                direction /= np.linalg.norm(direction)
3062
3063                if s[0] <= s[1]:
3064                    current_direction = np.asarray([0,1,0])
3065                else:
3066                    current_direction = np.asarray([1,0,0])
3067
3068                transformed_current_direction = t.transform_point(current_direction)
3069                n = transformed_current_direction / np.linalg.norm(transformed_current_direction)
3070
3071                if np.linalg.norm(transformed_current_direction) >= 1e-6:
3072                    angle = np.arccos(np.dot(n, direction))
3073                    t.rotate(axis=axis, angle=np.rad2deg(angle))
3074
3075            t.translate(pos)
3076            self.apply_transform(t)
3077
3078        self.lighting("off")
3079        self.name = "Plane"
3080        self.variance = 0 # used by pointcloud.fit_plane()

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

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
  • edge_direction : (list) direction vector to align one edge of the plane
def clone(self, deep=True) -> Plane:
3082    def clone(self, deep=True) -> "Plane":
3083        newplane = Plane()
3084        if deep:
3085            newplane.dataset.DeepCopy(self.dataset)
3086        else:
3087            newplane.dataset.ShallowCopy(self.dataset)
3088        newplane.copy_properties_from(self)
3089        newplane.transform = self.transform.clone()
3090        newplane.variance = 0
3091        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:
3120    def contains(self, points, tol=0) -> np.ndarray:
3121        """
3122        Check if each of the provided point lies on this plane.
3123        `points` is an array of shape (n, 3).
3124        """
3125        points = np.array(points, dtype=float)
3126        bounds = self.coordinates
3127
3128        mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol)
3129
3130        for i in [1, 3]:
3131            AB = bounds[i] - bounds[0]
3132            AP = points - bounds[0]
3133            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3134            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3135            mask = np.logical_and(mask, mask_l)
3136            mask = np.logical_and(mask, mask_g)
3137        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):
3234class Box(Mesh):
3235    """
3236    Build a box of specified dimensions.
3237    """
3238
3239    def __init__(
3240            self, pos=(0, 0, 0), 
3241            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3242        """
3243        Build a box of dimensions `x=length, y=width and z=height`.
3244        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3245
3246        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3247        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3248
3249        Note that the shape polygonal data contains duplicated vertices. This is to allow
3250        each face to have its own normal, which is essential for some operations.
3251        Use the `clean()` method to remove duplicate points.
3252
3253        Examples:
3254            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3255
3256                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3257        """
3258        src = vtki.new("CubeSource")
3259
3260        if len(pos) == 2:
3261            pos = (pos[0], pos[1], 0)
3262
3263        if len(pos) == 6:
3264            src.SetBounds(pos)
3265            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3266        elif len(size) == 3:
3267            length, width, height = size
3268            src.SetXLength(length)
3269            src.SetYLength(width)
3270            src.SetZLength(height)
3271            src.SetCenter(pos)
3272        else:
3273            src.SetXLength(length)
3274            src.SetYLength(width)
3275            src.SetZLength(height)
3276            src.SetCenter(pos)
3277
3278        src.Update()
3279        pd = src.GetOutput()
3280
3281        tc = [
3282            [0.0, 0.0],
3283            [1.0, 0.0],
3284            [0.0, 1.0],
3285            [1.0, 1.0],
3286            [1.0, 0.0],
3287            [0.0, 0.0],
3288            [1.0, 1.0],
3289            [0.0, 1.0],
3290            [1.0, 1.0],
3291            [1.0, 0.0],
3292            [0.0, 1.0],
3293            [0.0, 0.0],
3294            [0.0, 1.0],
3295            [0.0, 0.0],
3296            [1.0, 1.0],
3297            [1.0, 0.0],
3298            [1.0, 0.0],
3299            [0.0, 0.0],
3300            [1.0, 1.0],
3301            [0.0, 1.0],
3302            [0.0, 0.0],
3303            [1.0, 0.0],
3304            [0.0, 1.0],
3305            [1.0, 1.0],
3306        ]
3307        vtc = utils.numpy2vtk(tc)
3308        pd.GetPointData().SetTCoords(vtc)
3309        super().__init__(pd, c, alpha)
3310        self.transform = LinearTransform().translate(pos)
3311        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)
3239    def __init__(
3240            self, pos=(0, 0, 0), 
3241            length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None:
3242        """
3243        Build a box of dimensions `x=length, y=width and z=height`.
3244        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3245
3246        If `pos` is a list of 6 numbers, this will be interpreted as the bounding box:
3247        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3248
3249        Note that the shape polygonal data contains duplicated vertices. This is to allow
3250        each face to have its own normal, which is essential for some operations.
3251        Use the `clean()` method to remove duplicate points.
3252
3253        Examples:
3254            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3255
3256                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3257        """
3258        src = vtki.new("CubeSource")
3259
3260        if len(pos) == 2:
3261            pos = (pos[0], pos[1], 0)
3262
3263        if len(pos) == 6:
3264            src.SetBounds(pos)
3265            pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2]
3266        elif len(size) == 3:
3267            length, width, height = size
3268            src.SetXLength(length)
3269            src.SetYLength(width)
3270            src.SetZLength(height)
3271            src.SetCenter(pos)
3272        else:
3273            src.SetXLength(length)
3274            src.SetYLength(width)
3275            src.SetZLength(height)
3276            src.SetCenter(pos)
3277
3278        src.Update()
3279        pd = src.GetOutput()
3280
3281        tc = [
3282            [0.0, 0.0],
3283            [1.0, 0.0],
3284            [0.0, 1.0],
3285            [1.0, 1.0],
3286            [1.0, 0.0],
3287            [0.0, 0.0],
3288            [1.0, 1.0],
3289            [0.0, 1.0],
3290            [1.0, 1.0],
3291            [1.0, 0.0],
3292            [0.0, 1.0],
3293            [0.0, 0.0],
3294            [0.0, 1.0],
3295            [0.0, 0.0],
3296            [1.0, 1.0],
3297            [1.0, 0.0],
3298            [1.0, 0.0],
3299            [0.0, 0.0],
3300            [1.0, 1.0],
3301            [0.0, 1.0],
3302            [0.0, 0.0],
3303            [1.0, 0.0],
3304            [0.0, 1.0],
3305            [1.0, 1.0],
3306        ]
3307        vtc = utils.numpy2vtk(tc)
3308        pd.GetPointData().SetTCoords(vtc)
3309        super().__init__(pd, c, alpha)
3310        self.transform = LinearTransform().translate(pos)
3311        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):
3314class Cube(Box):
3315    """
3316    Build a cube shape.
3317    
3318    Note that the shape polygonal data contains duplicated vertices. This is to allow
3319    each face to have its own normal, which is essential for some operations.
3320    Use the `clean()` method to remove duplicate points.
3321    """
3322
3323    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3324        """Build a cube of size `side`."""
3325        super().__init__(pos, side, side, side, (), c, alpha)
3326        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)
3323    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None:
3324        """Build a cube of size `side`."""
3325        super().__init__(pos, side, side, side, (), c, alpha)
3326        self.name = "Cube"

Build a cube of size side.

class Spring(vedo.mesh.Mesh):
3372class Spring(Mesh):
3373    """
3374    Build a spring model.
3375    """
3376
3377    def __init__(
3378        self,
3379        start_pt=(0, 0, 0),
3380        end_pt=(1, 0, 0),
3381        coils=20,
3382        r1=0.1,
3383        r2=None,
3384        thickness=None,
3385        c="gray5",
3386        alpha=1.0,
3387    ) -> None:
3388        """
3389        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3390
3391        Arguments:
3392            coils : (int)
3393                number of coils
3394            r1 : (float)
3395                radius at start point
3396            r2 : (float)
3397                radius at end point
3398            thickness : (float)
3399                thickness of the coil section
3400        """
3401        start_pt = utils.make3d(start_pt)
3402        end_pt = utils.make3d(end_pt)
3403
3404        diff = end_pt - start_pt
3405        length = np.linalg.norm(diff)
3406        if not length:
3407            return
3408        if not r1:
3409            r1 = length / 20
3410        trange = np.linspace(0, length, num=50 * coils)
3411        om = 6.283 * (coils - 0.5) / length
3412        if not r2:
3413            r2 = r1
3414        pts = []
3415        for t in trange:
3416            f = (length - t) / length
3417            rd = r1 * f + r2 * (1 - f)
3418            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3419
3420        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3421        diff = diff / length
3422        theta = np.arccos(diff[2])
3423        phi = np.arctan2(diff[1], diff[0])
3424        sp = Line(pts)
3425        
3426        t = vtki.vtkTransform()
3427        t.Translate(start_pt)
3428        t.RotateZ(np.rad2deg(phi))
3429        t.RotateY(np.rad2deg(theta))
3430
3431        tf = vtki.new("TransformPolyDataFilter")
3432        tf.SetInputData(sp.dataset)
3433        tf.SetTransform(t)
3434        tf.Update()
3435
3436        tuf = vtki.new("TubeFilter")
3437        tuf.SetNumberOfSides(12)
3438        tuf.CappingOn()
3439        tuf.SetInputData(tf.GetOutput())
3440        if not thickness:
3441            thickness = r1 / 10
3442        tuf.SetRadius(thickness)
3443        tuf.Update()
3444
3445        super().__init__(tuf.GetOutput(), c, alpha)
3446
3447        self.phong().lighting("metallic")
3448        self.base = np.array(start_pt, dtype=float)
3449        self.top  = np.array(end_pt, dtype=float)
3450        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)
3377    def __init__(
3378        self,
3379        start_pt=(0, 0, 0),
3380        end_pt=(1, 0, 0),
3381        coils=20,
3382        r1=0.1,
3383        r2=None,
3384        thickness=None,
3385        c="gray5",
3386        alpha=1.0,
3387    ) -> None:
3388        """
3389        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3390
3391        Arguments:
3392            coils : (int)
3393                number of coils
3394            r1 : (float)
3395                radius at start point
3396            r2 : (float)
3397                radius at end point
3398            thickness : (float)
3399                thickness of the coil section
3400        """
3401        start_pt = utils.make3d(start_pt)
3402        end_pt = utils.make3d(end_pt)
3403
3404        diff = end_pt - start_pt
3405        length = np.linalg.norm(diff)
3406        if not length:
3407            return
3408        if not r1:
3409            r1 = length / 20
3410        trange = np.linspace(0, length, num=50 * coils)
3411        om = 6.283 * (coils - 0.5) / length
3412        if not r2:
3413            r2 = r1
3414        pts = []
3415        for t in trange:
3416            f = (length - t) / length
3417            rd = r1 * f + r2 * (1 - f)
3418            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3419
3420        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3421        diff = diff / length
3422        theta = np.arccos(diff[2])
3423        phi = np.arctan2(diff[1], diff[0])
3424        sp = Line(pts)
3425        
3426        t = vtki.vtkTransform()
3427        t.Translate(start_pt)
3428        t.RotateZ(np.rad2deg(phi))
3429        t.RotateY(np.rad2deg(theta))
3430
3431        tf = vtki.new("TransformPolyDataFilter")
3432        tf.SetInputData(sp.dataset)
3433        tf.SetTransform(t)
3434        tf.Update()
3435
3436        tuf = vtki.new("TubeFilter")
3437        tuf.SetNumberOfSides(12)
3438        tuf.CappingOn()
3439        tuf.SetInputData(tf.GetOutput())
3440        if not thickness:
3441            thickness = r1 / 10
3442        tuf.SetRadius(thickness)
3443        tuf.Update()
3444
3445        super().__init__(tuf.GetOutput(), c, alpha)
3446
3447        self.phong().lighting("metallic")
3448        self.base = np.array(start_pt, dtype=float)
3449        self.top  = np.array(end_pt, dtype=float)
3450        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):
3453class Cylinder(Mesh):
3454    """
3455    Build a cylinder of specified height and radius.
3456    """
3457
3458    def __init__(
3459        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3460        cap=True, res=24, c="teal3", alpha=1.0
3461    ) -> None:
3462        """
3463        Build a cylinder of specified height and radius `r`, centered at `pos`.
3464
3465        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3466        centered at `v1` and top at `v2`.
3467
3468        Arguments:
3469            cap : (bool)
3470                enable/disable the caps of the cylinder
3471            res : (int)
3472                resolution of the cylinder sides
3473
3474        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3475        """
3476        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3477            base = np.array(pos[0], dtype=float)
3478            top = np.array(pos[1], dtype=float)
3479            pos = (base + top) / 2
3480            height = np.linalg.norm(top - base)
3481            axis = top - base
3482            axis = utils.versor(axis)
3483        else:
3484            axis = utils.versor(axis)
3485            base = pos - axis * height / 2
3486            top = pos + axis * height / 2
3487
3488        cyl = vtki.new("CylinderSource")
3489        cyl.SetResolution(res)
3490        cyl.SetRadius(r)
3491        cyl.SetHeight(height)
3492        cyl.SetCapping(cap)
3493        cyl.Update()
3494
3495        theta = np.arccos(axis[2])
3496        phi = np.arctan2(axis[1], axis[0])
3497        t = vtki.vtkTransform()
3498        t.PostMultiply()
3499        t.RotateX(90)  # put it along Z
3500        t.RotateY(np.rad2deg(theta))
3501        t.RotateZ(np.rad2deg(phi))
3502        t.Translate(pos)
3503
3504        tf = vtki.new("TransformPolyDataFilter")
3505        tf.SetInputData(cyl.GetOutput())
3506        tf.SetTransform(t)
3507        tf.Update()
3508
3509        super().__init__(tf.GetOutput(), c, alpha)
3510
3511        self.phong()
3512        self.base = base
3513        self.top  = top
3514        self.transform = LinearTransform().translate(pos)
3515        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)
3458    def __init__(
3459        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1),
3460        cap=True, res=24, c="teal3", alpha=1.0
3461    ) -> None:
3462        """
3463        Build a cylinder of specified height and radius `r`, centered at `pos`.
3464
3465        If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base
3466        centered at `v1` and top at `v2`.
3467
3468        Arguments:
3469            cap : (bool)
3470                enable/disable the caps of the cylinder
3471            res : (int)
3472                resolution of the cylinder sides
3473
3474        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3475        """
3476        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3477            base = np.array(pos[0], dtype=float)
3478            top = np.array(pos[1], dtype=float)
3479            pos = (base + top) / 2
3480            height = np.linalg.norm(top - base)
3481            axis = top - base
3482            axis = utils.versor(axis)
3483        else:
3484            axis = utils.versor(axis)
3485            base = pos - axis * height / 2
3486            top = pos + axis * height / 2
3487
3488        cyl = vtki.new("CylinderSource")
3489        cyl.SetResolution(res)
3490        cyl.SetRadius(r)
3491        cyl.SetHeight(height)
3492        cyl.SetCapping(cap)
3493        cyl.Update()
3494
3495        theta = np.arccos(axis[2])
3496        phi = np.arctan2(axis[1], axis[0])
3497        t = vtki.vtkTransform()
3498        t.PostMultiply()
3499        t.RotateX(90)  # put it along Z
3500        t.RotateY(np.rad2deg(theta))
3501        t.RotateZ(np.rad2deg(phi))
3502        t.Translate(pos)
3503
3504        tf = vtki.new("TransformPolyDataFilter")
3505        tf.SetInputData(cyl.GetOutput())
3506        tf.SetTransform(t)
3507        tf.Update()
3508
3509        super().__init__(tf.GetOutput(), c, alpha)
3510
3511        self.phong()
3512        self.base = base
3513        self.top  = top
3514        self.transform = LinearTransform().translate(pos)
3515        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):
3518class Cone(Mesh):
3519    """Build a cone of specified radius and height."""
3520
3521    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3522                 res=48, c="green3", alpha=1.0) -> None:
3523        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3524        con = vtki.new("ConeSource")
3525        con.SetResolution(res)
3526        con.SetRadius(r)
3527        con.SetHeight(height)
3528        con.SetDirection(axis)
3529        con.Update()
3530        super().__init__(con.GetOutput(), c, alpha)
3531        self.phong()
3532        if len(pos) == 2:
3533            pos = (pos[0], pos[1], 0)
3534        self.pos(pos)
3535        v = utils.versor(axis) * height / 2
3536        self.base = pos - v
3537        self.top  = pos + v
3538        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)
3521    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3522                 res=48, c="green3", alpha=1.0) -> None:
3523        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3524        con = vtki.new("ConeSource")
3525        con.SetResolution(res)
3526        con.SetRadius(r)
3527        con.SetHeight(height)
3528        con.SetDirection(axis)
3529        con.Update()
3530        super().__init__(con.GetOutput(), c, alpha)
3531        self.phong()
3532        if len(pos) == 2:
3533            pos = (pos[0], pos[1], 0)
3534        self.pos(pos)
3535        v = utils.versor(axis) * height / 2
3536        self.base = pos - v
3537        self.top  = pos + v
3538        self.name = "Cone"

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

class Pyramid(Cone):
3541class Pyramid(Cone):
3542    """Build a pyramidal shape."""
3543
3544    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3545                 c="green3", alpha=1) -> None:
3546        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3547        super().__init__(pos, s, height, axis, 4, c, alpha)
3548        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)
3544    def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1),
3545                 c="green3", alpha=1) -> None:
3546        """Build a pyramid of specified base size `s` and `height`, centered at `pos`."""
3547        super().__init__(pos, s, height, axis, 4, c, alpha)
3548        self.name = "Pyramid"

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

class Torus(vedo.mesh.Mesh):
3551class Torus(Mesh):
3552    """
3553    Build a toroidal shape.
3554    """
3555
3556    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3557        """
3558        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3559        If `quad=True` a quad-mesh is generated.
3560        """
3561        if utils.is_sequence(res):
3562            res_u, res_v = res
3563        else:
3564            res_u, res_v = 3 * res, res
3565
3566        if quads:
3567            # https://github.com/marcomusy/vedo/issues/710
3568
3569            n = res_v
3570            m = res_u
3571
3572            theta = np.linspace(0, 2.0 * np.pi, n)
3573            phi = np.linspace(0, 2.0 * np.pi, m)
3574            theta, phi = np.meshgrid(theta, phi)
3575            t = r1 + r2 * np.cos(theta)
3576            x = t * np.cos(phi)
3577            y = t * np.sin(phi)
3578            z = r2 * np.sin(theta)
3579            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3580
3581            faces = []
3582            for j in range(m - 1):
3583                j1n = (j + 1) * n
3584                for i in range(n - 1):
3585                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3586
3587            super().__init__([pts, faces], c, alpha)
3588
3589        else:
3590            rs = vtki.new("ParametricTorus")
3591            rs.SetRingRadius(r1)
3592            rs.SetCrossSectionRadius(r2)
3593            pfs = vtki.new("ParametricFunctionSource")
3594            pfs.SetParametricFunction(rs)
3595            pfs.SetUResolution(res_u)
3596            pfs.SetVResolution(res_v)
3597            pfs.Update()
3598
3599            super().__init__(pfs.GetOutput(), c, alpha)
3600
3601        self.phong()
3602        if len(pos) == 2:
3603            pos = (pos[0], pos[1], 0)
3604        self.pos(pos)
3605        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)
3556    def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None:
3557        """
3558        Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`.
3559        If `quad=True` a quad-mesh is generated.
3560        """
3561        if utils.is_sequence(res):
3562            res_u, res_v = res
3563        else:
3564            res_u, res_v = 3 * res, res
3565
3566        if quads:
3567            # https://github.com/marcomusy/vedo/issues/710
3568
3569            n = res_v
3570            m = res_u
3571
3572            theta = np.linspace(0, 2.0 * np.pi, n)
3573            phi = np.linspace(0, 2.0 * np.pi, m)
3574            theta, phi = np.meshgrid(theta, phi)
3575            t = r1 + r2 * np.cos(theta)
3576            x = t * np.cos(phi)
3577            y = t * np.sin(phi)
3578            z = r2 * np.sin(theta)
3579            pts = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
3580
3581            faces = []
3582            for j in range(m - 1):
3583                j1n = (j + 1) * n
3584                for i in range(n - 1):
3585                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3586
3587            super().__init__([pts, faces], c, alpha)
3588
3589        else:
3590            rs = vtki.new("ParametricTorus")
3591            rs.SetRingRadius(r1)
3592            rs.SetCrossSectionRadius(r2)
3593            pfs = vtki.new("ParametricFunctionSource")
3594            pfs.SetParametricFunction(rs)
3595            pfs.SetUResolution(res_u)
3596            pfs.SetVResolution(res_v)
3597            pfs.Update()
3598
3599            super().__init__(pfs.GetOutput(), c, alpha)
3600
3601        self.phong()
3602        if len(pos) == 2:
3603            pos = (pos[0], pos[1], 0)
3604        self.pos(pos)
3605        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):
3608class Paraboloid(Mesh):
3609    """
3610    Build a paraboloid.
3611    """
3612
3613    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3614        """
3615        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3616
3617        Full volumetric expression is:
3618            `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`
3619
3620        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3621        """
3622        quadric = vtki.new("Quadric")
3623        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3624        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3625        #         + a3*x*y + a4*y*z + a5*x*z
3626        #         + a6*x   + a7*y   + a8*z  +a9
3627        sample = vtki.new("SampleFunction")
3628        sample.SetSampleDimensions(res, res, res)
3629        sample.SetImplicitFunction(quadric)
3630
3631        contours = vtki.new("ContourFilter")
3632        contours.SetInputConnection(sample.GetOutputPort())
3633        contours.GenerateValues(1, 0.01, 0.01)
3634        contours.Update()
3635
3636        super().__init__(contours.GetOutput(), c, alpha)
3637        self.compute_normals().phong()
3638        self.mapper.ScalarVisibilityOff()
3639        self.pos(pos)
3640        self.name = "Paraboloid"

Build a paraboloid.

Paraboloid(pos=(0, 0, 0), height=1.0, res=50, c='cyan5', alpha=1.0)
3613    def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None:
3614        """
3615        Build a paraboloid of specified height and radius `r`, centered at `pos`.
3616
3617        Full volumetric expression is:
3618            `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`
3619
3620        ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png)
3621        """
3622        quadric = vtki.new("Quadric")
3623        quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0)
3624        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3625        #         + a3*x*y + a4*y*z + a5*x*z
3626        #         + a6*x   + a7*y   + a8*z  +a9
3627        sample = vtki.new("SampleFunction")
3628        sample.SetSampleDimensions(res, res, res)
3629        sample.SetImplicitFunction(quadric)
3630
3631        contours = vtki.new("ContourFilter")
3632        contours.SetInputConnection(sample.GetOutputPort())
3633        contours.GenerateValues(1, 0.01, 0.01)
3634        contours.Update()
3635
3636        super().__init__(contours.GetOutput(), c, alpha)
3637        self.compute_normals().phong()
3638        self.mapper.ScalarVisibilityOff()
3639        self.pos(pos)
3640        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):
3643class Hyperboloid(Mesh):
3644    """
3645    Build a hyperboloid.
3646    """
3647
3648    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3649        """
3650        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3651
3652        Full volumetric expression is:
3653            `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`
3654        """
3655        q = vtki.new("Quadric")
3656        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3657        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3658        #         + a3*x*y + a4*y*z + a5*x*z
3659        #         + a6*x   + a7*y   + a8*z  +a9
3660        sample = vtki.new("SampleFunction")
3661        sample.SetSampleDimensions(res, res, res)
3662        sample.SetImplicitFunction(q)
3663
3664        contours = vtki.new("ContourFilter")
3665        contours.SetInputConnection(sample.GetOutputPort())
3666        contours.GenerateValues(1, value, value)
3667        contours.Update()
3668
3669        super().__init__(contours.GetOutput(), c, alpha)
3670        self.compute_normals().phong()
3671        self.mapper.ScalarVisibilityOff()
3672        self.pos(pos)
3673        self.name = "Hyperboloid"

Build a hyperboloid.

Hyperboloid(pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c='pink4', alpha=1.0)
3648    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None:
3649        """
3650        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3651
3652        Full volumetric expression is:
3653            `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`
3654        """
3655        q = vtki.new("Quadric")
3656        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3657        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3658        #         + a3*x*y + a4*y*z + a5*x*z
3659        #         + a6*x   + a7*y   + a8*z  +a9
3660        sample = vtki.new("SampleFunction")
3661        sample.SetSampleDimensions(res, res, res)
3662        sample.SetImplicitFunction(q)
3663
3664        contours = vtki.new("ContourFilter")
3665        contours.SetInputConnection(sample.GetOutputPort())
3666        contours.GenerateValues(1, value, value)
3667        contours.Update()
3668
3669        super().__init__(contours.GetOutput(), c, alpha)
3670        self.compute_normals().phong()
3671        self.mapper.ScalarVisibilityOff()
3672        self.pos(pos)
3673        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:
4438class TextBase:
4439    "Base class."
4440
4441    def __init__(self):
4442        "Do not instantiate this base class."
4443
4444        self.rendered_at = set()
4445        self.properties = None
4446
4447        self.name = "Text"
4448        self.filename = ""
4449        self.time = 0
4450        self.info = {}
4451
4452        if isinstance(settings.default_font, int):
4453            lfonts = list(settings.font_parameters.keys())
4454            font = settings.default_font % len(lfonts)
4455            self.fontname = lfonts[font]
4456        else:
4457            self.fontname = settings.default_font
4458
4459    def angle(self, value: float):
4460        """Orientation angle in degrees"""
4461        self.properties.SetOrientation(value)
4462        return self
4463
4464    def line_spacing(self, value: float):
4465        """Set the extra spacing between lines
4466        expressed as a text height multiplicative factor."""
4467        self.properties.SetLineSpacing(value)
4468        return self
4469
4470    def line_offset(self, value: float):
4471        """Set/Get the vertical offset (measured in pixels)."""
4472        self.properties.SetLineOffset(value)
4473        return self
4474
4475    def bold(self, value=True):
4476        """Set bold face"""
4477        self.properties.SetBold(value)
4478        return self
4479
4480    def italic(self, value=True):
4481        """Set italic face"""
4482        self.properties.SetItalic(value)
4483        return self
4484
4485    def shadow(self, offset=(1, -1)):
4486        """Text shadowing. Set to `None` to disable it."""
4487        if offset is None:
4488            self.properties.ShadowOff()
4489        else:
4490            self.properties.ShadowOn()
4491            self.properties.SetShadowOffset(offset)
4492        return self
4493
4494    def color(self, c=None):
4495        """Set the text color"""
4496        if c is None:
4497            return get_color(self.properties.GetColor())
4498        self.properties.SetColor(get_color(c))
4499        return self
4500
4501    def c(self, color=None):
4502        """Set the text color"""
4503        if color is None:
4504            return get_color(self.properties.GetColor())
4505        return self.color(color)
4506
4507    def alpha(self, value: float):
4508        """Set the text opacity"""
4509        self.properties.SetBackgroundOpacity(value)
4510        return self
4511
4512    def background(self, color="k9", alpha=1.0):
4513        """Text background. Set to `None` to disable it."""
4514        bg = get_color(color)
4515        if color is None:
4516            self.properties.SetBackgroundOpacity(0)
4517        else:
4518            self.properties.SetBackgroundColor(bg)
4519            if alpha:
4520                self.properties.SetBackgroundOpacity(alpha)
4521        return self
4522
4523    def frame(self, color="k1", lw=2):
4524        """Border color and width"""
4525        if color is None:
4526            self.properties.FrameOff()
4527        else:
4528            c = get_color(color)
4529            self.properties.FrameOn()
4530            self.properties.SetFrameColor(c)
4531            self.properties.SetFrameWidth(lw)
4532        return self
4533
4534    def font(self, font: str):
4535        """Text font face"""
4536        if isinstance(font, int):
4537            lfonts = list(settings.font_parameters.keys())
4538            n = font % len(lfonts)
4539            font = lfonts[n]
4540            self.fontname = font
4541
4542        if not font:  # use default font
4543            font = self.fontname
4544            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4545        elif font.startswith("https"):  # user passed URL link, make it a path
4546            fpath = vedo.file_io.download(font, verbose=False, force=False)
4547        elif font.endswith(".ttf"):  # user passing a local path to font file
4548            fpath = font
4549        else:  # user passing name of preset font
4550            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4551
4552        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4553        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4554        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4555        else:
4556            fpath = utils.get_font_path(font)
4557            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4558            self.properties.SetFontFile(fpath)
4559        self.fontname = font  # io.tonumpy() uses it
4560
4561        return self
4562
4563    def on(self):
4564        """Make text visible"""
4565        self.actor.SetVisibility(True)
4566        return self
4567
4568    def off(self):
4569        """Make text invisible"""
4570        self.actor.SetVisibility(False)
4571        return self

Base class.

TextBase()
4441    def __init__(self):
4442        "Do not instantiate this base class."
4443
4444        self.rendered_at = set()
4445        self.properties = None
4446
4447        self.name = "Text"
4448        self.filename = ""
4449        self.time = 0
4450        self.info = {}
4451
4452        if isinstance(settings.default_font, int):
4453            lfonts = list(settings.font_parameters.keys())
4454            font = settings.default_font % len(lfonts)
4455            self.fontname = lfonts[font]
4456        else:
4457            self.fontname = settings.default_font

Do not instantiate this base class.

def angle(self, value: float):
4459    def angle(self, value: float):
4460        """Orientation angle in degrees"""
4461        self.properties.SetOrientation(value)
4462        return self

Orientation angle in degrees

def line_spacing(self, value: float):
4464    def line_spacing(self, value: float):
4465        """Set the extra spacing between lines
4466        expressed as a text height multiplicative factor."""
4467        self.properties.SetLineSpacing(value)
4468        return self

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

def line_offset(self, value: float):
4470    def line_offset(self, value: float):
4471        """Set/Get the vertical offset (measured in pixels)."""
4472        self.properties.SetLineOffset(value)
4473        return self

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

def bold(self, value=True):
4475    def bold(self, value=True):
4476        """Set bold face"""
4477        self.properties.SetBold(value)
4478        return self

Set bold face

def italic(self, value=True):
4480    def italic(self, value=True):
4481        """Set italic face"""
4482        self.properties.SetItalic(value)
4483        return self

Set italic face

def shadow(self, offset=(1, -1)):
4485    def shadow(self, offset=(1, -1)):
4486        """Text shadowing. Set to `None` to disable it."""
4487        if offset is None:
4488            self.properties.ShadowOff()
4489        else:
4490            self.properties.ShadowOn()
4491            self.properties.SetShadowOffset(offset)
4492        return self

Text shadowing. Set to None to disable it.

def color(self, c=None):
4494    def color(self, c=None):
4495        """Set the text color"""
4496        if c is None:
4497            return get_color(self.properties.GetColor())
4498        self.properties.SetColor(get_color(c))
4499        return self

Set the text color

def c(self, color=None):
4501    def c(self, color=None):
4502        """Set the text color"""
4503        if color is None:
4504            return get_color(self.properties.GetColor())
4505        return self.color(color)

Set the text color

def alpha(self, value: float):
4507    def alpha(self, value: float):
4508        """Set the text opacity"""
4509        self.properties.SetBackgroundOpacity(value)
4510        return self

Set the text opacity

def background(self, color='k9', alpha=1.0):
4512    def background(self, color="k9", alpha=1.0):
4513        """Text background. Set to `None` to disable it."""
4514        bg = get_color(color)
4515        if color is None:
4516            self.properties.SetBackgroundOpacity(0)
4517        else:
4518            self.properties.SetBackgroundColor(bg)
4519            if alpha:
4520                self.properties.SetBackgroundOpacity(alpha)
4521        return self

Text background. Set to None to disable it.

def frame(self, color='k1', lw=2):
4523    def frame(self, color="k1", lw=2):
4524        """Border color and width"""
4525        if color is None:
4526            self.properties.FrameOff()
4527        else:
4528            c = get_color(color)
4529            self.properties.FrameOn()
4530            self.properties.SetFrameColor(c)
4531            self.properties.SetFrameWidth(lw)
4532        return self

Border color and width

def font(self, font: str):
4534    def font(self, font: str):
4535        """Text font face"""
4536        if isinstance(font, int):
4537            lfonts = list(settings.font_parameters.keys())
4538            n = font % len(lfonts)
4539            font = lfonts[n]
4540            self.fontname = font
4541
4542        if not font:  # use default font
4543            font = self.fontname
4544            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4545        elif font.startswith("https"):  # user passed URL link, make it a path
4546            fpath = vedo.file_io.download(font, verbose=False, force=False)
4547        elif font.endswith(".ttf"):  # user passing a local path to font file
4548            fpath = font
4549        else:  # user passing name of preset font
4550            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4551
4552        if   font == "Courier": self.properties.SetFontFamilyToCourier()
4553        elif font == "Times":   self.properties.SetFontFamilyToTimes()
4554        elif font == "Arial":   self.properties.SetFontFamilyToArial()
4555        else:
4556            fpath = utils.get_font_path(font)
4557            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
4558            self.properties.SetFontFile(fpath)
4559        self.fontname = font  # io.tonumpy() uses it
4560
4561        return self

Text font face

def on(self):
4563    def on(self):
4564        """Make text visible"""
4565        self.actor.SetVisibility(True)
4566        return self

Make text visible

def off(self):
4568    def off(self):
4569        """Make text invisible"""
4570        self.actor.SetVisibility(False)
4571        return self

Make text invisible

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

Update the text and some of its properties.

Check available fonts here.

class Text2D(TextBase, vedo.visual.Actor2D):
4573class Text2D(TextBase, vedo.visual.Actor2D):
4574    """
4575    Create a 2D text object.
4576    """
4577    def __init__(
4578        self,
4579        txt="",
4580        pos="top-left",
4581        s=1.0,
4582        bg=None,
4583        font="",
4584        justify="",
4585        bold=False,
4586        italic=False,
4587        c=None,
4588        alpha=0.5,
4589    ) -> None:
4590        """
4591        Create a 2D text object.
4592
4593        All properties of the text, and the text itself, can be changed after creation
4594        (which is especially useful in loops).
4595
4596        Arguments:
4597            pos : (str)
4598                text is placed in one of the 8 positions:
4599                - bottom-left
4600                - bottom-right
4601                - top-left
4602                - top-right
4603                - bottom-middle
4604                - middle-right
4605                - middle-left
4606                - top-middle
4607
4608                If a pair (x,y) is passed as input the 2D text is place at that
4609                position in the coordinate system of the 2D screen (with the
4610                origin sitting at the bottom left).
4611
4612            s : (float)
4613                size of text
4614            bg : (color)
4615                background color
4616            alpha : (float)
4617                background opacity
4618            justify : (str)
4619                text justification
4620
4621            font : (str)
4622                built-in available fonts are:
4623                - Antares
4624                - Arial
4625                - Bongas
4626                - Calco
4627                - Comae
4628                - ComicMono
4629                - Courier
4630                - Glasgo
4631                - Kanopus
4632                - LogoType
4633                - Normografo
4634                - Quikhand
4635                - SmartCouric
4636                - Theemim
4637                - Times
4638                - VictorMono
4639                - More fonts at: https://vedo.embl.es/fonts/
4640
4641                A path to a `.otf` or `.ttf` font-file can also be supplied as input.
4642
4643        Examples:
4644            - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py)
4645            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
4646            - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py)
4647
4648                ![](https://vedo.embl.es/images/basic/colorcubes.png)
4649        """
4650        super().__init__()
4651        self.name = "Text2D"
4652
4653        self.mapper = vtki.new("TextMapper")
4654        self.SetMapper(self.mapper)
4655
4656        self.properties = self.mapper.GetTextProperty()
4657        self.actor = self
4658        self.actor.retrieve_object = weak_ref_to(self)
4659
4660        self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport()
4661
4662        # automatic black or white
4663        if c is None:
4664            c = (0.1, 0.1, 0.1)
4665            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4666                if vedo.plotter_instance.renderer.GetGradientBackground():
4667                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4668                else:
4669                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4670                c = (0.9, 0.9, 0.9)
4671                if np.sum(bgcol) > 1.5:
4672                    c = (0.1, 0.1, 0.1)
4673
4674        self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic)
4675        self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5)
4676        self.PickableOff()
4677
4678    def pos(self, pos="top-left", justify=""):
4679        """
4680        Set position of the text to draw. Keyword `pos` can be a string
4681        or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4682        """
4683        ajustify = "top-left"  # autojustify
4684        if isinstance(pos, str):  # corners
4685            ajustify = pos
4686            if "top" in pos:
4687                if "left" in pos:
4688                    pos = (0.008, 0.994)
4689                elif "right" in pos:
4690                    pos = (0.994, 0.994)
4691                elif "mid" in pos or "cent" in pos:
4692                    pos = (0.5, 0.994)
4693            elif "bottom" in pos:
4694                if "left" in pos:
4695                    pos = (0.008, 0.008)
4696                elif "right" in pos:
4697                    pos = (0.994, 0.008)
4698                elif "mid" in pos or "cent" in pos:
4699                    pos = (0.5, 0.008)
4700            elif "mid" in pos or "cent" in pos:
4701                if "left" in pos:
4702                    pos = (0.008, 0.5)
4703                elif "right" in pos:
4704                    pos = (0.994, 0.5)
4705                else:
4706                    pos = (0.5, 0.5)
4707
4708            else:
4709                vedo.logger.warning(f"cannot understand text position {pos}")
4710                pos = (0.008, 0.994)
4711                ajustify = "top-left"
4712
4713        elif len(pos) != 2:
4714            vedo.logger.error("pos must be of length 2 or integer value or string")
4715            raise RuntimeError()
4716
4717        if not justify:
4718            justify = ajustify
4719
4720        self.properties.SetJustificationToLeft()
4721        if "top" in justify:
4722            self.properties.SetVerticalJustificationToTop()
4723        if "bottom" in justify:
4724            self.properties.SetVerticalJustificationToBottom()
4725        if "cent" in justify or "mid" in justify:
4726            self.properties.SetJustificationToCentered()
4727        if "left" in justify:
4728            self.properties.SetJustificationToLeft()
4729        if "right" in justify:
4730            self.properties.SetJustificationToRight()
4731
4732        self.SetPosition(pos)
4733        return self
4734
4735    def text(self, txt=None):
4736        """Set/get the input text string."""
4737        if txt is None:
4738            return self.mapper.GetInput()
4739
4740        if ":" in txt:
4741            for r in _reps:
4742                txt = txt.replace(r[0], r[1])
4743        else:
4744            txt = str(txt)
4745
4746        self.mapper.SetInput(txt)
4747        return self
4748
4749    def size(self, s):
4750        """Set the font size."""
4751        self.properties.SetFontSize(int(s * 22.5))
4752        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)
4577    def __init__(
4578        self,
4579        txt="",
4580        pos="top-left",
4581        s=1.0,
4582        bg=None,
4583        font="",
4584        justify="",
4585        bold=False,
4586        italic=False,
4587        c=None,
4588        alpha=0.5,
4589    ) -> None:
4590        """
4591        Create a 2D text object.
4592
4593        All properties of the text, and the text itself, can be changed after creation
4594        (which is especially useful in loops).
4595
4596        Arguments:
4597            pos : (str)
4598                text is placed in one of the 8 positions:
4599                - bottom-left
4600                - bottom-right
4601                - top-left
4602                - top-right
4603                - bottom-middle
4604                - middle-right
4605                - middle-left
4606                - top-middle
4607
4608                If a pair (x,y) is passed as input the 2D text is place at that
4609                position in the coordinate system of the 2D screen (with the
4610                origin sitting at the bottom left).
4611
4612            s : (float)
4613                size of text
4614            bg : (color)
4615                background color
4616            alpha : (float)
4617                background opacity
4618            justify : (str)
4619                text justification
4620
4621            font : (str)
4622                built-in available fonts are:
4623                - Antares
4624                - Arial
4625                - Bongas
4626                - Calco
4627                - Comae
4628                - ComicMono
4629                - Courier
4630                - Glasgo
4631                - Kanopus
4632                - LogoType
4633                - Normografo
4634                - Quikhand
4635                - SmartCouric
4636                - Theemim
4637                - Times
4638                - VictorMono
4639                - More fonts at: https://vedo.embl.es/fonts/
4640
4641                A path to a `.otf` or `.ttf` font-file can also be supplied as input.
4642
4643        Examples:
4644            - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py)
4645            - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py)
4646            - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py)
4647
4648                ![](https://vedo.embl.es/images/basic/colorcubes.png)
4649        """
4650        super().__init__()
4651        self.name = "Text2D"
4652
4653        self.mapper = vtki.new("TextMapper")
4654        self.SetMapper(self.mapper)
4655
4656        self.properties = self.mapper.GetTextProperty()
4657        self.actor = self
4658        self.actor.retrieve_object = weak_ref_to(self)
4659
4660        self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport()
4661
4662        # automatic black or white
4663        if c is None:
4664            c = (0.1, 0.1, 0.1)
4665            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4666                if vedo.plotter_instance.renderer.GetGradientBackground():
4667                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4668                else:
4669                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4670                c = (0.9, 0.9, 0.9)
4671                if np.sum(bgcol) > 1.5:
4672                    c = (0.1, 0.1, 0.1)
4673
4674        self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic)
4675        self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5)
4676        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=''):
4678    def pos(self, pos="top-left", justify=""):
4679        """
4680        Set position of the text to draw. Keyword `pos` can be a string
4681        or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4682        """
4683        ajustify = "top-left"  # autojustify
4684        if isinstance(pos, str):  # corners
4685            ajustify = pos
4686            if "top" in pos:
4687                if "left" in pos:
4688                    pos = (0.008, 0.994)
4689                elif "right" in pos:
4690                    pos = (0.994, 0.994)
4691                elif "mid" in pos or "cent" in pos:
4692                    pos = (0.5, 0.994)
4693            elif "bottom" in pos:
4694                if "left" in pos:
4695                    pos = (0.008, 0.008)
4696                elif "right" in pos:
4697                    pos = (0.994, 0.008)
4698                elif "mid" in pos or "cent" in pos:
4699                    pos = (0.5, 0.008)
4700            elif "mid" in pos or "cent" in pos:
4701                if "left" in pos:
4702                    pos = (0.008, 0.5)
4703                elif "right" in pos:
4704                    pos = (0.994, 0.5)
4705                else:
4706                    pos = (0.5, 0.5)
4707
4708            else:
4709                vedo.logger.warning(f"cannot understand text position {pos}")
4710                pos = (0.008, 0.994)
4711                ajustify = "top-left"
4712
4713        elif len(pos) != 2:
4714            vedo.logger.error("pos must be of length 2 or integer value or string")
4715            raise RuntimeError()
4716
4717        if not justify:
4718            justify = ajustify
4719
4720        self.properties.SetJustificationToLeft()
4721        if "top" in justify:
4722            self.properties.SetVerticalJustificationToTop()
4723        if "bottom" in justify:
4724            self.properties.SetVerticalJustificationToBottom()
4725        if "cent" in justify or "mid" in justify:
4726            self.properties.SetJustificationToCentered()
4727        if "left" in justify:
4728            self.properties.SetJustificationToLeft()
4729        if "right" in justify:
4730            self.properties.SetJustificationToRight()
4731
4732        self.SetPosition(pos)
4733        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):
4735    def text(self, txt=None):
4736        """Set/get the input text string."""
4737        if txt is None:
4738            return self.mapper.GetInput()
4739
4740        if ":" in txt:
4741            for r in _reps:
4742                txt = txt.replace(r[0], r[1])
4743        else:
4744            txt = str(txt)
4745
4746        self.mapper.SetInput(txt)
4747        return self

Set/get the input text string.

def size(self, s):
4749    def size(self, s):
4750        """Set the font size."""
4751        self.properties.SetFontSize(int(s * 22.5))
4752        return self

Set the font size.

class CornerAnnotation(TextBase, vtkmodules.vtkRenderingAnnotation.vtkCornerAnnotation):
4755class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation):
4756    # PROBABLY USELESS given that Text2D does pretty much the same ...
4757    """
4758    Annotate the window corner with 2D text.
4759
4760    See `Text2D` description as the basic functionality is very similar.
4761
4762    The added value of this class is the possibility to manage with one single
4763    object the all corner annotations (instead of creating 4 `Text2D` instances).
4764
4765    Examples:
4766        - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
4767    """
4768
4769    def __init__(self, c=None) -> None:
4770
4771        super().__init__()
4772
4773        self.properties = self.GetTextProperty()
4774
4775        # automatic black or white
4776        if c is None:
4777            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4778                c = (0.9, 0.9, 0.9)
4779                if vedo.plotter_instance.renderer.GetGradientBackground():
4780                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4781                else:
4782                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4783                if np.sum(bgcol) > 1.5:
4784                    c = (0.1, 0.1, 0.1)
4785            else:
4786                c = (0.5, 0.5, 0.5)
4787
4788        self.SetNonlinearFontScaleFactor(1 / 2.75)
4789        self.PickableOff()
4790        self.properties.SetColor(get_color(c))
4791        self.properties.SetBold(False)
4792        self.properties.SetItalic(False)
4793
4794    def size(self, s:float, linear=False) -> "CornerAnnotation":
4795        """
4796        The font size is calculated as the largest possible value such that the annotations
4797        for the given viewport do not overlap.
4798
4799        This font size can be scaled non-linearly with the viewport size, to maintain an
4800        acceptable readable size at larger viewport sizes, without being too big.
4801        `f' = linearScale * pow(f,nonlinearScale)`
4802        """
4803        if linear:
4804            self.SetLinearFontScaleFactor(s * 5.5)
4805        else:
4806            self.SetNonlinearFontScaleFactor(s / 2.75)
4807        return self
4808
4809    def text(self, txt: str, pos=2) -> "CornerAnnotation":
4810        """Set text at the assigned position"""
4811
4812        if isinstance(pos, str):  # corners
4813            if "top" in pos:
4814                if "left" in pos: pos = 2
4815                elif "right" in pos: pos = 3
4816                elif "mid" in pos or "cent" in pos: pos = 7
4817            elif "bottom" in pos:
4818                if "left" in pos: pos = 0
4819                elif "right" in pos: pos = 1
4820                elif "mid" in pos or "cent" in pos: pos = 4
4821            else:
4822                if "left" in pos: pos = 6
4823                elif "right" in pos: pos = 5
4824                else: pos = 2
4825
4826        if "\\" in repr(txt):
4827            for r in _reps:
4828                txt = txt.replace(r[0], r[1])
4829        else:
4830            txt = str(txt)
4831
4832        self.SetText(pos, txt)
4833        return self
4834
4835    def clear(self) -> "CornerAnnotation":
4836        """Remove all text from all corners"""
4837        self.ClearAllTexts()
4838        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)
4769    def __init__(self, c=None) -> None:
4770
4771        super().__init__()
4772
4773        self.properties = self.GetTextProperty()
4774
4775        # automatic black or white
4776        if c is None:
4777            if vedo.plotter_instance and vedo.plotter_instance.renderer:
4778                c = (0.9, 0.9, 0.9)
4779                if vedo.plotter_instance.renderer.GetGradientBackground():
4780                    bgcol = vedo.plotter_instance.renderer.GetBackground2()
4781                else:
4782                    bgcol = vedo.plotter_instance.renderer.GetBackground()
4783                if np.sum(bgcol) > 1.5:
4784                    c = (0.1, 0.1, 0.1)
4785            else:
4786                c = (0.5, 0.5, 0.5)
4787
4788        self.SetNonlinearFontScaleFactor(1 / 2.75)
4789        self.PickableOff()
4790        self.properties.SetColor(get_color(c))
4791        self.properties.SetBold(False)
4792        self.properties.SetItalic(False)

Do not instantiate this base class.

def size(self, s: float, linear=False) -> CornerAnnotation:
4794    def size(self, s:float, linear=False) -> "CornerAnnotation":
4795        """
4796        The font size is calculated as the largest possible value such that the annotations
4797        for the given viewport do not overlap.
4798
4799        This font size can be scaled non-linearly with the viewport size, to maintain an
4800        acceptable readable size at larger viewport sizes, without being too big.
4801        `f' = linearScale * pow(f,nonlinearScale)`
4802        """
4803        if linear:
4804            self.SetLinearFontScaleFactor(s * 5.5)
4805        else:
4806            self.SetNonlinearFontScaleFactor(s / 2.75)
4807        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:
4809    def text(self, txt: str, pos=2) -> "CornerAnnotation":
4810        """Set text at the assigned position"""
4811
4812        if isinstance(pos, str):  # corners
4813            if "top" in pos:
4814                if "left" in pos: pos = 2
4815                elif "right" in pos: pos = 3
4816                elif "mid" in pos or "cent" in pos: pos = 7
4817            elif "bottom" in pos:
4818                if "left" in pos: pos = 0
4819                elif "right" in pos: pos = 1
4820                elif "mid" in pos or "cent" in pos: pos = 4
4821            else:
4822                if "left" in pos: pos = 6
4823                elif "right" in pos: pos = 5
4824                else: pos = 2
4825
4826        if "\\" in repr(txt):
4827            for r in _reps:
4828                txt = txt.replace(r[0], r[1])
4829        else:
4830            txt = str(txt)
4831
4832        self.SetText(pos, txt)
4833        return self

Set text at the assigned position

def clear(self) -> CornerAnnotation:
4835    def clear(self) -> "CornerAnnotation":
4836        """Remove all text from all corners"""
4837        self.ClearAllTexts()
4838        return self

Remove all text from all corners

class Latex(vedo.image.Image):
4841class Latex(Image):
4842    """
4843    Render Latex text and formulas.
4844    """
4845
4846    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4847        """
4848        Render Latex text and formulas.
4849
4850        Arguments:
4851            formula : (str)
4852                latex text string
4853            pos : (list)
4854                position coordinates in space
4855            bg : (color)
4856                background color box
4857            res : (int)
4858                dpi resolution
4859            usetex : (bool)
4860                use latex compiler of matplotlib if available
4861
4862        You can access the latex formula in `Latex.formula`.
4863
4864        Examples:
4865            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4866
4867            ![](https://vedo.embl.es/images/pyplot/latex.png)
4868        """
4869        from tempfile import NamedTemporaryFile
4870        import matplotlib.pyplot as mpltib
4871
4872        def build_img_plt(formula, tfile):
4873
4874            mpltib.rc("text", usetex=usetex)
4875
4876            formula1 = "$" + formula + "$"
4877            mpltib.axis("off")
4878            col = get_color(c)
4879            if bg:
4880                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4881            else:
4882                bx = None
4883            mpltib.text(
4884                0.5,
4885                0.5,
4886                formula1,
4887                size=res,
4888                color=col,
4889                alpha=alpha,
4890                ha="center",
4891                va="center",
4892                bbox=bx,
4893            )
4894            mpltib.savefig(
4895                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4896            )
4897            mpltib.close()
4898
4899        if len(pos) == 2:
4900            pos = (pos[0], pos[1], 0)
4901
4902        tmp_file = NamedTemporaryFile(delete=True)
4903        tmp_file.name = tmp_file.name + ".png"
4904
4905        build_img_plt(formula, tmp_file.name)
4906
4907        super().__init__(tmp_file.name, channels=4)
4908        self.alpha(alpha)
4909        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4910        self.pos(pos)
4911        self.name = "Latex"
4912        self.formula = formula
4913
4914        # except:
4915        #     printc("Error in Latex()\n", formula, c="r")
4916        #     printc(" latex or dvipng not installed?", c="r")
4917        #     printc(" Try: usetex=False", c="r")
4918        #     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)
4846    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None:
4847        """
4848        Render Latex text and formulas.
4849
4850        Arguments:
4851            formula : (str)
4852                latex text string
4853            pos : (list)
4854                position coordinates in space
4855            bg : (color)
4856                background color box
4857            res : (int)
4858                dpi resolution
4859            usetex : (bool)
4860                use latex compiler of matplotlib if available
4861
4862        You can access the latex formula in `Latex.formula`.
4863
4864        Examples:
4865            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4866
4867            ![](https://vedo.embl.es/images/pyplot/latex.png)
4868        """
4869        from tempfile import NamedTemporaryFile
4870        import matplotlib.pyplot as mpltib
4871
4872        def build_img_plt(formula, tfile):
4873
4874            mpltib.rc("text", usetex=usetex)
4875
4876            formula1 = "$" + formula + "$"
4877            mpltib.axis("off")
4878            col = get_color(c)
4879            if bg:
4880                bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4881            else:
4882                bx = None
4883            mpltib.text(
4884                0.5,
4885                0.5,
4886                formula1,
4887                size=res,
4888                color=col,
4889                alpha=alpha,
4890                ha="center",
4891                va="center",
4892                bbox=bx,
4893            )
4894            mpltib.savefig(
4895                tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4896            )
4897            mpltib.close()
4898
4899        if len(pos) == 2:
4900            pos = (pos[0], pos[1], 0)
4901
4902        tmp_file = NamedTemporaryFile(delete=True)
4903        tmp_file.name = tmp_file.name + ".png"
4904
4905        build_img_plt(formula, tmp_file.name)
4906
4907        super().__init__(tmp_file.name, channels=4)
4908        self.alpha(alpha)
4909        self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s])
4910        self.pos(pos)
4911        self.name = "Latex"
4912        self.formula = formula
4913
4914        # except:
4915        #     printc("Error in Latex()\n", formula, c="r")
4916        #     printc(" latex or dvipng not installed?", c="r")
4917        #     printc(" Try: usetex=False", c="r")
4918        #     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):
3912class ParametricShape(Mesh):
3913    """
3914    A set of built-in shapes mainly for illustration purposes.
3915    """
3916
3917    def __init__(self, name, res=51, n=25, seed=1):
3918        """
3919        A set of built-in shapes mainly for illustration purposes.
3920
3921        Name can be an integer or a string in this list:
3922            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3923            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3924            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3925            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3926
3927        Example:
3928            ```python
3929            from vedo import *
3930            settings.immediate_rendering = False
3931            plt = Plotter(N=18)
3932            for i in range(18):
3933                ps = ParametricShape(i).color(i)
3934                plt.at(i).show(ps, ps.name)
3935            plt.interactive().close()
3936            ```
3937            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3938        """
3939
3940        shapes = [
3941            "Boy",
3942            "ConicSpiral",
3943            "CrossCap",
3944            "Enneper",
3945            "Figure8Klein",
3946            "Klein",
3947            "Dini",
3948            "Mobius",
3949            "RandomHills",
3950            "Roman",
3951            "SuperEllipsoid",
3952            "BohemianDome",
3953            "Bour",
3954            "CatalanMinimal",
3955            "Henneberg",
3956            "Kuen",
3957            "PluckerConoid",
3958            "Pseudosphere",
3959        ]
3960
3961        if isinstance(name, int):
3962            name = name % len(shapes)
3963            name = shapes[name]
3964
3965        if name == "Boy":
3966            ps = vtki.new("ParametricBoy")
3967        elif name == "ConicSpiral":
3968            ps = vtki.new("ParametricConicSpiral")
3969        elif name == "CrossCap":
3970            ps = vtki.new("ParametricCrossCap")
3971        elif name == "Dini":
3972            ps = vtki.new("ParametricDini")
3973        elif name == "Enneper":
3974            ps = vtki.new("ParametricEnneper")
3975        elif name == "Figure8Klein":
3976            ps = vtki.new("ParametricFigure8Klein")
3977        elif name == "Klein":
3978            ps = vtki.new("ParametricKlein")
3979        elif name == "Mobius":
3980            ps = vtki.new("ParametricMobius")
3981            ps.SetRadius(2.0)
3982            ps.SetMinimumV(-0.5)
3983            ps.SetMaximumV(0.5)
3984        elif name == "RandomHills":
3985            ps = vtki.new("ParametricRandomHills")
3986            ps.AllowRandomGenerationOn()
3987            ps.SetRandomSeed(seed)
3988            ps.SetNumberOfHills(n)
3989        elif name == "Roman":
3990            ps = vtki.new("ParametricRoman")
3991        elif name == "SuperEllipsoid":
3992            ps = vtki.new("ParametricSuperEllipsoid")
3993            ps.SetN1(0.5)
3994            ps.SetN2(0.4)
3995        elif name == "BohemianDome":
3996            ps = vtki.new("ParametricBohemianDome")
3997            ps.SetA(5.0)
3998            ps.SetB(1.0)
3999            ps.SetC(2.0)
4000        elif name == "Bour":
4001            ps = vtki.new("ParametricBour")
4002        elif name == "CatalanMinimal":
4003            ps = vtki.new("ParametricCatalanMinimal")
4004        elif name == "Henneberg":
4005            ps = vtki.new("ParametricHenneberg")
4006        elif name == "Kuen":
4007            ps = vtki.new("ParametricKuen")
4008            ps.SetDeltaV0(0.001)
4009        elif name == "PluckerConoid":
4010            ps = vtki.new("ParametricPluckerConoid")
4011        elif name == "Pseudosphere":
4012            ps = vtki.new("ParametricPseudosphere")
4013        else:
4014            vedo.logger.error(f"unknown ParametricShape {name}")
4015            return
4016
4017        pfs = vtki.new("ParametricFunctionSource")
4018        pfs.SetParametricFunction(ps)
4019        pfs.SetUResolution(res)
4020        pfs.SetVResolution(res)
4021        pfs.SetWResolution(res)
4022        pfs.SetScalarModeToZ()
4023        pfs.Update()
4024
4025        super().__init__(pfs.GetOutput())
4026
4027        if name == "RandomHills": self.shift([0,-10,-2.25])
4028        if name != 'Kuen': self.normalize()
4029        if name == 'Dini': self.scale(0.4)
4030        if name == 'Enneper': self.scale(0.4)
4031        if name == 'ConicSpiral': self.bc('tomato')
4032        self.name = name

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

ParametricShape(name, res=51, n=25, seed=1)
3917    def __init__(self, name, res=51, n=25, seed=1):
3918        """
3919        A set of built-in shapes mainly for illustration purposes.
3920
3921        Name can be an integer or a string in this list:
3922            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3923            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3924            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3925            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3926
3927        Example:
3928            ```python
3929            from vedo import *
3930            settings.immediate_rendering = False
3931            plt = Plotter(N=18)
3932            for i in range(18):
3933                ps = ParametricShape(i).color(i)
3934                plt.at(i).show(ps, ps.name)
3935            plt.interactive().close()
3936            ```
3937            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3938        """
3939
3940        shapes = [
3941            "Boy",
3942            "ConicSpiral",
3943            "CrossCap",
3944            "Enneper",
3945            "Figure8Klein",
3946            "Klein",
3947            "Dini",
3948            "Mobius",
3949            "RandomHills",
3950            "Roman",
3951            "SuperEllipsoid",
3952            "BohemianDome",
3953            "Bour",
3954            "CatalanMinimal",
3955            "Henneberg",
3956            "Kuen",
3957            "PluckerConoid",
3958            "Pseudosphere",
3959        ]
3960
3961        if isinstance(name, int):
3962            name = name % len(shapes)
3963            name = shapes[name]
3964
3965        if name == "Boy":
3966            ps = vtki.new("ParametricBoy")
3967        elif name == "ConicSpiral":
3968            ps = vtki.new("ParametricConicSpiral")
3969        elif name == "CrossCap":
3970            ps = vtki.new("ParametricCrossCap")
3971        elif name == "Dini":
3972            ps = vtki.new("ParametricDini")
3973        elif name == "Enneper":
3974            ps = vtki.new("ParametricEnneper")
3975        elif name == "Figure8Klein":
3976            ps = vtki.new("ParametricFigure8Klein")
3977        elif name == "Klein":
3978            ps = vtki.new("ParametricKlein")
3979        elif name == "Mobius":
3980            ps = vtki.new("ParametricMobius")
3981            ps.SetRadius(2.0)
3982            ps.SetMinimumV(-0.5)
3983            ps.SetMaximumV(0.5)
3984        elif name == "RandomHills":
3985            ps = vtki.new("ParametricRandomHills")
3986            ps.AllowRandomGenerationOn()
3987            ps.SetRandomSeed(seed)
3988            ps.SetNumberOfHills(n)
3989        elif name == "Roman":
3990            ps = vtki.new("ParametricRoman")
3991        elif name == "SuperEllipsoid":
3992            ps = vtki.new("ParametricSuperEllipsoid")
3993            ps.SetN1(0.5)
3994            ps.SetN2(0.4)
3995        elif name == "BohemianDome":
3996            ps = vtki.new("ParametricBohemianDome")
3997            ps.SetA(5.0)
3998            ps.SetB(1.0)
3999            ps.SetC(2.0)
4000        elif name == "Bour":
4001            ps = vtki.new("ParametricBour")
4002        elif name == "CatalanMinimal":
4003            ps = vtki.new("ParametricCatalanMinimal")
4004        elif name == "Henneberg":
4005            ps = vtki.new("ParametricHenneberg")
4006        elif name == "Kuen":
4007            ps = vtki.new("ParametricKuen")
4008            ps.SetDeltaV0(0.001)
4009        elif name == "PluckerConoid":
4010            ps = vtki.new("ParametricPluckerConoid")
4011        elif name == "Pseudosphere":
4012            ps = vtki.new("ParametricPseudosphere")
4013        else:
4014            vedo.logger.error(f"unknown ParametricShape {name}")
4015            return
4016
4017        pfs = vtki.new("ParametricFunctionSource")
4018        pfs.SetParametricFunction(ps)
4019        pfs.SetUResolution(res)
4020        pfs.SetVResolution(res)
4021        pfs.SetWResolution(res)
4022        pfs.SetScalarModeToZ()
4023        pfs.Update()
4024
4025        super().__init__(pfs.GetOutput())
4026
4027        if name == "RandomHills": self.shift([0,-10,-2.25])
4028        if name != 'Kuen': self.normalize()
4029        if name == 'Dini': self.scale(0.4)
4030        if name == 'Enneper': self.scale(0.4)
4031        if name == 'ConicSpiral': self.bc('tomato')
4032        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):
4921class ConvexHull(Mesh):
4922    """
4923    Create the 2D/3D convex hull from a set of points.
4924    """
4925
4926    def __init__(self, pts) -> None:
4927        """
4928        Create the 2D/3D convex hull from a set of input points or input Mesh.
4929
4930        Examples:
4931            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4932
4933                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4934        """
4935        if utils.is_sequence(pts):
4936            pts = utils.make3d(pts).astype(float)
4937            mesh = Points(pts)
4938        else:
4939            mesh = pts
4940        apoly = mesh.clean().dataset
4941
4942        # Create the convex hull of the pointcloud
4943        z0, z1 = mesh.zbounds()
4944        d = mesh.diagonal_size()
4945        if (z1 - z0) / d > 0.0001:
4946            delaunay = vtki.new("Delaunay3D")
4947            delaunay.SetInputData(apoly)
4948            delaunay.Update()
4949            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4950            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4951            surfaceFilter.Update()
4952            out = surfaceFilter.GetOutput()
4953        else:
4954            delaunay = vtki.new("Delaunay2D")
4955            delaunay.SetInputData(apoly)
4956            delaunay.Update()
4957            fe = vtki.new("FeatureEdges")
4958            fe.SetInputConnection(delaunay.GetOutputPort())
4959            fe.BoundaryEdgesOn()
4960            fe.Update()
4961            out = fe.GetOutput()
4962
4963        super().__init__(out, c=mesh.color(), alpha=0.75)
4964        self.flat()
4965        self.name = "ConvexHull"

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

ConvexHull(pts)
4926    def __init__(self, pts) -> None:
4927        """
4928        Create the 2D/3D convex hull from a set of input points or input Mesh.
4929
4930        Examples:
4931            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4932
4933                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4934        """
4935        if utils.is_sequence(pts):
4936            pts = utils.make3d(pts).astype(float)
4937            mesh = Points(pts)
4938        else:
4939            mesh = pts
4940        apoly = mesh.clean().dataset
4941
4942        # Create the convex hull of the pointcloud
4943        z0, z1 = mesh.zbounds()
4944        d = mesh.diagonal_size()
4945        if (z1 - z0) / d > 0.0001:
4946            delaunay = vtki.new("Delaunay3D")
4947            delaunay.SetInputData(apoly)
4948            delaunay.Update()
4949            surfaceFilter = vtki.new("DataSetSurfaceFilter")
4950            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4951            surfaceFilter.Update()
4952            out = surfaceFilter.GetOutput()
4953        else:
4954            delaunay = vtki.new("Delaunay2D")
4955            delaunay.SetInputData(apoly)
4956            delaunay.Update()
4957            fe = vtki.new("FeatureEdges")
4958            fe.SetInputConnection(delaunay.GetOutputPort())
4959            fe.BoundaryEdgesOn()
4960            fe.Update()
4961            out = fe.GetOutput()
4962
4963        super().__init__(out, c=mesh.color(), alpha=0.75)
4964        self.flat()
4965        self.name = "ConvexHull"

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

Examples: