vedo.shapes

Submodule to generate simple and complex geometric shapes

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

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

class Line(vedo.mesh.Mesh):
416class Line(Mesh):
417    """
418    Build the line segment between points `p0` and `p1`.
419
420    If `p0` is already a list of points, return the line connecting them.
421
422    A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`.
423    """
424
425    def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0):
426        """
427        Arguments:
428            closed : (bool)
429                join last to first point
430            res : (int)
431                resolution, number of points along the line
432                (only relevant if only 2 points are specified)
433            lw : (int)
434                line width in pixel units
435            c : (color), int, str, list
436                color name, number, or list of [R,G,B] colors
437            alpha : (float)
438                opacity in range [0,1]
439        """
440        self.slope = []  # populated by analysis.fitLine
441        self.center = []
442        self.variances = []
443
444        self.coefficients = []  # populated by pyplot.fit()
445        self.covariance_matrix = []
446        self.coefficients = []
447        self.coefficient_errors = []
448        self.monte_carlo_coefficients = []
449        self.reduced_chi2 = -1
450        self.ndof = 0
451        self.data_sigma = 0
452        self.error_lines = []
453        self.error_band = None
454        self.res = res
455
456        if isinstance(p1, Points):
457            p1 = p1.GetPosition()
458            if isinstance(p0, Points):
459                p0 = p0.GetPosition()
460        if isinstance(p0, Points):
461            p0 = p0.points()
462
463        # detect if user is passing a 2D list of points as p0=xlist, p1=ylist:
464        if len(p0) > 3:
465            if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1):
466                # assume input is 2D xlist, ylist
467                p0 = np.stack((p0, p1), axis=1)
468                p1 = None
469            p0 = utils.make3d(p0)
470
471        # detect if user is passing a list of points:
472        if utils.is_sequence(p0[0]):
473            p0 = utils.make3d(p0)
474
475            ppoints = vtk.vtkPoints()  # Generate the polyline
476            ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32))
477            lines = vtk.vtkCellArray()
478            npt = len(p0)
479            if closed:
480                lines.InsertNextCell(npt + 1)
481            else:
482                lines.InsertNextCell(npt)
483            for i in range(npt):
484                lines.InsertCellPoint(i)
485            if closed:
486                lines.InsertCellPoint(0)
487            poly = vtk.vtkPolyData()
488            poly.SetPoints(ppoints)
489            poly.SetLines(lines)
490            top = p0[-1]
491            base = p0[0]
492            self.res = 2
493
494        else:  # or just 2 points to link
495
496            line_source = vtk.vtkLineSource()
497            p0 = utils.make3d(p0)
498            p1 = utils.make3d(p1)
499            line_source.SetPoint1(p0)
500            line_source.SetPoint2(p1)
501            line_source.SetResolution(res - 1)
502            line_source.Update()
503            poly = line_source.GetOutput()
504            top = np.asarray(p1, dtype=float)
505            base = np.asarray(p0, dtype=float)
506
507        Mesh.__init__(self, poly, c, alpha)
508        self.lw(lw)
509        self.property.LightingOff()
510        self.PickableOff()
511        self.DragableOff()
512        self.base = base
513        self.top = top
514        self.name = "Line"
515
516    def linecolor(self, lc=None):
517        """Assign a color to the line"""
518        # overrides mesh.linecolor which would have no effect here
519        return self.color(lc)
520
521    def eval(self, x):
522        """
523        Calculate the position of an intermediate point
524        as a fraction of the length of the line,
525        being x=0 the first point and x=1 the last point.
526        This corresponds to an imaginary point that travels along the line
527        at constant speed.
528
529        Can be used in conjunction with `lin_interpolate()`
530        to map any range to the [0,1] range.
531        """
532        distance1 = 0.0
533        length = self.length()
534        pts = self.points()
535        for i in range(1, len(pts)):
536            p0 = pts[i - 1]
537            p1 = pts[i]
538            seg = p1 - p0
539            distance0 = distance1
540            distance1 += np.linalg.norm(seg)
541            w1 = distance1 / length
542            if w1 >= x:
543                break
544        w0 = distance0 / length
545        v = p0 + seg * (x - w0) / (w1 - w0)
546        return v
547
548    def pattern(self, stipple, repeats=10):
549        """
550        Define a stipple pattern for dashing the line.
551        Pass the stipple pattern as a string like `'- - -'`.
552        Repeats controls the number of times the pattern repeats in a single segment.
553
554        Examples are: `'- -', '--  -  --'`, etc.
555
556        The resolution of the line (nr of points) can affect how pattern will show up.
557
558        Example:
559            ```python
560            from vedo import Line
561            pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]]
562            ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10)
563            ln.show(axes=1).close()
564            ```
565            ![](https://vedo.embl.es/images/feats/line_pattern.png)
566        """
567        stipple = str(stipple) * int(2 * repeats)
568        dimension = len(stipple)
569
570        image = vtk.vtkImageData()
571        image.SetDimensions(dimension, 1, 1)
572        image.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 4)
573        image.SetExtent(0, dimension - 1, 0, 0, 0, 0)
574        i_dim = 0
575        while i_dim < dimension:
576            for i in range(dimension):
577                image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255)
578                image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255)
579                image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255)
580                if stipple[i] == " ":
581                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0)
582                else:
583                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255)
584                i_dim += 1
585
586        polyData = self.polydata(False)
587
588        # Create texture coordinates
589        tcoords = vtk.vtkDoubleArray()
590        tcoords.SetName("TCoordsStippledLine")
591        tcoords.SetNumberOfComponents(1)
592        tcoords.SetNumberOfTuples(polyData.GetNumberOfPoints())
593        for i in range(polyData.GetNumberOfPoints()):
594            tcoords.SetTypedTuple(i, [i / 2])
595        polyData.GetPointData().SetTCoords(tcoords)
596        polyData.GetPointData().Modified()
597        texture = vtk.vtkTexture()
598        texture.SetInputData(image)
599        texture.InterpolateOff()
600        texture.RepeatOn()
601        self.SetTexture(texture)
602        return self
603
604    def length(self):
605        """Calculate length of the line."""
606        distance = 0.0
607        pts = self.points()
608        for i in range(1, len(pts)):
609            distance += np.linalg.norm(pts[i] - pts[i - 1])
610        return distance
611
612    def tangents(self):
613        """
614        Compute the tangents of a line in space.
615
616        Example:
617            ```python
618            from vedo import *
619            shape = load(dataurl+"timecourse1d.npy")[58]
620            pts = shape.rotate_x(30).points()
621            tangents = Line(pts).tangents()
622            arrs = Arrows(pts, pts+tangents, c='blue9')
623            show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()
624            ```
625            ![](https://vedo.embl.es/images/feats/line_tangents.png)
626        """
627        v = np.gradient(self.points())[0]
628        ds_dt = np.linalg.norm(v, axis=1)
629        tangent = np.array([1 / ds_dt] * 3).transpose() * v
630        return tangent
631
632    def curvature(self):
633        """
634        Compute the signed curvature of a line in space.
635        The signed is computed assuming the line is about coplanar to the xy plane.
636
637        Example:
638            ```python
639            from vedo import *
640            from vedo.pyplot import plot
641            shape = load(dataurl+"timecourse1d.npy")[55]
642            curvs = Line(shape.points()).curvature()
643            shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w')
644            shape.render_lines_as_tubes().lw(12)
645            pp = plot(curvs, ac='white', lc='yellow5')
646            show(shape, pp, N=2, bg='bb', sharecam=False).close()
647            ```
648            ![](https://vedo.embl.es/images/feats/line_curvature.png)
649        """
650        v = np.gradient(self.points())[0]
651        a = np.gradient(v)[0]
652        av = np.cross(a, v)
653        mav = np.linalg.norm(av, axis=1)
654        mv = utils.mag2(v)
655        val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5)
656        val[0] = val[1]
657        val[-1] = val[-2]
658        return val
659
660    def compute_curvature(self, method=0):
661        """
662        Add a pointdata array named 'Curvatures' which contains
663        the curvature value at each point.
664
665        Keyword method is overridden in Mesh and has no effect here.
666        """
667        # overrides mesh.compute_curvature
668        curvs = self.curvature()
669        vmin, vmax = np.min(curvs), np.max(curvs)
670        if vmin < 0 and vmax > 0:
671            v = max(-vmin, vmax)
672            self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature")
673        else:
674            self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature")
675        return self
676
677    def sweep(self, direction=(1, 0, 0), res=1):
678        """
679        Sweep the `Line` along the specified vector direction.
680
681        Returns a `Mesh` surface.
682        Line position is updated to allow for additional sweepings.
683
684        Example:
685            ```python
686            from vedo import Line, show
687            aline = Line([(0,0,0),(1,3,0),(2,4,0)])
688            surf1 = aline.sweep((1,0.2,0), res=3)
689            surf2 = aline.sweep((0.2,0,1))
690            aline.color('r').linewidth(4)
691            show(surf1, surf2, aline, axes=1).close()
692            ```
693            ![](https://vedo.embl.es/images/feats/sweepline.png)
694        """
695        line = self.polydata()
696        rows = line.GetNumberOfPoints()
697
698        spacing = 1 / res
699        surface = vtk.vtkPolyData()
700
701        res += 1
702        npts = rows * res
703        npolys = (rows - 1) * (res - 1)
704        points = vtk.vtkPoints()
705        points.Allocate(npts)
706
707        cnt = 0
708        x = [0.0, 0.0, 0.0]
709        for row in range(rows):
710            for col in range(res):
711                p = [0.0, 0.0, 0.0]
712                line.GetPoint(row, p)
713                x[0] = p[0] + direction[0] * col * spacing
714                x[1] = p[1] + direction[1] * col * spacing
715                x[2] = p[2] + direction[2] * col * spacing
716                points.InsertPoint(cnt, x)
717                cnt += 1
718
719        # Generate the quads
720        polys = vtk.vtkCellArray()
721        polys.Allocate(npolys * 4)
722        pts = [0, 0, 0, 0]
723        for row in range(rows - 1):
724            for col in range(res - 1):
725                pts[0] = col + row * res
726                pts[1] = pts[0] + 1
727                pts[2] = pts[0] + res + 1
728                pts[3] = pts[0] + res
729                polys.InsertNextCell(4, pts)
730        surface.SetPoints(points)
731        surface.SetPolys(polys)
732        asurface = vedo.Mesh(surface)
733        prop = vtk.vtkProperty()
734        prop.DeepCopy(self.GetProperty())
735        asurface.SetProperty(prop)
736        asurface.property = prop
737        asurface.lighting("default")
738        self.points(self.points() + direction)
739        return asurface
740
741    def reverse(self):
742        """Reverse the points sequence order."""
743        pts = np.flip(self.points(), axis=0)
744        self.points(pts)
745        return self

Build the line segment between points p0 and 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)
425    def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0):
426        """
427        Arguments:
428            closed : (bool)
429                join last to first point
430            res : (int)
431                resolution, number of points along the line
432                (only relevant if only 2 points are specified)
433            lw : (int)
434                line width in pixel units
435            c : (color), int, str, list
436                color name, number, or list of [R,G,B] colors
437            alpha : (float)
438                opacity in range [0,1]
439        """
440        self.slope = []  # populated by analysis.fitLine
441        self.center = []
442        self.variances = []
443
444        self.coefficients = []  # populated by pyplot.fit()
445        self.covariance_matrix = []
446        self.coefficients = []
447        self.coefficient_errors = []
448        self.monte_carlo_coefficients = []
449        self.reduced_chi2 = -1
450        self.ndof = 0
451        self.data_sigma = 0
452        self.error_lines = []
453        self.error_band = None
454        self.res = res
455
456        if isinstance(p1, Points):
457            p1 = p1.GetPosition()
458            if isinstance(p0, Points):
459                p0 = p0.GetPosition()
460        if isinstance(p0, Points):
461            p0 = p0.points()
462
463        # detect if user is passing a 2D list of points as p0=xlist, p1=ylist:
464        if len(p0) > 3:
465            if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1):
466                # assume input is 2D xlist, ylist
467                p0 = np.stack((p0, p1), axis=1)
468                p1 = None
469            p0 = utils.make3d(p0)
470
471        # detect if user is passing a list of points:
472        if utils.is_sequence(p0[0]):
473            p0 = utils.make3d(p0)
474
475            ppoints = vtk.vtkPoints()  # Generate the polyline
476            ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32))
477            lines = vtk.vtkCellArray()
478            npt = len(p0)
479            if closed:
480                lines.InsertNextCell(npt + 1)
481            else:
482                lines.InsertNextCell(npt)
483            for i in range(npt):
484                lines.InsertCellPoint(i)
485            if closed:
486                lines.InsertCellPoint(0)
487            poly = vtk.vtkPolyData()
488            poly.SetPoints(ppoints)
489            poly.SetLines(lines)
490            top = p0[-1]
491            base = p0[0]
492            self.res = 2
493
494        else:  # or just 2 points to link
495
496            line_source = vtk.vtkLineSource()
497            p0 = utils.make3d(p0)
498            p1 = utils.make3d(p1)
499            line_source.SetPoint1(p0)
500            line_source.SetPoint2(p1)
501            line_source.SetResolution(res - 1)
502            line_source.Update()
503            poly = line_source.GetOutput()
504            top = np.asarray(p1, dtype=float)
505            base = np.asarray(p0, dtype=float)
506
507        Mesh.__init__(self, poly, c, alpha)
508        self.lw(lw)
509        self.property.LightingOff()
510        self.PickableOff()
511        self.DragableOff()
512        self.base = base
513        self.top = top
514        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
  • c : (color), int, str, list color name, number, or list of [R,G,B] colors
  • alpha : (float) opacity in range [0,1]
def linecolor(self, lc=None):
516    def linecolor(self, lc=None):
517        """Assign a color to the line"""
518        # overrides mesh.linecolor which would have no effect here
519        return self.color(lc)

Assign a color to the line

def eval(self, x):
521    def eval(self, x):
522        """
523        Calculate the position of an intermediate point
524        as a fraction of the length of the line,
525        being x=0 the first point and x=1 the last point.
526        This corresponds to an imaginary point that travels along the line
527        at constant speed.
528
529        Can be used in conjunction with `lin_interpolate()`
530        to map any range to the [0,1] range.
531        """
532        distance1 = 0.0
533        length = self.length()
534        pts = self.points()
535        for i in range(1, len(pts)):
536            p0 = pts[i - 1]
537            p1 = pts[i]
538            seg = p1 - p0
539            distance0 = distance1
540            distance1 += np.linalg.norm(seg)
541            w1 = distance1 / length
542            if w1 >= x:
543                break
544        w0 = distance0 / length
545        v = p0 + seg * (x - w0) / (w1 - w0)
546        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 pattern(self, stipple, repeats=10):
548    def pattern(self, stipple, repeats=10):
549        """
550        Define a stipple pattern for dashing the line.
551        Pass the stipple pattern as a string like `'- - -'`.
552        Repeats controls the number of times the pattern repeats in a single segment.
553
554        Examples are: `'- -', '--  -  --'`, etc.
555
556        The resolution of the line (nr of points) can affect how pattern will show up.
557
558        Example:
559            ```python
560            from vedo import Line
561            pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]]
562            ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10)
563            ln.show(axes=1).close()
564            ```
565            ![](https://vedo.embl.es/images/feats/line_pattern.png)
566        """
567        stipple = str(stipple) * int(2 * repeats)
568        dimension = len(stipple)
569
570        image = vtk.vtkImageData()
571        image.SetDimensions(dimension, 1, 1)
572        image.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 4)
573        image.SetExtent(0, dimension - 1, 0, 0, 0, 0)
574        i_dim = 0
575        while i_dim < dimension:
576            for i in range(dimension):
577                image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255)
578                image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255)
579                image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255)
580                if stipple[i] == " ":
581                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0)
582                else:
583                    image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255)
584                i_dim += 1
585
586        polyData = self.polydata(False)
587
588        # Create texture coordinates
589        tcoords = vtk.vtkDoubleArray()
590        tcoords.SetName("TCoordsStippledLine")
591        tcoords.SetNumberOfComponents(1)
592        tcoords.SetNumberOfTuples(polyData.GetNumberOfPoints())
593        for i in range(polyData.GetNumberOfPoints()):
594            tcoords.SetTypedTuple(i, [i / 2])
595        polyData.GetPointData().SetTCoords(tcoords)
596        polyData.GetPointData().Modified()
597        texture = vtk.vtkTexture()
598        texture.SetInputData(image)
599        texture.InterpolateOff()
600        texture.RepeatOn()
601        self.SetTexture(texture)
602        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):
604    def length(self):
605        """Calculate length of the line."""
606        distance = 0.0
607        pts = self.points()
608        for i in range(1, len(pts)):
609            distance += np.linalg.norm(pts[i] - pts[i - 1])
610        return distance

Calculate length of the line.

def tangents(self):
612    def tangents(self):
613        """
614        Compute the tangents of a line in space.
615
616        Example:
617            ```python
618            from vedo import *
619            shape = load(dataurl+"timecourse1d.npy")[58]
620            pts = shape.rotate_x(30).points()
621            tangents = Line(pts).tangents()
622            arrs = Arrows(pts, pts+tangents, c='blue9')
623            show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()
624            ```
625            ![](https://vedo.embl.es/images/feats/line_tangents.png)
626        """
627        v = np.gradient(self.points())[0]
628        ds_dt = np.linalg.norm(v, axis=1)
629        tangent = np.array([1 / ds_dt] * 3).transpose() * v
630        return tangent

Compute the tangents of a line in space.

Example:
from vedo import *
shape = load(dataurl+"timecourse1d.npy")[58]
pts = shape.rotate_x(30).points()
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):
632    def curvature(self):
633        """
634        Compute the signed curvature of a line in space.
635        The signed is computed assuming the line is about coplanar to the xy plane.
636
637        Example:
638            ```python
639            from vedo import *
640            from vedo.pyplot import plot
641            shape = load(dataurl+"timecourse1d.npy")[55]
642            curvs = Line(shape.points()).curvature()
643            shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w')
644            shape.render_lines_as_tubes().lw(12)
645            pp = plot(curvs, ac='white', lc='yellow5')
646            show(shape, pp, N=2, bg='bb', sharecam=False).close()
647            ```
648            ![](https://vedo.embl.es/images/feats/line_curvature.png)
649        """
650        v = np.gradient(self.points())[0]
651        a = np.gradient(v)[0]
652        av = np.cross(a, v)
653        mav = np.linalg.norm(av, axis=1)
654        mv = utils.mag2(v)
655        val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5)
656        val[0] = val[1]
657        val[-1] = val[-2]
658        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 = load(dataurl+"timecourse1d.npy")[55]
curvs = Line(shape.points()).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):
660    def compute_curvature(self, method=0):
661        """
662        Add a pointdata array named 'Curvatures' which contains
663        the curvature value at each point.
664
665        Keyword method is overridden in Mesh and has no effect here.
666        """
667        # overrides mesh.compute_curvature
668        curvs = self.curvature()
669        vmin, vmax = np.min(curvs), np.max(curvs)
670        if vmin < 0 and vmax > 0:
671            v = max(-vmin, vmax)
672            self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature")
673        else:
674            self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature")
675        return self

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

Keyword method is overridden in Mesh and has no effect here.

def sweep(self, direction=(1, 0, 0), res=1):
677    def sweep(self, direction=(1, 0, 0), res=1):
678        """
679        Sweep the `Line` along the specified vector direction.
680
681        Returns a `Mesh` surface.
682        Line position is updated to allow for additional sweepings.
683
684        Example:
685            ```python
686            from vedo import Line, show
687            aline = Line([(0,0,0),(1,3,0),(2,4,0)])
688            surf1 = aline.sweep((1,0.2,0), res=3)
689            surf2 = aline.sweep((0.2,0,1))
690            aline.color('r').linewidth(4)
691            show(surf1, surf2, aline, axes=1).close()
692            ```
693            ![](https://vedo.embl.es/images/feats/sweepline.png)
694        """
695        line = self.polydata()
696        rows = line.GetNumberOfPoints()
697
698        spacing = 1 / res
699        surface = vtk.vtkPolyData()
700
701        res += 1
702        npts = rows * res
703        npolys = (rows - 1) * (res - 1)
704        points = vtk.vtkPoints()
705        points.Allocate(npts)
706
707        cnt = 0
708        x = [0.0, 0.0, 0.0]
709        for row in range(rows):
710            for col in range(res):
711                p = [0.0, 0.0, 0.0]
712                line.GetPoint(row, p)
713                x[0] = p[0] + direction[0] * col * spacing
714                x[1] = p[1] + direction[1] * col * spacing
715                x[2] = p[2] + direction[2] * col * spacing
716                points.InsertPoint(cnt, x)
717                cnt += 1
718
719        # Generate the quads
720        polys = vtk.vtkCellArray()
721        polys.Allocate(npolys * 4)
722        pts = [0, 0, 0, 0]
723        for row in range(rows - 1):
724            for col in range(res - 1):
725                pts[0] = col + row * res
726                pts[1] = pts[0] + 1
727                pts[2] = pts[0] + res + 1
728                pts[3] = pts[0] + res
729                polys.InsertNextCell(4, pts)
730        surface.SetPoints(points)
731        surface.SetPolys(polys)
732        asurface = vedo.Mesh(surface)
733        prop = vtk.vtkProperty()
734        prop.DeepCopy(self.GetProperty())
735        asurface.SetProperty(prop)
736        asurface.property = prop
737        asurface.lighting("default")
738        self.points(self.points() + direction)
739        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))
aline.color('r').linewidth(4)
show(surf1, surf2, aline, axes=1).close()

def reverse(self):
741    def reverse(self):
742        """Reverse the points sequence order."""
743        pts = np.flip(self.points(), axis=0)
744        self.points(pts)
745        return self

Reverse the points sequence order.

class DashedLine(vedo.mesh.Mesh):
748class DashedLine(Mesh):
749    """
750    Consider using `Line.pattern()` instead.
751
752    Build a dashed line segment between points `p0` and `p1`.
753    If `p0` is a list of points returns the line connecting them.
754    A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`.
755    """
756
757    def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0):
758        """
759        Arguments:
760            closed : (bool)
761                join last to first point
762            spacing : (float)
763                relative size of the dash
764            lw : (int)
765                line width in pixels
766        """
767        if isinstance(p1, vtk.vtkActor):
768            p1 = p1.GetPosition()
769            if isinstance(p0, vtk.vtkActor):
770                p0 = p0.GetPosition()
771        if isinstance(p0, Points):
772            p0 = p0.points()
773
774        # detect if user is passing a 2D list of points as p0=xlist, p1=ylist:
775        if len(p0) > 3:
776            if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1):
777                # assume input is 2D xlist, ylist
778                p0 = np.stack((p0, p1), axis=1)
779                p1 = None
780            p0 = utils.make3d(p0)
781            if closed:
782                p0 = np.append(p0, [p0[0]], axis=0)
783
784        if p1 is not None:  # assume passing p0=[x,y]
785            if len(p0) == 2 and not utils.is_sequence(p0[0]):
786                p0 = (p0[0], p0[1], 0)
787            if len(p1) == 2 and not utils.is_sequence(p1[0]):
788                p1 = (p1[0], p1[1], 0)
789
790        # detect if user is passing a list of points:
791        if utils.is_sequence(p0[0]):
792            listp = p0
793        else:  # or just 2 points to link
794            listp = [p0, p1]
795
796        listp = np.array(listp)
797        if listp.shape[1] == 2:
798            listp = np.c_[listp, np.zeros(listp.shape[0])]
799
800        xmn = np.min(listp, axis=0)
801        xmx = np.max(listp, axis=0)
802        dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10
803        if not dlen:
804            Mesh.__init__(self, vtk.vtkPolyData(), c, alpha)
805            self.name = "DashedLine (void)"
806            return
807
808        qs = []
809        for ipt in range(len(listp) - 1):
810            p0 = listp[ipt]
811            p1 = listp[ipt + 1]
812            v = p1 - p0
813            vdist = np.linalg.norm(v)
814            n1 = int(vdist / dlen)
815            if not n1:
816                continue
817
818            res = 0
819            for i in range(n1 + 2):
820                ist = (i - 0.5) / n1
821                ist = max(ist, 0)
822                qi = p0 + v * (ist - res / vdist)
823                if ist > 1:
824                    qi = p1
825                    res = np.linalg.norm(qi - p1)
826                    qs.append(qi)
827                    break
828                qs.append(qi)
829
830        polylns = vtk.vtkAppendPolyData()
831        for i, q1 in enumerate(qs):
832            if not i % 2:
833                continue
834            q0 = qs[i - 1]
835            line_source = vtk.vtkLineSource()
836            line_source.SetPoint1(q0)
837            line_source.SetPoint2(q1)
838            line_source.Update()
839            polylns.AddInputData(line_source.GetOutput())
840        polylns.Update()
841
842        Mesh.__init__(self, polylns.GetOutput(), c, alpha)
843        self.lw(lw).lighting("off")
844        self.base = listp[0]
845        if closed:
846            self.top = listp[-2]
847        else:
848            self.top = listp[-1]
849        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)
757    def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0):
758        """
759        Arguments:
760            closed : (bool)
761                join last to first point
762            spacing : (float)
763                relative size of the dash
764            lw : (int)
765                line width in pixels
766        """
767        if isinstance(p1, vtk.vtkActor):
768            p1 = p1.GetPosition()
769            if isinstance(p0, vtk.vtkActor):
770                p0 = p0.GetPosition()
771        if isinstance(p0, Points):
772            p0 = p0.points()
773
774        # detect if user is passing a 2D list of points as p0=xlist, p1=ylist:
775        if len(p0) > 3:
776            if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1):
777                # assume input is 2D xlist, ylist
778                p0 = np.stack((p0, p1), axis=1)
779                p1 = None
780            p0 = utils.make3d(p0)
781            if closed:
782                p0 = np.append(p0, [p0[0]], axis=0)
783
784        if p1 is not None:  # assume passing p0=[x,y]
785            if len(p0) == 2 and not utils.is_sequence(p0[0]):
786                p0 = (p0[0], p0[1], 0)
787            if len(p1) == 2 and not utils.is_sequence(p1[0]):
788                p1 = (p1[0], p1[1], 0)
789
790        # detect if user is passing a list of points:
791        if utils.is_sequence(p0[0]):
792            listp = p0
793        else:  # or just 2 points to link
794            listp = [p0, p1]
795
796        listp = np.array(listp)
797        if listp.shape[1] == 2:
798            listp = np.c_[listp, np.zeros(listp.shape[0])]
799
800        xmn = np.min(listp, axis=0)
801        xmx = np.max(listp, axis=0)
802        dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10
803        if not dlen:
804            Mesh.__init__(self, vtk.vtkPolyData(), c, alpha)
805            self.name = "DashedLine (void)"
806            return
807
808        qs = []
809        for ipt in range(len(listp) - 1):
810            p0 = listp[ipt]
811            p1 = listp[ipt + 1]
812            v = p1 - p0
813            vdist = np.linalg.norm(v)
814            n1 = int(vdist / dlen)
815            if not n1:
816                continue
817
818            res = 0
819            for i in range(n1 + 2):
820                ist = (i - 0.5) / n1
821                ist = max(ist, 0)
822                qi = p0 + v * (ist - res / vdist)
823                if ist > 1:
824                    qi = p1
825                    res = np.linalg.norm(qi - p1)
826                    qs.append(qi)
827                    break
828                qs.append(qi)
829
830        polylns = vtk.vtkAppendPolyData()
831        for i, q1 in enumerate(qs):
832            if not i % 2:
833                continue
834            q0 = qs[i - 1]
835            line_source = vtk.vtkLineSource()
836            line_source.SetPoint1(q0)
837            line_source.SetPoint2(q1)
838            line_source.Update()
839            polylns.AddInputData(line_source.GetOutput())
840        polylns.Update()
841
842        Mesh.__init__(self, polylns.GetOutput(), c, alpha)
843        self.lw(lw).lighting("off")
844        self.base = listp[0]
845        if closed:
846            self.top = listp[-2]
847        else:
848            self.top = listp[-1]
849        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):
852class RoundedLine(Mesh):
853    """
854    Create a 2D line of specified thickness (in absolute units) passing through
855    a list of input points. Borders of the line are rounded.
856    """
857
858    def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0):
859        """
860        Arguments:
861            pts : (list)
862                a list of points in 2D or 3D (z will be ignored).
863            lw : (float)
864                thickness of the line.
865            res : (int)
866                resolution of the rounded regions
867
868        Example:
869            ```python
870            from vedo import *
871            pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)]
872            ln = Line(pts, c='r', lw=2).z(0.01)
873            rl = RoundedLine(pts, 0.6)
874            show(Points(pts), ln, rl, axes=1).close()
875            ```
876            ![](https://vedo.embl.es/images/feats/rounded_line.png)
877        """
878        pts = utils.make3d(pts)
879
880        def _getpts(pts, revd=False):
881
882            if revd:
883                pts = list(reversed(pts))
884
885            if len(pts) == 2:
886                p0, p1 = pts
887                v = p1 - p0
888                dv = np.linalg.norm(v)
889                nv = np.cross(v, (0, 0, -1))
890                nv = nv / np.linalg.norm(nv) * lw
891                return [p0 + nv, p1 + nv]
892
893            ptsnew = []
894            for k in range(len(pts) - 2):
895                p0 = pts[k]
896                p1 = pts[k + 1]
897                p2 = pts[k + 2]
898                v = p1 - p0
899                u = p2 - p1
900                du = np.linalg.norm(u)
901                dv = np.linalg.norm(v)
902                nv = np.cross(v, (0, 0, -1))
903                nv = nv / np.linalg.norm(nv) * lw
904                nu = np.cross(u, (0, 0, -1))
905                nu = nu / np.linalg.norm(nu) * lw
906                uv = np.cross(u, v)
907                if k == 0:
908                    ptsnew.append(p0 + nv)
909                if uv[2] <= 0:
910                    alpha = np.arccos(np.dot(u, v) / du / dv)
911                    db = lw * np.tan(alpha / 2)
912                    p1new = p1 + nv - v / dv * db
913                    ptsnew.append(p1new)
914                else:
915                    p1a = p1 + nv
916                    p1b = p1 + nu
917                    for i in range(0, res + 1):
918                        pab = p1a * (res - i) / res + p1b * i / res
919                        vpab = pab - p1
920                        vpab = vpab / np.linalg.norm(vpab) * lw
921                        ptsnew.append(p1 + vpab)
922                if k == len(pts) - 3:
923                    ptsnew.append(p2 + nu)
924                    if revd:
925                        ptsnew.append(p2 - nu)
926            return ptsnew
927
928        ptsnew = _getpts(pts) + _getpts(pts, revd=True)
929
930        ppoints = vtk.vtkPoints()  # Generate the polyline
931        ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32))
932        lines = vtk.vtkCellArray()
933        npt = len(ptsnew)
934        lines.InsertNextCell(npt)
935        for i in range(npt):
936            lines.InsertCellPoint(i)
937        poly = vtk.vtkPolyData()
938        poly.SetPoints(ppoints)
939        poly.SetLines(lines)
940        vct = vtk.vtkContourTriangulator()
941        vct.SetInputData(poly)
942        vct.Update()
943        Mesh.__init__(self, vct.GetOutput(), c, alpha)
944        self.flat()
945        self.property.LightingOff()
946        self.name = "RoundedLine"
947        self.base = ptsnew[0]
948        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)
858    def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0):
859        """
860        Arguments:
861            pts : (list)
862                a list of points in 2D or 3D (z will be ignored).
863            lw : (float)
864                thickness of the line.
865            res : (int)
866                resolution of the rounded regions
867
868        Example:
869            ```python
870            from vedo import *
871            pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)]
872            ln = Line(pts, c='r', lw=2).z(0.01)
873            rl = RoundedLine(pts, 0.6)
874            show(Points(pts), ln, rl, axes=1).close()
875            ```
876            ![](https://vedo.embl.es/images/feats/rounded_line.png)
877        """
878        pts = utils.make3d(pts)
879
880        def _getpts(pts, revd=False):
881
882            if revd:
883                pts = list(reversed(pts))
884
885            if len(pts) == 2:
886                p0, p1 = pts
887                v = p1 - p0
888                dv = np.linalg.norm(v)
889                nv = np.cross(v, (0, 0, -1))
890                nv = nv / np.linalg.norm(nv) * lw
891                return [p0 + nv, p1 + nv]
892
893            ptsnew = []
894            for k in range(len(pts) - 2):
895                p0 = pts[k]
896                p1 = pts[k + 1]
897                p2 = pts[k + 2]
898                v = p1 - p0
899                u = p2 - p1
900                du = np.linalg.norm(u)
901                dv = np.linalg.norm(v)
902                nv = np.cross(v, (0, 0, -1))
903                nv = nv / np.linalg.norm(nv) * lw
904                nu = np.cross(u, (0, 0, -1))
905                nu = nu / np.linalg.norm(nu) * lw
906                uv = np.cross(u, v)
907                if k == 0:
908                    ptsnew.append(p0 + nv)
909                if uv[2] <= 0:
910                    alpha = np.arccos(np.dot(u, v) / du / dv)
911                    db = lw * np.tan(alpha / 2)
912                    p1new = p1 + nv - v / dv * db
913                    ptsnew.append(p1new)
914                else:
915                    p1a = p1 + nv
916                    p1b = p1 + nu
917                    for i in range(0, res + 1):
918                        pab = p1a * (res - i) / res + p1b * i / res
919                        vpab = pab - p1
920                        vpab = vpab / np.linalg.norm(vpab) * lw
921                        ptsnew.append(p1 + vpab)
922                if k == len(pts) - 3:
923                    ptsnew.append(p2 + nu)
924                    if revd:
925                        ptsnew.append(p2 - nu)
926            return ptsnew
927
928        ptsnew = _getpts(pts) + _getpts(pts, revd=True)
929
930        ppoints = vtk.vtkPoints()  # Generate the polyline
931        ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32))
932        lines = vtk.vtkCellArray()
933        npt = len(ptsnew)
934        lines.InsertNextCell(npt)
935        for i in range(npt):
936            lines.InsertCellPoint(i)
937        poly = vtk.vtkPolyData()
938        poly.SetPoints(ppoints)
939        poly.SetLines(lines)
940        vct = vtk.vtkContourTriangulator()
941        vct.SetInputData(poly)
942        vct.Update()
943        Mesh.__init__(self, vct.GetOutput(), c, alpha)
944        self.flat()
945        self.property.LightingOff()
946        self.name = "RoundedLine"
947        self.base = ptsnew[0]
948        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, c='r', lw=2).z(0.01)
rl = RoundedLine(pts, 0.6)
show(Points(pts), ln, rl, axes=1).close()

class Tube(vedo.mesh.Mesh):
1652class Tube(Mesh):
1653    """
1654    Build a tube along the line defined by a set of points.
1655    """
1656
1657    def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0):
1658        """
1659        Arguments:
1660            r :  (float, list)
1661                constant radius or list of radii.
1662            res : (int)
1663                resolution, number of the sides of the tube
1664            c : (color)
1665                constant color or list of colors for each point.
1666
1667        Examples:
1668            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1669            - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py)
1670
1671                ![](https://vedo.embl.es/images/basic/tube.png)
1672        """
1673        if isinstance(points, vedo.Points):
1674            points = points.points()
1675
1676        base = np.asarray(points[0], dtype=float)
1677        top = np.asarray(points[-1], dtype=float)
1678
1679        vpoints = vtk.vtkPoints()
1680        idx = len(points)
1681        for p in points:
1682            vpoints.InsertNextPoint(p)
1683        line = vtk.vtkPolyLine()
1684        line.GetPointIds().SetNumberOfIds(idx)
1685        for i in range(idx):
1686            line.GetPointIds().SetId(i, i)
1687        lines = vtk.vtkCellArray()
1688        lines.InsertNextCell(line)
1689        polyln = vtk.vtkPolyData()
1690        polyln.SetPoints(vpoints)
1691        polyln.SetLines(lines)
1692
1693        tuf = vtk.vtkTubeFilter()
1694        tuf.SetCapping(cap)
1695        tuf.SetNumberOfSides(res)
1696        tuf.SetInputData(polyln)
1697        if utils.is_sequence(r):
1698            arr = utils.numpy2vtk(r, dtype=float)
1699            arr.SetName("TubeRadius")
1700            polyln.GetPointData().AddArray(arr)
1701            polyln.GetPointData().SetActiveScalars("TubeRadius")
1702            tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar()
1703        else:
1704            tuf.SetRadius(r)
1705
1706        usingColScals = False
1707        if utils.is_sequence(c):
1708            usingColScals = True
1709            cc = vtk.vtkUnsignedCharArray()
1710            cc.SetName("TubeColors")
1711            cc.SetNumberOfComponents(3)
1712            cc.SetNumberOfTuples(len(c))
1713            for i, ic in enumerate(c):
1714                r, g, b = get_color(ic)
1715                cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b))
1716            polyln.GetPointData().AddArray(cc)
1717            c = None
1718        tuf.Update()
1719
1720        Mesh.__init__(self, tuf.GetOutput(), c, alpha)
1721        self.phong()
1722        if usingColScals:
1723            self.mapper().SetScalarModeToUsePointFieldData()
1724            self.mapper().ScalarVisibilityOn()
1725            self.mapper().SelectColorArray("TubeColors")
1726            self.mapper().Modified()
1727
1728        self.base = base
1729        self.top = top
1730        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)
1657    def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0):
1658        """
1659        Arguments:
1660            r :  (float, list)
1661                constant radius or list of radii.
1662            res : (int)
1663                resolution, number of the sides of the tube
1664            c : (color)
1665                constant color or list of colors for each point.
1666
1667        Examples:
1668            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1669            - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py)
1670
1671                ![](https://vedo.embl.es/images/basic/tube.png)
1672        """
1673        if isinstance(points, vedo.Points):
1674            points = points.points()
1675
1676        base = np.asarray(points[0], dtype=float)
1677        top = np.asarray(points[-1], dtype=float)
1678
1679        vpoints = vtk.vtkPoints()
1680        idx = len(points)
1681        for p in points:
1682            vpoints.InsertNextPoint(p)
1683        line = vtk.vtkPolyLine()
1684        line.GetPointIds().SetNumberOfIds(idx)
1685        for i in range(idx):
1686            line.GetPointIds().SetId(i, i)
1687        lines = vtk.vtkCellArray()
1688        lines.InsertNextCell(line)
1689        polyln = vtk.vtkPolyData()
1690        polyln.SetPoints(vpoints)
1691        polyln.SetLines(lines)
1692
1693        tuf = vtk.vtkTubeFilter()
1694        tuf.SetCapping(cap)
1695        tuf.SetNumberOfSides(res)
1696        tuf.SetInputData(polyln)
1697        if utils.is_sequence(r):
1698            arr = utils.numpy2vtk(r, dtype=float)
1699            arr.SetName("TubeRadius")
1700            polyln.GetPointData().AddArray(arr)
1701            polyln.GetPointData().SetActiveScalars("TubeRadius")
1702            tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar()
1703        else:
1704            tuf.SetRadius(r)
1705
1706        usingColScals = False
1707        if utils.is_sequence(c):
1708            usingColScals = True
1709            cc = vtk.vtkUnsignedCharArray()
1710            cc.SetName("TubeColors")
1711            cc.SetNumberOfComponents(3)
1712            cc.SetNumberOfTuples(len(c))
1713            for i, ic in enumerate(c):
1714                r, g, b = get_color(ic)
1715                cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b))
1716            polyln.GetPointData().AddArray(cc)
1717            c = None
1718        tuf.Update()
1719
1720        Mesh.__init__(self, tuf.GetOutput(), c, alpha)
1721        self.phong()
1722        if usingColScals:
1723            self.mapper().SetScalarModeToUsePointFieldData()
1724            self.mapper().ScalarVisibilityOn()
1725            self.mapper().SelectColorArray("TubeColors")
1726            self.mapper().Modified()
1727
1728        self.base = base
1729        self.top = top
1730        self.name = "Tube"
Arguments:
  • r : (float, list) constant radius or list of radii.
  • res : (int) resolution, number of the sides of the tube
  • c : (color) constant color or list of colors for each point.
Examples:
def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0):
1733def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0):
1734    """
1735    Create a tube with a thickness along a line of points.
1736
1737    Example:
1738    ```python
1739    from vedo import *
1740    pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)]
1741    vline = Line(pts, lw=5, c='red5')
1742    thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1)
1743    show(vline, thick_tube, axes=1).close()
1744    ```
1745    ![](https://vedo.embl.es/images/feats/thick_tube.png)
1746    """
1747
1748    def make_cap(t1, t2):
1749        newpoints = t1.points().tolist() + t2.points().tolist()
1750        newfaces = []
1751        for i in range(n - 1):
1752            newfaces.append([i, i + 1, i + n])
1753            newfaces.append([i + n, i + 1, i + n + 1])
1754        newfaces.append([2 * n - 1, 0, n])
1755        newfaces.append([2 * n - 1, n - 1, 0])
1756        capm = utils.buildPolyData(newpoints, newfaces)
1757        return capm
1758
1759    assert r1 < r2
1760
1761    t1 = Tube(pts, r=r1, cap=False, res=res)
1762    t2 = Tube(pts, r=r2, cap=False, res=res)
1763
1764    tc1a, tc1b = t1.boundaries().split()
1765    tc2a, tc2b = t2.boundaries().split()
1766    n = tc1b.npoints
1767
1768    tc1b.join(reset=True).clean()  # needed because indices are flipped
1769    tc2b.join(reset=True).clean()
1770
1771    capa = make_cap(tc1a, tc2a)
1772    capb = make_cap(tc1b, tc2b)
1773
1774    thick_tube = merge(t1, t2, capa, capb).c(c).alpha(alpha)
1775    thick_tube.base = t1.base
1776    thick_tube.top = t1.top
1777    thick_tube.name = "ThickTube"
1778    return thick_tube

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):
 951class Lines(Mesh):
 952    """
 953    Build the line segments between two lists of points `start_pts` and `end_pts`.
 954    `start_pts` can be also passed in the form `[[point1, point2], ...]`.
 955    """
 956
 957    def __init__(
 958        self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0
 959    ):
 960        """
 961        Arguments:
 962            scale : (float)
 963                apply a rescaling factor to the lengths.
 964            c : (color, int, str, list)
 965                color name, number, or list of [R,G,B] colors
 966            alpha : (float)
 967                opacity in range [0,1]
 968            lw : (int)
 969                line width in pixel units
 970            res : (int)
 971                resolution, number of points along the line
 972                (only relevant if only 2 points are specified)
 973
 974        Examples:
 975            - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py)
 976
 977            ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png)
 978        """
 979        if isinstance(start_pts, Points):
 980            start_pts = start_pts.points()
 981        if isinstance(end_pts, Points):
 982            end_pts = end_pts.points()
 983
 984        if end_pts is not None:
 985            start_pts = np.stack((start_pts, end_pts), axis=1)
 986
 987        polylns = vtk.vtkAppendPolyData()
 988
 989        if not utils.is_ragged(start_pts):
 990
 991            for twopts in start_pts:
 992                line_source = vtk.vtkLineSource()
 993                line_source.SetResolution(res)
 994                if len(twopts[0]) == 2:
 995                    line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0)
 996                else:
 997                    line_source.SetPoint1(twopts[0])
 998
 999                if scale == 1:
1000                    pt2 = twopts[1]
1001                else:
1002                    vers = (np.array(twopts[1]) - twopts[0]) * scale
1003                    pt2 = np.array(twopts[0]) + vers
1004
1005                if len(pt2) == 2:
1006                    line_source.SetPoint2(pt2[0], pt2[1], 0.0)
1007                else:
1008                    line_source.SetPoint2(pt2)
1009                polylns.AddInputConnection(line_source.GetOutputPort())
1010
1011        else:
1012
1013            polylns = vtk.vtkAppendPolyData()
1014            for t in start_pts:
1015                t = utils.make3d(t)
1016                ppoints = vtk.vtkPoints()  # Generate the polyline
1017                ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32))
1018                lines = vtk.vtkCellArray()
1019                npt = len(t)
1020                lines.InsertNextCell(npt)
1021                for i in range(npt):
1022                    lines.InsertCellPoint(i)
1023                poly = vtk.vtkPolyData()
1024                poly.SetPoints(ppoints)
1025                poly.SetLines(lines)
1026                polylns.AddInputData(poly)
1027
1028        polylns.Update()
1029
1030        Mesh.__init__(self, polylns.GetOutput(), c, alpha)
1031        self.lw(lw).lighting("off")
1032        if dotted:
1033            self.GetProperty().SetLineStipplePattern(0xF0F0)
1034            self.GetProperty().SetLineStippleRepeatFactor(1)
1035
1036        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)
 957    def __init__(
 958        self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0
 959    ):
 960        """
 961        Arguments:
 962            scale : (float)
 963                apply a rescaling factor to the lengths.
 964            c : (color, int, str, list)
 965                color name, number, or list of [R,G,B] colors
 966            alpha : (float)
 967                opacity in range [0,1]
 968            lw : (int)
 969                line width in pixel units
 970            res : (int)
 971                resolution, number of points along the line
 972                (only relevant if only 2 points are specified)
 973
 974        Examples:
 975            - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py)
 976
 977            ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png)
 978        """
 979        if isinstance(start_pts, Points):
 980            start_pts = start_pts.points()
 981        if isinstance(end_pts, Points):
 982            end_pts = end_pts.points()
 983
 984        if end_pts is not None:
 985            start_pts = np.stack((start_pts, end_pts), axis=1)
 986
 987        polylns = vtk.vtkAppendPolyData()
 988
 989        if not utils.is_ragged(start_pts):
 990
 991            for twopts in start_pts:
 992                line_source = vtk.vtkLineSource()
 993                line_source.SetResolution(res)
 994                if len(twopts[0]) == 2:
 995                    line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0)
 996                else:
 997                    line_source.SetPoint1(twopts[0])
 998
 999                if scale == 1:
1000                    pt2 = twopts[1]
1001                else:
1002                    vers = (np.array(twopts[1]) - twopts[0]) * scale
1003                    pt2 = np.array(twopts[0]) + vers
1004
1005                if len(pt2) == 2:
1006                    line_source.SetPoint2(pt2[0], pt2[1], 0.0)
1007                else:
1008                    line_source.SetPoint2(pt2)
1009                polylns.AddInputConnection(line_source.GetOutputPort())
1010
1011        else:
1012
1013            polylns = vtk.vtkAppendPolyData()
1014            for t in start_pts:
1015                t = utils.make3d(t)
1016                ppoints = vtk.vtkPoints()  # Generate the polyline
1017                ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32))
1018                lines = vtk.vtkCellArray()
1019                npt = len(t)
1020                lines.InsertNextCell(npt)
1021                for i in range(npt):
1022                    lines.InsertCellPoint(i)
1023                poly = vtk.vtkPolyData()
1024                poly.SetPoints(ppoints)
1025                poly.SetLines(lines)
1026                polylns.AddInputData(poly)
1027
1028        polylns.Update()
1029
1030        Mesh.__init__(self, polylns.GetOutput(), c, alpha)
1031        self.lw(lw).lighting("off")
1032        if dotted:
1033            self.GetProperty().SetLineStipplePattern(0xF0F0)
1034            self.GetProperty().SetLineStippleRepeatFactor(1)
1035
1036        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
  • res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
Examples:

class Spline(Line):
1039class Spline(Line):
1040    """
1041    Find the B-Spline curve through a set of points. This curve does not necessarily
1042    pass exactly through all the input points. Needs to import `scipy`.
1043    """
1044
1045    def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing=""):
1046        """
1047        Arguments:
1048            smooth : (float)
1049                smoothing factor.
1050                - 0 = interpolate points exactly [default].
1051                - 1 = average point positions.
1052            degree : (int)
1053                degree of the spline (between 1 and 5).
1054            easing : (str)
1055                control sensity of points along the spline.
1056                Available options are
1057                `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].`
1058                Can be used to create animations (move objects at varying speed).
1059                See e.g.: https://easings.net
1060            res : (int)
1061                number of points on the spline
1062
1063        See also: `CSpline` and `KSpline`.
1064
1065        Examples:
1066            - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py)
1067
1068                ![](https://vedo.embl.es/images/simulations/spline_ease.gif)
1069        """
1070        from scipy.interpolate import splprep, splev
1071
1072        if isinstance(points, Points):
1073            points = points.points()
1074
1075        points = utils.make3d(points)
1076
1077        per = 0
1078        if closed:
1079            points = np.append(points, [points[0]], axis=0)
1080            per = 1
1081
1082        if res is None:
1083            res = len(points) * 10
1084
1085        points = np.array(points, dtype=float)
1086
1087        minx, miny, minz = np.min(points, axis=0)
1088        maxx, maxy, maxz = np.max(points, axis=0)
1089        maxb = max(maxx - minx, maxy - miny, maxz - minz)
1090        smooth *= maxb / 2  # must be in absolute units
1091
1092        x = np.linspace(0, 1, res)
1093        if easing:
1094            if easing == "InSine":
1095                x = 1 - np.cos((x * np.pi) / 2)
1096            elif easing == "OutSine":
1097                x = np.sin((x * np.pi) / 2)
1098            elif easing == "Sine":
1099                x = -(np.cos(np.pi * x) - 1) / 2
1100            elif easing == "InQuad":
1101                x = x * x
1102            elif easing == "OutQuad":
1103                x = 1 - (1 - x) * (1 - x)
1104            elif easing == "InCubic":
1105                x = x * x
1106            elif easing == "OutCubic":
1107                x = 1 - np.power(1 - x, 3)
1108            elif easing == "InQuart":
1109                x = x * x * x * x
1110            elif easing == "OutQuart":
1111                x = 1 - np.power(1 - x, 4)
1112            elif easing == "InCirc":
1113                x = 1 - np.sqrt(1 - np.power(x, 2))
1114            elif easing == "OutCirc":
1115                x = np.sqrt(1 - np.power(x - 1, 2))
1116            else:
1117                vedo.logger.error(f"unknown ease mode {easing}")
1118
1119        # find the knots
1120        tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per)
1121        # evaluate spLine, including interpolated points:
1122        xnew, ynew, znew = splev(x, tckp)
1123
1124        Line.__init__(self, np.c_[xnew, ynew, znew], lw=2)
1125        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='')
1045    def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing=""):
1046        """
1047        Arguments:
1048            smooth : (float)
1049                smoothing factor.
1050                - 0 = interpolate points exactly [default].
1051                - 1 = average point positions.
1052            degree : (int)
1053                degree of the spline (between 1 and 5).
1054            easing : (str)
1055                control sensity of points along the spline.
1056                Available options are
1057                `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].`
1058                Can be used to create animations (move objects at varying speed).
1059                See e.g.: https://easings.net
1060            res : (int)
1061                number of points on the spline
1062
1063        See also: `CSpline` and `KSpline`.
1064
1065        Examples:
1066            - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py)
1067
1068                ![](https://vedo.embl.es/images/simulations/spline_ease.gif)
1069        """
1070        from scipy.interpolate import splprep, splev
1071
1072        if isinstance(points, Points):
1073            points = points.points()
1074
1075        points = utils.make3d(points)
1076
1077        per = 0
1078        if closed:
1079            points = np.append(points, [points[0]], axis=0)
1080            per = 1
1081
1082        if res is None:
1083            res = len(points) * 10
1084
1085        points = np.array(points, dtype=float)
1086
1087        minx, miny, minz = np.min(points, axis=0)
1088        maxx, maxy, maxz = np.max(points, axis=0)
1089        maxb = max(maxx - minx, maxy - miny, maxz - minz)
1090        smooth *= maxb / 2  # must be in absolute units
1091
1092        x = np.linspace(0, 1, res)
1093        if easing:
1094            if easing == "InSine":
1095                x = 1 - np.cos((x * np.pi) / 2)
1096            elif easing == "OutSine":
1097                x = np.sin((x * np.pi) / 2)
1098            elif easing == "Sine":
1099                x = -(np.cos(np.pi * x) - 1) / 2
1100            elif easing == "InQuad":
1101                x = x * x
1102            elif easing == "OutQuad":
1103                x = 1 - (1 - x) * (1 - x)
1104            elif easing == "InCubic":
1105                x = x * x
1106            elif easing == "OutCubic":
1107                x = 1 - np.power(1 - x, 3)
1108            elif easing == "InQuart":
1109                x = x * x * x * x
1110            elif easing == "OutQuart":
1111                x = 1 - np.power(1 - x, 4)
1112            elif easing == "InCirc":
1113                x = 1 - np.sqrt(1 - np.power(x, 2))
1114            elif easing == "OutCirc":
1115                x = np.sqrt(1 - np.power(x - 1, 2))
1116            else:
1117                vedo.logger.error(f"unknown ease mode {easing}")
1118
1119        # find the knots
1120        tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per)
1121        # evaluate spLine, including interpolated points:
1122        xnew, ynew, znew = splev(x, tckp)
1123
1124        Line.__init__(self, np.c_[xnew, ynew, znew], lw=2)
1125        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):
1128class KSpline(Line):
1129    """
1130    Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline)
1131    which runs exactly through all the input points.
1132    """
1133
1134    def __init__(self, points, 
1135                 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None):
1136        """
1137        Arguments:
1138            continuity : (float)
1139                changes the sharpness in change between tangents
1140            tension : (float)
1141                changes the length of the tangent vector
1142            bias : (float)
1143                changes the direction of the tangent vector
1144            closed : (bool)
1145                join last to first point to produce a closed curve
1146            res : (int)
1147                approximate resolution of the output line.
1148                Default is 20 times the number of input points.
1149
1150        ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png)
1151
1152        See also: `Spline` and `CSpline`.
1153        """
1154        if isinstance(points, Points):
1155            points = points.points()
1156
1157        if not res:
1158            res = len(points) * 20
1159
1160        points = utils.make3d(points).astype(float)
1161
1162        xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline()
1163        yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline()
1164        zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline()
1165        for s in [xspline, yspline, zspline]:
1166            if bias:
1167                s.SetDefaultBias(bias)
1168            if tension:
1169                s.SetDefaultTension(tension)
1170            if continuity:
1171                s.SetDefaultContinuity(continuity)
1172            s.SetClosed(closed)
1173
1174        lenp = len(points[0]) > 2
1175
1176        for i, p in enumerate(points):
1177            xspline.AddPoint(i, p[0])
1178            yspline.AddPoint(i, p[1])
1179            if lenp:
1180                zspline.AddPoint(i, p[2])
1181
1182        ln = []
1183        for pos in np.linspace(0, len(points), res):
1184            x = xspline.Evaluate(pos)
1185            y = yspline.Evaluate(pos)
1186            z = 0
1187            if lenp:
1188                z = zspline.Evaluate(pos)
1189            ln.append((x, y, z))
1190
1191        Line.__init__(self, ln, lw=2)
1192        self.clean()
1193        self.lighting("off")
1194        self.name = "KSpline"
1195        self.base = np.array(points[0], dtype=float)
1196        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)
1134    def __init__(self, points, 
1135                 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None):
1136        """
1137        Arguments:
1138            continuity : (float)
1139                changes the sharpness in change between tangents
1140            tension : (float)
1141                changes the length of the tangent vector
1142            bias : (float)
1143                changes the direction of the tangent vector
1144            closed : (bool)
1145                join last to first point to produce a closed curve
1146            res : (int)
1147                approximate resolution of the output line.
1148                Default is 20 times the number of input points.
1149
1150        ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png)
1151
1152        See also: `Spline` and `CSpline`.
1153        """
1154        if isinstance(points, Points):
1155            points = points.points()
1156
1157        if not res:
1158            res = len(points) * 20
1159
1160        points = utils.make3d(points).astype(float)
1161
1162        xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline()
1163        yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline()
1164        zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline()
1165        for s in [xspline, yspline, zspline]:
1166            if bias:
1167                s.SetDefaultBias(bias)
1168            if tension:
1169                s.SetDefaultTension(tension)
1170            if continuity:
1171                s.SetDefaultContinuity(continuity)
1172            s.SetClosed(closed)
1173
1174        lenp = len(points[0]) > 2
1175
1176        for i, p in enumerate(points):
1177            xspline.AddPoint(i, p[0])
1178            yspline.AddPoint(i, p[1])
1179            if lenp:
1180                zspline.AddPoint(i, p[2])
1181
1182        ln = []
1183        for pos in np.linspace(0, len(points), res):
1184            x = xspline.Evaluate(pos)
1185            y = yspline.Evaluate(pos)
1186            z = 0
1187            if lenp:
1188                z = zspline.Evaluate(pos)
1189            ln.append((x, y, z))
1190
1191        Line.__init__(self, ln, lw=2)
1192        self.clean()
1193        self.lighting("off")
1194        self.name = "KSpline"
1195        self.base = np.array(points[0], dtype=float)
1196        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.

See also: Spline and CSpline.

class CSpline(Line):
1199class CSpline(Line):
1200    """
1201    Return a Cardinal spline which runs exactly through all the input points.
1202    """
1203
1204    def __init__(self, points, closed=False, res=None):
1205        """
1206        Arguments:
1207            closed : (bool)
1208                join last to first point to produce a closed curve
1209            res : (int)
1210                approximate resolution of the output line.
1211                Default is 20 times the number of input points.
1212
1213        See also: `Spline` and `KSpline`.
1214        """
1215
1216        if isinstance(points, Points):
1217            points = points.points()
1218
1219        if not res:
1220            res = len(points) * 20
1221
1222        points = utils.make3d(points).astype(float)
1223
1224        xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline()
1225        yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline()
1226        zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline()
1227        for s in [xspline, yspline, zspline]:
1228            s.SetClosed(closed)
1229
1230        lenp = len(points[0]) > 2
1231
1232        for i, p in enumerate(points):
1233            xspline.AddPoint(i, p[0])
1234            yspline.AddPoint(i, p[1])
1235            if lenp:
1236                zspline.AddPoint(i, p[2])
1237
1238        ln = []
1239        for pos in np.linspace(0, len(points), res):
1240            x = xspline.Evaluate(pos)
1241            y = yspline.Evaluate(pos)
1242            z = 0
1243            if lenp:
1244                z = zspline.Evaluate(pos)
1245            ln.append((x, y, z))
1246
1247        Line.__init__(self, ln, lw=2)
1248        self.clean()
1249        self.lighting("off")
1250        self.name = "CSpline"
1251        self.base = points[0]
1252        self.top = points[-1]

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

CSpline(points, closed=False, res=None)
1204    def __init__(self, points, closed=False, res=None):
1205        """
1206        Arguments:
1207            closed : (bool)
1208                join last to first point to produce a closed curve
1209            res : (int)
1210                approximate resolution of the output line.
1211                Default is 20 times the number of input points.
1212
1213        See also: `Spline` and `KSpline`.
1214        """
1215
1216        if isinstance(points, Points):
1217            points = points.points()
1218
1219        if not res:
1220            res = len(points) * 20
1221
1222        points = utils.make3d(points).astype(float)
1223
1224        xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline()
1225        yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline()
1226        zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline()
1227        for s in [xspline, yspline, zspline]:
1228            s.SetClosed(closed)
1229
1230        lenp = len(points[0]) > 2
1231
1232        for i, p in enumerate(points):
1233            xspline.AddPoint(i, p[0])
1234            yspline.AddPoint(i, p[1])
1235            if lenp:
1236                zspline.AddPoint(i, p[2])
1237
1238        ln = []
1239        for pos in np.linspace(0, len(points), res):
1240            x = xspline.Evaluate(pos)
1241            y = yspline.Evaluate(pos)
1242            z = 0
1243            if lenp:
1244                z = zspline.Evaluate(pos)
1245            ln.append((x, y, z))
1246
1247        Line.__init__(self, ln, lw=2)
1248        self.clean()
1249        self.lighting("off")
1250        self.name = "CSpline"
1251        self.base = points[0]
1252        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.

See also: Spline and KSpline.

class Bezier(Line):
1255class Bezier(Line):
1256    """
1257    Generate the Bezier line that links the first to the last point.
1258    """
1259
1260    def __init__(self, points, res=None):
1261        """
1262        Example:
1263            ```python
1264            from vedo import *
1265            import numpy as np
1266            pts = np.random.randn(25,3)
1267            for i,p in enumerate(pts):
1268                p += [5*i, 15*sin(i/2), i*i*i/200]
1269            show(Points(pts), Bezier(pts), axes=1).close()
1270            ```
1271            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1272        """
1273        N = len(points)
1274        if res is None:
1275            res = 10 * N
1276        t = np.linspace(0, 1, num=res)
1277        bcurve = np.zeros((res, len(points[0])))
1278
1279        def binom(n, k):
1280            b = 1
1281            for t in range(1, min(k, n - k) + 1):
1282                b *= n / t
1283                n -= 1
1284            return b
1285
1286        def bernstein(n, k):
1287            coeff = binom(n, k)
1288
1289            def _bpoly(x):
1290                return coeff * x ** k * (1 - x) ** (n - k)
1291
1292            return _bpoly
1293
1294        for ii in range(N):
1295            b = bernstein(N - 1, ii)(t)
1296            bcurve += np.outer(b, points[ii])
1297        Line.__init__(self, bcurve, lw=2)
1298        self.name = "BezierLine"

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

Bezier(points, res=None)
1260    def __init__(self, points, res=None):
1261        """
1262        Example:
1263            ```python
1264            from vedo import *
1265            import numpy as np
1266            pts = np.random.randn(25,3)
1267            for i,p in enumerate(pts):
1268                p += [5*i, 15*sin(i/2), i*i*i/200]
1269            show(Points(pts), Bezier(pts), axes=1).close()
1270            ```
1271            ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png)
1272        """
1273        N = len(points)
1274        if res is None:
1275            res = 10 * N
1276        t = np.linspace(0, 1, num=res)
1277        bcurve = np.zeros((res, len(points[0])))
1278
1279        def binom(n, k):
1280            b = 1
1281            for t in range(1, min(k, n - k) + 1):
1282                b *= n / t
1283                n -= 1
1284            return b
1285
1286        def bernstein(n, k):
1287            coeff = binom(n, k)
1288
1289            def _bpoly(x):
1290                return coeff * x ** k * (1 - x) ** (n - k)
1291
1292            return _bpoly
1293
1294        for ii in range(N):
1295            b = bernstein(N - 1, ii)(t)
1296            bcurve += np.outer(b, points[ii])
1297        Line.__init__(self, bcurve, lw=2)
1298        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):
3687class Brace(Mesh):
3688    """
3689    Create a brace (bracket) shape.
3690    """
3691
3692    def __init__(
3693        self,
3694        q1,
3695        q2,
3696        style="}",
3697        padding1=0.0,
3698        font="Theemim",
3699        comment="",
3700        justify=None,
3701        angle=0.0,
3702        padding2=0.2,
3703        s=1.0,
3704        italic=0,
3705        c="k1",
3706        alpha=1.0,
3707    ):
3708        """
3709        Create a brace (bracket) shape which spans from point q1 to point q2.
3710
3711        Arguments:
3712            q1 : (list)
3713                point 1.
3714            q2 : (list)
3715                point 2.
3716            style : (str)
3717                style of the bracket, eg. `{}, [], (), <>`.
3718            padding1 : (float)
3719                padding space in percent form the input points.
3720            font : (str)
3721                font type
3722            comment : (str)
3723                additional text to appear next to the brace symbol.
3724            justify : (str)
3725                specify the anchor point to justify text comment, e.g. "top-left".
3726            italic : float
3727                italicness of the text comment (can be a positive or negative number)
3728            angle : (float)
3729                rotation angle of text. Use `None` to keep it horizontal.
3730            padding2 : (float)
3731                padding space in percent form brace to text comment.
3732            s : (float)
3733                scale factor for the comment
3734
3735        Examples:
3736            - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py)
3737
3738                ![](https://vedo.embl.es/images/pyplot/scatter3.png)
3739        """
3740        if isinstance(q1, vtk.vtkActor):
3741            q1 = q1.GetPosition()
3742        if isinstance(q2, vtk.vtkActor):
3743            q2 = q2.GetPosition()
3744        if len(q1) == 2:
3745            q1 = [q1[0], q1[1], 0.0]
3746        if len(q2) == 2:
3747            q2 = [q2[0], q2[1], 0.0]
3748        q1 = np.array(q1, dtype=float)
3749        q2 = np.array(q2, dtype=float)
3750        mq = (q1 + q2) / 2
3751        q1 = q1 - mq
3752        q2 = q2 - mq
3753        d = np.linalg.norm(q2 - q1)
3754        q2[2] = q1[2]
3755
3756        if style not in "{}[]()<>|I":
3757            vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I")
3758            style = "}"
3759
3760        flip = False
3761        if style in ["{", "[", "(", "<"]:
3762            flip = True
3763            i = ["{", "[", "(", "<"].index(style)
3764            style = ["}", "]", ")", ">"][i]
3765
3766        br = Text3D(style, font="Theemim", justify="center-left")
3767        br.scale([0.4, 1, 1])
3768
3769        angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90
3770        if flip:
3771            angler += 180
3772
3773        _, x1, y0, y1, _, _ = br.bounds()
3774        if comment:
3775            just = "center-top"
3776            if angle is None:
3777                angle = -angler + 90
3778                if not flip:
3779                    angle += 180
3780
3781            if flip:
3782                angle += 180
3783                just = "center-bottom"
3784            if justify is not None:
3785                just = justify
3786            cmt = Text3D(comment, font=font, justify=just, italic=italic)
3787            cx0, cx1 = cmt.xbounds()
3788            cmt.rotate_z(90 + angle)
3789            cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5)
3790            cmt.shift(x1 * (1 + padding2), 0, 0)
3791            poly = merge(br, cmt).polydata()
3792
3793        else:
3794            poly = br.polydata()
3795
3796        tr = vtk.vtkTransform()
3797        tr.RotateZ(angler)
3798        tr.Translate(padding1 * d, 0, 0)
3799        pscale = 1
3800        tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1)
3801        tf = vtk.vtkTransformPolyDataFilter()
3802        tf.SetInputData(poly)
3803        tf.SetTransform(tr)
3804        tf.Update()
3805        poly = tf.GetOutput()
3806
3807        Mesh.__init__(self, poly, c, alpha)
3808        self.SetPosition(mq)
3809        self.name = "Brace"
3810        self.base = q1
3811        self.top = q2

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

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):
1301class NormalLines(Mesh):
1302    """
1303    Build an `Glyph` to show the normals at cell centers or at mesh vertices.
1304
1305    Arguments:
1306        ratio : (int)
1307            show 1 normal every `ratio` cells.
1308        on : (str)
1309            either "cells" or "points".
1310        scale : (float)
1311            scale factor to control size.
1312    """
1313
1314    def __init__(self, msh, ratio=1, on="cells", scale=1.0):
1315
1316        poly = msh.clone().compute_normals().polydata()
1317
1318        if "cell" in on:
1319            centers = vtk.vtkCellCenters()
1320            centers.SetInputData(poly)
1321            centers.Update()
1322            poly = centers.GetOutput()
1323
1324        mask_pts = vtk.vtkMaskPoints()
1325        mask_pts.SetInputData(poly)
1326        mask_pts.SetOnRatio(ratio)
1327        mask_pts.RandomModeOff()
1328        mask_pts.Update()
1329
1330        ln = vtk.vtkLineSource()
1331        ln.SetPoint1(0, 0, 0)
1332        ln.SetPoint2(1, 0, 0)
1333        ln.Update()
1334        glyph = vtk.vtkGlyph3D()
1335        glyph.SetSourceData(ln.GetOutput())
1336        glyph.SetInputData(mask_pts.GetOutput())
1337        glyph.SetVectorModeToUseNormal()
1338
1339        b = poly.GetBounds()
1340        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1341        glyph.SetScaleFactor(f)
1342        glyph.OrientOn()
1343        glyph.Update()
1344
1345        Mesh.__init__(self, glyph.GetOutput())
1346
1347        self.PickableOff()
1348        prop = vtk.vtkProperty()
1349        prop.DeepCopy(msh.GetProperty())
1350        self.SetProperty(prop)
1351        self.property = prop
1352        self.property.LightingOff()
1353        self.mapper().ScalarVisibilityOff()
1354        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)
1314    def __init__(self, msh, ratio=1, on="cells", scale=1.0):
1315
1316        poly = msh.clone().compute_normals().polydata()
1317
1318        if "cell" in on:
1319            centers = vtk.vtkCellCenters()
1320            centers.SetInputData(poly)
1321            centers.Update()
1322            poly = centers.GetOutput()
1323
1324        mask_pts = vtk.vtkMaskPoints()
1325        mask_pts.SetInputData(poly)
1326        mask_pts.SetOnRatio(ratio)
1327        mask_pts.RandomModeOff()
1328        mask_pts.Update()
1329
1330        ln = vtk.vtkLineSource()
1331        ln.SetPoint1(0, 0, 0)
1332        ln.SetPoint2(1, 0, 0)
1333        ln.Update()
1334        glyph = vtk.vtkGlyph3D()
1335        glyph.SetSourceData(ln.GetOutput())
1336        glyph.SetInputData(mask_pts.GetOutput())
1337        glyph.SetVectorModeToUseNormal()
1338
1339        b = poly.GetBounds()
1340        f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale
1341        glyph.SetScaleFactor(f)
1342        glyph.OrientOn()
1343        glyph.Update()
1344
1345        Mesh.__init__(self, glyph.GetOutput())
1346
1347        self.PickableOff()
1348        prop = vtk.vtkProperty()
1349        prop.DeepCopy(msh.GetProperty())
1350        self.SetProperty(prop)
1351        self.property = prop
1352        self.property.LightingOff()
1353        self.mapper().ScalarVisibilityOff()
1354        self.name = "NormalLines"

Input can be a list of vertices and their connectivity (faces of the polygonal mesh), or directly a vtkPolydata object. For point clouds - e.i. no faces - just substitute the faces list with None.

Example:

Mesh( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )

Arguments:
  • c : (color) color in RGB format, hex, symbol or name
  • alpha : (float) mesh opacity [0,1]
Examples:

def StreamLines( domain, probe, active_vectors='', integrator='rk4', direction='forward', initial_step_size=None, max_propagation=None, max_steps=10000, step_length=None, extrapolate_to_box=(), surface_constrained=False, compute_vorticity=False, ribbons=None, tubes=(), scalar_range=None, lw=None, **opts):
1417def StreamLines(
1418    domain,
1419    probe,
1420    active_vectors="",
1421    integrator="rk4",
1422    direction="forward",
1423    initial_step_size=None,
1424    max_propagation=None,
1425    max_steps=10000,
1426    step_length=None,
1427    extrapolate_to_box=(),
1428    surface_constrained=False,
1429    compute_vorticity=False,
1430    ribbons=None,
1431    tubes=(),
1432    scalar_range=None,
1433    lw=None,
1434    **opts,
1435):
1436    """
1437    Integrate a vector field on a domain (a Points/Mesh or other vtk datasets types)
1438    to generate streamlines.
1439
1440    The integration is performed using a specified integrator (Runge-Kutta).
1441    The length of a streamline is governed by specifying a maximum value either
1442    in physical arc length or in (local) cell length.
1443    Otherwise, the integration terminates upon exiting the field domain.
1444
1445    Arguments:
1446        domain : (Points, Volume, vtkDataSet)
1447            the object that contains the vector field
1448        probe : (Mesh, list)
1449            the Mesh that probes the domain. Its coordinates will
1450            be the seeds for the streamlines, can also be an array of positions.
1451        active_vectors : (str)
1452            name of the vector array to be used
1453        integrator : (str)
1454            Runge-Kutta integrator, either 'rk2', 'rk4' of 'rk45'
1455        initial_step_size : (float)
1456            initial step size of integration
1457        max_propagation : (float)
1458            maximum physical length of the streamline
1459        max_steps : (int)
1460            maximum nr of steps allowed
1461        step_length : (float)
1462            length of step integration.
1463        extrapolate_to_box : (dict)
1464            Vectors that are defined on a discrete set of points
1465            are extrapolated to a 3D domain defined by its bounding box:
1466                - bounds (list), bounding box of the domain
1467                - kernel (str), interpolation kernel `["shepard","gaussian","voronoi","linear"]`
1468                - radius (float), radius of the local search
1469                - dims (list), nr of subdivisions of the domain along x, y, and z
1470                - null_value (float), value to be assigned to invalid points
1471
1472        surface_constrained : (bool)
1473            force streamlines to be computed on a surface
1474        compute_vorticity : (bool)
1475            Turn on/off vorticity computation at streamline points
1476            (necessary for generating proper stream-ribbons)
1477        ribbons : (int)
1478            render lines as ribbons by joining them.
1479            An integer value represent the ratio of joining (e.g.: ribbons=2 groups lines 2 by 2)
1480
1481        tubes : (dict)
1482            dictionary containing the parameters for the tube representation:
1483            - ratio (int), draws tube as longitudinal stripes
1484            - res (int), tube resolution (nr. of sides, 12 by default)
1485            - max_radius_factor (float), max tube radius as a multiple of the min radius
1486            - cap (bool), capping of the tube
1487            - mode (int), radius varies based on the scalar or vector magnitude:
1488                - 0 do not vary radius
1489                - 1 vary radius by scalar
1490                - 2 vary radius by vector
1491                - 3 vary radius by absolute value of scalar
1492                - 4 vary radius by vector norm
1493
1494        scalar_range : (list)
1495            specify the scalar range for coloring
1496
1497    Examples:
1498        - [streamlines1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamlines1.py)
1499        - [streamlines2.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamlines2.py)
1500
1501            ![](https://vedo.embl.es/images/volumetric/81459343-b9210d00-919f-11ea-846c-152d62cba06e.png)
1502
1503        - [streamribbons.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamribbons.py)
1504        - [office.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/office.py)
1505
1506            ![](https://vedo.embl.es/images/volumetric/56964003-9145a500-6b5a-11e9-9d9e-9736d90e1900.png)
1507    """
1508    if len(opts):  # Deprecations
1509        printc(" Warning! In StreamLines() unrecognized keywords:", opts, c="y")
1510        initial_step_size = opts.pop("initialStepSize", initial_step_size)
1511        max_propagation = opts.pop("maxPropagation", max_propagation)
1512        max_steps = opts.pop("maxSteps", max_steps)
1513        step_length = opts.pop("stepLength", step_length)
1514        extrapolate_to_box = opts.pop("extrapolateToBox", extrapolate_to_box)
1515        surface_constrained = opts.pop("surfaceConstrained", surface_constrained)
1516        compute_vorticity = opts.pop("computeVorticity", compute_vorticity)
1517        scalar_range = opts.pop("scalarRange", scalar_range)
1518        printc("          Please use 'snake_case' instead of 'camelCase' keywords", c="y")
1519
1520    if isinstance(domain, vedo.Points):
1521        if extrapolate_to_box:
1522            grid = _interpolate2vol(domain.polydata(), **extrapolate_to_box)
1523        else:
1524            grid = domain.polydata()
1525    elif isinstance(domain, vedo.BaseVolume):
1526        grid = domain.inputdata()
1527    else:
1528        grid = domain
1529
1530    if active_vectors:
1531        grid.GetPointData().SetActiveVectors(active_vectors)
1532
1533    b = grid.GetBounds()
1534    size = (b[5] - b[4] + b[3] - b[2] + b[1] - b[0]) / 3
1535    if initial_step_size is None:
1536        initial_step_size = size / 500.0
1537    if max_propagation is None:
1538        max_propagation = size
1539
1540    if utils.is_sequence(probe):
1541        pts = utils.make3d(probe)
1542    else:
1543        pts = probe.clean().points()
1544
1545    src = vtk.vtkProgrammableSource()
1546
1547    def read_points():
1548        output = src.GetPolyDataOutput()
1549        points = vtk.vtkPoints()
1550        for x, y, z in pts:
1551            points.InsertNextPoint(x, y, z)
1552        output.SetPoints(points)
1553
1554    src.SetExecuteMethod(read_points)
1555    src.Update()
1556
1557    st = vtk.vtkStreamTracer()
1558    st.SetInputDataObject(grid)
1559    st.SetSourceConnection(src.GetOutputPort())
1560
1561    st.SetInitialIntegrationStep(initial_step_size)
1562    st.SetComputeVorticity(compute_vorticity)
1563    st.SetMaximumNumberOfSteps(max_steps)
1564    st.SetMaximumPropagation(max_propagation)
1565    st.SetSurfaceStreamlines(surface_constrained)
1566    if step_length:
1567        st.SetMaximumIntegrationStep(step_length)
1568
1569    if "f" in direction:
1570        st.SetIntegrationDirectionToForward()
1571    elif "back" in direction:
1572        st.SetIntegrationDirectionToBackward()
1573    elif "both" in direction:
1574        st.SetIntegrationDirectionToBoth()
1575
1576    if integrator == "rk2":
1577        st.SetIntegratorTypeToRungeKutta2()
1578    elif integrator == "rk4":
1579        st.SetIntegratorTypeToRungeKutta4()
1580    elif integrator == "rk45":
1581        st.SetIntegratorTypeToRungeKutta45()
1582    else:
1583        vedo.logger.error(f"in streamlines, unknown integrator {integrator}")
1584
1585    st.Update()
1586    output = st.GetOutput()
1587
1588    if ribbons:
1589        scalar_surface = vtk.vtkRuledSurfaceFilter()
1590        scalar_surface.SetInputConnection(st.GetOutputPort())
1591        scalar_surface.SetOnRatio(int(ribbons))
1592        scalar_surface.SetRuledModeToPointWalk()
1593        scalar_surface.Update()
1594        output = scalar_surface.GetOutput()
1595
1596    if tubes:
1597        radius = tubes.pop("radius", domain.GetLength() / 500)
1598        res = tubes.pop("res", 24)
1599        radfact = tubes.pop("max_radius_factor", 10)
1600        ratio = tubes.pop("ratio", 1)
1601        mode = tubes.pop("mode", 0)
1602        cap = tubes.pop("mode", False)
1603        if tubes:
1604            vedo.logger.warning(f"in StreamLines unknown 'tubes' parameters: {tubes}")
1605
1606        stream_tube = vtk.vtkTubeFilter()
1607        stream_tube.SetNumberOfSides(res)
1608        stream_tube.SetRadius(radius)
1609        stream_tube.SetCapping(cap)
1610        # max tube radius as a multiple of the min radius
1611        stream_tube.SetRadiusFactor(radfact)
1612        stream_tube.SetOnRatio(int(ratio))
1613        stream_tube.SetVaryRadius(int(mode))
1614
1615        stream_tube.SetInputData(output)
1616        vname = grid.GetPointData().GetVectors().GetName()
1617        stream_tube.SetInputArrayToProcess(
1618            1, 0, 0, vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS, vname
1619        )
1620        stream_tube.Update()
1621        sta = vedo.mesh.Mesh(stream_tube.GetOutput(), c=None)
1622
1623        scals = grid.GetPointData().GetScalars()
1624        if scals:
1625            sta.mapper().SetScalarRange(scals.GetRange())
1626        if scalar_range is not None:
1627            sta.mapper().SetScalarRange(scalar_range)
1628
1629        sta.phong()
1630        sta.name = "StreamLines"
1631        #############
1632        return sta  #############
1633        #############
1634
1635    sta = vedo.mesh.Mesh(output, c=None)
1636
1637    if lw is not None and len(tubes) == 0 and not ribbons:
1638        sta.lw(lw)
1639        sta.mapper().SetResolveCoincidentTopologyToPolygonOffset()
1640        sta.lighting("off")
1641
1642    scals = grid.GetPointData().GetScalars()
1643    if scals:
1644        sta.mapper().SetScalarRange(scals.GetRange())
1645    if scalar_range is not None:
1646        sta.mapper().SetScalarRange(scalar_range)
1647
1648    sta.name = "StreamLines"
1649    return sta

Integrate a vector field on a domain (a Points/Mesh or other vtk datasets types) to generate streamlines.

The integration is performed using a specified integrator (Runge-Kutta). The length of a streamline is governed by specifying a maximum value either in physical arc length or in (local) cell length. Otherwise, the integration terminates upon exiting the field domain.

Arguments:
  • domain : (Points, Volume, vtkDataSet) the object that contains the vector field
  • probe : (Mesh, list) the Mesh that probes the domain. Its coordinates will be the seeds for the streamlines, can also be an array of positions.
  • active_vectors : (str) name of the vector array to be used
  • integrator : (str) Runge-Kutta integrator, either 'rk2', 'rk4' of 'rk45'
  • initial_step_size : (float) initial step size of integration
  • max_propagation : (float) maximum physical length of the streamline
  • max_steps : (int) maximum nr of steps allowed
  • step_length : (float) length of step integration.
  • extrapolate_to_box : (dict) Vectors that are defined on a discrete set of points are extrapolated to a 3D domain defined by its bounding box: - bounds (list), bounding box of the domain - kernel (str), interpolation kernel ["shepard","gaussian","voronoi","linear"] - radius (float), radius of the local search - dims (list), nr of subdivisions of the domain along x, y, and z - null_value (float), value to be assigned to invalid points
  • surface_constrained : (bool) force streamlines to be computed on a surface
  • compute_vorticity : (bool) Turn on/off vorticity computation at streamline points (necessary for generating proper stream-ribbons)
  • ribbons : (int) render lines as ribbons by joining them. An integer value represent the ratio of joining (e.g.: ribbons=2 groups lines 2 by 2)
  • tubes : (dict) dictionary containing the parameters for the tube representation:
    • ratio (int), draws tube as longitudinal stripes
    • res (int), tube resolution (nr. of sides, 12 by default)
    • max_radius_factor (float), max tube radius as a multiple of the min radius
    • cap (bool), capping of the tube
    • mode (int), radius varies based on the scalar or vector magnitude:
      • 0 do not vary radius
      • 1 vary radius by scalar
      • 2 vary radius by vector
      • 3 vary radius by absolute value of scalar
      • 4 vary radius by vector norm
  • scalar_range : (list) specify the scalar range for coloring
Examples:
class Ribbon(vedo.mesh.Mesh):
1781class Ribbon(Mesh):
1782    """
1783    Connect two lines to generate the surface inbetween.
1784    Set the mode by which to create the ruled surface.
1785
1786    It also works with a single line in input. In this case the ribbon
1787    is formed by following the local plane of the line in space.
1788    """
1789
1790    def __init__(
1791        self,
1792        line1,
1793        line2=None,
1794        mode=0,
1795        closed=False,
1796        width=None,
1797        res=(200, 5),
1798        c="indigo3",
1799        alpha=1.0,
1800    ):
1801        """
1802        Arguments:
1803            mode : (int)
1804                If mode=0, resample evenly the input lines (based on length)
1805                and generates triangle strips.
1806
1807                If mode=1, use the existing points and walks around the
1808                polyline using existing points.
1809
1810            closed : (bool)
1811                if True, join the last point with the first to form a closed surface
1812
1813            res : (list)
1814                ribbon resolutions along the line and perpendicularly to it.
1815
1816        Examples:
1817            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1818
1819                ![](https://vedo.embl.es/images/basic/ribbon.png)
1820        """
1821
1822        if isinstance(line1, Points):
1823            line1 = line1.points()
1824
1825        if isinstance(line2, Points):
1826            line2 = line2.points()
1827
1828        elif line2 is None:
1829            #############################################
1830            ribbon_filter = vtk.vtkRibbonFilter()
1831            aline = Line(line1)
1832            ribbon_filter.SetInputData(aline.polydata())
1833            if width is None:
1834                width = aline.diagonal_size() / 20.0
1835            ribbon_filter.SetWidth(width)
1836            ribbon_filter.Update()
1837            Mesh.__init__(self, ribbon_filter.GetOutput(), c, alpha)
1838            self.name = "Ribbon"
1839            ##############################################
1840            return  ######################################
1841            ##############################################
1842
1843        line1 = np.asarray(line1)
1844        line2 = np.asarray(line2)
1845
1846        if closed:
1847            line1 = line1.tolist()
1848            line1 += [line1[0]]
1849            line2 = line2.tolist()
1850            line2 += [line2[0]]
1851            line1 = np.array(line1)
1852            line2 = np.array(line2)
1853
1854        if len(line1[0]) == 2:
1855            line1 = np.c_[line1, np.zeros(len(line1))]
1856        if len(line2[0]) == 2:
1857            line2 = np.c_[line2, np.zeros(len(line2))]
1858
1859        ppoints1 = vtk.vtkPoints()  # Generate the polyline1
1860        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1861        lines1 = vtk.vtkCellArray()
1862        lines1.InsertNextCell(len(line1))
1863        for i in range(len(line1)):
1864            lines1.InsertCellPoint(i)
1865        poly1 = vtk.vtkPolyData()
1866        poly1.SetPoints(ppoints1)
1867        poly1.SetLines(lines1)
1868
1869        ppoints2 = vtk.vtkPoints()  # Generate the polyline2
1870        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1871        lines2 = vtk.vtkCellArray()
1872        lines2.InsertNextCell(len(line2))
1873        for i in range(len(line2)):
1874            lines2.InsertCellPoint(i)
1875        poly2 = vtk.vtkPolyData()
1876        poly2.SetPoints(ppoints2)
1877        poly2.SetLines(lines2)
1878
1879        # build the lines
1880        lines1 = vtk.vtkCellArray()
1881        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1882        for i in range(poly1.GetNumberOfPoints()):
1883            lines1.InsertCellPoint(i)
1884
1885        polygon1 = vtk.vtkPolyData()
1886        polygon1.SetPoints(ppoints1)
1887        polygon1.SetLines(lines1)
1888
1889        lines2 = vtk.vtkCellArray()
1890        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1891        for i in range(poly2.GetNumberOfPoints()):
1892            lines2.InsertCellPoint(i)
1893
1894        polygon2 = vtk.vtkPolyData()
1895        polygon2.SetPoints(ppoints2)
1896        polygon2.SetLines(lines2)
1897
1898        merged_pd = vtk.vtkAppendPolyData()
1899        merged_pd.AddInputData(polygon1)
1900        merged_pd.AddInputData(polygon2)
1901        merged_pd.Update()
1902
1903        rsf = vtk.vtkRuledSurfaceFilter()
1904        rsf.CloseSurfaceOff()
1905        rsf.SetRuledMode(mode)
1906        rsf.SetResolution(res[0], res[1])
1907        rsf.SetInputData(merged_pd.GetOutput())
1908        rsf.Update()
1909
1910        Mesh.__init__(self, rsf.GetOutput(), c, alpha)
1911        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)
1790    def __init__(
1791        self,
1792        line1,
1793        line2=None,
1794        mode=0,
1795        closed=False,
1796        width=None,
1797        res=(200, 5),
1798        c="indigo3",
1799        alpha=1.0,
1800    ):
1801        """
1802        Arguments:
1803            mode : (int)
1804                If mode=0, resample evenly the input lines (based on length)
1805                and generates triangle strips.
1806
1807                If mode=1, use the existing points and walks around the
1808                polyline using existing points.
1809
1810            closed : (bool)
1811                if True, join the last point with the first to form a closed surface
1812
1813            res : (list)
1814                ribbon resolutions along the line and perpendicularly to it.
1815
1816        Examples:
1817            - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py)
1818
1819                ![](https://vedo.embl.es/images/basic/ribbon.png)
1820        """
1821
1822        if isinstance(line1, Points):
1823            line1 = line1.points()
1824
1825        if isinstance(line2, Points):
1826            line2 = line2.points()
1827
1828        elif line2 is None:
1829            #############################################
1830            ribbon_filter = vtk.vtkRibbonFilter()
1831            aline = Line(line1)
1832            ribbon_filter.SetInputData(aline.polydata())
1833            if width is None:
1834                width = aline.diagonal_size() / 20.0
1835            ribbon_filter.SetWidth(width)
1836            ribbon_filter.Update()
1837            Mesh.__init__(self, ribbon_filter.GetOutput(), c, alpha)
1838            self.name = "Ribbon"
1839            ##############################################
1840            return  ######################################
1841            ##############################################
1842
1843        line1 = np.asarray(line1)
1844        line2 = np.asarray(line2)
1845
1846        if closed:
1847            line1 = line1.tolist()
1848            line1 += [line1[0]]
1849            line2 = line2.tolist()
1850            line2 += [line2[0]]
1851            line1 = np.array(line1)
1852            line2 = np.array(line2)
1853
1854        if len(line1[0]) == 2:
1855            line1 = np.c_[line1, np.zeros(len(line1))]
1856        if len(line2[0]) == 2:
1857            line2 = np.c_[line2, np.zeros(len(line2))]
1858
1859        ppoints1 = vtk.vtkPoints()  # Generate the polyline1
1860        ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32))
1861        lines1 = vtk.vtkCellArray()
1862        lines1.InsertNextCell(len(line1))
1863        for i in range(len(line1)):
1864            lines1.InsertCellPoint(i)
1865        poly1 = vtk.vtkPolyData()
1866        poly1.SetPoints(ppoints1)
1867        poly1.SetLines(lines1)
1868
1869        ppoints2 = vtk.vtkPoints()  # Generate the polyline2
1870        ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32))
1871        lines2 = vtk.vtkCellArray()
1872        lines2.InsertNextCell(len(line2))
1873        for i in range(len(line2)):
1874            lines2.InsertCellPoint(i)
1875        poly2 = vtk.vtkPolyData()
1876        poly2.SetPoints(ppoints2)
1877        poly2.SetLines(lines2)
1878
1879        # build the lines
1880        lines1 = vtk.vtkCellArray()
1881        lines1.InsertNextCell(poly1.GetNumberOfPoints())
1882        for i in range(poly1.GetNumberOfPoints()):
1883            lines1.InsertCellPoint(i)
1884
1885        polygon1 = vtk.vtkPolyData()
1886        polygon1.SetPoints(ppoints1)
1887        polygon1.SetLines(lines1)
1888
1889        lines2 = vtk.vtkCellArray()
1890        lines2.InsertNextCell(poly2.GetNumberOfPoints())
1891        for i in range(poly2.GetNumberOfPoints()):
1892            lines2.InsertCellPoint(i)
1893
1894        polygon2 = vtk.vtkPolyData()
1895        polygon2.SetPoints(ppoints2)
1896        polygon2.SetLines(lines2)
1897
1898        merged_pd = vtk.vtkAppendPolyData()
1899        merged_pd.AddInputData(polygon1)
1900        merged_pd.AddInputData(polygon2)
1901        merged_pd.Update()
1902
1903        rsf = vtk.vtkRuledSurfaceFilter()
1904        rsf.CloseSurfaceOff()
1905        rsf.SetRuledMode(mode)
1906        rsf.SetResolution(res[0], res[1])
1907        rsf.SetInputData(merged_pd.GetOutput())
1908        rsf.Update()
1909
1910        Mesh.__init__(self, rsf.GetOutput(), c, alpha)
1911        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):
1914class Arrow(Mesh):
1915    """
1916    Build a 3D arrow from `start_pt` to `end_pt` of section size `s`,
1917    expressed as the fraction of the window size.
1918    """
1919
1920    def __init__(
1921        self,
1922        start_pt=(0, 0, 0),
1923        end_pt=(1, 0, 0),
1924        s=None,
1925        shaft_radius=None,
1926        head_radius=None,
1927        head_length=None,
1928        res=12,
1929        c="r4",
1930        alpha=1.0,
1931    ):
1932        """
1933        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1934        from white to red.
1935
1936        .. note:: If `s=None` the arrow is scaled proportionally to its length
1937
1938        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1939        """
1940        # in case user is passing meshs
1941        if isinstance(start_pt, vtk.vtkActor):
1942            start_pt = start_pt.GetPosition()
1943        if isinstance(end_pt, vtk.vtkActor):
1944            end_pt = end_pt.GetPosition()
1945
1946        axis = np.asarray(end_pt) - np.asarray(start_pt)
1947        length = np.linalg.norm(axis)
1948        if length:
1949            axis = axis / length
1950        if len(axis) < 3:  # its 2d
1951            theta = np.pi / 2
1952            start_pt = [start_pt[0], start_pt[1], 0.0]
1953            end_pt = [end_pt[0], end_pt[1], 0.0]
1954        else:
1955            theta = np.arccos(axis[2])
1956        phi = np.arctan2(axis[1], axis[0])
1957        self.source = vtk.vtkArrowSource()
1958        self.source.SetShaftResolution(res)
1959        self.source.SetTipResolution(res)
1960
1961        if s:
1962            sz = 0.02
1963            self.source.SetTipRadius(sz)
1964            self.source.SetShaftRadius(sz / 1.75)
1965            self.source.SetTipLength(sz * 15)
1966
1967        # if s:
1968        #     sz = 0.02 * s * length
1969        #     tl = sz / 20
1970        #     print(s, sz)
1971        #     self.source.SetShaftRadius(sz)
1972        #     self.source.SetTipRadius(sz*1.75)
1973        #     self.source.SetTipLength(sz*15)
1974
1975        if head_length:
1976            self.source.SetTipLength(head_length)
1977        if head_radius:
1978            self.source.SetTipRadius(head_radius)
1979        if shaft_radius:
1980            self.source.SetShaftRadius(shaft_radius)
1981
1982        self.source.Update()
1983
1984        t = vtk.vtkTransform()
1985        t.RotateZ(np.rad2deg(phi))
1986        t.RotateY(np.rad2deg(theta))
1987        t.RotateY(-90)  # put it along Z
1988        if s:
1989            sz = 800 * s
1990            t.Scale(length, sz, sz)
1991        else:
1992            t.Scale(length, length, length)
1993        tf = vtk.vtkTransformPolyDataFilter()
1994        tf.SetInputData(self.source.GetOutput())
1995        tf.SetTransform(t)
1996        tf.Update()
1997
1998        Mesh.__init__(self, tf.GetOutput(), c, alpha)
1999
2000        self.phong().lighting("plastic")
2001        self.SetPosition(start_pt)
2002        self.PickableOff()
2003        self.DragableOff()
2004        self.base = np.array(start_pt, dtype=float)
2005        self.top = np.array(end_pt, dtype=float)
2006        self.tip_index = None
2007        self.fill = True  # used by pyplot.__iadd__()
2008        self.s = s if s is not None else 1  ## used by pyplot.__iadd()
2009        self.name = "Arrow"
2010
2011    def tip_point(self, return_index=False):
2012        """Return the coordinates of the tip of the Arrow, or the point index."""
2013        if self.tip_index is None:
2014            arrpts = utils.vtk2numpy(self.source.GetOutput().GetPoints().GetData())
2015            self.tip_index = np.argmax(arrpts[:, 0])
2016        if return_index:
2017            return self.tip_index
2018        return self.points()[self.tip_index]

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)
1920    def __init__(
1921        self,
1922        start_pt=(0, 0, 0),
1923        end_pt=(1, 0, 0),
1924        s=None,
1925        shaft_radius=None,
1926        head_radius=None,
1927        head_length=None,
1928        res=12,
1929        c="r4",
1930        alpha=1.0,
1931    ):
1932        """
1933        If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
1934        from white to red.
1935
1936        .. note:: If `s=None` the arrow is scaled proportionally to its length
1937
1938        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
1939        """
1940        # in case user is passing meshs
1941        if isinstance(start_pt, vtk.vtkActor):
1942            start_pt = start_pt.GetPosition()
1943        if isinstance(end_pt, vtk.vtkActor):
1944            end_pt = end_pt.GetPosition()
1945
1946        axis = np.asarray(end_pt) - np.asarray(start_pt)
1947        length = np.linalg.norm(axis)
1948        if length:
1949            axis = axis / length
1950        if len(axis) < 3:  # its 2d
1951            theta = np.pi / 2
1952            start_pt = [start_pt[0], start_pt[1], 0.0]
1953            end_pt = [end_pt[0], end_pt[1], 0.0]
1954        else:
1955            theta = np.arccos(axis[2])
1956        phi = np.arctan2(axis[1], axis[0])
1957        self.source = vtk.vtkArrowSource()
1958        self.source.SetShaftResolution(res)
1959        self.source.SetTipResolution(res)
1960
1961        if s:
1962            sz = 0.02
1963            self.source.SetTipRadius(sz)
1964            self.source.SetShaftRadius(sz / 1.75)
1965            self.source.SetTipLength(sz * 15)
1966
1967        # if s:
1968        #     sz = 0.02 * s * length
1969        #     tl = sz / 20
1970        #     print(s, sz)
1971        #     self.source.SetShaftRadius(sz)
1972        #     self.source.SetTipRadius(sz*1.75)
1973        #     self.source.SetTipLength(sz*15)
1974
1975        if head_length:
1976            self.source.SetTipLength(head_length)
1977        if head_radius:
1978            self.source.SetTipRadius(head_radius)
1979        if shaft_radius:
1980            self.source.SetShaftRadius(shaft_radius)
1981
1982        self.source.Update()
1983
1984        t = vtk.vtkTransform()
1985        t.RotateZ(np.rad2deg(phi))
1986        t.RotateY(np.rad2deg(theta))
1987        t.RotateY(-90)  # put it along Z
1988        if s:
1989            sz = 800 * s
1990            t.Scale(length, sz, sz)
1991        else:
1992            t.Scale(length, length, length)
1993        tf = vtk.vtkTransformPolyDataFilter()
1994        tf.SetInputData(self.source.GetOutput())
1995        tf.SetTransform(t)
1996        tf.Update()
1997
1998        Mesh.__init__(self, tf.GetOutput(), c, alpha)
1999
2000        self.phong().lighting("plastic")
2001        self.SetPosition(start_pt)
2002        self.PickableOff()
2003        self.DragableOff()
2004        self.base = np.array(start_pt, dtype=float)
2005        self.top = np.array(end_pt, dtype=float)
2006        self.tip_index = None
2007        self.fill = True  # used by pyplot.__iadd__()
2008        self.s = s if s is not None else 1  ## used by pyplot.__iadd()
2009        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 tip_point(self, return_index=False):
2011    def tip_point(self, return_index=False):
2012        """Return the coordinates of the tip of the Arrow, or the point index."""
2013        if self.tip_index is None:
2014            arrpts = utils.vtk2numpy(self.source.GetOutput().GetPoints().GetData())
2015            self.tip_index = np.argmax(arrpts[:, 0])
2016        if return_index:
2017            return self.tip_index
2018        return self.points()[self.tip_index]

Return the coordinates of the tip of the Arrow, or the point index.

class Arrows(Glyph):
2021class Arrows(Glyph):
2022    """
2023    Build arrows between two lists of points.
2024    """
2025
2026    def __init__(
2027        self,
2028        start_pts,
2029        end_pts=None,
2030        s=None,
2031        shaft_radius=None,
2032        head_radius=None,
2033        head_length=None,
2034        thickness=1.0,
2035        res=12,
2036        c=None,
2037        alpha=1.0,
2038    ):
2039        """
2040        Build arrows between two lists of points `start_pts` and `end_pts`.
2041         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2042
2043        Color can be specified as a colormap which maps the size of the arrows.
2044
2045        Arguments:
2046            s : (float)
2047                fix aspect-ratio of the arrow and scale its cross section
2048            c : (color)
2049                color or color map name
2050            alpha : (float)
2051                set object opacity
2052            res : (int)
2053                set arrow resolution
2054
2055        Examples:
2056            - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py)
2057
2058            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
2059        """
2060        if isinstance(start_pts, Points):
2061            start_pts = start_pts.points()
2062        if isinstance(end_pts, Points):
2063            end_pts = end_pts.points()
2064
2065        start_pts = np.asarray(start_pts)
2066        if end_pts is None:
2067            strt = start_pts[:, 0]
2068            end_pts = start_pts[:, 1]
2069            start_pts = strt
2070        else:
2071            end_pts = np.asarray(end_pts)
2072
2073        start_pts = utils.make3d(start_pts)
2074        end_pts = utils.make3d(end_pts)
2075
2076        arr = vtk.vtkArrowSource()
2077        arr.SetShaftResolution(res)
2078        arr.SetTipResolution(res)
2079
2080        if s:
2081            sz = 0.02 * s
2082            arr.SetTipRadius(sz * 2)
2083            arr.SetShaftRadius(sz * thickness)
2084            arr.SetTipLength(sz * 10)
2085
2086        if head_radius:
2087            arr.SetTipRadius(head_radius)
2088        if shaft_radius:
2089            arr.SetShaftRadius(shaft_radius)
2090        if head_length:
2091            arr.SetTipLength(head_length)
2092
2093        arr.Update()
2094        out = arr.GetOutput()
2095
2096        orients = end_pts - start_pts
2097        Glyph.__init__(
2098            self,
2099            start_pts,
2100            out,
2101            orientation_array=orients,
2102            scale_by_vector_size=True,
2103            color_by_vector_size=True,
2104            c=c,
2105            alpha=alpha,
2106        )
2107        self.flat().lighting("plastic")
2108        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=12, c=None, alpha=1.0)
2026    def __init__(
2027        self,
2028        start_pts,
2029        end_pts=None,
2030        s=None,
2031        shaft_radius=None,
2032        head_radius=None,
2033        head_length=None,
2034        thickness=1.0,
2035        res=12,
2036        c=None,
2037        alpha=1.0,
2038    ):
2039        """
2040        Build arrows between two lists of points `start_pts` and `end_pts`.
2041         `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2042
2043        Color can be specified as a colormap which maps the size of the arrows.
2044
2045        Arguments:
2046            s : (float)
2047                fix aspect-ratio of the arrow and scale its cross section
2048            c : (color)
2049                color or color map name
2050            alpha : (float)
2051                set object opacity
2052            res : (int)
2053                set arrow resolution
2054
2055        Examples:
2056            - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py)
2057
2058            ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg)
2059        """
2060        if isinstance(start_pts, Points):
2061            start_pts = start_pts.points()
2062        if isinstance(end_pts, Points):
2063            end_pts = end_pts.points()
2064
2065        start_pts = np.asarray(start_pts)
2066        if end_pts is None:
2067            strt = start_pts[:, 0]
2068            end_pts = start_pts[:, 1]
2069            start_pts = strt
2070        else:
2071            end_pts = np.asarray(end_pts)
2072
2073        start_pts = utils.make3d(start_pts)
2074        end_pts = utils.make3d(end_pts)
2075
2076        arr = vtk.vtkArrowSource()
2077        arr.SetShaftResolution(res)
2078        arr.SetTipResolution(res)
2079
2080        if s:
2081            sz = 0.02 * s
2082            arr.SetTipRadius(sz * 2)
2083            arr.SetShaftRadius(sz * thickness)
2084            arr.SetTipLength(sz * 10)
2085
2086        if head_radius:
2087            arr.SetTipRadius(head_radius)
2088        if shaft_radius:
2089            arr.SetShaftRadius(shaft_radius)
2090        if head_length:
2091            arr.SetTipLength(head_length)
2092
2093        arr.Update()
2094        out = arr.GetOutput()
2095
2096        orients = end_pts - start_pts
2097        Glyph.__init__(
2098            self,
2099            start_pts,
2100            out,
2101            orientation_array=orients,
2102            scale_by_vector_size=True,
2103            color_by_vector_size=True,
2104            c=c,
2105            alpha=alpha,
2106        )
2107        self.flat().lighting("plastic")
2108        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):
2111class Arrow2D(Mesh):
2112    """
2113    Build a 2D arrow.
2114    """
2115
2116    def __init__(
2117        self,
2118        start_pt=(0, 0, 0),
2119        end_pt=(1, 0, 0),
2120        s=1,
2121        shaft_length=0.8,
2122        shaft_width=0.05,
2123        head_length=0.225,
2124        head_width=0.175,
2125        fill=True,
2126    ):
2127        """
2128        Build a 2D arrow from `start_pt` to `end_pt`.
2129
2130        Arguments:
2131            s : (float)
2132                a global multiplicative convenience factor controlling the arrow size
2133            shaft_length : (float)
2134                fractional shaft length
2135            shaft_width : (float)
2136                fractional shaft width
2137            head_length : (float)
2138                fractional head length
2139            head_width : (float)
2140                fractional head width
2141            fill : (bool)
2142                if False only generate the outline
2143        """
2144        self.fill = fill  ## needed by pyplot.__iadd()
2145        self.s = s  #  # needed by pyplot.__iadd()
2146
2147        if s != 1:
2148            shaft_width *= s
2149            head_width *= np.sqrt(s)
2150
2151        # in case user is passing meshs
2152        if isinstance(start_pt, vtk.vtkActor):
2153            start_pt = start_pt.GetPosition()
2154        if isinstance(end_pt, vtk.vtkActor):
2155            end_pt = end_pt.GetPosition()
2156        if len(start_pt) == 2:
2157            start_pt = [start_pt[0], start_pt[1], 0]
2158        if len(end_pt) == 2:
2159            end_pt = [end_pt[0], end_pt[1], 0]
2160
2161        headBase = 1 - head_length
2162        head_width = max(head_width, shaft_width)
2163        if head_length is None or headBase > shaft_length:
2164            headBase = shaft_length
2165
2166        verts = []
2167        verts.append([0, -shaft_width / 2, 0])
2168        verts.append([shaft_length, -shaft_width / 2, 0])
2169        verts.append([headBase, -head_width / 2, 0])
2170        verts.append([1, 0, 0])
2171        verts.append([headBase, head_width / 2, 0])
2172        verts.append([shaft_length, shaft_width / 2, 0])
2173        verts.append([0, shaft_width / 2, 0])
2174        if fill:
2175            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2176            poly = utils.buildPolyData(verts, faces)
2177        else:
2178            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2179            poly = utils.buildPolyData(verts, [], lines=lines)
2180
2181        axis = np.array(end_pt) - np.array(start_pt)
2182        length = np.linalg.norm(axis)
2183        if length:
2184            axis = axis / length
2185        theta = 0
2186        if len(axis) > 2:
2187            theta = np.arccos(axis[2])
2188        phi = np.arctan2(axis[1], axis[0])
2189        t = vtk.vtkTransform()
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        t.Scale(length, length, length)
2196        tf = vtk.vtkTransformPolyDataFilter()
2197        tf.SetInputData(poly)
2198        tf.SetTransform(t)
2199        tf.Update()
2200
2201        Mesh.__init__(self, tf.GetOutput(), c="k1")
2202        self.SetPosition(start_pt)
2203        self.lighting("off")
2204        self.DragableOff()
2205        self.PickableOff()
2206        self.base = np.array(start_pt, dtype=float)
2207        self.top = np.array(end_pt, dtype=float)
2208        self.name = "Arrow2D"

Build a 2D arrow.

Arrow2D( start_pt=(0, 0, 0), end_pt=(1, 0, 0), s=1, shaft_length=0.8, shaft_width=0.05, head_length=0.225, head_width=0.175, fill=True)
2116    def __init__(
2117        self,
2118        start_pt=(0, 0, 0),
2119        end_pt=(1, 0, 0),
2120        s=1,
2121        shaft_length=0.8,
2122        shaft_width=0.05,
2123        head_length=0.225,
2124        head_width=0.175,
2125        fill=True,
2126    ):
2127        """
2128        Build a 2D arrow from `start_pt` to `end_pt`.
2129
2130        Arguments:
2131            s : (float)
2132                a global multiplicative convenience factor controlling the arrow size
2133            shaft_length : (float)
2134                fractional shaft length
2135            shaft_width : (float)
2136                fractional shaft width
2137            head_length : (float)
2138                fractional head length
2139            head_width : (float)
2140                fractional head width
2141            fill : (bool)
2142                if False only generate the outline
2143        """
2144        self.fill = fill  ## needed by pyplot.__iadd()
2145        self.s = s  #  # needed by pyplot.__iadd()
2146
2147        if s != 1:
2148            shaft_width *= s
2149            head_width *= np.sqrt(s)
2150
2151        # in case user is passing meshs
2152        if isinstance(start_pt, vtk.vtkActor):
2153            start_pt = start_pt.GetPosition()
2154        if isinstance(end_pt, vtk.vtkActor):
2155            end_pt = end_pt.GetPosition()
2156        if len(start_pt) == 2:
2157            start_pt = [start_pt[0], start_pt[1], 0]
2158        if len(end_pt) == 2:
2159            end_pt = [end_pt[0], end_pt[1], 0]
2160
2161        headBase = 1 - head_length
2162        head_width = max(head_width, shaft_width)
2163        if head_length is None or headBase > shaft_length:
2164            headBase = shaft_length
2165
2166        verts = []
2167        verts.append([0, -shaft_width / 2, 0])
2168        verts.append([shaft_length, -shaft_width / 2, 0])
2169        verts.append([headBase, -head_width / 2, 0])
2170        verts.append([1, 0, 0])
2171        verts.append([headBase, head_width / 2, 0])
2172        verts.append([shaft_length, shaft_width / 2, 0])
2173        verts.append([0, shaft_width / 2, 0])
2174        if fill:
2175            faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3))
2176            poly = utils.buildPolyData(verts, faces)
2177        else:
2178            lines = (0, 1, 2, 3, 4, 5, 6, 0)
2179            poly = utils.buildPolyData(verts, [], lines=lines)
2180
2181        axis = np.array(end_pt) - np.array(start_pt)
2182        length = np.linalg.norm(axis)
2183        if length:
2184            axis = axis / length
2185        theta = 0
2186        if len(axis) > 2:
2187            theta = np.arccos(axis[2])
2188        phi = np.arctan2(axis[1], axis[0])
2189        t = vtk.vtkTransform()
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        t.Scale(length, length, length)
2196        tf = vtk.vtkTransformPolyDataFilter()
2197        tf.SetInputData(poly)
2198        tf.SetTransform(t)
2199        tf.Update()
2200
2201        Mesh.__init__(self, tf.GetOutput(), c="k1")
2202        self.SetPosition(start_pt)
2203        self.lighting("off")
2204        self.DragableOff()
2205        self.PickableOff()
2206        self.base = np.array(start_pt, dtype=float)
2207        self.top = np.array(end_pt, dtype=float)
2208        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):
2211class Arrows2D(Glyph):
2212    """
2213    Build 2D arrows between two lists of points.
2214    """
2215
2216    def __init__(
2217        self,
2218        start_pts,
2219        end_pts=None,
2220        s=1.0,
2221        shaft_length=0.8,
2222        shaft_width=0.05,
2223        head_length=0.225,
2224        head_width=0.175,
2225        fill=True,
2226        c=None,
2227        alpha=1.0,
2228    ):
2229        """
2230        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2231        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2232
2233        Color can be specified as a colormap which maps the size of the arrows.
2234
2235        Arguments:
2236            shaft_length : (float)
2237                fractional shaft length
2238            shaft_width : (float)
2239                fractional shaft width
2240            head_length : (float)
2241                fractional head length
2242            head_width : (float)
2243                fractional head width
2244            fill : (bool)
2245                if False only generate the outline
2246        """
2247        if isinstance(start_pts, Points):
2248            start_pts = start_pts.points()
2249        if isinstance(end_pts, Points):
2250            end_pts = end_pts.points()
2251
2252        start_pts = np.asarray(start_pts, dtype=float)
2253        if end_pts is None:
2254            strt = start_pts[:, 0]
2255            end_pts = start_pts[:, 1]
2256            start_pts = strt
2257        else:
2258            end_pts = np.asarray(end_pts, dtype=float)
2259
2260        if head_length is None:
2261            head_length = 1 - shaft_length
2262
2263        arr = Arrow2D(
2264            (0, 0, 0),
2265            (1, 0, 0),
2266            s=s,
2267            shaft_length=shaft_length,
2268            shaft_width=shaft_width,
2269            head_length=head_length,
2270            head_width=head_width,
2271            fill=fill,
2272        )
2273
2274        orients = end_pts - start_pts
2275        orients = utils.make3d(orients)
2276
2277        pts = Points(start_pts)
2278        Glyph.__init__(
2279            self,
2280            pts,
2281            arr.polydata(False),
2282            orientation_array=orients,
2283            scale_by_vector_size=True,
2284            c=c,
2285            alpha=alpha,
2286        )
2287        self.flat().lighting("off")
2288        if c is not None:
2289            self.color(c)
2290        self.name = "Arrows2D"

Build 2D arrows between two lists of points.

Arrows2D( start_pts, end_pts=None, s=1.0, shaft_length=0.8, shaft_width=0.05, head_length=0.225, head_width=0.175, fill=True, c=None, alpha=1.0)
2216    def __init__(
2217        self,
2218        start_pts,
2219        end_pts=None,
2220        s=1.0,
2221        shaft_length=0.8,
2222        shaft_width=0.05,
2223        head_length=0.225,
2224        head_width=0.175,
2225        fill=True,
2226        c=None,
2227        alpha=1.0,
2228    ):
2229        """
2230        Build 2D arrows between two lists of points `start_pts` and `end_pts`.
2231        `start_pts` can be also passed in the form `[[point1, point2], ...]`.
2232
2233        Color can be specified as a colormap which maps the size of the arrows.
2234
2235        Arguments:
2236            shaft_length : (float)
2237                fractional shaft length
2238            shaft_width : (float)
2239                fractional shaft width
2240            head_length : (float)
2241                fractional head length
2242            head_width : (float)
2243                fractional head width
2244            fill : (bool)
2245                if False only generate the outline
2246        """
2247        if isinstance(start_pts, Points):
2248            start_pts = start_pts.points()
2249        if isinstance(end_pts, Points):
2250            end_pts = end_pts.points()
2251
2252        start_pts = np.asarray(start_pts, dtype=float)
2253        if end_pts is None:
2254            strt = start_pts[:, 0]
2255            end_pts = start_pts[:, 1]
2256            start_pts = strt
2257        else:
2258            end_pts = np.asarray(end_pts, dtype=float)
2259
2260        if head_length is None:
2261            head_length = 1 - shaft_length
2262
2263        arr = Arrow2D(
2264            (0, 0, 0),
2265            (1, 0, 0),
2266            s=s,
2267            shaft_length=shaft_length,
2268            shaft_width=shaft_width,
2269            head_length=head_length,
2270            head_width=head_width,
2271            fill=fill,
2272        )
2273
2274        orients = end_pts - start_pts
2275        orients = utils.make3d(orients)
2276
2277        pts = Points(start_pts)
2278        Glyph.__init__(
2279            self,
2280            pts,
2281            arr.polydata(False),
2282            orientation_array=orients,
2283            scale_by_vector_size=True,
2284            c=c,
2285            alpha=alpha,
2286        )
2287        self.flat().lighting("off")
2288        if c is not None:
2289            self.color(c)
2290        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):
2293class FlatArrow(Ribbon):
2294    """
2295    Build a 2D arrow in 3D space by joining two close lines.
2296    """
2297
2298    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0):
2299        """
2300        Build a 2D arrow in 3D space by joining two close lines.
2301
2302        Examples:
2303            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2304
2305                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2306        """
2307        if isinstance(line1, Points):
2308            line1 = line1.points()
2309        if isinstance(line2, Points):
2310            line2 = line2.points()
2311
2312        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2313
2314        v = (sm1 - sm2) / 3 * tip_width
2315        p1 = sm1 + v
2316        p2 = sm2 - v
2317        pm1 = (sm1 + sm2) / 2
2318        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2319        pm12 = pm1 - pm2
2320        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2321
2322        line1.append(p1)
2323        line1.append(tip)
2324        line2.append(p2)
2325        line2.append(tip)
2326        resm = max(100, len(line1))
2327
2328        Ribbon.__init__(self, line1, line2, res=(resm, 1))
2329        self.phong()
2330        self.PickableOff()
2331        self.DragableOff()
2332        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)
2298    def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0):
2299        """
2300        Build a 2D arrow in 3D space by joining two close lines.
2301
2302        Examples:
2303            - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py)
2304
2305                ![](https://vedo.embl.es/images/basic/flatarrow.png)
2306        """
2307        if isinstance(line1, Points):
2308            line1 = line1.points()
2309        if isinstance(line2, Points):
2310            line2 = line2.points()
2311
2312        sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float)
2313
2314        v = (sm1 - sm2) / 3 * tip_width
2315        p1 = sm1 + v
2316        p2 = sm2 - v
2317        pm1 = (sm1 + sm2) / 2
2318        pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2
2319        pm12 = pm1 - pm2
2320        tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1
2321
2322        line1.append(p1)
2323        line1.append(tip)
2324        line2.append(p2)
2325        line2.append(tip)
2326        resm = max(100, len(line1))
2327
2328        Ribbon.__init__(self, line1, line2, res=(resm, 1))
2329        self.phong()
2330        self.PickableOff()
2331        self.DragableOff()
2332        self.name = "FlatArrow"

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

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

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

class Triangle(vedo.mesh.Mesh):
2335class Triangle(Mesh):
2336    """Create a triangle from 3 points in space."""
2337
2338    def __init__(self, p1, p2, p3, c="green7", alpha=1.0):
2339        """Create a triangle from 3 points in space."""
2340        Mesh.__init__(self, [[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2341        self.GetProperty().LightingOff()
2342        self.name = "Triangle"

Create a triangle from 3 points in space.

Triangle(p1, p2, p3, c='green7', alpha=1.0)
2338    def __init__(self, p1, p2, p3, c="green7", alpha=1.0):
2339        """Create a triangle from 3 points in space."""
2340        Mesh.__init__(self, [[p1, p2, p3], [[0, 1, 2]]], c, alpha)
2341        self.GetProperty().LightingOff()
2342        self.name = "Triangle"

Create a triangle from 3 points in space.

class Rectangle(vedo.mesh.Mesh):
3120class Rectangle(Mesh):
3121    """
3122    Build a rectangle in the xy plane.
3123    """
3124
3125    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0):
3126        """
3127        Build a rectangle in the xy plane identified by any two corner points.
3128
3129        Arguments:
3130            p1 : (list)
3131                bottom-left position of the corner
3132            p2 : (list)
3133                top-right position of the corner
3134            radius : (float, list)
3135                smoothing radius of the corner in world units.
3136                A list can be passed with 4 individual values.
3137        """
3138        if len(p1) == 2:
3139            p1 = np.array([p1[0], p1[1], 0.0])
3140        else:
3141            p1 = np.array(p1, dtype=float)
3142        if len(p2) == 2:
3143            p2 = np.array([p2[0], p2[1], 0.0])
3144        else:
3145            p2 = np.array(p2, dtype=float)
3146
3147        self.corner1 = p1
3148        self.corner2 = p2
3149
3150        color = c
3151        smoothr = False
3152        risseq = False
3153        if utils.is_sequence(radius):
3154            risseq = True
3155            smoothr = True
3156            if max(radius) == 0:
3157                smoothr = False
3158        elif radius:
3159            smoothr = True
3160
3161        if not smoothr:
3162            radius = None
3163        self.radius = radius
3164
3165        if smoothr:
3166            r = radius
3167            if not risseq:
3168                r = [r, r, r, r]
3169            rd, ra, rb, rc = r
3170
3171            if p1[0] > p2[0]:  # flip p1 - p2
3172                p1, p2 = p2, p1
3173            if p1[1] > p2[1]:  # flip p1y - p2y
3174                p1[1], p2[1] = p2[1], p1[1]
3175
3176            px, py, _ = p2 - p1
3177            k = min(px / 2, py / 2)
3178            ra = min(abs(ra), k)
3179            rb = min(abs(rb), k)
3180            rc = min(abs(rc), k)
3181            rd = min(abs(rd), k)
3182            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3183            betas = np.split(beta, 4)
3184            rrx = np.cos(betas)
3185            rry = np.sin(betas)
3186
3187            q1 = (rd, 0)
3188            # q2 = (px-ra, 0)
3189            q3 = (px, ra)
3190            # q4 = (px, py-rb)
3191            q5 = (px - rb, py)
3192            # q6 = (rc, py)
3193            q7 = (0, py - rc)
3194            # q8 = (0, rd)
3195            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3196            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3197            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3198            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3199
3200            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3201            faces = [list(range(len(pts)))]
3202        else:
3203            p1r = np.array([p2[0], p1[1], 0.0])
3204            p2l = np.array([p1[0], p2[1], 0.0])
3205            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3206            faces = [(0, 1, 2, 3)]
3207
3208        Mesh.__init__(self, [pts, faces], color, alpha)
3209        self.SetPosition(p1)
3210        self.property.LightingOff()
3211        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)
3125    def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0):
3126        """
3127        Build a rectangle in the xy plane identified by any two corner points.
3128
3129        Arguments:
3130            p1 : (list)
3131                bottom-left position of the corner
3132            p2 : (list)
3133                top-right position of the corner
3134            radius : (float, list)
3135                smoothing radius of the corner in world units.
3136                A list can be passed with 4 individual values.
3137        """
3138        if len(p1) == 2:
3139            p1 = np.array([p1[0], p1[1], 0.0])
3140        else:
3141            p1 = np.array(p1, dtype=float)
3142        if len(p2) == 2:
3143            p2 = np.array([p2[0], p2[1], 0.0])
3144        else:
3145            p2 = np.array(p2, dtype=float)
3146
3147        self.corner1 = p1
3148        self.corner2 = p2
3149
3150        color = c
3151        smoothr = False
3152        risseq = False
3153        if utils.is_sequence(radius):
3154            risseq = True
3155            smoothr = True
3156            if max(radius) == 0:
3157                smoothr = False
3158        elif radius:
3159            smoothr = True
3160
3161        if not smoothr:
3162            radius = None
3163        self.radius = radius
3164
3165        if smoothr:
3166            r = radius
3167            if not risseq:
3168                r = [r, r, r, r]
3169            rd, ra, rb, rc = r
3170
3171            if p1[0] > p2[0]:  # flip p1 - p2
3172                p1, p2 = p2, p1
3173            if p1[1] > p2[1]:  # flip p1y - p2y
3174                p1[1], p2[1] = p2[1], p1[1]
3175
3176            px, py, _ = p2 - p1
3177            k = min(px / 2, py / 2)
3178            ra = min(abs(ra), k)
3179            rb = min(abs(rb), k)
3180            rc = min(abs(rc), k)
3181            rd = min(abs(rd), k)
3182            beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False)
3183            betas = np.split(beta, 4)
3184            rrx = np.cos(betas)
3185            rry = np.sin(betas)
3186
3187            q1 = (rd, 0)
3188            # q2 = (px-ra, 0)
3189            q3 = (px, ra)
3190            # q4 = (px, py-rb)
3191            q5 = (px - rb, py)
3192            # q6 = (rc, py)
3193            q7 = (0, py - rc)
3194            # q8 = (0, rd)
3195            a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra]    if ra else np.array([])
3196            b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([])
3197            c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc]    if rc else np.array([])
3198            d = np.c_[rrx[2], rry[2]]*rd + [rd, rd]       if rd else np.array([])
3199
3200            pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()]
3201            faces = [list(range(len(pts)))]
3202        else:
3203            p1r = np.array([p2[0], p1[1], 0.0])
3204            p2l = np.array([p1[0], p2[1], 0.0])
3205            pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1)
3206            faces = [(0, 1, 2, 3)]
3207
3208        Mesh.__init__(self, [pts, faces], color, alpha)
3209        self.SetPosition(p1)
3210        self.property.LightingOff()
3211        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):
2465class Disc(Mesh):
2466    """
2467    Build a 2D disc.
2468    """
2469
2470    def __init__(
2471        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2472    ):
2473        """
2474        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2475
2476        Set `res` as the resolution in R and Phi (can be a list).
2477
2478        Use `angle_range` to create a disc sector between the 2 specified angles.
2479
2480        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2481        """
2482        if utils.is_sequence(res):
2483            res_r, res_phi = res
2484        else:
2485            res_r, res_phi = res, 12 * res
2486
2487        if len(angle_range) == 0:
2488            ps = vtk.vtkDiskSource()
2489        else:
2490            ps = vtk.vtkSectorSource()
2491            ps.SetStartAngle(angle_range[0])
2492            ps.SetEndAngle(angle_range[1])
2493
2494        ps.SetInnerRadius(r1)
2495        ps.SetOuterRadius(r2)
2496        ps.SetRadialResolution(res_r)
2497        ps.SetCircumferentialResolution(res_phi)
2498        ps.Update()
2499        Mesh.__init__(self, ps.GetOutput(), c, alpha)
2500        self.flat()
2501        self.SetPosition(utils.make3d(pos))
2502        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)
2470    def __init__(
2471        self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0
2472    ):
2473        """
2474        Build a 2D disc of inner radius `r1` and outer radius `r2`.
2475
2476        Set `res` as the resolution in R and Phi (can be a list).
2477
2478        Use `angle_range` to create a disc sector between the 2 specified angles.
2479
2480        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png)
2481        """
2482        if utils.is_sequence(res):
2483            res_r, res_phi = res
2484        else:
2485            res_r, res_phi = res, 12 * res
2486
2487        if len(angle_range) == 0:
2488            ps = vtk.vtkDiskSource()
2489        else:
2490            ps = vtk.vtkSectorSource()
2491            ps.SetStartAngle(angle_range[0])
2492            ps.SetEndAngle(angle_range[1])
2493
2494        ps.SetInnerRadius(r1)
2495        ps.SetOuterRadius(r2)
2496        ps.SetRadialResolution(res_r)
2497        ps.SetCircumferentialResolution(res_phi)
2498        ps.Update()
2499        Mesh.__init__(self, ps.GetOutput(), c, alpha)
2500        self.flat()
2501        self.SetPosition(utils.make3d(pos))
2502        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):
2368class Circle(Polygon):
2369    """
2370    Build a Circle of radius `r`.
2371    """
2372
2373    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0):
2374        """
2375        Build a Circle of radius `r`.
2376        """
2377        Polygon.__init__(self, pos, nsides=res, r=r)
2378
2379        self.center = []  # filled by pointcloud.pcaEllipse
2380        self.nr_of_points = 0
2381        self.va = 0
2382        self.vb = 0
2383        self.axis1 = []
2384        self.axis2 = []
2385        self.alpha(alpha).c(c)
2386        self.name = "Circle"

Build a Circle of radius r.

Circle(pos=(0, 0, 0), r=1.0, res=120, c='gray5', alpha=1.0)
2373    def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0):
2374        """
2375        Build a Circle of radius `r`.
2376        """
2377        Polygon.__init__(self, pos, nsides=res, r=r)
2378
2379        self.center = []  # filled by pointcloud.pcaEllipse
2380        self.nr_of_points = 0
2381        self.va = 0
2382        self.vb = 0
2383        self.axis1 = []
2384        self.axis2 = []
2385        self.alpha(alpha).c(c)
2386        self.name = "Circle"

Build a Circle of radius r.

class GeoCircle(Polygon):
2389class GeoCircle(Polygon):
2390    """
2391    Build a Circle of radius `r`.
2392    """
2393
2394    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0):
2395        """
2396        Build a Circle of radius `r` as projected on a geographic map.
2397        Circles near the poles will look very squashed.
2398
2399        See example:
2400            ```bash
2401            vedo -r earthquake
2402            ```
2403        """
2404        coords = []
2405        sinr, cosr = np.sin(r), np.cos(r)
2406        sinlat, coslat = np.sin(lat), np.cos(lat)
2407        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2408            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2409            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2410            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2411
2412        Polygon.__init__(self, nsides=res, c=c, alpha=alpha)
2413        self.points(coords)  # warp polygon points to match geo projection
2414        self.name = "Circle"

Build a Circle of radius r.

GeoCircle(lat, lon, r=1.0, res=60, c='red4', alpha=1.0)
2394    def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0):
2395        """
2396        Build a Circle of radius `r` as projected on a geographic map.
2397        Circles near the poles will look very squashed.
2398
2399        See example:
2400            ```bash
2401            vedo -r earthquake
2402            ```
2403        """
2404        coords = []
2405        sinr, cosr = np.sin(r), np.cos(r)
2406        sinlat, coslat = np.sin(lat), np.cos(lat)
2407        for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False):
2408            clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi))
2409            clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat))
2410            coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0])
2411
2412        Polygon.__init__(self, nsides=res, c=c, alpha=alpha)
2413        self.points(coords)  # warp polygon points to match geo projection
2414        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
class Arc(vedo.mesh.Mesh):
2505class Arc(Mesh):
2506    """
2507    Build a 2D circular arc between 2 points.
2508    """
2509
2510    def __init__(
2511        self,
2512        center,
2513        point1,
2514        point2=None,
2515        normal=None,
2516        angle=None,
2517        invert=False,
2518        res=50,
2519        c="gray4",
2520        alpha=1.0,
2521    ):
2522        """
2523        Build a 2D circular arc between 2 points `point1` and `point2`.
2524
2525        If `normal` is specified then `center` is ignored, and
2526        normal vector, a starting `point1` (polar vector)
2527        and an angle defining the arc length need to be assigned.
2528
2529        Arc spans the shortest angular sector point1 and point2,
2530        if `invert=True`, then the opposite happens.
2531        """
2532        if len(point1) == 2:
2533            point1 = (point1[0], point1[1], 0)
2534        if point2 is not None and len(point2) == 2:
2535            point2 = (point2[0], point2[1], 0)
2536
2537        self.base = point1
2538        self.top = point2
2539
2540        ar = vtk.vtkArcSource()
2541        if point2 is not None:
2542            self.top = point2
2543            point2 = point2 - np.asarray(point1)
2544            ar.UseNormalAndAngleOff()
2545            ar.SetPoint1([0, 0, 0])
2546            ar.SetPoint2(point2)
2547            ar.SetCenter(center)
2548        elif normal is not None and angle is not None:
2549            ar.UseNormalAndAngleOn()
2550            ar.SetAngle(angle)
2551            ar.SetPolarVector(point1)
2552            ar.SetNormal(normal)
2553        else:
2554            vedo.logger.error("incorrect input combination")
2555            return
2556        ar.SetNegative(invert)
2557        ar.SetResolution(res)
2558        ar.Update()
2559        Mesh.__init__(self, ar.GetOutput(), c, alpha)
2560        self.SetPosition(self.base)
2561        self.lw(2).lighting("off")
2562        self.name = "Arc"

Build a 2D circular arc between 2 points.

Arc( center, point1, point2=None, normal=None, angle=None, invert=False, res=50, c='gray4', alpha=1.0)
2510    def __init__(
2511        self,
2512        center,
2513        point1,
2514        point2=None,
2515        normal=None,
2516        angle=None,
2517        invert=False,
2518        res=50,
2519        c="gray4",
2520        alpha=1.0,
2521    ):
2522        """
2523        Build a 2D circular arc between 2 points `point1` and `point2`.
2524
2525        If `normal` is specified then `center` is ignored, and
2526        normal vector, a starting `point1` (polar vector)
2527        and an angle defining the arc length need to be assigned.
2528
2529        Arc spans the shortest angular sector point1 and point2,
2530        if `invert=True`, then the opposite happens.
2531        """
2532        if len(point1) == 2:
2533            point1 = (point1[0], point1[1], 0)
2534        if point2 is not None and len(point2) == 2:
2535            point2 = (point2[0], point2[1], 0)
2536
2537        self.base = point1
2538        self.top = point2
2539
2540        ar = vtk.vtkArcSource()
2541        if point2 is not None:
2542            self.top = point2
2543            point2 = point2 - np.asarray(point1)
2544            ar.UseNormalAndAngleOff()
2545            ar.SetPoint1([0, 0, 0])
2546            ar.SetPoint2(point2)
2547            ar.SetCenter(center)
2548        elif normal is not None and angle is not None:
2549            ar.UseNormalAndAngleOn()
2550            ar.SetAngle(angle)
2551            ar.SetPolarVector(point1)
2552            ar.SetNormal(normal)
2553        else:
2554            vedo.logger.error("incorrect input combination")
2555            return
2556        ar.SetNegative(invert)
2557        ar.SetResolution(res)
2558        ar.Update()
2559        Mesh.__init__(self, ar.GetOutput(), c, alpha)
2560        self.SetPosition(self.base)
2561        self.lw(2).lighting("off")
2562        self.name = "Arc"

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

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

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

class Star(vedo.mesh.Mesh):
2417class Star(Mesh):
2418    """
2419    Build a 2D star shape.
2420    """
2421
2422    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0):
2423        """
2424        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2425
2426        If line is True then only build the outer line (no internal surface meshing).
2427
2428        Example:
2429            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2430
2431                ![](https://vedo.embl.es/images/basic/extrude.png)
2432        """
2433        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2434        x, y = utils.pol2cart(np.ones_like(t) * r2, t)
2435        pts = np.c_[x, y, np.zeros_like(x)]
2436
2437        apts = []
2438        for i, p in enumerate(pts):
2439            apts.append(p)
2440            if i + 1 < n:
2441                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2442        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2443
2444        if line:
2445            apts.append(pts[0])
2446            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2447            Mesh.__init__(self, poly, c, alpha)
2448            self.lw(2)
2449        else:
2450            apts.append((0, 0, 0))
2451            cells = []
2452            for i in range(2 * n - 1):
2453                cell = [2 * n, i, i + 1]
2454                cells.append(cell)
2455            cells.append([2 * n, i + 1, 0])
2456            Mesh.__init__(self, [apts, cells], c, alpha)
2457
2458        if len(pos) == 2:
2459            pos = (pos[0], pos[1], 0)
2460        self.SetPosition(pos)
2461        self.property.LightingOff()
2462        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)
2422    def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0):
2423        """
2424        Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`.
2425
2426        If line is True then only build the outer line (no internal surface meshing).
2427
2428        Example:
2429            - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py)
2430
2431                ![](https://vedo.embl.es/images/basic/extrude.png)
2432        """
2433        t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False)
2434        x, y = utils.pol2cart(np.ones_like(t) * r2, t)
2435        pts = np.c_[x, y, np.zeros_like(x)]
2436
2437        apts = []
2438        for i, p in enumerate(pts):
2439            apts.append(p)
2440            if i + 1 < n:
2441                apts.append((p + pts[i + 1]) / 2 * r1 / r2)
2442        apts.append((pts[-1] + pts[0]) / 2 * r1 / r2)
2443
2444        if line:
2445            apts.append(pts[0])
2446            poly = utils.buildPolyData(apts, lines=list(range(len(apts))))
2447            Mesh.__init__(self, poly, c, alpha)
2448            self.lw(2)
2449        else:
2450            apts.append((0, 0, 0))
2451            cells = []
2452            for i in range(2 * n - 1):
2453                cell = [2 * n, i, i + 1]
2454                cells.append(cell)
2455            cells.append([2 * n, i + 1, 0])
2456            Mesh.__init__(self, [apts, cells], c, alpha)
2457
2458        if len(pos) == 2:
2459            pos = (pos[0], pos[1], 0)
2460        self.SetPosition(pos)
2461        self.property.LightingOff()
2462        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):
3814class Star3D(Mesh):
3815    """
3816    Build a 3D starred shape.
3817    """
3818
3819    def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0):
3820        """
3821        Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3822        """
3823        pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38),
3824               (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385),
3825               (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761),
3826               (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10))
3827        fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2],
3828               [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5],
3829               [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8],
3830               [10,1, 0],[10,11, 9]]
3831
3832        Mesh.__init__(self, [pts, fcs], c, alpha)
3833        self.RotateX(90)
3834        self.scale(r).lighting("shiny")
3835
3836        if len(pos) == 2:
3837            pos = (pos[0], pos[1], 0)
3838        self.SetPosition(pos)
3839        self.name = "Star3D"

Build a 3D starred shape.

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

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

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

Build a 3D cross shape.

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

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

class IcoSphere(vedo.mesh.Mesh):
2565class IcoSphere(Mesh):
2566    """
2567    Create a sphere made of a uniform triangle mesh.
2568    """
2569
2570    def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=3, c="r5", alpha=1.0):
2571        """
2572        Create a sphere made of a uniform triangle mesh
2573        (from recursive subdivision of an icosahedron).
2574
2575        Example:
2576        ```python
2577        from vedo import *
2578        icos = IcoSphere(subdivisions=3)
2579        icos.compute_quality().cmap('coolwarm')
2580        icos.show(axes=1).close()
2581        ```
2582        ![](https://vedo.embl.es/images/basic/icosphere.jpg)
2583        """
2584        subdivisions = int(min(subdivisions, 9))  # to avoid disasters
2585
2586        t = (1.0 + np.sqrt(5.0)) / 2.0
2587        points = np.array(
2588            [
2589                [-1, t, 0],
2590                [1, t, 0],
2591                [-1, -t, 0],
2592                [1, -t, 0],
2593                [0, -1, t],
2594                [0, 1, t],
2595                [0, -1, -t],
2596                [0, 1, -t],
2597                [t, 0, -1],
2598                [t, 0, 1],
2599                [-t, 0, -1],
2600                [-t, 0, 1],
2601            ]
2602        )
2603        faces = [
2604            [0, 11, 5],
2605            [0, 5, 1],
2606            [0, 1, 7],
2607            [0, 7, 10],
2608            [0, 10, 11],
2609            [1, 5, 9],
2610            [5, 11, 4],
2611            [11, 10, 2],
2612            [10, 7, 6],
2613            [7, 1, 8],
2614            [3, 9, 4],
2615            [3, 4, 2],
2616            [3, 2, 6],
2617            [3, 6, 8],
2618            [3, 8, 9],
2619            [4, 9, 5],
2620            [2, 4, 11],
2621            [6, 2, 10],
2622            [8, 6, 7],
2623            [9, 8, 1],
2624        ]
2625        Mesh.__init__(self, [points * r, faces], c=c, alpha=alpha)
2626
2627        for _ in range(subdivisions):
2628            self.subdivide(method=1)
2629            pts = utils.versor(self.points()) * r
2630            self.points(pts)
2631
2632        self.SetPosition(pos)
2633        self.name = "IcoSphere"

Create a sphere made of a uniform triangle mesh.

IcoSphere(pos=(0, 0, 0), r=1.0, subdivisions=3, c='r5', alpha=1.0)
2570    def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=3, c="r5", alpha=1.0):
2571        """
2572        Create a sphere made of a uniform triangle mesh
2573        (from recursive subdivision of an icosahedron).
2574
2575        Example:
2576        ```python
2577        from vedo import *
2578        icos = IcoSphere(subdivisions=3)
2579        icos.compute_quality().cmap('coolwarm')
2580        icos.show(axes=1).close()
2581        ```
2582        ![](https://vedo.embl.es/images/basic/icosphere.jpg)
2583        """
2584        subdivisions = int(min(subdivisions, 9))  # to avoid disasters
2585
2586        t = (1.0 + np.sqrt(5.0)) / 2.0
2587        points = np.array(
2588            [
2589                [-1, t, 0],
2590                [1, t, 0],
2591                [-1, -t, 0],
2592                [1, -t, 0],
2593                [0, -1, t],
2594                [0, 1, t],
2595                [0, -1, -t],
2596                [0, 1, -t],
2597                [t, 0, -1],
2598                [t, 0, 1],
2599                [-t, 0, -1],
2600                [-t, 0, 1],
2601            ]
2602        )
2603        faces = [
2604            [0, 11, 5],
2605            [0, 5, 1],
2606            [0, 1, 7],
2607            [0, 7, 10],
2608            [0, 10, 11],
2609            [1, 5, 9],
2610            [5, 11, 4],
2611            [11, 10, 2],
2612            [10, 7, 6],
2613            [7, 1, 8],
2614            [3, 9, 4],
2615            [3, 4, 2],
2616            [3, 2, 6],
2617            [3, 6, 8],
2618            [3, 8, 9],
2619            [4, 9, 5],
2620            [2, 4, 11],
2621            [6, 2, 10],
2622            [8, 6, 7],
2623            [9, 8, 1],
2624        ]
2625        Mesh.__init__(self, [points * r, faces], c=c, alpha=alpha)
2626
2627        for _ in range(subdivisions):
2628            self.subdivide(method=1)
2629            pts = utils.versor(self.points()) * r
2630            self.points(pts)
2631
2632        self.SetPosition(pos)
2633        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):
2636class Sphere(Mesh):
2637    """
2638    Build a sphere.
2639    """
2640
2641    def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0):
2642        """
2643        Build a sphere at position `pos` of radius `r`.
2644
2645        Arguments:
2646            r : (float)
2647                sphere radius
2648            res : (int, list)
2649                resolution in phi, resolution in theta is by default `2*res`
2650            quads : (bool)
2651                sphere mesh will be made of quads instead of triangles
2652
2653        [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png)
2654        """
2655        if len(pos) == 2:
2656            pos = np.asarray([pos[0], pos[1], 0])
2657
2658        self.radius = r  # used by fitSphere
2659        self.center = pos
2660        self.residue = 0
2661
2662        if quads:
2663            res = max(res, 4)
2664            img = vtk.vtkImageData()
2665            img.SetDimensions(res - 1, res - 1, res - 1)
2666            rs = 1.0 / (res - 2)
2667            img.SetSpacing(rs, rs, rs)
2668            gf = vtk.vtkGeometryFilter()
2669            gf.SetInputData(img)
2670            gf.Update()
2671            Mesh.__init__(self, gf.GetOutput(), c, alpha)
2672            self.lw(0.1)
2673
2674            cgpts = self.points() - (0.5, 0.5, 0.5)
2675
2676            x, y, z = cgpts.T
2677            x = x * (1 + x * x) / 2
2678            y = y * (1 + y * y) / 2
2679            z = z * (1 + z * z) / 2
2680            _, theta, phi = utils.cart2spher(x, y, z)
2681
2682            pts = utils.spher2cart(np.ones_like(phi) * r, theta, phi)
2683            self.points(pts)
2684
2685        else:
2686            if utils.is_sequence(res):
2687                res_t, res_phi = res
2688            else:
2689                res_t, res_phi = 2 * res, res
2690
2691            ss = vtk.vtkSphereSource()
2692            ss.SetRadius(r)
2693            ss.SetThetaResolution(res_t)
2694            ss.SetPhiResolution(res_phi)
2695            ss.Update()
2696
2697            Mesh.__init__(self, ss.GetOutput(), c, alpha)
2698
2699        self.phong()
2700        self.SetPosition(pos)
2701        self.name = "Sphere"

Build a sphere.

Sphere(pos=(0, 0, 0), r=1.0, res=24, quads=False, c='r5', alpha=1.0)
2641    def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0):
2642        """
2643        Build a sphere at position `pos` of radius `r`.
2644
2645        Arguments:
2646            r : (float)
2647                sphere radius
2648            res : (int, list)
2649                resolution in phi, resolution in theta is by default `2*res`
2650            quads : (bool)
2651                sphere mesh will be made of quads instead of triangles
2652
2653        [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png)
2654        """
2655        if len(pos) == 2:
2656            pos = np.asarray([pos[0], pos[1], 0])
2657
2658        self.radius = r  # used by fitSphere
2659        self.center = pos
2660        self.residue = 0
2661
2662        if quads:
2663            res = max(res, 4)
2664            img = vtk.vtkImageData()
2665            img.SetDimensions(res - 1, res - 1, res - 1)
2666            rs = 1.0 / (res - 2)
2667            img.SetSpacing(rs, rs, rs)
2668            gf = vtk.vtkGeometryFilter()
2669            gf.SetInputData(img)
2670            gf.Update()
2671            Mesh.__init__(self, gf.GetOutput(), c, alpha)
2672            self.lw(0.1)
2673
2674            cgpts = self.points() - (0.5, 0.5, 0.5)
2675
2676            x, y, z = cgpts.T
2677            x = x * (1 + x * x) / 2
2678            y = y * (1 + y * y) / 2
2679            z = z * (1 + z * z) / 2
2680            _, theta, phi = utils.cart2spher(x, y, z)
2681
2682            pts = utils.spher2cart(np.ones_like(phi) * r, theta, phi)
2683            self.points(pts)
2684
2685        else:
2686            if utils.is_sequence(res):
2687                res_t, res_phi = res
2688            else:
2689                res_t, res_phi = 2 * res, res
2690
2691            ss = vtk.vtkSphereSource()
2692            ss.SetRadius(r)
2693            ss.SetThetaResolution(res_t)
2694            ss.SetPhiResolution(res_phi)
2695            ss.Update()
2696
2697            Mesh.__init__(self, ss.GetOutput(), c, alpha)
2698
2699        self.phong()
2700        self.SetPosition(pos)
2701        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):
2704class Spheres(Mesh):
2705    """
2706    Build a large set of spheres.
2707    """
2708
2709    def __init__(self, centers, r=1.0, res=8, c="r5", alpha=1):
2710        """
2711        Build a (possibly large) set of spheres at `centers` of radius `r`.
2712
2713        Either `c` or `r` can be a list of RGB colors or radii.
2714
2715        Examples:
2716            - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py)
2717
2718            ![](https://vedo.embl.es/images/basic/manyspheres.jpg)
2719        """
2720
2721        if isinstance(centers, Points):
2722            centers = centers.points()
2723        centers = np.asarray(centers, dtype=float)
2724        base = centers[0]
2725
2726        cisseq = False
2727        if utils.is_sequence(c):
2728            cisseq = True
2729
2730        if cisseq:
2731            if len(centers) != len(c):
2732                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors")
2733                raise RuntimeError()
2734
2735        risseq = False
2736        if utils.is_sequence(r):
2737            risseq = True
2738
2739        if risseq:
2740            if len(centers) != len(r):
2741                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii")
2742                raise RuntimeError()
2743        if cisseq and risseq:
2744            vedo.logger.error("Limitation: c and r cannot be both sequences.")
2745            raise RuntimeError()
2746
2747        src = vtk.vtkSphereSource()
2748        if not risseq:
2749            src.SetRadius(r)
2750        if utils.is_sequence(res):
2751            res_t, res_phi = res
2752        else:
2753            res_t, res_phi = 2 * res, res
2754
2755        src.SetThetaResolution(res_t)
2756        src.SetPhiResolution(res_phi)
2757        src.Update()
2758
2759        psrc = vtk.vtkPointSource()
2760        psrc.SetNumberOfPoints(len(centers))
2761        psrc.Update()
2762        pd = psrc.GetOutput()
2763        vpts = pd.GetPoints()
2764
2765        glyph = vtk.vtkGlyph3D()
2766        glyph.SetSourceConnection(src.GetOutputPort())
2767
2768        if cisseq:
2769            glyph.SetColorModeToColorByScalar()
2770            ucols = vtk.vtkUnsignedCharArray()
2771            ucols.SetNumberOfComponents(3)
2772            ucols.SetName("Colors")
2773            for acol in c:
2774                cx, cy, cz = get_color(acol)
2775                ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255)
2776            pd.GetPointData().AddArray(ucols)
2777            pd.GetPointData().SetActiveScalars("Colors")
2778            glyph.ScalingOff()
2779        elif risseq:
2780            glyph.SetScaleModeToScaleByScalar()
2781            urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32)
2782            urads.SetName("Radii")
2783            pd.GetPointData().AddArray(urads)
2784            pd.GetPointData().SetActiveScalars("Radii")
2785
2786        vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32))
2787
2788        glyph.SetInputData(pd)
2789        glyph.Update()
2790
2791        Mesh.__init__(self, glyph.GetOutput(), alpha=alpha)
2792        self.SetPosition(base)
2793        self.base = base
2794        self.top = centers[-1]
2795        self.phong()
2796        if cisseq:
2797            self.mapper().ScalarVisibilityOn()
2798        else:
2799            self.mapper().ScalarVisibilityOff()
2800            self.GetProperty().SetColor(get_color(c))
2801        self.name = "Spheres"

Build a large set of spheres.

Spheres(centers, r=1.0, res=8, c='r5', alpha=1)
2709    def __init__(self, centers, r=1.0, res=8, c="r5", alpha=1):
2710        """
2711        Build a (possibly large) set of spheres at `centers` of radius `r`.
2712
2713        Either `c` or `r` can be a list of RGB colors or radii.
2714
2715        Examples:
2716            - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py)
2717
2718            ![](https://vedo.embl.es/images/basic/manyspheres.jpg)
2719        """
2720
2721        if isinstance(centers, Points):
2722            centers = centers.points()
2723        centers = np.asarray(centers, dtype=float)
2724        base = centers[0]
2725
2726        cisseq = False
2727        if utils.is_sequence(c):
2728            cisseq = True
2729
2730        if cisseq:
2731            if len(centers) != len(c):
2732                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors")
2733                raise RuntimeError()
2734
2735        risseq = False
2736        if utils.is_sequence(r):
2737            risseq = True
2738
2739        if risseq:
2740            if len(centers) != len(r):
2741                vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii")
2742                raise RuntimeError()
2743        if cisseq and risseq:
2744            vedo.logger.error("Limitation: c and r cannot be both sequences.")
2745            raise RuntimeError()
2746
2747        src = vtk.vtkSphereSource()
2748        if not risseq:
2749            src.SetRadius(r)
2750        if utils.is_sequence(res):
2751            res_t, res_phi = res
2752        else:
2753            res_t, res_phi = 2 * res, res
2754
2755        src.SetThetaResolution(res_t)
2756        src.SetPhiResolution(res_phi)
2757        src.Update()
2758
2759        psrc = vtk.vtkPointSource()
2760        psrc.SetNumberOfPoints(len(centers))
2761        psrc.Update()
2762        pd = psrc.GetOutput()
2763        vpts = pd.GetPoints()
2764
2765        glyph = vtk.vtkGlyph3D()
2766        glyph.SetSourceConnection(src.GetOutputPort())
2767
2768        if cisseq:
2769            glyph.SetColorModeToColorByScalar()
2770            ucols = vtk.vtkUnsignedCharArray()
2771            ucols.SetNumberOfComponents(3)
2772            ucols.SetName("Colors")
2773            for acol in c:
2774                cx, cy, cz = get_color(acol)
2775                ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255)
2776            pd.GetPointData().AddArray(ucols)
2777            pd.GetPointData().SetActiveScalars("Colors")
2778            glyph.ScalingOff()
2779        elif risseq:
2780            glyph.SetScaleModeToScaleByScalar()
2781            urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32)
2782            urads.SetName("Radii")
2783            pd.GetPointData().AddArray(urads)
2784            pd.GetPointData().SetActiveScalars("Radii")
2785
2786        vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32))
2787
2788        glyph.SetInputData(pd)
2789        glyph.Update()
2790
2791        Mesh.__init__(self, glyph.GetOutput(), alpha=alpha)
2792        self.SetPosition(base)
2793        self.base = base
2794        self.top = centers[-1]
2795        self.phong()
2796        if cisseq:
2797            self.mapper().ScalarVisibilityOn()
2798        else:
2799            self.mapper().ScalarVisibilityOff()
2800            self.GetProperty().SetColor(get_color(c))
2801        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):
2804class Earth(Mesh):
2805    """
2806    Build a textured mesh representing the Earth.
2807    """
2808
2809    def __init__(self, style=1, r=1.0):
2810        """
2811        Build a textured mesh representing the Earth.
2812
2813        Example:
2814            - [geodesic.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic.py)
2815
2816                ![](https://vedo.embl.es/images/advanced/geodesic.png)
2817        """
2818        tss = vtk.vtkTexturedSphereSource()
2819        tss.SetRadius(r)
2820        tss.SetThetaResolution(72)
2821        tss.SetPhiResolution(36)
2822        Mesh.__init__(self, tss, c="w")
2823        atext = vtk.vtkTexture()
2824        pnm_reader = vtk.vtkJPEGReader()
2825        fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False)
2826        pnm_reader.SetFileName(fn)
2827        atext.SetInputConnection(pnm_reader.GetOutputPort())
2828        atext.InterpolateOn()
2829        self.SetTexture(atext)
2830        self.name = "Earth"

Build a textured mesh representing the Earth.

Earth(style=1, r=1.0)
2809    def __init__(self, style=1, r=1.0):
2810        """
2811        Build a textured mesh representing the Earth.
2812
2813        Example:
2814            - [geodesic.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic.py)
2815
2816                ![](https://vedo.embl.es/images/advanced/geodesic.png)
2817        """
2818        tss = vtk.vtkTexturedSphereSource()
2819        tss.SetRadius(r)
2820        tss.SetThetaResolution(72)
2821        tss.SetPhiResolution(36)
2822        Mesh.__init__(self, tss, c="w")
2823        atext = vtk.vtkTexture()
2824        pnm_reader = vtk.vtkJPEGReader()
2825        fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False)
2826        pnm_reader.SetFileName(fn)
2827        atext.SetInputConnection(pnm_reader.GetOutputPort())
2828        atext.InterpolateOn()
2829        self.SetTexture(atext)
2830        self.name = "Earth"

Build a textured mesh representing the Earth.

Example:
class Ellipsoid(vedo.mesh.Mesh):
2833class Ellipsoid(Mesh):
2834    """
2835    Build a 3D ellipsoid.
2836    """
2837
2838    def __init__(
2839        self,
2840        pos=(0, 0, 0),
2841        axis1=(1, 0, 0),
2842        axis2=(0, 2, 0),
2843        axis3=(0, 0, 3),
2844        res=24,
2845        c="cyan4",
2846        alpha=1.0,
2847    ):
2848        """
2849        Build a 3D ellipsoid centered at position `pos`.
2850
2851        Arguments:
2852            axis1 : (list)
2853                First axis
2854            axis2 : (list)
2855                Second axis
2856            axis3 : (list)
2857                Third axis
2858
2859        .. note:: `axis1` and `axis2` are only used to define sizes and one azimuth angle.
2860        """
2861
2862        self.center = pos
2863        self.va_error = 0
2864        self.vb_error = 0
2865        self.vc_error = 0
2866        self.axis1 = axis1
2867        self.axis2 = axis2
2868        self.axis3 = axis3
2869        self.nr_of_points = 1  # used by pcaEllipsoid
2870
2871        if utils.is_sequence(res):
2872            res_t, res_phi = res
2873        else:
2874            res_t, res_phi = 2 * res, res
2875
2876        elli_source = vtk.vtkSphereSource()
2877        elli_source.SetThetaResolution(res_t)
2878        elli_source.SetPhiResolution(res_phi)
2879        elli_source.Update()
2880        l1 = np.linalg.norm(axis1)
2881        l2 = np.linalg.norm(axis2)
2882        l3 = np.linalg.norm(axis3)
2883        self.va = l1
2884        self.vb = l2
2885        self.vc = l3
2886        axis1 = np.array(axis1) / l1
2887        axis2 = np.array(axis2) / l2
2888        axis3 = np.array(axis3) / l3
2889        angle = np.arcsin(np.dot(axis1, axis2))
2890        theta = np.arccos(axis3[2])
2891        phi = np.arctan2(axis3[1], axis3[0])
2892
2893        t = vtk.vtkTransform()
2894        t.PostMultiply()
2895        t.Scale(l1, l2, l3)
2896        t.RotateX(np.rad2deg(angle))
2897        t.RotateY(np.rad2deg(theta))
2898        t.RotateZ(np.rad2deg(phi))
2899        tf = vtk.vtkTransformPolyDataFilter()
2900        tf.SetInputData(elli_source.GetOutput())
2901        tf.SetTransform(t)
2902        tf.Update()
2903        pd = tf.GetOutput()
2904        self.transformation = t
2905
2906        Mesh.__init__(self, pd, c, alpha)
2907        self.phong()
2908        if len(pos) == 2:
2909            pos = (pos[0], pos[1], 0)
2910        self.SetPosition(pos)
2911        self.name = "Ellipsoid"
2912
2913    def asphericity(self):
2914        """
2915        Return a measure of how different an ellipsoid is froma sphere.
2916        Values close to zero correspond to a spheric object.
2917        """
2918        a,b,c = self.va, self.vb, self.vc
2919        asp = ( ((a-b)/(a+b))**2
2920              + ((a-c)/(a+c))**2
2921              + ((b-c)/(b+c))**2 )/3. * 4.
2922        return asp
2923
2924    def asphericity_error(self):
2925        """
2926        Calculate statistical error on the asphericity value.
2927
2928        Errors on the main axes are stored in
2929        `Ellipsoid.va_error, Ellipsoid.vb_error and Ellipsoid.vc_error`.
2930        """
2931        a, b, c = self.va, self.vb, self.vc
2932        sqrtn = np.sqrt(self.nr_of_points)
2933        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2934
2935        # from sympy import *
2936        # init_printing(use_unicode=True)
2937        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2938        # L = (
2939        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2940        #    / 3 * 4)
2941        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2942        # print(dl2)
2943        # exit()
2944        dL2 = (
2945            ea ** 2
2946            * (
2947                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2948                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2949                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2950                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2951            ) ** 2
2952            + eb ** 2
2953            * (
2954                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2955                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2956                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2957                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2958            ) ** 2
2959            + ec ** 2
2960            * (
2961                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2962                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2963                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2964                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2965            ) ** 2
2966        )
2967
2968        err = np.sqrt(dL2)
2969
2970        self.va_error = ea
2971        self.vb_error = eb
2972        self.vc_error = ec
2973        return err

Build a 3D ellipsoid.

Ellipsoid( pos=(0, 0, 0), axis1=(1, 0, 0), axis2=(0, 2, 0), axis3=(0, 0, 3), res=24, c='cyan4', alpha=1.0)
2838    def __init__(
2839        self,
2840        pos=(0, 0, 0),
2841        axis1=(1, 0, 0),
2842        axis2=(0, 2, 0),
2843        axis3=(0, 0, 3),
2844        res=24,
2845        c="cyan4",
2846        alpha=1.0,
2847    ):
2848        """
2849        Build a 3D ellipsoid centered at position `pos`.
2850
2851        Arguments:
2852            axis1 : (list)
2853                First axis
2854            axis2 : (list)
2855                Second axis
2856            axis3 : (list)
2857                Third axis
2858
2859        .. note:: `axis1` and `axis2` are only used to define sizes and one azimuth angle.
2860        """
2861
2862        self.center = pos
2863        self.va_error = 0
2864        self.vb_error = 0
2865        self.vc_error = 0
2866        self.axis1 = axis1
2867        self.axis2 = axis2
2868        self.axis3 = axis3
2869        self.nr_of_points = 1  # used by pcaEllipsoid
2870
2871        if utils.is_sequence(res):
2872            res_t, res_phi = res
2873        else:
2874            res_t, res_phi = 2 * res, res
2875
2876        elli_source = vtk.vtkSphereSource()
2877        elli_source.SetThetaResolution(res_t)
2878        elli_source.SetPhiResolution(res_phi)
2879        elli_source.Update()
2880        l1 = np.linalg.norm(axis1)
2881        l2 = np.linalg.norm(axis2)
2882        l3 = np.linalg.norm(axis3)
2883        self.va = l1
2884        self.vb = l2
2885        self.vc = l3
2886        axis1 = np.array(axis1) / l1
2887        axis2 = np.array(axis2) / l2
2888        axis3 = np.array(axis3) / l3
2889        angle = np.arcsin(np.dot(axis1, axis2))
2890        theta = np.arccos(axis3[2])
2891        phi = np.arctan2(axis3[1], axis3[0])
2892
2893        t = vtk.vtkTransform()
2894        t.PostMultiply()
2895        t.Scale(l1, l2, l3)
2896        t.RotateX(np.rad2deg(angle))
2897        t.RotateY(np.rad2deg(theta))
2898        t.RotateZ(np.rad2deg(phi))
2899        tf = vtk.vtkTransformPolyDataFilter()
2900        tf.SetInputData(elli_source.GetOutput())
2901        tf.SetTransform(t)
2902        tf.Update()
2903        pd = tf.GetOutput()
2904        self.transformation = t
2905
2906        Mesh.__init__(self, pd, c, alpha)
2907        self.phong()
2908        if len(pos) == 2:
2909            pos = (pos[0], pos[1], 0)
2910        self.SetPosition(pos)
2911        self.name = "Ellipsoid"

Build a 3D ellipsoid centered at position pos.

Arguments:
  • axis1 : (list) First axis
  • axis2 : (list) Second axis
  • axis3 : (list) Third axis
axis1 and axis2 are only used to define sizes and one azimuth angle.
def asphericity(self):
2913    def asphericity(self):
2914        """
2915        Return a measure of how different an ellipsoid is froma sphere.
2916        Values close to zero correspond to a spheric object.
2917        """
2918        a,b,c = self.va, self.vb, self.vc
2919        asp = ( ((a-b)/(a+b))**2
2920              + ((a-c)/(a+c))**2
2921              + ((b-c)/(b+c))**2 )/3. * 4.
2922        return asp

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

def asphericity_error(self):
2924    def asphericity_error(self):
2925        """
2926        Calculate statistical error on the asphericity value.
2927
2928        Errors on the main axes are stored in
2929        `Ellipsoid.va_error, Ellipsoid.vb_error and Ellipsoid.vc_error`.
2930        """
2931        a, b, c = self.va, self.vb, self.vc
2932        sqrtn = np.sqrt(self.nr_of_points)
2933        ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn
2934
2935        # from sympy import *
2936        # init_printing(use_unicode=True)
2937        # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec")
2938        # L = (
2939        #    (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2)
2940        #    / 3 * 4)
2941        # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2
2942        # print(dl2)
2943        # exit()
2944        dL2 = (
2945            ea ** 2
2946            * (
2947                -8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2948                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2949                + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2)
2950                + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2)
2951            ) ** 2
2952            + eb ** 2
2953            * (
2954                4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2)
2955                - 8 * (a - b) ** 2 / (3 * (a + b) ** 3)
2956                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2957                + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2)
2958            ) ** 2
2959            + ec ** 2
2960            * (
2961                4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2)
2962                - 8 * (a - c) ** 2 / (3 * (a + c) ** 3)
2963                + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2)
2964                - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3)
2965            ) ** 2
2966        )
2967
2968        err = np.sqrt(dL2)
2969
2970        self.va_error = ea
2971        self.vb_error = eb
2972        self.vc_error = ec
2973        return err

Calculate statistical error on the asphericity value.

Errors on the main axes are stored in Ellipsoid.va_error, Ellipsoid.vb_error and Ellipsoid.vc_error.

class Grid(vedo.mesh.Mesh):
2976class Grid(Mesh):
2977    """
2978    An even or uneven 2D grid.
2979    """
2980
2981    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0):
2982        """
2983        Create an even or uneven 2D grid.
2984
2985        Arguments:
2986            s : (float, list)
2987                if a float is provided it is interpreted as the total size along x and y,
2988                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2989                In this case keyword `res` is ignored (see example below).
2990            res : (list)
2991                resolutions along x and y, e.i. the number of subdivisions
2992            lw : (int)
2993                line width
2994
2995        Example:
2996            ```python
2997            from vedo import *
2998            import numpy as np
2999            xcoords = np.arange(0, 2, 0.2)
3000            ycoords = np.arange(0, 1, 0.2)
3001            sqrtx = sqrt(xcoords)
3002            grid = Grid(s=(sqrtx, ycoords)).lw(2)
3003            grid.show(axes=8)
3004
3005            # can also create a grid from np.mgrid:
3006            X, Y = np.mgrid[-12:12:1000*1j, 0:15:1000*1j]
3007            vgrid = Grid(s=(X[:,0], Y[0]))
3008            vgrid.show(axes=1).close()
3009            ```
3010            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
3011        """
3012        resx, resy = res
3013        sx, sy = s
3014
3015        if len(pos) == 2:
3016            pos = (pos[0], pos[1], 0)
3017
3018        if utils.is_sequence(sx) and utils.is_sequence(sy):
3019            verts = []
3020            for y in sy:
3021                for x in sx:
3022                    verts.append([x, y, 0])
3023            faces = []
3024            n = len(sx)
3025            m = len(sy)
3026            for j in range(m - 1):
3027                j1n = (j + 1) * n
3028                for i in range(n - 1):
3029                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3030
3031            verts = np.array(verts)
3032            Mesh.__init__(self, [verts, faces], c, alpha)
3033
3034        else:
3035            ps = vtk.vtkPlaneSource()
3036            ps.SetResolution(resx, resy)
3037            ps.Update()
3038            poly0 = ps.GetOutput()
3039            t0 = vtk.vtkTransform()
3040            t0.Scale(sx, sy, 1)
3041            tf0 = vtk.vtkTransformPolyDataFilter()
3042            tf0.SetInputData(poly0)
3043            tf0.SetTransform(t0)
3044            tf0.Update()
3045            poly = tf0.GetOutput()
3046            Mesh.__init__(self, poly, c, alpha)
3047            self.SetPosition(pos)
3048
3049        self.wireframe().lw(lw)
3050        self.GetProperty().LightingOff()
3051        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)
2981    def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0):
2982        """
2983        Create an even or uneven 2D grid.
2984
2985        Arguments:
2986            s : (float, list)
2987                if a float is provided it is interpreted as the total size along x and y,
2988                if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
2989                In this case keyword `res` is ignored (see example below).
2990            res : (list)
2991                resolutions along x and y, e.i. the number of subdivisions
2992            lw : (int)
2993                line width
2994
2995        Example:
2996            ```python
2997            from vedo import *
2998            import numpy as np
2999            xcoords = np.arange(0, 2, 0.2)
3000            ycoords = np.arange(0, 1, 0.2)
3001            sqrtx = sqrt(xcoords)
3002            grid = Grid(s=(sqrtx, ycoords)).lw(2)
3003            grid.show(axes=8)
3004
3005            # can also create a grid from np.mgrid:
3006            X, Y = np.mgrid[-12:12:1000*1j, 0:15:1000*1j]
3007            vgrid = Grid(s=(X[:,0], Y[0]))
3008            vgrid.show(axes=1).close()
3009            ```
3010            ![](https://vedo.embl.es/images/feats/uneven_grid.png)
3011        """
3012        resx, resy = res
3013        sx, sy = s
3014
3015        if len(pos) == 2:
3016            pos = (pos[0], pos[1], 0)
3017
3018        if utils.is_sequence(sx) and utils.is_sequence(sy):
3019            verts = []
3020            for y in sy:
3021                for x in sx:
3022                    verts.append([x, y, 0])
3023            faces = []
3024            n = len(sx)
3025            m = len(sy)
3026            for j in range(m - 1):
3027                j1n = (j + 1) * n
3028                for i in range(n - 1):
3029                    faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n])
3030
3031            verts = np.array(verts)
3032            Mesh.__init__(self, [verts, faces], c, alpha)
3033
3034        else:
3035            ps = vtk.vtkPlaneSource()
3036            ps.SetResolution(resx, resy)
3037            ps.Update()
3038            poly0 = ps.GetOutput()
3039            t0 = vtk.vtkTransform()
3040            t0.Scale(sx, sy, 1)
3041            tf0 = vtk.vtkTransformPolyDataFilter()
3042            tf0.SetInputData(poly0)
3043            tf0.SetTransform(t0)
3044            tf0.Update()
3045            poly = tf0.GetOutput()
3046            Mesh.__init__(self, poly, c, alpha)
3047            self.SetPosition(pos)
3048
3049        self.wireframe().lw(lw)
3050        self.GetProperty().LightingOff()
3051        self.name = "Grid"

Create an even or uneven 2D grid.

Arguments:
  • 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 *
import numpy as np
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)

# can also create a grid from np.mgrid:
X, Y = np.mgrid[-12:12:1000*1j, 0:15:1000*1j]
vgrid = Grid(s=(X[:,0], Y[0]))
vgrid.show(axes=1).close()

class TessellatedBox(vedo.mesh.Mesh):
3296class TessellatedBox(Mesh):
3297    """
3298    Build a cubic `Mesh` made of quads.
3299    """
3300
3301    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5):
3302        """
3303        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3304
3305        Arguments:
3306            pos : (list)
3307                position of the left bottom corner
3308            n : (int, list)
3309                number of subdivisions along each side
3310            spacing : (float)
3311                size of the side of the single quad in the 3 directions
3312        """
3313        if utils.is_sequence(n):  # slow
3314            img = vtk.vtkImageData()
3315            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3316            img.SetSpacing(spacing)
3317            gf = vtk.vtkGeometryFilter()
3318            gf.SetInputData(img)
3319            gf.Update()
3320            poly = gf.GetOutput()
3321        else:  # fast
3322            n -= 1
3323            tbs = vtk.vtkTessellatedBoxSource()
3324            tbs.SetLevel(n)
3325            if len(bounds):
3326                tbs.SetBounds(bounds)
3327            else:
3328                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3329            tbs.QuadsOn()
3330            tbs.SetOutputPointsPrecision(vtk.vtkAlgorithm.SINGLE_PRECISION)
3331            tbs.Update()
3332            poly = tbs.GetOutput()
3333        Mesh.__init__(self, poly, c=c, alpha=alpha)
3334        self.SetPosition(pos)
3335        self.lw(1).lighting("off")
3336        self.base = np.array([0.5, 0.5, 0.0])
3337        self.top = np.array([0.5, 0.5, 1.0])
3338        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)
3301    def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5):
3302        """
3303        Build a cubic `Mesh` made of `n` small quads in the 3 axis directions.
3304
3305        Arguments:
3306            pos : (list)
3307                position of the left bottom corner
3308            n : (int, list)
3309                number of subdivisions along each side
3310            spacing : (float)
3311                size of the side of the single quad in the 3 directions
3312        """
3313        if utils.is_sequence(n):  # slow
3314            img = vtk.vtkImageData()
3315            img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1)
3316            img.SetSpacing(spacing)
3317            gf = vtk.vtkGeometryFilter()
3318            gf.SetInputData(img)
3319            gf.Update()
3320            poly = gf.GetOutput()
3321        else:  # fast
3322            n -= 1
3323            tbs = vtk.vtkTessellatedBoxSource()
3324            tbs.SetLevel(n)
3325            if len(bounds):
3326                tbs.SetBounds(bounds)
3327            else:
3328                tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2])
3329            tbs.QuadsOn()
3330            tbs.SetOutputPointsPrecision(vtk.vtkAlgorithm.SINGLE_PRECISION)
3331            tbs.Update()
3332            poly = tbs.GetOutput()
3333        Mesh.__init__(self, poly, c=c, alpha=alpha)
3334        self.SetPosition(pos)
3335        self.lw(1).lighting("off")
3336        self.base = np.array([0.5, 0.5, 0.0])
3337        self.top = np.array([0.5, 0.5, 1.0])
3338        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):
3054class Plane(Mesh):
3055    """
3056    Create a plane in space.
3057    """
3058
3059    def __init__(self, pos=(0, 0, 0), normal=(0, 0, 1), s=(1, 1), res=(1, 1), c="gray5", alpha=1.0):
3060        """
3061        Create a plane of size `s=(xsize, ysize)` oriented perpendicular to vector `normal`
3062        and so that it passes through point `pos`.
3063
3064        Arguments:
3065            normal : (list)
3066                normal vector to the plane
3067        """
3068        pos = utils.make3d(pos)
3069        sx, sy = s
3070
3071        self.normal = np.asarray(normal, dtype=float)
3072        self.center = np.asarray(pos, dtype=float)
3073        self.variance = 0
3074
3075        ps = vtk.vtkPlaneSource()
3076        ps.SetResolution(res[0], res[1])
3077        tri = vtk.vtkTriangleFilter()
3078        tri.SetInputConnection(ps.GetOutputPort())
3079        tri.Update()
3080        poly = tri.GetOutput()
3081        axis = self.normal / np.linalg.norm(normal)
3082        theta = np.arccos(axis[2])
3083        phi = np.arctan2(axis[1], axis[0])
3084        t = vtk.vtkTransform()
3085        t.PostMultiply()
3086        t.Scale(sx, sy, 1)
3087        t.RotateY(np.rad2deg(theta))
3088        t.RotateZ(np.rad2deg(phi))
3089        tf = vtk.vtkTransformPolyDataFilter()
3090        tf.SetInputData(poly)
3091        tf.SetTransform(t)
3092        tf.Update()
3093        Mesh.__init__(self, tf.GetOutput(), c, alpha)
3094        self.lighting("off")
3095        self.SetPosition(pos)
3096        self.name = "Plane"
3097        self.top = self.normal
3098        self.bottom = np.array([0.0, 0.0, 0.0])
3099
3100    def contains(self, points):
3101        """
3102        Check if each of the provided point lies on this plane.
3103        `points` is an array of shape (n, 3).
3104        """
3105        points = np.array(points, dtype=float)
3106        bounds = self.points()
3107
3108        mask = np.isclose(np.dot(points - self.center, self.normal), 0)
3109
3110        for i in [1, 3]:
3111            AB = bounds[i] - bounds[0]
3112            AP = points - bounds[0]
3113            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3114            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3115            mask = np.logical_and(mask, mask_l)
3116            mask = np.logical_and(mask, mask_g)
3117        return mask

Create a plane in space.

Plane( pos=(0, 0, 0), normal=(0, 0, 1), s=(1, 1), res=(1, 1), c='gray5', alpha=1.0)
3059    def __init__(self, pos=(0, 0, 0), normal=(0, 0, 1), s=(1, 1), res=(1, 1), c="gray5", alpha=1.0):
3060        """
3061        Create a plane of size `s=(xsize, ysize)` oriented perpendicular to vector `normal`
3062        and so that it passes through point `pos`.
3063
3064        Arguments:
3065            normal : (list)
3066                normal vector to the plane
3067        """
3068        pos = utils.make3d(pos)
3069        sx, sy = s
3070
3071        self.normal = np.asarray(normal, dtype=float)
3072        self.center = np.asarray(pos, dtype=float)
3073        self.variance = 0
3074
3075        ps = vtk.vtkPlaneSource()
3076        ps.SetResolution(res[0], res[1])
3077        tri = vtk.vtkTriangleFilter()
3078        tri.SetInputConnection(ps.GetOutputPort())
3079        tri.Update()
3080        poly = tri.GetOutput()
3081        axis = self.normal / np.linalg.norm(normal)
3082        theta = np.arccos(axis[2])
3083        phi = np.arctan2(axis[1], axis[0])
3084        t = vtk.vtkTransform()
3085        t.PostMultiply()
3086        t.Scale(sx, sy, 1)
3087        t.RotateY(np.rad2deg(theta))
3088        t.RotateZ(np.rad2deg(phi))
3089        tf = vtk.vtkTransformPolyDataFilter()
3090        tf.SetInputData(poly)
3091        tf.SetTransform(t)
3092        tf.Update()
3093        Mesh.__init__(self, tf.GetOutput(), c, alpha)
3094        self.lighting("off")
3095        self.SetPosition(pos)
3096        self.name = "Plane"
3097        self.top = self.normal
3098        self.bottom = np.array([0.0, 0.0, 0.0])

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

Arguments:
  • normal : (list) normal vector to the plane
def contains(self, points):
3100    def contains(self, points):
3101        """
3102        Check if each of the provided point lies on this plane.
3103        `points` is an array of shape (n, 3).
3104        """
3105        points = np.array(points, dtype=float)
3106        bounds = self.points()
3107
3108        mask = np.isclose(np.dot(points - self.center, self.normal), 0)
3109
3110        for i in [1, 3]:
3111            AB = bounds[i] - bounds[0]
3112            AP = points - bounds[0]
3113            mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB))
3114            mask_g = np.greater_equal(np.dot(AP, AB), 0)
3115            mask = np.logical_and(mask, mask_l)
3116            mask = np.logical_and(mask, mask_g)
3117        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):
3214class Box(Mesh):
3215    """
3216    Build a box of specified dimensions.
3217    """
3218
3219    def __init__(self, pos=(0, 0, 0), 
3220                 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0):
3221        """
3222        Build a box of dimensions `x=length, y=width and z=height`.
3223        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3224
3225        If `size` is a list of 6 numbers, this will be interpreted as the bounding box:
3226        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3227
3228        Examples:
3229            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3230
3231                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3232        """
3233        if len(size) == 6:
3234            bounds = size
3235            length = bounds[1] - bounds[0]
3236            width = bounds[3] - bounds[2]
3237            height = bounds[5] - bounds[4]
3238            xp = (bounds[1] + bounds[0]) / 2
3239            yp = (bounds[3] + bounds[2]) / 2
3240            zp = (bounds[5] + bounds[4]) / 2
3241            pos = (xp, yp, zp)
3242        elif len(size) == 3:
3243            length, width, height = size
3244
3245        src = vtk.vtkCubeSource()
3246        src.SetXLength(length)
3247        src.SetYLength(width)
3248        src.SetZLength(height)
3249        src.Update()
3250        pd = src.GetOutput()
3251
3252        tc = [
3253            [0.0, 0.0],
3254            [1.0, 0.0],
3255            [0.0, 1.0],
3256            [1.0, 1.0],
3257            [1.0, 0.0],
3258            [0.0, 0.0],
3259            [1.0, 1.0],
3260            [0.0, 1.0],
3261            [1.0, 1.0],
3262            [1.0, 0.0],
3263            [0.0, 1.0],
3264            [0.0, 0.0],
3265            [0.0, 1.0],
3266            [0.0, 0.0],
3267            [1.0, 1.0],
3268            [1.0, 0.0],
3269            [1.0, 0.0],
3270            [0.0, 0.0],
3271            [1.0, 1.0],
3272            [0.0, 1.0],
3273            [0.0, 0.0],
3274            [1.0, 0.0],
3275            [0.0, 1.0],
3276            [1.0, 1.0],
3277        ]
3278        vtc = utils.numpy2vtk(tc)
3279        pd.GetPointData().SetTCoords(vtc)
3280        Mesh.__init__(self, pd, c, alpha)
3281        if len(pos) == 2:
3282            pos = (pos[0], pos[1], 0)
3283        self.SetPosition(pos)
3284        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)
3219    def __init__(self, pos=(0, 0, 0), 
3220                 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0):
3221        """
3222        Build a box of dimensions `x=length, y=width and z=height`.
3223        Alternatively dimensions can be defined by setting `size` keyword with a tuple.
3224
3225        If `size` is a list of 6 numbers, this will be interpreted as the bounding box:
3226        `[xmin,xmax, ymin,ymax, zmin,zmax]`
3227
3228        Examples:
3229            - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py)
3230
3231                ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif)
3232        """
3233        if len(size) == 6:
3234            bounds = size
3235            length = bounds[1] - bounds[0]
3236            width = bounds[3] - bounds[2]
3237            height = bounds[5] - bounds[4]
3238            xp = (bounds[1] + bounds[0]) / 2
3239            yp = (bounds[3] + bounds[2]) / 2
3240            zp = (bounds[5] + bounds[4]) / 2
3241            pos = (xp, yp, zp)
3242        elif len(size) == 3:
3243            length, width, height = size
3244
3245        src = vtk.vtkCubeSource()
3246        src.SetXLength(length)
3247        src.SetYLength(width)
3248        src.SetZLength(height)
3249        src.Update()
3250        pd = src.GetOutput()
3251
3252        tc = [
3253            [0.0, 0.0],
3254            [1.0, 0.0],
3255            [0.0, 1.0],
3256            [1.0, 1.0],
3257            [1.0, 0.0],
3258            [0.0, 0.0],
3259            [1.0, 1.0],
3260            [0.0, 1.0],
3261            [1.0, 1.0],
3262            [1.0, 0.0],
3263            [0.0, 1.0],
3264            [0.0, 0.0],
3265            [0.0, 1.0],
3266            [0.0, 0.0],
3267            [1.0, 1.0],
3268            [1.0, 0.0],
3269            [1.0, 0.0],
3270            [0.0, 0.0],
3271            [1.0, 1.0],
3272            [0.0, 1.0],
3273            [0.0, 0.0],
3274            [1.0, 0.0],
3275            [0.0, 1.0],
3276            [1.0, 1.0],
3277        ]
3278        vtc = utils.numpy2vtk(tc)
3279        pd.GetPointData().SetTCoords(vtc)
3280        Mesh.__init__(self, pd, c, alpha)
3281        if len(pos) == 2:
3282            pos = (pos[0], pos[1], 0)
3283        self.SetPosition(pos)
3284        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 size is a list of 6 numbers, this will be interpreted as the bounding box: [xmin,xmax, ymin,ymax, zmin,zmax]

Examples:
class Cube(Box):
3287class Cube(Box):
3288    """Build a cube."""
3289
3290    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0):
3291        """Build a cube of size `side`."""
3292        Box.__init__(self, pos, side, side, side, (), c, alpha)
3293        self.name = "Cube"

Build a cube.

Cube(pos=(0, 0, 0), side=1.0, c='g4', alpha=1.0)
3290    def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0):
3291        """Build a cube of size `side`."""
3292        Box.__init__(self, pos, side, side, side, (), c, alpha)
3293        self.name = "Cube"

Build a cube of size side.

class Spring(vedo.mesh.Mesh):
3341class Spring(Mesh):
3342    """
3343    Build a spring model.
3344    """
3345
3346    def __init__(
3347        self,
3348        start_pt=(0, 0, 0),
3349        end_pt=(1, 0, 0),
3350        coils=20,
3351        r1=0.1,
3352        r2=None,
3353        thickness=None,
3354        c="gray5",
3355        alpha=1.0,
3356    ):
3357        """
3358        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3359
3360        Arguments:
3361            coils : (int)
3362                number of coils
3363            r1 : (float)
3364                radius at start point
3365            r2 : (float)
3366                radius at end point
3367            thickness : (float)
3368                thickness of the coil section
3369        """
3370        diff = end_pt - np.array(start_pt, dtype=float)
3371        length = np.linalg.norm(diff)
3372        if not length:
3373            return
3374        if not r1:
3375            r1 = length / 20
3376        trange = np.linspace(0, length, num=50 * coils)
3377        om = 6.283 * (coils - 0.5) / length
3378        if not r2:
3379            r2 = r1
3380        pts = []
3381        for t in trange:
3382            f = (length - t) / length
3383            rd = r1 * f + r2 * (1 - f)
3384            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3385
3386        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3387        diff = diff / length
3388        theta = np.arccos(diff[2])
3389        phi = np.arctan2(diff[1], diff[0])
3390        sp = Line(pts).polydata(False)
3391        t = vtk.vtkTransform()
3392        t.RotateZ(np.rad2deg(phi))
3393        t.RotateY(np.rad2deg(theta))
3394        tf = vtk.vtkTransformPolyDataFilter()
3395        tf.SetInputData(sp)
3396        tf.SetTransform(t)
3397        tf.Update()
3398        tuf = vtk.vtkTubeFilter()
3399        tuf.SetNumberOfSides(12)
3400        tuf.CappingOn()
3401        tuf.SetInputData(tf.GetOutput())
3402        if not thickness:
3403            thickness = r1 / 10
3404        tuf.SetRadius(thickness)
3405        tuf.Update()
3406        Mesh.__init__(self, tuf.GetOutput(), c, alpha)
3407        self.phong()
3408        self.SetPosition(start_pt)
3409        self.base = np.array(start_pt, dtype=float)
3410        self.top = np.array(end_pt, dtype=float)
3411        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)
3346    def __init__(
3347        self,
3348        start_pt=(0, 0, 0),
3349        end_pt=(1, 0, 0),
3350        coils=20,
3351        r1=0.1,
3352        r2=None,
3353        thickness=None,
3354        c="gray5",
3355        alpha=1.0,
3356    ):
3357        """
3358        Build a spring of specified nr of `coils` between `start_pt` and `end_pt`.
3359
3360        Arguments:
3361            coils : (int)
3362                number of coils
3363            r1 : (float)
3364                radius at start point
3365            r2 : (float)
3366                radius at end point
3367            thickness : (float)
3368                thickness of the coil section
3369        """
3370        diff = end_pt - np.array(start_pt, dtype=float)
3371        length = np.linalg.norm(diff)
3372        if not length:
3373            return
3374        if not r1:
3375            r1 = length / 20
3376        trange = np.linspace(0, length, num=50 * coils)
3377        om = 6.283 * (coils - 0.5) / length
3378        if not r2:
3379            r2 = r1
3380        pts = []
3381        for t in trange:
3382            f = (length - t) / length
3383            rd = r1 * f + r2 * (1 - f)
3384            pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t])
3385
3386        pts = [[0, 0, 0]] + pts + [[0, 0, length]]
3387        diff = diff / length
3388        theta = np.arccos(diff[2])
3389        phi = np.arctan2(diff[1], diff[0])
3390        sp = Line(pts).polydata(False)
3391        t = vtk.vtkTransform()
3392        t.RotateZ(np.rad2deg(phi))
3393        t.RotateY(np.rad2deg(theta))
3394        tf = vtk.vtkTransformPolyDataFilter()
3395        tf.SetInputData(sp)
3396        tf.SetTransform(t)
3397        tf.Update()
3398        tuf = vtk.vtkTubeFilter()
3399        tuf.SetNumberOfSides(12)
3400        tuf.CappingOn()
3401        tuf.SetInputData(tf.GetOutput())
3402        if not thickness:
3403            thickness = r1 / 10
3404        tuf.SetRadius(thickness)
3405        tuf.Update()
3406        Mesh.__init__(self, tuf.GetOutput(), c, alpha)
3407        self.phong()
3408        self.SetPosition(start_pt)
3409        self.base = np.array(start_pt, dtype=float)
3410        self.top = np.array(end_pt, dtype=float)
3411        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):
3414class Cylinder(Mesh):
3415    """
3416    Build a cylinder of specified height and radius.
3417    """
3418
3419    def __init__(
3420        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), cap=True, res=24, c="teal3", alpha=1.0
3421    ):
3422        """
3423        Build a cylinder of specified height and radius `r`, centered at `pos`.
3424
3425        If `pos` is a list of 2 points, e.g. `pos=[v1,v2]`, build a cylinder with base
3426        centered at `v1` and top at `v2`.
3427
3428        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3429        """
3430        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3431            base = np.array(pos[0], dtype=float)
3432            top = np.array(pos[1], dtype=float)
3433            pos = (base + top) / 2
3434            height = np.linalg.norm(top - base)
3435            axis = top - base
3436            axis = utils.versor(axis)
3437        else:
3438            axis = utils.versor(axis)
3439            base = pos - axis * height / 2
3440            top = pos + axis * height / 2
3441
3442        cyl = vtk.vtkCylinderSource()
3443        cyl.SetResolution(res)
3444        cyl.SetRadius(r)
3445        cyl.SetHeight(height)
3446        cyl.SetCapping(cap)
3447        cyl.Update()
3448
3449        theta = np.arccos(axis[2])
3450        phi = np.arctan2(axis[1], axis[0])
3451        t = vtk.vtkTransform()
3452        t.PostMultiply()
3453        t.RotateX(90)  # put it along Z
3454        t.RotateY(np.rad2deg(theta))
3455        t.RotateZ(np.rad2deg(phi))
3456        tf = vtk.vtkTransformPolyDataFilter()
3457        tf.SetInputData(cyl.GetOutput())
3458        tf.SetTransform(t)
3459        tf.Update()
3460        pd = tf.GetOutput()
3461
3462        Mesh.__init__(self, pd, c, alpha)
3463        self.phong()
3464        self.SetPosition(pos)
3465        self.base = base + pos
3466        self.top = top + pos
3467        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)
3419    def __init__(
3420        self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), cap=True, res=24, c="teal3", alpha=1.0
3421    ):
3422        """
3423        Build a cylinder of specified height and radius `r`, centered at `pos`.
3424
3425        If `pos` is a list of 2 points, e.g. `pos=[v1,v2]`, build a cylinder with base
3426        centered at `v1` and top at `v2`.
3427
3428        ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png)
3429        """
3430        if utils.is_sequence(pos[0]):  # assume user is passing pos=[base, top]
3431            base = np.array(pos[0], dtype=float)
3432            top = np.array(pos[1], dtype=float)
3433            pos = (base + top) / 2
3434            height = np.linalg.norm(top - base)
3435            axis = top - base
3436            axis = utils.versor(axis)
3437        else:
3438            axis = utils.versor(axis)
3439            base = pos - axis * height / 2
3440            top = pos + axis * height / 2
3441
3442        cyl = vtk.vtkCylinderSource()
3443        cyl.SetResolution(res)
3444        cyl.SetRadius(r)
3445        cyl.SetHeight(height)
3446        cyl.SetCapping(cap)
3447        cyl.Update()
3448
3449        theta = np.arccos(axis[2])
3450        phi = np.arctan2(axis[1], axis[0])
3451        t = vtk.vtkTransform()
3452        t.PostMultiply()
3453        t.RotateX(90)  # put it along Z
3454        t.RotateY(np.rad2deg(theta))
3455        t.RotateZ(np.rad2deg(phi))
3456        tf = vtk.vtkTransformPolyDataFilter()
3457        tf.SetInputData(cyl.GetOutput())
3458        tf.SetTransform(t)
3459        tf.Update()
3460        pd = tf.GetOutput()
3461
3462        Mesh.__init__(self, pd, c, alpha)
3463        self.phong()
3464        self.SetPosition(pos)
3465        self.base = base + pos
3466        self.top = top + pos
3467        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.

class Cone(vedo.mesh.Mesh):
3470class Cone(Mesh):
3471    """Build a cone of specified radius and height."""
3472
3473    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3474                 res=48, c="green3", alpha=1.0):
3475        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3476        con = vtk.vtkConeSource()
3477        con.SetResolution(res)
3478        con.SetRadius(r)
3479        con.SetHeight(height)
3480        con.SetDirection(axis)
3481        con.Update()
3482        Mesh.__init__(self, con.GetOutput(), c, alpha)
3483        self.phong()
3484        if len(pos) == 2:
3485            pos = (pos[0], pos[1], 0)
3486        self.SetPosition(pos)
3487        v = utils.versor(axis) * height / 2
3488        self.base = pos - v
3489        self.top = pos + v
3490        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)
3473    def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1),
3474                 res=48, c="green3", alpha=1.0):
3475        """Build a cone of specified radius `r` and `height`, centered at `pos`."""
3476        con = vtk.vtkConeSource()
3477        con.SetResolution(res)
3478        con.SetRadius(r)
3479        con.SetHeight(height)
3480        con.SetDirection(axis)
3481        con.Update()
3482        Mesh.__init__(self, con.GetOutput(), c, alpha)
3483        self.phong()
3484        if len(pos) == 2:
3485            pos = (pos[0], pos[1], 0)
3486        self.SetPosition(pos)
3487        v = utils.versor(axis) * height / 2
3488        self.base = pos - v
3489        self.top = pos + v
3490        self.name = "Cone"

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

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

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

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

Build a paraboloid.

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

Build a hyperboloid.

Hyperboloid(pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c='pink4', alpha=1.0)
3600    def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0):
3601        """
3602        Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`.
3603
3604        Full volumetric expression is:
3605            `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`
3606        """
3607        q = vtk.vtkQuadric()
3608        q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0)
3609        # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2
3610        #         + a3*x*y + a4*y*z + a5*x*z
3611        #         + a6*x   + a7*y   + a8*z  +a9
3612        sample = vtk.vtkSampleFunction()
3613        sample.SetSampleDimensions(res, res, res)
3614        sample.SetImplicitFunction(q)
3615
3616        contours = vtk.vtkContourFilter()
3617        contours.SetInputConnection(sample.GetOutputPort())
3618        contours.GenerateValues(1, value, value)
3619        contours.Update()
3620
3621        Mesh.__init__(self, contours.GetOutput(), c, alpha)
3622        self.compute_normals().phong()
3623        self.mapper().ScalarVisibilityOff()
3624        self.SetPosition(pos)
3625        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:
4368class TextBase:
4369    "Base class."
4370
4371    def __init__(self):
4372        "Do not instantiate this base class."
4373
4374        self.rendered_at = set()
4375        self.property = None
4376
4377        if isinstance(settings.default_font, int):
4378            lfonts = list(settings.font_parameters.keys())
4379            font = settings.default_font % len(lfonts)
4380            self.fontname = lfonts[font]
4381        else:
4382            self.fontname = settings.default_font
4383        self.name = "Text"
4384
4385    def angle(self, a):
4386        """Orientation angle in degrees"""
4387        self.property.SetOrientation(a)
4388        return self
4389
4390    def line_spacing(self, ls):
4391        """Set the extra spacing between lines, expressed as a text height multiplication factor."""
4392        self.property.SetLineSpacing(ls)
4393        return self
4394
4395    def line_offset(self, lo):
4396        """Set/Get the vertical offset (measured in pixels)."""
4397        self.property.SetLineOffset(lo)
4398        return self
4399
4400    def bold(self, value=True):
4401        """Set bold face"""
4402        self.property.SetBold(value)
4403        return self
4404
4405    def italic(self, value=True):
4406        """Set italic face"""
4407        self.property.SetItalic(value)
4408        return self
4409
4410    def shadow(self, offset=(1, -1)):
4411        """Text shadowing. Set to `None` to disable it."""
4412        if offset is None:
4413            self.property.ShadowOff()
4414        else:
4415            self.property.ShadowOn()
4416            self.property.SetShadowOffset(offset)
4417        return self
4418
4419    def color(self, c=None):
4420        """Set the text color"""
4421        if c is None:
4422            return get_color(self.property.GetColor())
4423        self.property.SetColor(get_color(c))
4424        return self
4425
4426    def c(self, color=None):
4427        """Set the text color"""
4428        if color is None:
4429            return get_color(self.property.GetColor())
4430        return self.color(color)
4431
4432    def alpha(self, value):
4433        """Set the text opacity"""
4434        self.property.SetBackgroundOpacity(value)
4435        return self
4436
4437    def background(self, color="k9", alpha=1.0):
4438        """Text background. Set to `None` to disable it."""
4439        bg = get_color(color)
4440        if color is None:
4441            self.property.SetBackgroundOpacity(0)
4442        else:
4443            self.property.SetBackgroundColor(bg)
4444            if alpha:
4445                self.property.SetBackgroundOpacity(alpha)
4446        return self
4447
4448    def frame(self, color="k1", lw=2):
4449        """Border color and width"""
4450        if color is None:
4451            self.property.FrameOff()
4452        else:
4453            c = get_color(color)
4454            self.property.FrameOn()
4455            self.property.SetFrameColor(c)
4456            self.property.SetFrameWidth(lw)
4457        return self
4458
4459    def font(self, font):
4460        """Text font face"""
4461        if isinstance(font, int):
4462            lfonts = list(settings.font_parameters.keys())
4463            n = font % len(lfonts)
4464            font = lfonts[n]
4465            self.fontname = font
4466
4467        if not font:  # use default font
4468            font = self.fontname
4469            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4470        elif font.startswith("https"):  # user passed URL link, make it a path
4471            fpath = vedo.file_io.download(font, verbose=False, force=False)
4472        elif font.endswith(".ttf"):  # user passing a local path to font file
4473            fpath = font
4474        else:  # user passing name of preset font
4475            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
4476
4477        if   font == "Courier": self.property.SetFontFamilyToCourier()
4478        elif font == "Times":   self.property.SetFontFamilyToTimes()
4479        elif font == "Arial":   self.property.SetFontFamilyToArial()
4480        else:
4481            fpath = utils.get_font_path(font)
4482            self.property.SetFontFamily(vtk.VTK_FONT_FILE)
4483            self.property.SetFontFile(fpath)
4484        self.fontname = font  # io.tonumpy() uses it
4485
4486        return self

Base class.

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

Do not instantiate this base class.

def angle(self, a):
4385    def angle(self, a):
4386        """Orientation angle in degrees"""
4387        self.property.SetOrientation(a)
4388        return self

Orientation angle in degrees

def line_spacing(self, ls):
4390    def line_spacing(self, ls):
4391        """Set the extra spacing between lines, expressed as a text height multiplication factor."""
4392        self.property.SetLineSpacing(ls)
4393        return self

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

def line_offset(self, lo):
4395    def line_offset(self, lo):
4396        """Set/Get the vertical offset (measured in pixels)."""
4397        self.property.SetLineOffset(lo)
4398        return self

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

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

Set bold face

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

Set italic face

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

Text shadowing. Set to None to disable it.

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

Set the text color

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

Set the text color

def alpha(self, value):
4432    def alpha(self, value):
4433        """Set the text opacity"""
4434        self.property.SetBackgroundOpacity(value)
4435        return self

Set the text opacity

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

Text background. Set to None to disable it.

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

Border color and width

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

Text font face

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

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) size of the text
  • 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='bottom-left', literal=False):
4151    def text(
4152        self,
4153        txt=None,
4154        s=1,
4155        font="",
4156        hspacing=1.15,
4157        vspacing=2.15,
4158        depth=0,
4159        italic=False,
4160        justify="bottom-left",
4161        literal=False,
4162    ):
4163        """
4164        Update the font style of the text.
4165        Check [available fonts here](https://vedo.embl.es/fonts).
4166        """
4167        if txt is None:
4168            return self.txt
4169
4170        tpoly = self._get_text3d_poly(
4171            txt, s, font, hspacing, vspacing, depth, italic, justify, literal
4172        )
4173        self._update(tpoly)
4174        self.txt = txt
4175        return self

Update the font style of the text. Check available fonts here.

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

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

Set/get the input text string.

def size(self, s):
4662    def size(self, s):
4663        """Set the font size."""
4664        self.property.SetFontSize(int(s * 22.5))
4665        return self

Set the font size.

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

Do not instantiate this base class.

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

Set text at the assigned position

def clear(self):
4748    def clear(self):
4749        """Remove all text from all corners"""
4750        self.ClearAllTexts()
4751        return self

Remove all text from all corners

class Latex(vedo.picture.Picture):
4754class Latex(Picture):
4755    """
4756    Render Latex text and formulas.
4757    """
4758
4759    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0):
4760        """
4761        Render Latex text and formulas.
4762
4763        Arguments:
4764            formula : (str)
4765                latex text string
4766            pos : (list)
4767                position coordinates in space
4768            bg : (color)
4769                background color box
4770            res : (int)
4771                dpi resolution
4772            usetex : (bool)
4773                use latex compiler of matplotlib if available
4774
4775        You can access the latex formula in `Latex.formula`.
4776
4777        Examples:
4778            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4779
4780            ![](https://vedo.embl.es/images/pyplot/latex.png)
4781        """
4782        self.formula = formula
4783
4784        try:
4785            from tempfile import NamedTemporaryFile
4786            import matplotlib.pyplot as mpltib
4787
4788            def build_img_plt(formula, tfile):
4789
4790                mpltib.rc("text", usetex=usetex)
4791
4792                formula1 = "$" + formula + "$"
4793                mpltib.axis("off")
4794                col = get_color(c)
4795                if bg:
4796                    bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4797                else:
4798                    bx = None
4799                mpltib.text(
4800                    0.5,
4801                    0.5,
4802                    formula1,
4803                    size=res,
4804                    color=col,
4805                    alpha=alpha,
4806                    ha="center",
4807                    va="center",
4808                    bbox=bx,
4809                )
4810                mpltib.savefig(
4811                    tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4812                )
4813                mpltib.close()
4814
4815            if len(pos) == 2:
4816                pos = (pos[0], pos[1], 0)
4817
4818            tmp_file = NamedTemporaryFile(delete=True)
4819            tmp_file.name = tmp_file.name + ".png"
4820
4821            build_img_plt(formula, tmp_file.name)
4822
4823            Picture.__init__(self, tmp_file.name, channels=4)
4824            self.alpha(alpha)
4825            self.SetScale(0.25 / res * s, 0.25 / res * s, 0.25 / res * s)
4826            self.SetPosition(pos)
4827            self.name = "Latex"
4828
4829        except:
4830            printc("Error in Latex()\n", formula, c="r")
4831            printc(" latex or dvipng not installed?", c="r")
4832            printc(" Try: usetex=False", c="r")
4833            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)
4759    def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0):
4760        """
4761        Render Latex text and formulas.
4762
4763        Arguments:
4764            formula : (str)
4765                latex text string
4766            pos : (list)
4767                position coordinates in space
4768            bg : (color)
4769                background color box
4770            res : (int)
4771                dpi resolution
4772            usetex : (bool)
4773                use latex compiler of matplotlib if available
4774
4775        You can access the latex formula in `Latex.formula`.
4776
4777        Examples:
4778            - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py)
4779
4780            ![](https://vedo.embl.es/images/pyplot/latex.png)
4781        """
4782        self.formula = formula
4783
4784        try:
4785            from tempfile import NamedTemporaryFile
4786            import matplotlib.pyplot as mpltib
4787
4788            def build_img_plt(formula, tfile):
4789
4790                mpltib.rc("text", usetex=usetex)
4791
4792                formula1 = "$" + formula + "$"
4793                mpltib.axis("off")
4794                col = get_color(c)
4795                if bg:
4796                    bx = dict(boxstyle="square", ec=col, fc=get_color(bg))
4797                else:
4798                    bx = None
4799                mpltib.text(
4800                    0.5,
4801                    0.5,
4802                    formula1,
4803                    size=res,
4804                    color=col,
4805                    alpha=alpha,
4806                    ha="center",
4807                    va="center",
4808                    bbox=bx,
4809                )
4810                mpltib.savefig(
4811                    tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0
4812                )
4813                mpltib.close()
4814
4815            if len(pos) == 2:
4816                pos = (pos[0], pos[1], 0)
4817
4818            tmp_file = NamedTemporaryFile(delete=True)
4819            tmp_file.name = tmp_file.name + ".png"
4820
4821            build_img_plt(formula, tmp_file.name)
4822
4823            Picture.__init__(self, tmp_file.name, channels=4)
4824            self.alpha(alpha)
4825            self.SetScale(0.25 / res * s, 0.25 / res * s, 0.25 / res * s)
4826            self.SetPosition(pos)
4827            self.name = "Latex"
4828
4829        except:
4830            printc("Error in Latex()\n", formula, c="r")
4831            printc(" latex or dvipng not installed?", c="r")
4832            printc(" Try: usetex=False", c="r")
4833            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):
155class Glyph(Mesh):
156    """
157    At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with
158    various orientation options and coloring.
159
160    The input can also be a simple list of 2D or 3D coordinates.
161    Color can be specified as a colormap which maps the size of the orientation
162    vectors in `orientation_array`.
163    """
164
165    def __init__(
166        self,
167        mesh,
168        glyph,
169        orientation_array=None,
170        scale_by_scalar=False,
171        scale_by_vector_size=False,
172        scale_by_vector_components=False,
173        color_by_scalar=False,
174        color_by_vector_size=False,
175        c="k8",
176        alpha=1.0,
177        **opts,
178    ):
179        """
180        Arguments:
181            orientation_array: (list, str, vtkArray)
182                list of vectors, `vtkArray` or name of an already existing pointdata array
183            scale_by_scalar : (bool)
184                glyph mesh is scaled by the active scalars
185            scale_by_vector_size : (bool)
186                glyph mesh is scaled by the size of the vectors
187            scale_by_vector_components : (bool)
188                glyph mesh is scaled by the 3 vectors components
189            color_by_scalar : (bool)
190                glyph mesh is colored based on the scalar value
191            color_by_vector_size : (bool)
192                glyph mesh is colored based on the vector size
193
194        Examples:
195            - [glyphs1.py](]https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py)
196            - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py)
197
198            ![](https://vedo.embl.es/images/basic/glyphs.png)
199        """
200        if len(opts) > 0:  # Deprecations
201            printc(":noentry: Warning! In Glyph() unrecognized keywords:", opts, c="y")
202            orientation_array = opts.pop("orientationArray", orientation_array)
203            scale_by_scalar = opts.pop("scaleByScalar", scale_by_scalar)
204            scale_by_vector_size = opts.pop("scaleByVectorSize", scale_by_vector_size)
205            scale_by_vector_components = opts.pop(
206                "scaleByVectorComponents", scale_by_vector_components
207            )
208            color_by_scalar = opts.pop("colorByScalar", color_by_scalar)
209            color_by_vector_size = opts.pop("colorByVectorSize", color_by_vector_size)
210            printc("          Please use 'snake_case' instead of 'camelCase' keywords", c="y")
211
212        lighting = None
213        if utils.is_sequence(mesh):
214            # create a cloud of points
215            poly = Points(mesh).polydata()
216        elif isinstance(mesh, vtk.vtkPolyData):
217            poly = mesh
218        else:
219            poly = mesh.polydata()
220
221        if isinstance(glyph, Points):
222            lighting = glyph.property.GetLighting()
223            glyph = glyph.polydata()
224
225        cmap = ""
226        if isinstance(c, str) and c in cmaps_names:
227            cmap = c
228            c = None
229        elif utils.is_sequence(c):  # user passing an array of point colors
230            ucols = vtk.vtkUnsignedCharArray()
231            ucols.SetNumberOfComponents(3)
232            ucols.SetName("glyph_RGB")
233            for col in c:
234                cl = get_color(col)
235                ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255)
236            poly.GetPointData().AddArray(ucols)
237            poly.GetPointData().SetActiveScalars("glyph_RGB")
238            c = None
239
240        gly = vtk.vtkGlyph3D()
241        gly.GeneratePointIdsOn()
242        gly.SetInputData(poly)
243        gly.SetSourceData(glyph)
244
245        if scale_by_scalar:
246            gly.SetScaleModeToScaleByScalar()
247        elif scale_by_vector_size:
248            gly.SetScaleModeToScaleByVector()
249        elif scale_by_vector_components:
250            gly.SetScaleModeToScaleByVectorComponents()
251        else:
252            gly.SetScaleModeToDataScalingOff()
253
254        if color_by_vector_size:
255            gly.SetVectorModeToUseVector()
256            gly.SetColorModeToColorByVector()
257        elif color_by_scalar:
258            gly.SetColorModeToColorByScalar()
259        else:
260            gly.SetColorModeToColorByScale()
261
262        if orientation_array is not None:
263            gly.OrientOn()
264            if isinstance(orientation_array, str):
265                if orientation_array.lower() == "normals":
266                    gly.SetVectorModeToUseNormal()
267                else:  # passing a name
268                    poly.GetPointData().SetActiveVectors(orientation_array)
269                    gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array)
270                    gly.SetVectorModeToUseVector()
271            elif utils.is_sequence(orientation_array):  # passing a list
272                varr = vtk.vtkFloatArray()
273                varr.SetNumberOfComponents(3)
274                varr.SetName("glyph_vectors")
275                for v in orientation_array:
276                    varr.InsertNextTuple(v)
277                poly.GetPointData().AddArray(varr)
278                poly.GetPointData().SetActiveVectors("glyph_vectors")
279                gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors")
280                gly.SetVectorModeToUseVector()
281
282        gly.Update()
283
284        Mesh.__init__(self, gly.GetOutput(), c, alpha)
285        self.flat()
286        if lighting is not None:
287            self.property.SetLighting(lighting)
288
289        if cmap:
290            lut = vtk.vtkLookupTable()
291            lut.SetNumberOfTableValues(512)
292            lut.Build()
293            for i in range(512):
294                r, g, b = color_map(i, cmap, 0, 512)
295                lut.SetTableValue(i, r, g, b, 1)
296            self.mapper().SetLookupTable(lut)
297            self.mapper().ScalarVisibilityOn()
298            self.mapper().SetScalarModeToUsePointData()
299            if gly.GetOutput().GetPointData().GetScalars():
300                rng = gly.GetOutput().GetPointData().GetScalars().GetRange()
301                self.mapper().SetScalarRange(rng[0], rng[1])
302
303        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, **opts)
165    def __init__(
166        self,
167        mesh,
168        glyph,
169        orientation_array=None,
170        scale_by_scalar=False,
171        scale_by_vector_size=False,
172        scale_by_vector_components=False,
173        color_by_scalar=False,
174        color_by_vector_size=False,
175        c="k8",
176        alpha=1.0,
177        **opts,
178    ):
179        """
180        Arguments:
181            orientation_array: (list, str, vtkArray)
182                list of vectors, `vtkArray` or name of an already existing pointdata array
183            scale_by_scalar : (bool)
184                glyph mesh is scaled by the active scalars
185            scale_by_vector_size : (bool)
186                glyph mesh is scaled by the size of the vectors
187            scale_by_vector_components : (bool)
188                glyph mesh is scaled by the 3 vectors components
189            color_by_scalar : (bool)
190                glyph mesh is colored based on the scalar value
191            color_by_vector_size : (bool)
192                glyph mesh is colored based on the vector size
193
194        Examples:
195            - [glyphs1.py](]https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py)
196            - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py)
197
198            ![](https://vedo.embl.es/images/basic/glyphs.png)
199        """
200        if len(opts) > 0:  # Deprecations
201            printc(":noentry: Warning! In Glyph() unrecognized keywords:", opts, c="y")
202            orientation_array = opts.pop("orientationArray", orientation_array)
203            scale_by_scalar = opts.pop("scaleByScalar", scale_by_scalar)
204            scale_by_vector_size = opts.pop("scaleByVectorSize", scale_by_vector_size)
205            scale_by_vector_components = opts.pop(
206                "scaleByVectorComponents", scale_by_vector_components
207            )
208            color_by_scalar = opts.pop("colorByScalar", color_by_scalar)
209            color_by_vector_size = opts.pop("colorByVectorSize", color_by_vector_size)
210            printc("          Please use 'snake_case' instead of 'camelCase' keywords", c="y")
211
212        lighting = None
213        if utils.is_sequence(mesh):
214            # create a cloud of points
215            poly = Points(mesh).polydata()
216        elif isinstance(mesh, vtk.vtkPolyData):
217            poly = mesh
218        else:
219            poly = mesh.polydata()
220
221        if isinstance(glyph, Points):
222            lighting = glyph.property.GetLighting()
223            glyph = glyph.polydata()
224
225        cmap = ""
226        if isinstance(c, str) and c in cmaps_names:
227            cmap = c
228            c = None
229        elif utils.is_sequence(c):  # user passing an array of point colors
230            ucols = vtk.vtkUnsignedCharArray()
231            ucols.SetNumberOfComponents(3)
232            ucols.SetName("glyph_RGB")
233            for col in c:
234                cl = get_color(col)
235                ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255)
236            poly.GetPointData().AddArray(ucols)
237            poly.GetPointData().SetActiveScalars("glyph_RGB")
238            c = None
239
240        gly = vtk.vtkGlyph3D()
241        gly.GeneratePointIdsOn()
242        gly.SetInputData(poly)
243        gly.SetSourceData(glyph)
244
245        if scale_by_scalar:
246            gly.SetScaleModeToScaleByScalar()
247        elif scale_by_vector_size:
248            gly.SetScaleModeToScaleByVector()
249        elif scale_by_vector_components:
250            gly.SetScaleModeToScaleByVectorComponents()
251        else:
252            gly.SetScaleModeToDataScalingOff()
253
254        if color_by_vector_size:
255            gly.SetVectorModeToUseVector()
256            gly.SetColorModeToColorByVector()
257        elif color_by_scalar:
258            gly.SetColorModeToColorByScalar()
259        else:
260            gly.SetColorModeToColorByScale()
261
262        if orientation_array is not None:
263            gly.OrientOn()
264            if isinstance(orientation_array, str):
265                if orientation_array.lower() == "normals":
266                    gly.SetVectorModeToUseNormal()
267                else:  # passing a name
268                    poly.GetPointData().SetActiveVectors(orientation_array)
269                    gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array)
270                    gly.SetVectorModeToUseVector()
271            elif utils.is_sequence(orientation_array):  # passing a list
272                varr = vtk.vtkFloatArray()
273                varr.SetNumberOfComponents(3)
274                varr.SetName("glyph_vectors")
275                for v in orientation_array:
276                    varr.InsertNextTuple(v)
277                poly.GetPointData().AddArray(varr)
278                poly.GetPointData().SetActiveVectors("glyph_vectors")
279                gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors")
280                gly.SetVectorModeToUseVector()
281
282        gly.Update()
283
284        Mesh.__init__(self, gly.GetOutput(), c, alpha)
285        self.flat()
286        if lighting is not None:
287            self.property.SetLighting(lighting)
288
289        if cmap:
290            lut = vtk.vtkLookupTable()
291            lut.SetNumberOfTableValues(512)
292            lut.Build()
293            for i in range(512):
294                r, g, b = color_map(i, cmap, 0, 512)
295                lut.SetTableValue(i, r, g, b, 1)
296            self.mapper().SetLookupTable(lut)
297            self.mapper().ScalarVisibilityOn()
298            self.mapper().SetScalarModeToUsePointData()
299            if gly.GetOutput().GetPointData().GetScalars():
300                rng = gly.GetOutput().GetPointData().GetScalars().GetRange()
301                self.mapper().SetScalarRange(rng[0], rng[1])
302
303        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):
306class Tensors(Mesh):
307    """
308    Geometric representation of tensors defined on a domain or set of points.
309    Tensors can be scaled and/or rotated according to the source at eache input point.
310    Scaling and rotation is controlled by the eigenvalues/eigenvectors of the symmetrical part
311    of the tensor as follows:
312
313    For each tensor, the eigenvalues (and associated eigenvectors) are sorted
314    to determine the major, medium, and minor eigenvalues/eigenvectors.
315    The eigenvalue decomposition only makes sense for symmetric tensors,
316    hence the need to only consider the symmetric part of the tensor,
317    which is `1/2*(T+T.transposed())`.
318    """
319
320    def __init__(
321        self,
322        domain,
323        source="ellipsoid",
324        use_eigenvalues=True,
325        is_symmetric=True,
326        three_axes=False,
327        scale=1.0,
328        max_scale=None,
329        length=None,
330        c=None,
331        alpha=1.0,
332    ):
333        """
334        Arguments:
335            source : (str, Mesh)
336                preset type of source shape
337                `['ellipsoid', 'cylinder', 'cube' or any specified `Mesh`]`
338            use_eigenvalues : (bool)
339                color source glyph using the eigenvalues or by scalars
340            three_axes : (bool)
341                if `False` scale the source in the x-direction,
342                the medium in the y-direction, and the minor in the z-direction.
343                Then, the source is rotated so that the glyph's local x-axis lies
344                along the major eigenvector, y-axis along the medium eigenvector,
345                and z-axis along the minor.
346
347                If `True` three sources are produced, each of them oriented along an eigenvector
348                and scaled according to the corresponding eigenvector.
349            is_symmetric : (bool)
350                If `True` each source glyph is mirrored (2 or 6 glyphs will be produced).
351                The x-axis of the source glyph will correspond to the eigenvector on output.
352            length : (float)
353                distance from the origin to the tip of the source glyph along the x-axis
354            scale : (float)
355                scaling factor of the source glyph.
356            max_scale : (float)
357                clamp scaling at this factor.
358
359        Examples:
360            - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py)
361            - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py)
362
363            ![](https://vedo.embl.es/images/volumetric/tensor_grid.png)
364        """
365        if isinstance(source, Points):
366            src = source.normalize().polydata(False)
367        else:
368            if "ellip" in source:
369                src = vtk.vtkSphereSource()
370                src.SetPhiResolution(24)
371                src.SetThetaResolution(12)
372            elif "cyl" in source:
373                src = vtk.vtkCylinderSource()
374                src.SetResolution(48)
375                src.CappingOn()
376            elif source == "cube":
377                src = vtk.vtkCubeSource()
378            src.Update()
379
380        tg = vtk.vtkTensorGlyph()
381        if isinstance(domain, vtk.vtkPolyData):
382            tg.SetInputData(domain)
383        else:
384            tg.SetInputData(domain.GetMapper().GetInput())
385        tg.SetSourceData(src.GetOutput())
386
387        if c is None:
388            tg.ColorGlyphsOn()
389        else:
390            tg.ColorGlyphsOff()
391
392        tg.SetSymmetric(int(is_symmetric))
393
394        if length is not None:
395            tg.SetLength(length)
396        if use_eigenvalues:
397            tg.ExtractEigenvaluesOn()
398            tg.SetColorModeToEigenvalues()
399        else:
400            tg.SetColorModeToScalars()
401        tg.SetThreeGlyphs(three_axes)
402        tg.ScalingOn()
403        tg.SetScaleFactor(scale)
404        if max_scale is None:
405            tg.ClampScalingOn()
406            max_scale = scale * 10
407        tg.SetMaxScaleFactor(max_scale)
408        tg.Update()
409        tgn = vtk.vtkPolyDataNormals()
410        tgn.SetInputData(tg.GetOutput())
411        tgn.Update()
412        Mesh.__init__(self, tgn.GetOutput(), c, alpha)
413        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 eache 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, c=None, alpha=1.0)
320    def __init__(
321        self,
322        domain,
323        source="ellipsoid",
324        use_eigenvalues=True,
325        is_symmetric=True,
326        three_axes=False,
327        scale=1.0,
328        max_scale=None,
329        length=None,
330        c=None,
331        alpha=1.0,
332    ):
333        """
334        Arguments:
335            source : (str, Mesh)
336                preset type of source shape
337                `['ellipsoid', 'cylinder', 'cube' or any specified `Mesh`]`
338            use_eigenvalues : (bool)
339                color source glyph using the eigenvalues or by scalars
340            three_axes : (bool)
341                if `False` scale the source in the x-direction,
342                the medium in the y-direction, and the minor in the z-direction.
343                Then, the source is rotated so that the glyph's local x-axis lies
344                along the major eigenvector, y-axis along the medium eigenvector,
345                and z-axis along the minor.
346
347                If `True` three sources are produced, each of them oriented along an eigenvector
348                and scaled according to the corresponding eigenvector.
349            is_symmetric : (bool)
350                If `True` each source glyph is mirrored (2 or 6 glyphs will be produced).
351                The x-axis of the source glyph will correspond to the eigenvector on output.
352            length : (float)
353                distance from the origin to the tip of the source glyph along the x-axis
354            scale : (float)
355                scaling factor of the source glyph.
356            max_scale : (float)
357                clamp scaling at this factor.
358
359        Examples:
360            - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py)
361            - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py)
362
363            ![](https://vedo.embl.es/images/volumetric/tensor_grid.png)
364        """
365        if isinstance(source, Points):
366            src = source.normalize().polydata(False)
367        else:
368            if "ellip" in source:
369                src = vtk.vtkSphereSource()
370                src.SetPhiResolution(24)
371                src.SetThetaResolution(12)
372            elif "cyl" in source:
373                src = vtk.vtkCylinderSource()
374                src.SetResolution(48)
375                src.CappingOn()
376            elif source == "cube":
377                src = vtk.vtkCubeSource()
378            src.Update()
379
380        tg = vtk.vtkTensorGlyph()
381        if isinstance(domain, vtk.vtkPolyData):
382            tg.SetInputData(domain)
383        else:
384            tg.SetInputData(domain.GetMapper().GetInput())
385        tg.SetSourceData(src.GetOutput())
386
387        if c is None:
388            tg.ColorGlyphsOn()
389        else:
390            tg.ColorGlyphsOff()
391
392        tg.SetSymmetric(int(is_symmetric))
393
394        if length is not None:
395            tg.SetLength(length)
396        if use_eigenvalues:
397            tg.ExtractEigenvaluesOn()
398            tg.SetColorModeToEigenvalues()
399        else:
400            tg.SetColorModeToScalars()
401        tg.SetThreeGlyphs(three_axes)
402        tg.ScalingOn()
403        tg.SetScaleFactor(scale)
404        if max_scale is None:
405            tg.ClampScalingOn()
406            max_scale = scale * 10
407        tg.SetMaxScaleFactor(max_scale)
408        tg.Update()
409        tgn = vtk.vtkPolyDataNormals()
410        tgn.SetInputData(tg.GetOutput())
411        tgn.Update()
412        Mesh.__init__(self, tgn.GetOutput(), c, alpha)
413        self.name = "Tensors"
Arguments:
  • source : (str, Mesh) preset type of source shape ['ellipsoid', 'cylinder', 'cube' or any specifiedMesh]
  • 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):
3863class ParametricShape(Mesh):
3864    """
3865    A set of built-in shapes mainly for illustration purposes.
3866    """
3867
3868    def __init__(self, name, res=51, n=25, seed=1):
3869        """
3870        A set of built-in shapes mainly for illustration purposes.
3871
3872        Name can be an integer or a string in this list:
3873            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3874            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3875            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3876            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3877
3878        Example:
3879            ```python
3880            from vedo import *
3881            settings.immediate_rendering = False
3882            plt = Plotter(N=18)
3883            for i in range(18):
3884                ps = ParametricShape(i).color(i)
3885                plt.at(i).show(ps, ps.name)
3886            plt.interactive().close()
3887            ```
3888            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3889        """
3890
3891        shapes = [
3892            "Boy",
3893            "ConicSpiral",
3894            "CrossCap",
3895            "Enneper",
3896            "Figure8Klein",
3897            "Klein",
3898            "Dini",
3899            "Mobius",
3900            "RandomHills",
3901            "Roman",
3902            "SuperEllipsoid",
3903            "BohemianDome",
3904            "Bour",
3905            "CatalanMinimal",
3906            "Henneberg",
3907            "Kuen",
3908            "PluckerConoid",
3909            "Pseudosphere",
3910        ]
3911
3912        if isinstance(name, int):
3913            name = name % len(shapes)
3914            name = shapes[name]
3915
3916        if name == "Boy":
3917            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBoy()
3918        elif name == "ConicSpiral":
3919            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricConicSpiral()
3920        elif name == "CrossCap":
3921            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCrossCap()
3922        elif name == "Dini":
3923            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricDini()
3924        elif name == "Enneper":
3925            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricEnneper()
3926        elif name == "Figure8Klein":
3927            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricFigure8Klein()
3928        elif name == "Klein":
3929            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKlein()
3930        elif name == "Mobius":
3931            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricMobius()
3932            ps.SetRadius(2.0)
3933            ps.SetMinimumV(-0.5)
3934            ps.SetMaximumV(0.5)
3935        elif name == "RandomHills":
3936            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRandomHills()
3937            ps.AllowRandomGenerationOn()
3938            ps.SetRandomSeed(seed)
3939            ps.SetNumberOfHills(n)
3940        elif name == "Roman":
3941            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRoman()
3942        elif name == "SuperEllipsoid":
3943            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricSuperEllipsoid()
3944            ps.SetN1(0.5)
3945            ps.SetN2(0.4)
3946        elif name == "BohemianDome":
3947            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBohemianDome()
3948            ps.SetA(5.0)
3949            ps.SetB(1.0)
3950            ps.SetC(2.0)
3951        elif name == "Bour":
3952            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBour()
3953        elif name == "CatalanMinimal":
3954            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCatalanMinimal()
3955        elif name == "Henneberg":
3956            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricHenneberg()
3957        elif name == "Kuen":
3958            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKuen()
3959            ps.SetDeltaV0(0.001)
3960        elif name == "PluckerConoid":
3961            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPluckerConoid()
3962        elif name == "Pseudosphere":
3963            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPseudosphere()
3964        else:
3965            vedo.logger.error(f"unknown ParametricShape {name}")
3966            return
3967
3968        pfs = vtk.vtkParametricFunctionSource()
3969        pfs.SetParametricFunction(ps)
3970        pfs.SetUResolution(res)
3971        pfs.SetVResolution(res)
3972        pfs.SetWResolution(res)
3973        pfs.SetScalarModeToZ()
3974        pfs.Update()
3975
3976        Mesh.__init__(self, pfs.GetOutput())
3977
3978        if name != 'Kuen': self.normalize()
3979        if name == 'Dini': self.scale(0.4)
3980        if name == 'Enneper': self.scale(0.4)
3981        if name == 'ConicSpiral': self.bc('tomato')
3982        self.name = name

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

ParametricShape(name, res=51, n=25, seed=1)
3868    def __init__(self, name, res=51, n=25, seed=1):
3869        """
3870        A set of built-in shapes mainly for illustration purposes.
3871
3872        Name can be an integer or a string in this list:
3873            `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper',
3874            'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman',
3875            'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal',
3876            'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`.
3877
3878        Example:
3879            ```python
3880            from vedo import *
3881            settings.immediate_rendering = False
3882            plt = Plotter(N=18)
3883            for i in range(18):
3884                ps = ParametricShape(i).color(i)
3885                plt.at(i).show(ps, ps.name)
3886            plt.interactive().close()
3887            ```
3888            <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700">
3889        """
3890
3891        shapes = [
3892            "Boy",
3893            "ConicSpiral",
3894            "CrossCap",
3895            "Enneper",
3896            "Figure8Klein",
3897            "Klein",
3898            "Dini",
3899            "Mobius",
3900            "RandomHills",
3901            "Roman",
3902            "SuperEllipsoid",
3903            "BohemianDome",
3904            "Bour",
3905            "CatalanMinimal",
3906            "Henneberg",
3907            "Kuen",
3908            "PluckerConoid",
3909            "Pseudosphere",
3910        ]
3911
3912        if isinstance(name, int):
3913            name = name % len(shapes)
3914            name = shapes[name]
3915
3916        if name == "Boy":
3917            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBoy()
3918        elif name == "ConicSpiral":
3919            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricConicSpiral()
3920        elif name == "CrossCap":
3921            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCrossCap()
3922        elif name == "Dini":
3923            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricDini()
3924        elif name == "Enneper":
3925            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricEnneper()
3926        elif name == "Figure8Klein":
3927            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricFigure8Klein()
3928        elif name == "Klein":
3929            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKlein()
3930        elif name == "Mobius":
3931            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricMobius()
3932            ps.SetRadius(2.0)
3933            ps.SetMinimumV(-0.5)
3934            ps.SetMaximumV(0.5)
3935        elif name == "RandomHills":
3936            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRandomHills()
3937            ps.AllowRandomGenerationOn()
3938            ps.SetRandomSeed(seed)
3939            ps.SetNumberOfHills(n)
3940        elif name == "Roman":
3941            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRoman()
3942        elif name == "SuperEllipsoid":
3943            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricSuperEllipsoid()
3944            ps.SetN1(0.5)
3945            ps.SetN2(0.4)
3946        elif name == "BohemianDome":
3947            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBohemianDome()
3948            ps.SetA(5.0)
3949            ps.SetB(1.0)
3950            ps.SetC(2.0)
3951        elif name == "Bour":
3952            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBour()
3953        elif name == "CatalanMinimal":
3954            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCatalanMinimal()
3955        elif name == "Henneberg":
3956            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricHenneberg()
3957        elif name == "Kuen":
3958            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKuen()
3959            ps.SetDeltaV0(0.001)
3960        elif name == "PluckerConoid":
3961            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPluckerConoid()
3962        elif name == "Pseudosphere":
3963            ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPseudosphere()
3964        else:
3965            vedo.logger.error(f"unknown ParametricShape {name}")
3966            return
3967
3968        pfs = vtk.vtkParametricFunctionSource()
3969        pfs.SetParametricFunction(ps)
3970        pfs.SetUResolution(res)
3971        pfs.SetVResolution(res)
3972        pfs.SetWResolution(res)
3973        pfs.SetScalarModeToZ()
3974        pfs.Update()
3975
3976        Mesh.__init__(self, pfs.GetOutput())
3977
3978        if name != 'Kuen': self.normalize()
3979        if name == 'Dini': self.scale(0.4)
3980        if name == 'Enneper': self.scale(0.4)
3981        if name == 'ConicSpiral': self.bc('tomato')
3982        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):
4836class ConvexHull(Mesh):
4837    """
4838    Create the 2D/3D convex hull from a set of points.
4839    """
4840
4841    def __init__(self, pts):
4842        """
4843        Create the 2D/3D convex hull from a set of input points or input Mesh.
4844
4845        Examples:
4846            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4847
4848                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4849        """
4850        if utils.is_sequence(pts):
4851            pts = utils.make3d(pts).astype(float)
4852            mesh = Points(pts)
4853        else:
4854            mesh = pts
4855        apoly = mesh.clean().polydata()
4856
4857        # Create the convex hull of the pointcloud
4858        z0, z1 = mesh.zbounds()
4859        d = mesh.diagonal_size()
4860        if (z1 - z0) / d > 0.0001:
4861            delaunay = vtk.vtkDelaunay3D()
4862            delaunay.SetInputData(apoly)
4863            delaunay.Update()
4864            surfaceFilter = vtk.vtkDataSetSurfaceFilter()
4865            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4866            surfaceFilter.Update()
4867            out = surfaceFilter.GetOutput()
4868        else:
4869            delaunay = vtk.vtkDelaunay2D()
4870            delaunay.SetInputData(apoly)
4871            delaunay.Update()
4872            fe = vtk.vtkFeatureEdges()
4873            fe.SetInputConnection(delaunay.GetOutputPort())
4874            fe.BoundaryEdgesOn()
4875            fe.Update()
4876            out = fe.GetOutput()
4877
4878        Mesh.__init__(self, out, c=mesh.color(), alpha=0.75)
4879        # self.triangulate()
4880        self.flat()
4881        self.name = "ConvexHull"

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

ConvexHull(pts)
4841    def __init__(self, pts):
4842        """
4843        Create the 2D/3D convex hull from a set of input points or input Mesh.
4844
4845        Examples:
4846            - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py)
4847
4848                ![](https://vedo.embl.es/images/advanced/convexHull.png)
4849        """
4850        if utils.is_sequence(pts):
4851            pts = utils.make3d(pts).astype(float)
4852            mesh = Points(pts)
4853        else:
4854            mesh = pts
4855        apoly = mesh.clean().polydata()
4856
4857        # Create the convex hull of the pointcloud
4858        z0, z1 = mesh.zbounds()
4859        d = mesh.diagonal_size()
4860        if (z1 - z0) / d > 0.0001:
4861            delaunay = vtk.vtkDelaunay3D()
4862            delaunay.SetInputData(apoly)
4863            delaunay.Update()
4864            surfaceFilter = vtk.vtkDataSetSurfaceFilter()
4865            surfaceFilter.SetInputConnection(delaunay.GetOutputPort())
4866            surfaceFilter.Update()
4867            out = surfaceFilter.GetOutput()
4868        else:
4869            delaunay = vtk.vtkDelaunay2D()
4870            delaunay.SetInputData(apoly)
4871            delaunay.Update()
4872            fe = vtk.vtkFeatureEdges()
4873            fe.SetInputConnection(delaunay.GetOutputPort())
4874            fe.BoundaryEdgesOn()
4875            fe.Update()
4876            out = fe.GetOutput()
4877
4878        Mesh.__init__(self, out, c=mesh.color(), alpha=0.75)
4879        # self.triangulate()
4880        self.flat()
4881        self.name = "ConvexHull"

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

Examples: