vedo.shapes
Submodule to generate simple and complex geometric shapes
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import os 4from typing import List, Union, Any 5from functools import lru_cache 6from weakref import ref as weak_ref_to 7 8import numpy as np 9import vedo.vtkclasses as vtki 10 11import vedo 12from vedo import settings 13from vedo.transformations import LinearTransform, pol2cart, cart2spher, spher2cart 14from vedo.colors import cmaps_names, get_color, printc 15from vedo import utils 16from vedo.pointcloud import Points, merge 17from vedo.mesh import Mesh 18from vedo.image import Image 19 20__docformat__ = "google" 21 22__doc__ = """ 23Submodule to generate simple and complex geometric shapes 24 25![](https://vedo.embl.es/images/basic/extrude.png) 26""" 27 28__all__ = [ 29 "Marker", 30 "Line", 31 "DashedLine", 32 "RoundedLine", 33 "Tube", 34 "Tubes", 35 "ThickTube", 36 "Lines", 37 "Spline", 38 "KSpline", 39 "CSpline", 40 "Bezier", 41 "Brace", 42 "NormalLines", 43 "Ribbon", 44 "Arrow", 45 "Arrows", 46 "Arrow2D", 47 "Arrows2D", 48 "FlatArrow", 49 "Polygon", 50 "Triangle", 51 "Rectangle", 52 "Disc", 53 "Circle", 54 "GeoCircle", 55 "Arc", 56 "Star", 57 "Star3D", 58 "Cross3D", 59 "IcoSphere", 60 "Sphere", 61 "Spheres", 62 "Earth", 63 "Ellipsoid", 64 "Grid", 65 "TessellatedBox", 66 "Plane", 67 "Box", 68 "Cube", 69 "Spring", 70 "Cylinder", 71 "Cone", 72 "Pyramid", 73 "Torus", 74 "Paraboloid", 75 "Hyperboloid", 76 "TextBase", 77 "Text3D", 78 "Text2D", 79 "CornerAnnotation", 80 "Latex", 81 "Glyph", 82 "Tensors", 83 "ParametricShape", 84 "ConvexHull", 85 "VedoLogo", 86] 87 88############################################## 89_reps = ( 90 (":nabla", "∇"), 91 (":inf", "∞"), 92 (":rightarrow", "→"), 93 (":leftarrow", "←"), 94 (":partial", "∂"), 95 (":sqrt", "√"), 96 (":approx", "≈"), 97 (":neq", "≠"), 98 (":leq", "≤"), 99 (":geq", "≥"), 100 (":foreach", "∀"), 101 (":permille", "‰"), 102 (":euro", "€"), 103 (":dot", "·"), 104 (":int", "∫"), 105 (":pm", "±"), 106 (":times", "×"), 107 (":Gamma", "Γ"), 108 (":Delta", "Δ"), 109 (":Theta", "Θ"), 110 (":Lambda", "Λ"), 111 (":Pi", "Π"), 112 (":Sigma", "Σ"), 113 (":Phi", "Φ"), 114 (":Chi", "X"), 115 (":Xi", "Ξ"), 116 (":Psi", "Ψ"), 117 (":Omega", "Ω"), 118 (":alpha", "α"), 119 (":beta", "β"), 120 (":gamma", "γ"), 121 (":delta", "δ"), 122 (":epsilon", "ε"), 123 (":zeta", "ζ"), 124 (":eta", "η"), 125 (":theta", "θ"), 126 (":kappa", "κ"), 127 (":lambda", "λ"), 128 (":mu", "μ"), 129 (":lowerxi", "ξ"), 130 (":nu", "ν"), 131 (":pi", "π"), 132 (":rho", "ρ"), 133 (":sigma", "σ"), 134 (":tau", "τ"), 135 (":varphi", "φ"), 136 (":phi", "φ"), 137 (":chi", "χ"), 138 (":psi", "ψ"), 139 (":omega", "ω"), 140 (":circ", "°"), 141 (":onehalf", "½"), 142 (":onefourth", "¼"), 143 (":threefourths", "¾"), 144 (":^1", "¹"), 145 (":^2", "²"), 146 (":^3", "³"), 147 (":,", "~"), 148) 149 150 151######################################################################## 152class Glyph(Mesh): 153 """ 154 At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with 155 various orientation options and coloring. 156 157 The input can also be a simple list of 2D or 3D coordinates. 158 Color can be specified as a colormap which maps the size of the orientation 159 vectors in `orientation_array`. 160 """ 161 162 def __init__( 163 self, 164 mesh, 165 glyph, 166 orientation_array=None, 167 scale_by_scalar=False, 168 scale_by_vector_size=False, 169 scale_by_vector_components=False, 170 color_by_scalar=False, 171 color_by_vector_size=False, 172 c="k8", 173 alpha=1.0, 174 ) -> None: 175 """ 176 Arguments: 177 orientation_array: (list, str, vtkArray) 178 list of vectors, `vtkArray` or name of an already existing pointdata array 179 scale_by_scalar : (bool) 180 glyph mesh is scaled by the active scalars 181 scale_by_vector_size : (bool) 182 glyph mesh is scaled by the size of the vectors 183 scale_by_vector_components : (bool) 184 glyph mesh is scaled by the 3 vectors components 185 color_by_scalar : (bool) 186 glyph mesh is colored based on the scalar value 187 color_by_vector_size : (bool) 188 glyph mesh is colored based on the vector size 189 190 Examples: 191 - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 192 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 193 194 ![](https://vedo.embl.es/images/basic/glyphs.png) 195 """ 196 if utils.is_sequence(mesh): 197 # create a cloud of points 198 poly = utils.buildPolyData(mesh) 199 else: 200 poly = mesh.dataset 201 202 cmap = "" 203 if isinstance(c, str) and c in cmaps_names: 204 cmap = c 205 c = None 206 elif utils.is_sequence(c): # user passing an array of point colors 207 ucols = vtki.vtkUnsignedCharArray() 208 ucols.SetNumberOfComponents(3) 209 ucols.SetName("GlyphRGB") 210 for col in c: 211 cl = get_color(col) 212 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 213 poly.GetPointData().AddArray(ucols) 214 poly.GetPointData().SetActiveScalars("GlyphRGB") 215 c = None 216 217 gly = vtki.vtkGlyph3D() 218 gly.GeneratePointIdsOn() 219 gly.SetInputData(poly) 220 try: 221 gly.SetSourceData(glyph) 222 except TypeError: 223 gly.SetSourceData(glyph.dataset) 224 225 if scale_by_scalar: 226 gly.SetScaleModeToScaleByScalar() 227 elif scale_by_vector_size: 228 gly.SetScaleModeToScaleByVector() 229 elif scale_by_vector_components: 230 gly.SetScaleModeToScaleByVectorComponents() 231 else: 232 gly.SetScaleModeToDataScalingOff() 233 234 if color_by_vector_size: 235 gly.SetVectorModeToUseVector() 236 gly.SetColorModeToColorByVector() 237 elif color_by_scalar: 238 gly.SetColorModeToColorByScalar() 239 else: 240 gly.SetColorModeToColorByScale() 241 242 if orientation_array is not None: 243 gly.OrientOn() 244 if isinstance(orientation_array, str): 245 if orientation_array.lower() == "normals": 246 gly.SetVectorModeToUseNormal() 247 else: # passing a name 248 poly.GetPointData().SetActiveVectors(orientation_array) 249 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 250 gly.SetVectorModeToUseVector() 251 elif utils.is_sequence(orientation_array): # passing a list 252 varr = vtki.vtkFloatArray() 253 varr.SetNumberOfComponents(3) 254 varr.SetName("glyph_vectors") 255 for v in orientation_array: 256 varr.InsertNextTuple(v) 257 poly.GetPointData().AddArray(varr) 258 poly.GetPointData().SetActiveVectors("glyph_vectors") 259 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 260 gly.SetVectorModeToUseVector() 261 262 gly.Update() 263 264 super().__init__(gly.GetOutput(), c, alpha) 265 self.flat() 266 267 if cmap: 268 self.cmap(cmap, "VectorMagnitude") 269 elif c is None: 270 self.pointdata.select("GlyphRGB") 271 272 self.name = "Glyph" 273 274 275class Tensors(Mesh): 276 """ 277 Geometric representation of tensors defined on a domain or set of points. 278 Tensors can be scaled and/or rotated according to the source at each input point. 279 Scaling and rotation is controlled by the eigenvalues/eigenvectors of the 280 symmetrical part of the tensor as follows: 281 282 For each tensor, the eigenvalues (and associated eigenvectors) are sorted 283 to determine the major, medium, and minor eigenvalues/eigenvectors. 284 The eigenvalue decomposition only makes sense for symmetric tensors, 285 hence the need to only consider the symmetric part of the tensor, 286 which is `1/2*(T+T.transposed())`. 287 """ 288 289 def __init__( 290 self, 291 domain, 292 source="ellipsoid", 293 use_eigenvalues=True, 294 is_symmetric=True, 295 three_axes=False, 296 scale=1.0, 297 max_scale=None, 298 length=None, 299 res=24, 300 c=None, 301 alpha=1.0, 302 ) -> None: 303 """ 304 Arguments: 305 source : (str, Mesh) 306 preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object. 307 use_eigenvalues : (bool) 308 color source glyph using the eigenvalues or by scalars 309 three_axes : (bool) 310 if `False` scale the source in the x-direction, 311 the medium in the y-direction, and the minor in the z-direction. 312 Then, the source is rotated so that the glyph's local x-axis lies 313 along the major eigenvector, y-axis along the medium eigenvector, 314 and z-axis along the minor. 315 316 If `True` three sources are produced, each of them oriented along an eigenvector 317 and scaled according to the corresponding eigenvector. 318 is_symmetric : (bool) 319 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 320 The x-axis of the source glyph will correspond to the eigenvector on output. 321 length : (float) 322 distance from the origin to the tip of the source glyph along the x-axis 323 scale : (float) 324 scaling factor of the source glyph. 325 max_scale : (float) 326 clamp scaling at this factor. 327 328 Examples: 329 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 330 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 331 - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py) 332 333 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 334 """ 335 if isinstance(source, Points): 336 src = source.dataset 337 else: # is string 338 if "ellip" in source: 339 src = vtki.new("SphereSource") 340 src.SetPhiResolution(res) 341 src.SetThetaResolution(res*2) 342 elif "cyl" in source: 343 src = vtki.new("CylinderSource") 344 src.SetResolution(res) 345 src.CappingOn() 346 elif source == "cube": 347 src = vtki.new("CubeSource") 348 else: 349 vedo.logger.error(f"Unknown source type {source}") 350 raise ValueError() 351 src.Update() 352 src = src.GetOutput() 353 354 tg = vtki.new("TensorGlyph") 355 if isinstance(domain, vtki.vtkPolyData): 356 tg.SetInputData(domain) 357 else: 358 tg.SetInputData(domain.dataset) 359 tg.SetSourceData(src) 360 361 if c is None: 362 tg.ColorGlyphsOn() 363 else: 364 tg.ColorGlyphsOff() 365 366 tg.SetSymmetric(int(is_symmetric)) 367 368 if length is not None: 369 tg.SetLength(length) 370 if use_eigenvalues: 371 tg.ExtractEigenvaluesOn() 372 tg.SetColorModeToEigenvalues() 373 else: 374 tg.SetColorModeToScalars() 375 376 tg.SetThreeGlyphs(three_axes) 377 tg.ScalingOn() 378 tg.SetScaleFactor(scale) 379 if max_scale is None: 380 tg.ClampScalingOn() 381 max_scale = scale * 10 382 tg.SetMaxScaleFactor(max_scale) 383 384 tg.Update() 385 tgn = vtki.new("PolyDataNormals") 386 tgn.ComputeCellNormalsOff() 387 tgn.SetInputData(tg.GetOutput()) 388 tgn.Update() 389 390 super().__init__(tgn.GetOutput(), c, alpha) 391 self.name = "Tensors" 392 393 394class Line(Mesh): 395 """ 396 Build the line segment between point `p0` and point `p1`. 397 398 If `p0` is already a list of points, return the line connecting them. 399 400 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 401 """ 402 403 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None: 404 """ 405 Arguments: 406 closed : (bool) 407 join last to first point 408 res : (int) 409 resolution, number of points along the line 410 (only relevant if only 2 points are specified) 411 lw : (int) 412 line width in pixel units 413 """ 414 415 if isinstance(p1, Points): 416 p1 = p1.pos() 417 if isinstance(p0, Points): 418 p0 = p0.pos() 419 try: 420 p0 = p0.dataset 421 except AttributeError: 422 pass 423 424 if isinstance(p0, vtki.vtkPolyData): 425 poly = p0 426 top = np.array([0,0,1]) 427 base = np.array([0,0,0]) 428 429 elif utils.is_sequence(p0[0]): # detect if user is passing a list of points 430 431 p0 = utils.make3d(p0) 432 ppoints = vtki.vtkPoints() # Generate the polyline 433 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 434 lines = vtki.vtkCellArray() 435 npt = len(p0) 436 if closed: 437 lines.InsertNextCell(npt + 1) 438 else: 439 lines.InsertNextCell(npt) 440 for i in range(npt): 441 lines.InsertCellPoint(i) 442 if closed: 443 lines.InsertCellPoint(0) 444 poly = vtki.vtkPolyData() 445 poly.SetPoints(ppoints) 446 poly.SetLines(lines) 447 top = p0[-1] 448 base = p0[0] 449 if res != 2: 450 printc(f"Warning: calling Line(res={res}), try remove []?", c='y') 451 res = 2 452 453 else: # or just 2 points to link 454 455 line_source = vtki.new("LineSource") 456 p0 = utils.make3d(p0) 457 p1 = utils.make3d(p1) 458 line_source.SetPoint1(p0) 459 line_source.SetPoint2(p1) 460 line_source.SetResolution(res - 1) 461 line_source.Update() 462 poly = line_source.GetOutput() 463 top = np.asarray(p1, dtype=float) 464 base = np.asarray(p0, dtype=float) 465 466 super().__init__(poly, c, alpha) 467 468 self.slope: List[float] = [] # populated by analysis.fit_line 469 self.center: List[float] = [] 470 self.variances: List[float] = [] 471 472 self.coefficients: List[float] = [] # populated by pyplot.fit() 473 self.covariance_matrix: List[float] = [] 474 self.coefficient_errors: List[float] = [] 475 self.monte_carlo_coefficients: List[float] = [] 476 self.reduced_chi2 = -1 477 self.ndof = 0 478 self.data_sigma = 0 479 self.error_lines: List[Any] = [] 480 self.error_band = None 481 self.res = res 482 483 self.lw(lw) 484 self.properties.LightingOff() 485 self.actor.PickableOff() 486 self.actor.DragableOff() 487 self.base = base 488 self.top = top 489 self.name = "Line" 490 491 def clone(self, deep=True) -> "Line": 492 """ 493 Return a copy of the ``Line`` object. 494 495 Example: 496 ```python 497 from vedo import * 498 ln1 = Line([1,1,1], [2,2,2], lw=3).print() 499 ln2 = ln1.clone().shift(0,0,1).c('red').print() 500 show(ln1, ln2, axes=1, viewup='z').close() 501 ``` 502 ![](https://vedo.embl.es/images/feats/line_clone.png) 503 """ 504 poly = vtki.vtkPolyData() 505 if deep: 506 poly.DeepCopy(self.dataset) 507 else: 508 poly.ShallowCopy(self.dataset) 509 ln = Line(poly) 510 ln.copy_properties_from(self) 511 ln.transform = self.transform.clone() 512 ln.name = self.name 513 ln.base = self.base 514 ln.top = self.top 515 ln.pipeline = utils.OperationNode( 516 "clone", parents=[self], shape="diamond", c="#edede9") 517 return ln 518 519 def linecolor(self, lc=None) -> "Line": 520 """Assign a color to the line""" 521 # overrides mesh.linecolor which would have no effect here 522 return self.color(lc) 523 524 def eval(self, x: float) -> np.ndarray: 525 """ 526 Calculate the position of an intermediate point 527 as a fraction of the length of the line, 528 being x=0 the first point and x=1 the last point. 529 This corresponds to an imaginary point that travels along the line 530 at constant speed. 531 532 Can be used in conjunction with `lin_interpolate()` 533 to map any range to the [0,1] range. 534 """ 535 distance1 = 0.0 536 length = self.length() 537 pts = self.vertices 538 for i in range(1, len(pts)): 539 p0 = pts[i - 1] 540 p1 = pts[i] 541 seg = p1 - p0 542 distance0 = distance1 543 distance1 += np.linalg.norm(seg) 544 w1 = distance1 / length 545 if w1 >= x: 546 break 547 w0 = distance0 / length 548 v = p0 + seg * (x - w0) / (w1 - w0) 549 return v 550 551 def find_index_at_position(self, p) -> float: 552 """ 553 Find the index of the line vertex that is closest to the point `p`. 554 Note that the returned index can be fractional if `p` is not exactly 555 one of the vertices of the line. 556 """ 557 q = self.closest_point(p) 558 a, b = sorted(self.closest_point(q, n=2, return_point_id=True)) 559 pts = self.vertices 560 d = np.linalg.norm(pts[a] - pts[b]) 561 t = a + np.linalg.norm(pts[a] - q) / d 562 return t 563 564 def pattern(self, stipple, repeats=10) -> "Line": 565 """ 566 Define a stipple pattern for dashing the line. 567 Pass the stipple pattern as a string like `'- - -'`. 568 Repeats controls the number of times the pattern repeats in a single segment. 569 570 Examples are: `'- -', '-- - --'`, etc. 571 572 The resolution of the line (nr of points) can affect how pattern will show up. 573 574 Example: 575 ```python 576 from vedo import Line 577 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 578 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 579 ln.show(axes=1).close() 580 ``` 581 ![](https://vedo.embl.es/images/feats/line_pattern.png) 582 """ 583 stipple = str(stipple) * int(2 * repeats) 584 dimension = len(stipple) 585 586 image = vtki.vtkImageData() 587 image.SetDimensions(dimension, 1, 1) 588 image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4) 589 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 590 i_dim = 0 591 while i_dim < dimension: 592 for i in range(dimension): 593 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 594 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 595 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 596 if stipple[i] == " ": 597 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 598 else: 599 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 600 i_dim += 1 601 602 poly = self.dataset 603 604 # Create texture coordinates 605 tcoords = vtki.vtkDoubleArray() 606 tcoords.SetName("TCoordsStippledLine") 607 tcoords.SetNumberOfComponents(1) 608 tcoords.SetNumberOfTuples(poly.GetNumberOfPoints()) 609 for i in range(poly.GetNumberOfPoints()): 610 tcoords.SetTypedTuple(i, [i / 2]) 611 poly.GetPointData().SetTCoords(tcoords) 612 poly.GetPointData().Modified() 613 texture = vtki.vtkTexture() 614 texture.SetInputData(image) 615 texture.InterpolateOff() 616 texture.RepeatOn() 617 self.actor.SetTexture(texture) 618 return self 619 620 def length(self) -> float: 621 """Calculate length of the line.""" 622 distance = 0.0 623 pts = self.vertices 624 for i in range(1, len(pts)): 625 distance += np.linalg.norm(pts[i] - pts[i - 1]) 626 return distance 627 628 def tangents(self) -> np.ndarray: 629 """ 630 Compute the tangents of a line in space. 631 632 Example: 633 ```python 634 from vedo import * 635 shape = Assembly(dataurl+"timecourse1d.npy")[58] 636 pts = shape.rotate_x(30).vertices 637 tangents = Line(pts).tangents() 638 arrs = Arrows(pts, pts+tangents, c='blue9') 639 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 640 ``` 641 ![](https://vedo.embl.es/images/feats/line_tangents.png) 642 """ 643 v = np.gradient(self.vertices)[0] 644 ds_dt = np.linalg.norm(v, axis=1) 645 tangent = np.array([1 / ds_dt] * 3).transpose() * v 646 return tangent 647 648 def curvature(self) -> np.ndarray: 649 """ 650 Compute the signed curvature of a line in space. 651 The signed is computed assuming the line is about coplanar to the xy plane. 652 653 Example: 654 ```python 655 from vedo import * 656 from vedo.pyplot import plot 657 shape = Assembly(dataurl+"timecourse1d.npy")[55] 658 curvs = Line(shape.vertices).curvature() 659 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 660 shape.render_lines_as_tubes().lw(12) 661 pp = plot(curvs, ac='white', lc='yellow5') 662 show(shape, pp, N=2, bg='bb', sharecam=False).close() 663 ``` 664 ![](https://vedo.embl.es/images/feats/line_curvature.png) 665 """ 666 v = np.gradient(self.vertices)[0] 667 a = np.gradient(v)[0] 668 av = np.cross(a, v) 669 mav = np.linalg.norm(av, axis=1) 670 mv = utils.mag2(v) 671 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 672 val[0] = val[1] 673 val[-1] = val[-2] 674 return val 675 676 def compute_curvature(self, method=0) -> "Line": 677 """ 678 Add a pointdata array named 'Curvatures' which contains 679 the curvature value at each point. 680 681 NB: keyword `method` is overridden in Mesh and has no effect here. 682 """ 683 # overrides mesh.compute_curvature 684 curvs = self.curvature() 685 vmin, vmax = np.min(curvs), np.max(curvs) 686 if vmin < 0 and vmax > 0: 687 v = max(-vmin, vmax) 688 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 689 else: 690 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 691 return self 692 693 def plot_scalar( 694 self, 695 radius=0.0, 696 height=1.1, 697 normal=(), 698 camera=None, 699 ) -> "Line": 700 """ 701 Generate a new `Line` which plots the active scalar along the line. 702 703 Arguments: 704 radius : (float) 705 distance radius to the line 706 height: (float) 707 height of the plot 708 normal: (list) 709 normal vector to the plane of the plot 710 camera: (vtkCamera) 711 camera object to use for the plot orientation 712 713 Example: 714 ```python 715 from vedo import * 716 circle = Circle(res=360).rotate_y(20) 717 pts = circle.vertices 718 bore = Line(pts).lw(5) 719 values = np.arctan2(pts[:,1], pts[:,0]) 720 bore.pointdata["scalars"] = values + np.random.randn(360)/5 721 vap = bore.plot_scalar(radius=0, height=1) 722 show(bore, vap, axes=1, viewup='z').close() 723 ``` 724 ![](https://vedo.embl.es/images/feats/line_plot_scalar.png) 725 """ 726 ap = vtki.new("ArcPlotter") 727 ap.SetInputData(self.dataset) 728 ap.SetCamera(camera) 729 ap.SetRadius(radius) 730 ap.SetHeight(height) 731 if len(normal)>0: 732 ap.UseDefaultNormalOn() 733 ap.SetDefaultNormal(normal) 734 ap.Update() 735 vap = Line(ap.GetOutput()) 736 vap.linewidth(3).lighting('off') 737 vap.name = "ArcPlot" 738 return vap 739 740 def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh": 741 """ 742 Sweep the `Line` along the specified vector direction. 743 744 Returns a `Mesh` surface. 745 Line position is updated to allow for additional sweepings. 746 747 Example: 748 ```python 749 from vedo import Line, show 750 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 751 surf1 = aline.sweep((1,0.2,0), res=3) 752 surf2 = aline.sweep((0.2,0,1)).alpha(0.5) 753 aline.color('r').linewidth(4) 754 show(surf1, surf2, aline, axes=1).close() 755 ``` 756 ![](https://vedo.embl.es/images/feats/sweepline.png) 757 """ 758 line = self.dataset 759 rows = line.GetNumberOfPoints() 760 761 spacing = 1 / res 762 surface = vtki.vtkPolyData() 763 764 res += 1 765 npts = rows * res 766 npolys = (rows - 1) * (res - 1) 767 points = vtki.vtkPoints() 768 points.Allocate(npts) 769 770 cnt = 0 771 x = [0.0, 0.0, 0.0] 772 for row in range(rows): 773 for col in range(res): 774 p = [0.0, 0.0, 0.0] 775 line.GetPoint(row, p) 776 x[0] = p[0] + direction[0] * col * spacing 777 x[1] = p[1] + direction[1] * col * spacing 778 x[2] = p[2] + direction[2] * col * spacing 779 points.InsertPoint(cnt, x) 780 cnt += 1 781 782 # Generate the quads 783 polys = vtki.vtkCellArray() 784 polys.Allocate(npolys * 4) 785 pts = [0, 0, 0, 0] 786 for row in range(rows - 1): 787 for col in range(res - 1): 788 pts[0] = col + row * res 789 pts[1] = pts[0] + 1 790 pts[2] = pts[0] + res + 1 791 pts[3] = pts[0] + res 792 polys.InsertNextCell(4, pts) 793 surface.SetPoints(points) 794 surface.SetPolys(polys) 795 asurface = Mesh(surface) 796 asurface.copy_properties_from(self) 797 asurface.lighting("default") 798 self.vertices = self.vertices + direction 799 return asurface 800 801 def reverse(self): 802 """Reverse the points sequence order.""" 803 pts = np.flip(self.vertices, axis=0) 804 self.vertices = pts 805 return self 806 807 808class DashedLine(Mesh): 809 """ 810 Consider using `Line.pattern()` instead. 811 812 Build a dashed line segment between points `p0` and `p1`. 813 If `p0` is a list of points returns the line connecting them. 814 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 815 """ 816 817 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None: 818 """ 819 Arguments: 820 closed : (bool) 821 join last to first point 822 spacing : (float) 823 relative size of the dash 824 lw : (int) 825 line width in pixels 826 """ 827 if isinstance(p1, vtki.vtkActor): 828 p1 = p1.GetPosition() 829 if isinstance(p0, vtki.vtkActor): 830 p0 = p0.GetPosition() 831 if isinstance(p0, Points): 832 p0 = p0.vertices 833 834 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 835 if len(p0) > 3: 836 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 837 # assume input is 2D xlist, ylist 838 p0 = np.stack((p0, p1), axis=1) 839 p1 = None 840 p0 = utils.make3d(p0) 841 if closed: 842 p0 = np.append(p0, [p0[0]], axis=0) 843 844 if p1 is not None: # assume passing p0=[x,y] 845 if len(p0) == 2 and not utils.is_sequence(p0[0]): 846 p0 = (p0[0], p0[1], 0) 847 if len(p1) == 2 and not utils.is_sequence(p1[0]): 848 p1 = (p1[0], p1[1], 0) 849 850 # detect if user is passing a list of points: 851 if utils.is_sequence(p0[0]): 852 listp = p0 853 else: # or just 2 points to link 854 listp = [p0, p1] 855 856 listp = np.array(listp) 857 if listp.shape[1] == 2: 858 listp = np.c_[listp, np.zeros(listp.shape[0])] 859 860 xmn = np.min(listp, axis=0) 861 xmx = np.max(listp, axis=0) 862 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 863 if not dlen: 864 super().__init__(vtki.vtkPolyData(), c, alpha) 865 self.name = "DashedLine (void)" 866 return 867 868 qs = [] 869 for ipt in range(len(listp) - 1): 870 p0 = listp[ipt] 871 p1 = listp[ipt + 1] 872 v = p1 - p0 873 vdist = np.linalg.norm(v) 874 n1 = int(vdist / dlen) 875 if not n1: 876 continue 877 878 res = 0.0 879 for i in range(n1 + 2): 880 ist = (i - 0.5) / n1 881 ist = max(ist, 0) 882 qi = p0 + v * (ist - res / vdist) 883 if ist > 1: 884 qi = p1 885 res = np.linalg.norm(qi - p1) 886 qs.append(qi) 887 break 888 qs.append(qi) 889 890 polylns = vtki.new("AppendPolyData") 891 for i, q1 in enumerate(qs): 892 if not i % 2: 893 continue 894 q0 = qs[i - 1] 895 line_source = vtki.new("LineSource") 896 line_source.SetPoint1(q0) 897 line_source.SetPoint2(q1) 898 line_source.Update() 899 polylns.AddInputData(line_source.GetOutput()) 900 polylns.Update() 901 902 super().__init__(polylns.GetOutput(), c, alpha) 903 self.lw(lw).lighting("off") 904 self.base = listp[0] 905 if closed: 906 self.top = listp[-2] 907 else: 908 self.top = listp[-1] 909 self.name = "DashedLine" 910 911 912class RoundedLine(Mesh): 913 """ 914 Create a 2D line of specified thickness (in absolute units) passing through 915 a list of input points. Borders of the line are rounded. 916 """ 917 918 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None: 919 """ 920 Arguments: 921 pts : (list) 922 a list of points in 2D or 3D (z will be ignored). 923 lw : (float) 924 thickness of the line. 925 res : (int) 926 resolution of the rounded regions 927 928 Example: 929 ```python 930 from vedo import * 931 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 932 ln = Line(pts).z(0.01) 933 ln.color("red5").linewidth(2) 934 rl = RoundedLine(pts, 0.6) 935 show(Points(pts), ln, rl, axes=1).close() 936 ``` 937 ![](https://vedo.embl.es/images/feats/rounded_line.png) 938 """ 939 pts = utils.make3d(pts) 940 941 def _getpts(pts, revd=False): 942 943 if revd: 944 pts = list(reversed(pts)) 945 946 if len(pts) == 2: 947 p0, p1 = pts 948 v = p1 - p0 949 dv = np.linalg.norm(v) 950 nv = np.cross(v, (0, 0, -1)) 951 nv = nv / np.linalg.norm(nv) * lw 952 return [p0 + nv, p1 + nv] 953 954 ptsnew = [] 955 for k in range(len(pts) - 2): 956 p0 = pts[k] 957 p1 = pts[k + 1] 958 p2 = pts[k + 2] 959 v = p1 - p0 960 u = p2 - p1 961 du = np.linalg.norm(u) 962 dv = np.linalg.norm(v) 963 nv = np.cross(v, (0, 0, -1)) 964 nv = nv / np.linalg.norm(nv) * lw 965 nu = np.cross(u, (0, 0, -1)) 966 nu = nu / np.linalg.norm(nu) * lw 967 uv = np.cross(u, v) 968 if k == 0: 969 ptsnew.append(p0 + nv) 970 if uv[2] <= 0: 971 # the following computation can return a value 972 # ever so slightly > 1.0 causing arccos to fail. 973 uv_arg = np.dot(u, v) / du / dv 974 if uv_arg > 1.0: 975 # since the argument to arcos is 1, simply 976 # assign alpha to 0.0 without calculating the 977 # arccos 978 alpha = 0.0 979 else: 980 alpha = np.arccos(uv_arg) 981 db = lw * np.tan(alpha / 2) 982 p1new = p1 + nv - v / dv * db 983 ptsnew.append(p1new) 984 else: 985 p1a = p1 + nv 986 p1b = p1 + nu 987 for i in range(0, res + 1): 988 pab = p1a * (res - i) / res + p1b * i / res 989 vpab = pab - p1 990 vpab = vpab / np.linalg.norm(vpab) * lw 991 ptsnew.append(p1 + vpab) 992 if k == len(pts) - 3: 993 ptsnew.append(p2 + nu) 994 if revd: 995 ptsnew.append(p2 - nu) 996 return ptsnew 997 998 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 999 1000 ppoints = vtki.vtkPoints() # Generate the polyline 1001 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 1002 lines = vtki.vtkCellArray() 1003 npt = len(ptsnew) 1004 lines.InsertNextCell(npt) 1005 for i in range(npt): 1006 lines.InsertCellPoint(i) 1007 poly = vtki.vtkPolyData() 1008 poly.SetPoints(ppoints) 1009 poly.SetLines(lines) 1010 vct = vtki.new("ContourTriangulator") 1011 vct.SetInputData(poly) 1012 vct.Update() 1013 1014 super().__init__(vct.GetOutput(), c, alpha) 1015 self.flat() 1016 self.properties.LightingOff() 1017 self.name = "RoundedLine" 1018 self.base = ptsnew[0] 1019 self.top = ptsnew[-1] 1020 1021 1022class Lines(Mesh): 1023 """ 1024 Build the line segments between two lists of points `start_pts` and `end_pts`. 1025 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1026 """ 1027 1028 def __init__( 1029 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 1030 ) -> None: 1031 """ 1032 Arguments: 1033 scale : (float) 1034 apply a rescaling factor to the lengths. 1035 c : (color, int, str, list) 1036 color name, number, or list of [R,G,B] colors 1037 alpha : (float) 1038 opacity in range [0,1] 1039 lw : (int) 1040 line width in pixel units 1041 dotted : (bool) 1042 draw a dotted line 1043 res : (int) 1044 resolution, number of points along the line 1045 (only relevant if only 2 points are specified) 1046 1047 Examples: 1048 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 1049 1050 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 1051 """ 1052 1053 if isinstance(start_pts, vtki.vtkPolyData):######## 1054 super().__init__(start_pts, c, alpha) 1055 self.lw(lw).lighting("off") 1056 self.name = "Lines" 1057 return ######################################## 1058 1059 if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line): 1060 # passing a list of Line, see tests/issues/issue_950.py 1061 polylns = vtki.new("AppendPolyData") 1062 for ln in start_pts: 1063 polylns.AddInputData(ln.dataset) 1064 polylns.Update() 1065 1066 super().__init__(polylns.GetOutput(), c, alpha) 1067 self.lw(lw).lighting("off") 1068 if dotted: 1069 self.properties.SetLineStipplePattern(0xF0F0) 1070 self.properties.SetLineStippleRepeatFactor(1) 1071 self.name = "Lines" 1072 return ######################################## 1073 1074 if isinstance(start_pts, Points): 1075 start_pts = start_pts.vertices 1076 if isinstance(end_pts, Points): 1077 end_pts = end_pts.vertices 1078 1079 if end_pts is not None: 1080 start_pts = np.stack((start_pts, end_pts), axis=1) 1081 1082 polylns = vtki.new("AppendPolyData") 1083 1084 if not utils.is_ragged(start_pts): 1085 1086 for twopts in start_pts: 1087 line_source = vtki.new("LineSource") 1088 line_source.SetResolution(res) 1089 if len(twopts[0]) == 2: 1090 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 1091 else: 1092 line_source.SetPoint1(twopts[0]) 1093 1094 if scale == 1: 1095 pt2 = twopts[1] 1096 else: 1097 vers = (np.array(twopts[1]) - twopts[0]) * scale 1098 pt2 = np.array(twopts[0]) + vers 1099 1100 if len(pt2) == 2: 1101 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1102 else: 1103 line_source.SetPoint2(pt2) 1104 polylns.AddInputConnection(line_source.GetOutputPort()) 1105 1106 else: 1107 1108 polylns = vtki.new("AppendPolyData") 1109 for t in start_pts: 1110 t = utils.make3d(t) 1111 ppoints = vtki.vtkPoints() # Generate the polyline 1112 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1113 lines = vtki.vtkCellArray() 1114 npt = len(t) 1115 lines.InsertNextCell(npt) 1116 for i in range(npt): 1117 lines.InsertCellPoint(i) 1118 poly = vtki.vtkPolyData() 1119 poly.SetPoints(ppoints) 1120 poly.SetLines(lines) 1121 polylns.AddInputData(poly) 1122 1123 polylns.Update() 1124 1125 super().__init__(polylns.GetOutput(), c, alpha) 1126 self.lw(lw).lighting("off") 1127 if dotted: 1128 self.properties.SetLineStipplePattern(0xF0F0) 1129 self.properties.SetLineStippleRepeatFactor(1) 1130 1131 self.name = "Lines" 1132 1133 1134class Spline(Line): 1135 """ 1136 Find the B-Spline curve through a set of points. This curve does not necessarily 1137 pass exactly through all the input points. Needs to import `scipy`. 1138 """ 1139 1140 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None: 1141 """ 1142 Arguments: 1143 smooth : (float) 1144 smoothing factor. 1145 - 0 = interpolate points exactly [default]. 1146 - 1 = average point positions. 1147 degree : (int) 1148 degree of the spline (between 1 and 5). 1149 easing : (str) 1150 control sensity of points along the spline. 1151 Available options are 1152 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1153 Can be used to create animations (move objects at varying speed). 1154 See e.g.: https://easings.net 1155 res : (int) 1156 number of points on the spline 1157 1158 See also: `CSpline` and `KSpline`. 1159 1160 Examples: 1161 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1162 1163 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1164 """ 1165 from scipy.interpolate import splprep, splev 1166 1167 if isinstance(points, Points): 1168 points = points.vertices 1169 1170 points = utils.make3d(points) 1171 1172 per = 0 1173 if closed: 1174 points = np.append(points, [points[0]], axis=0) 1175 per = 1 1176 1177 if res is None: 1178 res = len(points) * 10 1179 1180 points = np.array(points, dtype=float) 1181 1182 minx, miny, minz = np.min(points, axis=0) 1183 maxx, maxy, maxz = np.max(points, axis=0) 1184 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1185 smooth *= maxb / 2 # must be in absolute units 1186 1187 x = np.linspace(0.0, 1.0, res) 1188 if easing: 1189 if easing == "InSine": 1190 x = 1.0 - np.cos((x * np.pi) / 2) 1191 elif easing == "OutSine": 1192 x = np.sin((x * np.pi) / 2) 1193 elif easing == "Sine": 1194 x = -(np.cos(np.pi * x) - 1) / 2 1195 elif easing == "InQuad": 1196 x = x * x 1197 elif easing == "OutQuad": 1198 x = 1.0 - (1 - x) * (1 - x) 1199 elif easing == "InCubic": 1200 x = x * x 1201 elif easing == "OutCubic": 1202 x = 1.0 - np.power(1 - x, 3) 1203 elif easing == "InQuart": 1204 x = x * x * x * x 1205 elif easing == "OutQuart": 1206 x = 1.0 - np.power(1 - x, 4) 1207 elif easing == "InCirc": 1208 x = 1.0 - np.sqrt(1 - np.power(x, 2)) 1209 elif easing == "OutCirc": 1210 x = np.sqrt(1.0 - np.power(x - 1, 2)) 1211 else: 1212 vedo.logger.error(f"unknown ease mode {easing}") 1213 1214 # find the knots 1215 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1216 # evaluate spLine, including interpolated points: 1217 xnew, ynew, znew = splev(x, tckp) 1218 1219 super().__init__(np.c_[xnew, ynew, znew], lw=2) 1220 self.name = "Spline" 1221 1222 1223class KSpline(Line): 1224 """ 1225 Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline) 1226 which runs exactly through all the input points. 1227 """ 1228 1229 def __init__(self, points, 1230 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None: 1231 """ 1232 Arguments: 1233 continuity : (float) 1234 changes the sharpness in change between tangents 1235 tension : (float) 1236 changes the length of the tangent vector 1237 bias : (float) 1238 changes the direction of the tangent vector 1239 closed : (bool) 1240 join last to first point to produce a closed curve 1241 res : (int) 1242 approximate resolution of the output line. 1243 Default is 20 times the number of input points. 1244 1245 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1246 1247 Warning: 1248 This class is not necessarily generating the exact number of points 1249 as requested by `res`. Some points may be concident and removed. 1250 1251 See also: `Spline` and `CSpline`. 1252 """ 1253 if isinstance(points, Points): 1254 points = points.vertices 1255 1256 if not res: 1257 res = len(points) * 20 1258 1259 points = utils.make3d(points).astype(float) 1260 1261 vtkKochanekSpline = vtki.get_class("KochanekSpline") 1262 xspline = vtkKochanekSpline() 1263 yspline = vtkKochanekSpline() 1264 zspline = vtkKochanekSpline() 1265 for s in [xspline, yspline, zspline]: 1266 if bias: 1267 s.SetDefaultBias(bias) 1268 if tension: 1269 s.SetDefaultTension(tension) 1270 if continuity: 1271 s.SetDefaultContinuity(continuity) 1272 s.SetClosed(closed) 1273 1274 lenp = len(points[0]) > 2 1275 1276 for i, p in enumerate(points): 1277 xspline.AddPoint(i, p[0]) 1278 yspline.AddPoint(i, p[1]) 1279 if lenp: 1280 zspline.AddPoint(i, p[2]) 1281 1282 ln = [] 1283 for pos in np.linspace(0, len(points), res): 1284 x = xspline.Evaluate(pos) 1285 y = yspline.Evaluate(pos) 1286 z = 0 1287 if lenp: 1288 z = zspline.Evaluate(pos) 1289 ln.append((x, y, z)) 1290 1291 super().__init__(ln, lw=2) 1292 self.clean() 1293 self.lighting("off") 1294 self.name = "KSpline" 1295 self.base = np.array(points[0], dtype=float) 1296 self.top = np.array(points[-1], dtype=float) 1297 1298 1299class CSpline(Line): 1300 """ 1301 Return a Cardinal spline which runs exactly through all the input points. 1302 """ 1303 1304 def __init__(self, points, closed=False, res=None) -> None: 1305 """ 1306 Arguments: 1307 closed : (bool) 1308 join last to first point to produce a closed curve 1309 res : (int) 1310 approximate resolution of the output line. 1311 Default is 20 times the number of input points. 1312 1313 Warning: 1314 This class is not necessarily generating the exact number of points 1315 as requested by `res`. Some points may be concident and removed. 1316 1317 See also: `Spline` and `KSpline`. 1318 """ 1319 1320 if isinstance(points, Points): 1321 points = points.vertices 1322 1323 if not res: 1324 res = len(points) * 20 1325 1326 points = utils.make3d(points).astype(float) 1327 1328 vtkCardinalSpline = vtki.get_class("CardinalSpline") 1329 xspline = vtkCardinalSpline() 1330 yspline = vtkCardinalSpline() 1331 zspline = vtkCardinalSpline() 1332 for s in [xspline, yspline, zspline]: 1333 s.SetClosed(closed) 1334 1335 lenp = len(points[0]) > 2 1336 1337 for i, p in enumerate(points): 1338 xspline.AddPoint(i, p[0]) 1339 yspline.AddPoint(i, p[1]) 1340 if lenp: 1341 zspline.AddPoint(i, p[2]) 1342 1343 ln = [] 1344 for pos in np.linspace(0, len(points), res): 1345 x = xspline.Evaluate(pos) 1346 y = yspline.Evaluate(pos) 1347 z = 0 1348 if lenp: 1349 z = zspline.Evaluate(pos) 1350 ln.append((x, y, z)) 1351 1352 super().__init__(ln, lw=2) 1353 self.clean() 1354 self.lighting("off") 1355 self.name = "CSpline" 1356 self.base = points[0] 1357 self.top = points[-1] 1358 1359 1360class Bezier(Line): 1361 """ 1362 Generate the Bezier line that links the first to the last point. 1363 """ 1364 1365 def __init__(self, points, res=None) -> None: 1366 """ 1367 Example: 1368 ```python 1369 from vedo import * 1370 import numpy as np 1371 pts = np.random.randn(25,3) 1372 for i,p in enumerate(pts): 1373 p += [5*i, 15*sin(i/2), i*i*i/200] 1374 show(Points(pts), Bezier(pts), axes=1).close() 1375 ``` 1376 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1377 """ 1378 N = len(points) 1379 if res is None: 1380 res = 10 * N 1381 t = np.linspace(0, 1, num=res) 1382 bcurve = np.zeros((res, len(points[0]))) 1383 1384 def binom(n, k): 1385 b = 1 1386 for t in range(1, min(k, n - k) + 1): 1387 b *= n / t 1388 n -= 1 1389 return b 1390 1391 def bernstein(n, k): 1392 coeff = binom(n, k) 1393 1394 def _bpoly(x): 1395 return coeff * x ** k * (1 - x) ** (n - k) 1396 1397 return _bpoly 1398 1399 for ii in range(N): 1400 b = bernstein(N - 1, ii)(t) 1401 bcurve += np.outer(b, points[ii]) 1402 super().__init__(bcurve, lw=2) 1403 self.name = "BezierLine" 1404 1405 1406class NormalLines(Mesh): 1407 """ 1408 Build an `Glyph` to show the normals at cell centers or at mesh vertices. 1409 1410 Arguments: 1411 ratio : (int) 1412 show 1 normal every `ratio` cells. 1413 on : (str) 1414 either "cells" or "points". 1415 scale : (float) 1416 scale factor to control size. 1417 """ 1418 1419 def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None: 1420 1421 poly = msh.clone().dataset 1422 1423 if "cell" in on: 1424 centers = vtki.new("CellCenters") 1425 centers.SetInputData(poly) 1426 centers.Update() 1427 poly = centers.GetOutput() 1428 1429 mask_pts = vtki.new("MaskPoints") 1430 mask_pts.SetInputData(poly) 1431 mask_pts.SetOnRatio(ratio) 1432 mask_pts.RandomModeOff() 1433 mask_pts.Update() 1434 1435 ln = vtki.new("LineSource") 1436 ln.SetPoint1(0, 0, 0) 1437 ln.SetPoint2(1, 0, 0) 1438 ln.Update() 1439 glyph = vtki.vtkGlyph3D() 1440 glyph.SetSourceData(ln.GetOutput()) 1441 glyph.SetInputData(mask_pts.GetOutput()) 1442 glyph.SetVectorModeToUseNormal() 1443 1444 b = poly.GetBounds() 1445 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1446 glyph.SetScaleFactor(f) 1447 glyph.OrientOn() 1448 glyph.Update() 1449 1450 super().__init__(glyph.GetOutput()) 1451 1452 self.actor.PickableOff() 1453 prop = vtki.vtkProperty() 1454 prop.DeepCopy(msh.properties) 1455 self.actor.SetProperty(prop) 1456 self.properties = prop 1457 self.properties.LightingOff() 1458 self.mapper.ScalarVisibilityOff() 1459 self.name = "NormalLines" 1460 1461 1462class Tube(Mesh): 1463 """ 1464 Build a tube along the line defined by a set of points. 1465 """ 1466 1467 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None: 1468 """ 1469 Arguments: 1470 r : (float, list) 1471 constant radius or list of radii. 1472 res : (int) 1473 resolution, number of the sides of the tube 1474 c : (color) 1475 constant color or list of colors for each point. 1476 1477 Example: 1478 Create a tube along a line, with data associated to each point: 1479 1480 ```python 1481 from vedo import * 1482 line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5) 1483 scalars = np.array([0, 1, 2, 3]) 1484 line.pointdata["myscalars"] = scalars 1485 tube = Tube(line, r=0.1).lw(1) 1486 tube.cmap('viridis', "myscalars").add_scalarbar3d() 1487 show(line, tube, axes=1).close() 1488 ``` 1489 1490 Examples: 1491 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1492 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1493 1494 ![](https://vedo.embl.es/images/basic/tube.png) 1495 """ 1496 if utils.is_sequence(points): 1497 vpoints = vtki.vtkPoints() 1498 idx = len(points) 1499 for p in points: 1500 vpoints.InsertNextPoint(p) 1501 line = vtki.new("PolyLine") 1502 line.GetPointIds().SetNumberOfIds(idx) 1503 for i in range(idx): 1504 line.GetPointIds().SetId(i, i) 1505 lines = vtki.vtkCellArray() 1506 lines.InsertNextCell(line) 1507 polyln = vtki.vtkPolyData() 1508 polyln.SetPoints(vpoints) 1509 polyln.SetLines(lines) 1510 self.base = np.asarray(points[0], dtype=float) 1511 self.top = np.asarray(points[-1], dtype=float) 1512 1513 elif isinstance(points, Mesh): 1514 polyln = points.dataset 1515 n = polyln.GetNumberOfPoints() 1516 self.base = np.array(polyln.GetPoint(0)) 1517 self.top = np.array(polyln.GetPoint(n - 1)) 1518 1519 # from vtkmodules.vtkFiltersCore import vtkTubeBender 1520 # bender = vtkTubeBender() 1521 # bender.SetInputData(polyln) 1522 # bender.SetRadius(r) 1523 # bender.Update() 1524 # polyln = bender.GetOutput() 1525 1526 tuf = vtki.new("TubeFilter") 1527 tuf.SetCapping(cap) 1528 tuf.SetNumberOfSides(res) 1529 tuf.SetInputData(polyln) 1530 if utils.is_sequence(r): 1531 arr = utils.numpy2vtk(r, dtype=float) 1532 arr.SetName("TubeRadius") 1533 polyln.GetPointData().AddArray(arr) 1534 polyln.GetPointData().SetActiveScalars("TubeRadius") 1535 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1536 else: 1537 tuf.SetRadius(r) 1538 1539 usingColScals = False 1540 if utils.is_sequence(c): 1541 usingColScals = True 1542 cc = vtki.vtkUnsignedCharArray() 1543 cc.SetName("TubeColors") 1544 cc.SetNumberOfComponents(3) 1545 cc.SetNumberOfTuples(len(c)) 1546 for i, ic in enumerate(c): 1547 r, g, b = get_color(ic) 1548 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1549 polyln.GetPointData().AddArray(cc) 1550 c = None 1551 tuf.Update() 1552 1553 super().__init__(tuf.GetOutput(), c, alpha) 1554 self.phong() 1555 if usingColScals: 1556 self.mapper.SetScalarModeToUsePointFieldData() 1557 self.mapper.ScalarVisibilityOn() 1558 self.mapper.SelectColorArray("TubeColors") 1559 self.mapper.Modified() 1560 self.name = "Tube" 1561 1562 1563def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0) -> Union["Mesh", None]: 1564 """ 1565 Create a tube with a thickness along a line of points. 1566 1567 Example: 1568 ```python 1569 from vedo import * 1570 pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)] 1571 vline = Line(pts, lw=5, c='red5') 1572 thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1) 1573 show(vline, thick_tube, axes=1).close() 1574 ``` 1575 ![](https://vedo.embl.es/images/feats/thick_tube.png) 1576 """ 1577 1578 def make_cap(t1, t2): 1579 newpoints = t1.vertices.tolist() + t2.vertices.tolist() 1580 newfaces = [] 1581 for i in range(n - 1): 1582 newfaces.append([i, i + 1, i + n]) 1583 newfaces.append([i + n, i + 1, i + n + 1]) 1584 newfaces.append([2 * n - 1, 0, n]) 1585 newfaces.append([2 * n - 1, n - 1, 0]) 1586 capm = utils.buildPolyData(newpoints, newfaces) 1587 return capm 1588 1589 assert r1 < r2 1590 1591 t1 = Tube(pts, r=r1, cap=False, res=res) 1592 t2 = Tube(pts, r=r2, cap=False, res=res) 1593 1594 tc1a, tc1b = t1.boundaries().split() 1595 tc2a, tc2b = t2.boundaries().split() 1596 n = tc1b.npoints 1597 1598 tc1b.join(reset=True).clean() # needed because indices are flipped 1599 tc2b.join(reset=True).clean() 1600 1601 capa = make_cap(tc1a, tc2a) 1602 capb = make_cap(tc1b, tc2b) 1603 1604 thick_tube = merge(t1, t2, capa, capb) 1605 if thick_tube: 1606 thick_tube.c(c).alpha(alpha) 1607 thick_tube.base = t1.base 1608 thick_tube.top = t1.top 1609 thick_tube.name = "ThickTube" 1610 return thick_tube 1611 return None 1612 1613 1614class Tubes(Mesh): 1615 """ 1616 Build tubes around a `Lines` object. 1617 """ 1618 def __init__( 1619 self, 1620 lines, 1621 r=1, 1622 vary_radius_by_scalar=False, 1623 vary_radius_by_vector=False, 1624 vary_radius_by_vector_norm=False, 1625 vary_radius_by_absolute_scalar=False, 1626 max_radius_factor=100, 1627 cap=True, 1628 res=12 1629 ) -> None: 1630 """ 1631 Wrap tubes around the input `Lines` object. 1632 1633 Arguments: 1634 lines : (Lines) 1635 input Lines object. 1636 r : (float) 1637 constant radius 1638 vary_radius_by_scalar : (bool) 1639 use scalar array to control radius 1640 vary_radius_by_vector : (bool) 1641 use vector array to control radius 1642 vary_radius_by_vector_norm : (bool) 1643 use vector norm to control radius 1644 vary_radius_by_absolute_scalar : (bool) 1645 use absolute scalar value to control radius 1646 max_radius_factor : (float) 1647 max tube radius as a multiple of the min radius 1648 cap : (bool) 1649 capping of the tube 1650 res : (int) 1651 resolution, number of the sides of the tube 1652 c : (color) 1653 constant color or list of colors for each point. 1654 1655 Examples: 1656 - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py) 1657 """ 1658 plines = lines.dataset 1659 if plines.GetNumberOfLines() == 0: 1660 vedo.logger.warning("Tubes(): input Lines is empty.") 1661 1662 tuf = vtki.new("TubeFilter") 1663 if vary_radius_by_scalar: 1664 tuf.SetVaryRadiusToVaryRadiusByScalar() 1665 elif vary_radius_by_vector: 1666 tuf.SetVaryRadiusToVaryRadiusByVector() 1667 elif vary_radius_by_vector_norm: 1668 tuf.SetVaryRadiusToVaryRadiusByVectorNorm() 1669 elif vary_radius_by_absolute_scalar: 1670 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1671 tuf.SetRadius(r) 1672 tuf.SetCapping(cap) 1673 tuf.SetGenerateTCoords(0) 1674 tuf.SetSidesShareVertices(1) 1675 tuf.SetRadiusFactor(max_radius_factor) 1676 tuf.SetNumberOfSides(res) 1677 tuf.SetInputData(plines) 1678 tuf.Update() 1679 1680 super().__init__(tuf.GetOutput()) 1681 self.name = "Tubes" 1682 1683 1684class Ribbon(Mesh): 1685 """ 1686 Connect two lines to generate the surface inbetween. 1687 Set the mode by which to create the ruled surface. 1688 1689 It also works with a single line in input. In this case the ribbon 1690 is formed by following the local plane of the line in space. 1691 """ 1692 1693 def __init__( 1694 self, 1695 line1, 1696 line2=None, 1697 mode=0, 1698 closed=False, 1699 width=None, 1700 res=(200, 5), 1701 c="indigo3", 1702 alpha=1.0, 1703 ) -> None: 1704 """ 1705 Arguments: 1706 mode : (int) 1707 If mode=0, resample evenly the input lines (based on length) 1708 and generates triangle strips. 1709 1710 If mode=1, use the existing points and walks around the 1711 polyline using existing points. 1712 1713 closed : (bool) 1714 if True, join the last point with the first to form a closed surface 1715 1716 res : (list) 1717 ribbon resolutions along the line and perpendicularly to it. 1718 1719 Examples: 1720 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1721 1722 ![](https://vedo.embl.es/images/basic/ribbon.png) 1723 """ 1724 1725 if isinstance(line1, Points): 1726 line1 = line1.vertices 1727 1728 if isinstance(line2, Points): 1729 line2 = line2.vertices 1730 1731 elif line2 is None: 1732 ############################################# 1733 ribbon_filter = vtki.new("RibbonFilter") 1734 aline = Line(line1) 1735 ribbon_filter.SetInputData(aline.dataset) 1736 if width is None: 1737 width = aline.diagonal_size() / 20.0 1738 ribbon_filter.SetWidth(width) 1739 ribbon_filter.Update() 1740 # convert triangle strips to polygons 1741 tris = vtki.new("TriangleFilter") 1742 tris.SetInputData(ribbon_filter.GetOutput()) 1743 tris.Update() 1744 1745 super().__init__(tris.GetOutput(), c, alpha) 1746 self.name = "Ribbon" 1747 ############################################## 1748 return ###################################### 1749 ############################################## 1750 1751 line1 = np.asarray(line1) 1752 line2 = np.asarray(line2) 1753 1754 if closed: 1755 line1 = line1.tolist() 1756 line1 += [line1[0]] 1757 line2 = line2.tolist() 1758 line2 += [line2[0]] 1759 line1 = np.array(line1) 1760 line2 = np.array(line2) 1761 1762 if len(line1[0]) == 2: 1763 line1 = np.c_[line1, np.zeros(len(line1))] 1764 if len(line2[0]) == 2: 1765 line2 = np.c_[line2, np.zeros(len(line2))] 1766 1767 ppoints1 = vtki.vtkPoints() # Generate the polyline1 1768 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1769 lines1 = vtki.vtkCellArray() 1770 lines1.InsertNextCell(len(line1)) 1771 for i in range(len(line1)): 1772 lines1.InsertCellPoint(i) 1773 poly1 = vtki.vtkPolyData() 1774 poly1.SetPoints(ppoints1) 1775 poly1.SetLines(lines1) 1776 1777 ppoints2 = vtki.vtkPoints() # Generate the polyline2 1778 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1779 lines2 = vtki.vtkCellArray() 1780 lines2.InsertNextCell(len(line2)) 1781 for i in range(len(line2)): 1782 lines2.InsertCellPoint(i) 1783 poly2 = vtki.vtkPolyData() 1784 poly2.SetPoints(ppoints2) 1785 poly2.SetLines(lines2) 1786 1787 # build the lines 1788 lines1 = vtki.vtkCellArray() 1789 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1790 for i in range(poly1.GetNumberOfPoints()): 1791 lines1.InsertCellPoint(i) 1792 1793 polygon1 = vtki.vtkPolyData() 1794 polygon1.SetPoints(ppoints1) 1795 polygon1.SetLines(lines1) 1796 1797 lines2 = vtki.vtkCellArray() 1798 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1799 for i in range(poly2.GetNumberOfPoints()): 1800 lines2.InsertCellPoint(i) 1801 1802 polygon2 = vtki.vtkPolyData() 1803 polygon2.SetPoints(ppoints2) 1804 polygon2.SetLines(lines2) 1805 1806 merged_pd = vtki.new("AppendPolyData") 1807 merged_pd.AddInputData(polygon1) 1808 merged_pd.AddInputData(polygon2) 1809 merged_pd.Update() 1810 1811 rsf = vtki.new("RuledSurfaceFilter") 1812 rsf.CloseSurfaceOff() 1813 rsf.SetRuledMode(mode) 1814 rsf.SetResolution(res[0], res[1]) 1815 rsf.SetInputData(merged_pd.GetOutput()) 1816 rsf.Update() 1817 # convert triangle strips to polygons 1818 tris = vtki.new("TriangleFilter") 1819 tris.SetInputData(rsf.GetOutput()) 1820 tris.Update() 1821 out = tris.GetOutput() 1822 1823 super().__init__(out, c, alpha) 1824 1825 self.name = "Ribbon" 1826 1827 1828class Arrow(Mesh): 1829 """ 1830 Build a 3D arrow from `start_pt` to `end_pt` of section size `s`, 1831 expressed as the fraction of the window size. 1832 """ 1833 1834 def __init__( 1835 self, 1836 start_pt=(0, 0, 0), 1837 end_pt=(1, 0, 0), 1838 s=None, 1839 shaft_radius=None, 1840 head_radius=None, 1841 head_length=None, 1842 res=12, 1843 c="r4", 1844 alpha=1.0, 1845 ) -> None: 1846 """ 1847 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1848 from white to red. 1849 1850 .. note:: If `s=None` the arrow is scaled proportionally to its length 1851 1852 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1853 """ 1854 # in case user is passing meshs 1855 if isinstance(start_pt, vtki.vtkActor): 1856 start_pt = start_pt.GetPosition() 1857 if isinstance(end_pt, vtki.vtkActor): 1858 end_pt = end_pt.GetPosition() 1859 1860 axis = np.asarray(end_pt) - np.asarray(start_pt) 1861 length = float(np.linalg.norm(axis)) 1862 if length: 1863 axis = axis / length 1864 if len(axis) < 3: # its 2d 1865 theta = np.pi / 2 1866 start_pt = [start_pt[0], start_pt[1], 0.0] 1867 end_pt = [end_pt[0], end_pt[1], 0.0] 1868 else: 1869 theta = np.arccos(axis[2]) 1870 phi = np.arctan2(axis[1], axis[0]) 1871 self.source = vtki.new("ArrowSource") 1872 self.source.SetShaftResolution(res) 1873 self.source.SetTipResolution(res) 1874 1875 if s: 1876 sz = 0.02 1877 self.source.SetTipRadius(sz) 1878 self.source.SetShaftRadius(sz / 1.75) 1879 self.source.SetTipLength(sz * 15) 1880 1881 if head_length: 1882 self.source.SetTipLength(head_length) 1883 if head_radius: 1884 self.source.SetTipRadius(head_radius) 1885 if shaft_radius: 1886 self.source.SetShaftRadius(shaft_radius) 1887 1888 self.source.Update() 1889 1890 t = vtki.vtkTransform() 1891 t.Translate(start_pt) 1892 t.RotateZ(np.rad2deg(phi)) 1893 t.RotateY(np.rad2deg(theta)) 1894 t.RotateY(-90) # put it along Z 1895 if s: 1896 sz = 800 * s 1897 t.Scale(length, sz, sz) 1898 else: 1899 t.Scale(length, length, length) 1900 1901 tf = vtki.new("TransformPolyDataFilter") 1902 tf.SetInputData(self.source.GetOutput()) 1903 tf.SetTransform(t) 1904 tf.Update() 1905 1906 super().__init__(tf.GetOutput(), c, alpha) 1907 1908 self.transform = LinearTransform().translate(start_pt) 1909 1910 self.phong().lighting("plastic") 1911 self.actor.PickableOff() 1912 self.actor.DragableOff() 1913 self.base = np.array(start_pt, dtype=float) # used by pyplot 1914 self.top = np.array(end_pt, dtype=float) # used by pyplot 1915 self.top_index = self.source.GetTipResolution() * 4 1916 self.fill = True # used by pyplot.__iadd__() 1917 self.s = s if s is not None else 1 # used by pyplot.__iadd__() 1918 self.name = "Arrow" 1919 1920 def top_point(self): 1921 """Return the current coordinates of the tip of the Arrow.""" 1922 return self.transform.transform_point(self.top) 1923 1924 def base_point(self): 1925 """Return the current coordinates of the base of the Arrow.""" 1926 return self.transform.transform_point(self.base) 1927 1928class Arrows(Glyph): 1929 """ 1930 Build arrows between two lists of points. 1931 """ 1932 1933 def __init__( 1934 self, 1935 start_pts, 1936 end_pts=None, 1937 s=None, 1938 shaft_radius=None, 1939 head_radius=None, 1940 head_length=None, 1941 thickness=1.0, 1942 res=6, 1943 c='k3', 1944 alpha=1.0, 1945 ) -> None: 1946 """ 1947 Build arrows between two lists of points `start_pts` and `end_pts`. 1948 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1949 1950 Color can be specified as a colormap which maps the size of the arrows. 1951 1952 Arguments: 1953 s : (float) 1954 fix aspect-ratio of the arrow and scale its cross section 1955 c : (color) 1956 color or color map name 1957 alpha : (float) 1958 set object opacity 1959 res : (int) 1960 set arrow resolution 1961 1962 Examples: 1963 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 1964 1965 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 1966 """ 1967 if isinstance(start_pts, Points): 1968 start_pts = start_pts.vertices 1969 if isinstance(end_pts, Points): 1970 end_pts = end_pts.vertices 1971 1972 start_pts = np.asarray(start_pts) 1973 if end_pts is None: 1974 strt = start_pts[:, 0] 1975 end_pts = start_pts[:, 1] 1976 start_pts = strt 1977 else: 1978 end_pts = np.asarray(end_pts) 1979 1980 start_pts = utils.make3d(start_pts) 1981 end_pts = utils.make3d(end_pts) 1982 1983 arr = vtki.new("ArrowSource") 1984 arr.SetShaftResolution(res) 1985 arr.SetTipResolution(res) 1986 1987 if s: 1988 sz = 0.02 * s 1989 arr.SetTipRadius(sz * 2) 1990 arr.SetShaftRadius(sz * thickness) 1991 arr.SetTipLength(sz * 10) 1992 1993 if head_radius: 1994 arr.SetTipRadius(head_radius) 1995 if shaft_radius: 1996 arr.SetShaftRadius(shaft_radius) 1997 if head_length: 1998 arr.SetTipLength(head_length) 1999 2000 arr.Update() 2001 out = arr.GetOutput() 2002 2003 orients = end_pts - start_pts 2004 2005 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2006 2007 super().__init__( 2008 start_pts, 2009 out, 2010 orientation_array=orients, 2011 scale_by_vector_size=True, 2012 color_by_vector_size=color_by_vector_size, 2013 c=c, 2014 alpha=alpha, 2015 ) 2016 self.lighting("off") 2017 if color_by_vector_size: 2018 vals = np.linalg.norm(orients, axis=1) 2019 self.mapper.SetScalarRange(vals.min(), vals.max()) 2020 else: 2021 self.c(c) 2022 self.name = "Arrows" 2023 2024 2025class Arrow2D(Mesh): 2026 """ 2027 Build a 2D arrow. 2028 """ 2029 2030 def __init__( 2031 self, 2032 start_pt=(0, 0, 0), 2033 end_pt=(1, 0, 0), 2034 s=1, 2035 rotation=0.0, 2036 shaft_length=0.85, 2037 shaft_width=0.055, 2038 head_length=0.175, 2039 head_width=0.175, 2040 fill=True, 2041 c="red4", 2042 alpha=1.0, 2043 ) -> None: 2044 """ 2045 Build a 2D arrow from `start_pt` to `end_pt`. 2046 2047 Arguments: 2048 s : (float) 2049 a global multiplicative convenience factor controlling the arrow size 2050 shaft_length : (float) 2051 fractional shaft length 2052 shaft_width : (float) 2053 fractional shaft width 2054 head_length : (float) 2055 fractional head length 2056 head_width : (float) 2057 fractional head width 2058 fill : (bool) 2059 if False only generate the outline 2060 """ 2061 self.fill = fill ## needed by pyplot.__iadd() 2062 self.s = s ## needed by pyplot.__iadd() 2063 2064 if s != 1: 2065 shaft_width *= s 2066 head_width *= np.sqrt(s) 2067 2068 # in case user is passing meshs 2069 if isinstance(start_pt, vtki.vtkActor): 2070 start_pt = start_pt.GetPosition() 2071 if isinstance(end_pt, vtki.vtkActor): 2072 end_pt = end_pt.GetPosition() 2073 if len(start_pt) == 2: 2074 start_pt = [start_pt[0], start_pt[1], 0] 2075 if len(end_pt) == 2: 2076 end_pt = [end_pt[0], end_pt[1], 0] 2077 2078 headBase = 1 - head_length 2079 head_width = max(head_width, shaft_width) 2080 if head_length is None or headBase > shaft_length: 2081 headBase = shaft_length 2082 2083 verts = [] 2084 verts.append([0, -shaft_width / 2, 0]) 2085 verts.append([shaft_length, -shaft_width / 2, 0]) 2086 verts.append([headBase, -head_width / 2, 0]) 2087 verts.append([1, 0, 0]) 2088 verts.append([headBase, head_width / 2, 0]) 2089 verts.append([shaft_length, shaft_width / 2, 0]) 2090 verts.append([0, shaft_width / 2, 0]) 2091 if fill: 2092 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2093 poly = utils.buildPolyData(verts, faces) 2094 else: 2095 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2096 poly = utils.buildPolyData(verts, [], lines=lines) 2097 2098 axis = np.array(end_pt) - np.array(start_pt) 2099 length = float(np.linalg.norm(axis)) 2100 if length: 2101 axis = axis / length 2102 theta = 0 2103 if len(axis) > 2: 2104 theta = np.arccos(axis[2]) 2105 phi = np.arctan2(axis[1], axis[0]) 2106 2107 t = vtki.vtkTransform() 2108 t.Translate(start_pt) 2109 if phi: 2110 t.RotateZ(np.rad2deg(phi)) 2111 if theta: 2112 t.RotateY(np.rad2deg(theta)) 2113 t.RotateY(-90) # put it along Z 2114 if rotation: 2115 t.RotateX(rotation) 2116 t.Scale(length, length, length) 2117 2118 tf = vtki.new("TransformPolyDataFilter") 2119 tf.SetInputData(poly) 2120 tf.SetTransform(t) 2121 tf.Update() 2122 2123 super().__init__(tf.GetOutput(), c, alpha) 2124 2125 self.transform = LinearTransform().translate(start_pt) 2126 2127 self.lighting("off") 2128 self.actor.DragableOff() 2129 self.actor.PickableOff() 2130 self.base = np.array(start_pt, dtype=float) # used by pyplot 2131 self.top = np.array(end_pt, dtype=float) # used by pyplot 2132 self.name = "Arrow2D" 2133 2134 2135class Arrows2D(Glyph): 2136 """ 2137 Build 2D arrows between two lists of points. 2138 """ 2139 2140 def __init__( 2141 self, 2142 start_pts, 2143 end_pts=None, 2144 s=1.0, 2145 rotation=0.0, 2146 shaft_length=0.8, 2147 shaft_width=0.05, 2148 head_length=0.225, 2149 head_width=0.175, 2150 fill=True, 2151 c=None, 2152 alpha=1.0, 2153 ) -> None: 2154 """ 2155 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2156 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2157 2158 Color can be specified as a colormap which maps the size of the arrows. 2159 2160 Arguments: 2161 shaft_length : (float) 2162 fractional shaft length 2163 shaft_width : (float) 2164 fractional shaft width 2165 head_length : (float) 2166 fractional head length 2167 head_width : (float) 2168 fractional head width 2169 fill : (bool) 2170 if False only generate the outline 2171 """ 2172 if isinstance(start_pts, Points): 2173 start_pts = start_pts.vertices 2174 if isinstance(end_pts, Points): 2175 end_pts = end_pts.vertices 2176 2177 start_pts = np.asarray(start_pts, dtype=float) 2178 if end_pts is None: 2179 strt = start_pts[:, 0] 2180 end_pts = start_pts[:, 1] 2181 start_pts = strt 2182 else: 2183 end_pts = np.asarray(end_pts, dtype=float) 2184 2185 if head_length is None: 2186 head_length = 1 - shaft_length 2187 2188 arr = Arrow2D( 2189 (0, 0, 0), 2190 (1, 0, 0), 2191 s=s, 2192 rotation=rotation, 2193 shaft_length=shaft_length, 2194 shaft_width=shaft_width, 2195 head_length=head_length, 2196 head_width=head_width, 2197 fill=fill, 2198 ) 2199 2200 orients = end_pts - start_pts 2201 orients = utils.make3d(orients) 2202 2203 pts = Points(start_pts) 2204 super().__init__( 2205 pts, 2206 arr, 2207 orientation_array=orients, 2208 scale_by_vector_size=True, 2209 c=c, 2210 alpha=alpha, 2211 ) 2212 self.flat().lighting("off").pickable(False) 2213 if c is not None: 2214 self.color(c) 2215 self.name = "Arrows2D" 2216 2217 2218class FlatArrow(Ribbon): 2219 """ 2220 Build a 2D arrow in 3D space by joining two close lines. 2221 """ 2222 2223 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2224 """ 2225 Build a 2D arrow in 3D space by joining two close lines. 2226 2227 Examples: 2228 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2229 2230 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2231 """ 2232 if isinstance(line1, Points): 2233 line1 = line1.vertices 2234 if isinstance(line2, Points): 2235 line2 = line2.vertices 2236 2237 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2238 2239 v = (sm1 - sm2) / 3 * tip_width 2240 p1 = sm1 + v 2241 p2 = sm2 - v 2242 pm1 = (sm1 + sm2) / 2 2243 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2244 pm12 = pm1 - pm2 2245 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2246 2247 line1.append(p1) 2248 line1.append(tip) 2249 line2.append(p2) 2250 line2.append(tip) 2251 resm = max(100, len(line1)) 2252 2253 super().__init__(line1, line2, res=(resm, 1)) 2254 self.phong().lighting("off") 2255 self.actor.PickableOff() 2256 self.actor.DragableOff() 2257 self.name = "FlatArrow" 2258 2259 2260class Triangle(Mesh): 2261 """Create a triangle from 3 points in space.""" 2262 2263 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2264 """Create a triangle from 3 points in space.""" 2265 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2266 self.properties.LightingOff() 2267 self.name = "Triangle" 2268 2269 2270class Polygon(Mesh): 2271 """ 2272 Build a polygon in the `xy` plane. 2273 """ 2274 2275 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2276 """ 2277 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2278 2279 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2280 """ 2281 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2282 pts = pol2cart(np.ones_like(t) * r, t).T 2283 faces = [list(range(nsides))] 2284 # do not use: vtkRegularPolygonSource 2285 super().__init__([pts, faces], c, alpha) 2286 if len(pos) == 2: 2287 pos = (pos[0], pos[1], 0) 2288 self.pos(pos) 2289 self.properties.LightingOff() 2290 self.name = "Polygon " + str(nsides) 2291 2292 2293class Circle(Polygon): 2294 """ 2295 Build a Circle of radius `r`. 2296 """ 2297 2298 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2299 """ 2300 Build a Circle of radius `r`. 2301 """ 2302 super().__init__(pos, nsides=res, r=r) 2303 2304 self.nr_of_points = 0 2305 self.va = 0 2306 self.vb = 0 2307 self.axis1: List[float] = [] 2308 self.axis2: List[float] = [] 2309 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2310 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2311 self.alpha(alpha).c(c) 2312 self.name = "Circle" 2313 2314 def acircularity(self) -> float: 2315 """ 2316 Return a measure of how different an ellipse is from a circle. 2317 Values close to zero correspond to a circular object. 2318 """ 2319 a, b = self.va, self.vb 2320 value = 0.0 2321 if a+b: 2322 value = ((a-b)/(a+b))**2 2323 return value 2324 2325class GeoCircle(Polygon): 2326 """ 2327 Build a Circle of radius `r`. 2328 """ 2329 2330 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2331 """ 2332 Build a Circle of radius `r` as projected on a geographic map. 2333 Circles near the poles will look very squashed. 2334 2335 See example: 2336 ```bash 2337 vedo -r earthquake 2338 ``` 2339 """ 2340 coords = [] 2341 sinr, cosr = np.sin(r), np.cos(r) 2342 sinlat, coslat = np.sin(lat), np.cos(lat) 2343 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2344 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2345 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2346 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2347 2348 super().__init__(nsides=res, c=c, alpha=alpha) 2349 self.vertices = coords # warp polygon points to match geo projection 2350 self.name = "Circle" 2351 2352 2353class Star(Mesh): 2354 """ 2355 Build a 2D star shape. 2356 """ 2357 2358 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2359 """ 2360 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2361 2362 If line is True then only build the outer line (no internal surface meshing). 2363 2364 Example: 2365 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2366 2367 ![](https://vedo.embl.es/images/basic/extrude.png) 2368 """ 2369 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2370 x, y = pol2cart(np.ones_like(t) * r2, t) 2371 pts = np.c_[x, y, np.zeros_like(x)] 2372 2373 apts = [] 2374 for i, p in enumerate(pts): 2375 apts.append(p) 2376 if i + 1 < n: 2377 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2378 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2379 2380 if line: 2381 apts.append(pts[0]) 2382 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2383 super().__init__(poly, c, alpha) 2384 self.lw(2) 2385 else: 2386 apts.append((0, 0, 0)) 2387 cells = [] 2388 for i in range(2 * n - 1): 2389 cell = [2 * n, i, i + 1] 2390 cells.append(cell) 2391 cells.append([2 * n, i + 1, 0]) 2392 super().__init__([apts, cells], c, alpha) 2393 2394 if len(pos) == 2: 2395 pos = (pos[0], pos[1], 0) 2396 2397 self.properties.LightingOff() 2398 self.name = "Star" 2399 2400 2401class Disc(Mesh): 2402 """ 2403 Build a 2D disc. 2404 """ 2405 2406 def __init__( 2407 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2408 ) -> None: 2409 """ 2410 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2411 2412 Set `res` as the resolution in R and Phi (can be a list). 2413 2414 Use `angle_range` to create a disc sector between the 2 specified angles. 2415 2416 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2417 """ 2418 if utils.is_sequence(res): 2419 res_r, res_phi = res 2420 else: 2421 res_r, res_phi = res, 12 * res 2422 2423 if len(angle_range) == 0: 2424 ps = vtki.new("DiskSource") 2425 else: 2426 ps = vtki.new("SectorSource") 2427 ps.SetStartAngle(angle_range[0]) 2428 ps.SetEndAngle(angle_range[1]) 2429 2430 ps.SetInnerRadius(r1) 2431 ps.SetOuterRadius(r2) 2432 ps.SetRadialResolution(res_r) 2433 ps.SetCircumferentialResolution(res_phi) 2434 ps.Update() 2435 super().__init__(ps.GetOutput(), c, alpha) 2436 self.flat() 2437 self.pos(utils.make3d(pos)) 2438 self.name = "Disc" 2439 2440 2441class Arc(Mesh): 2442 """ 2443 Build a 2D circular arc between 2 points. 2444 """ 2445 2446 def __init__( 2447 self, 2448 center, 2449 point1, 2450 point2=None, 2451 normal=None, 2452 angle=None, 2453 invert=False, 2454 res=50, 2455 c="gray4", 2456 alpha=1.0, 2457 ) -> None: 2458 """ 2459 Build a 2D circular arc between 2 points `point1` and `point2`. 2460 2461 If `normal` is specified then `center` is ignored, and 2462 normal vector, a starting `point1` (polar vector) 2463 and an angle defining the arc length need to be assigned. 2464 2465 Arc spans the shortest angular sector point1 and point2, 2466 if `invert=True`, then the opposite happens. 2467 """ 2468 if len(point1) == 2: 2469 point1 = (point1[0], point1[1], 0) 2470 if point2 is not None and len(point2) == 2: 2471 point2 = (point2[0], point2[1], 0) 2472 2473 ar = vtki.new("ArcSource") 2474 if point2 is not None: 2475 self.top = point2 2476 point2 = point2 - np.asarray(point1) 2477 ar.UseNormalAndAngleOff() 2478 ar.SetPoint1([0, 0, 0]) 2479 ar.SetPoint2(point2) 2480 # ar.SetCenter(center) 2481 elif normal is not None and angle is not None: 2482 ar.UseNormalAndAngleOn() 2483 ar.SetAngle(angle) 2484 ar.SetPolarVector(point1) 2485 ar.SetNormal(normal) 2486 else: 2487 vedo.logger.error("incorrect input combination") 2488 return 2489 ar.SetNegative(invert) 2490 ar.SetResolution(res) 2491 ar.Update() 2492 2493 super().__init__(ar.GetOutput(), c, alpha) 2494 self.pos(center) 2495 self.lw(2).lighting("off") 2496 self.name = "Arc" 2497 2498 2499class IcoSphere(Mesh): 2500 """ 2501 Create a sphere made of a uniform triangle mesh. 2502 """ 2503 2504 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2505 """ 2506 Create a sphere made of a uniform triangle mesh 2507 (from recursive subdivision of an icosahedron). 2508 2509 Example: 2510 ```python 2511 from vedo import * 2512 icos = IcoSphere(subdivisions=3) 2513 icos.compute_quality().cmap('coolwarm') 2514 icos.show(axes=1).close() 2515 ``` 2516 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2517 """ 2518 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2519 2520 t = (1.0 + np.sqrt(5.0)) / 2.0 2521 points = np.array( 2522 [ 2523 [-1, t, 0], 2524 [1, t, 0], 2525 [-1, -t, 0], 2526 [1, -t, 0], 2527 [0, -1, t], 2528 [0, 1, t], 2529 [0, -1, -t], 2530 [0, 1, -t], 2531 [t, 0, -1], 2532 [t, 0, 1], 2533 [-t, 0, -1], 2534 [-t, 0, 1], 2535 ] 2536 ) 2537 faces = [ 2538 [0, 11, 5], 2539 [0, 5, 1], 2540 [0, 1, 7], 2541 [0, 7, 10], 2542 [0, 10, 11], 2543 [1, 5, 9], 2544 [5, 11, 4], 2545 [11, 10, 2], 2546 [10, 7, 6], 2547 [7, 1, 8], 2548 [3, 9, 4], 2549 [3, 4, 2], 2550 [3, 2, 6], 2551 [3, 6, 8], 2552 [3, 8, 9], 2553 [4, 9, 5], 2554 [2, 4, 11], 2555 [6, 2, 10], 2556 [8, 6, 7], 2557 [9, 8, 1], 2558 ] 2559 super().__init__([points * r, faces], c=c, alpha=alpha) 2560 2561 for _ in range(subdivisions): 2562 self.subdivide(method=1) 2563 pts = utils.versor(self.vertices) * r 2564 self.vertices = pts 2565 2566 self.pos(pos) 2567 self.name = "IcoSphere" 2568 2569 2570class Sphere(Mesh): 2571 """ 2572 Build a sphere. 2573 """ 2574 2575 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2576 """ 2577 Build a sphere at position `pos` of radius `r`. 2578 2579 Arguments: 2580 r : (float) 2581 sphere radius 2582 res : (int, list) 2583 resolution in phi, resolution in theta is by default `2*res` 2584 quads : (bool) 2585 sphere mesh will be made of quads instead of triangles 2586 2587 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2588 """ 2589 if len(pos) == 2: 2590 pos = np.asarray([pos[0], pos[1], 0]) 2591 2592 self.radius = r # used by fitSphere 2593 self.center = pos 2594 self.residue = 0 2595 2596 if quads: 2597 res = max(res, 4) 2598 img = vtki.vtkImageData() 2599 img.SetDimensions(res - 1, res - 1, res - 1) 2600 rs = 1.0 / (res - 2) 2601 img.SetSpacing(rs, rs, rs) 2602 gf = vtki.new("GeometryFilter") 2603 gf.SetInputData(img) 2604 gf.Update() 2605 super().__init__(gf.GetOutput(), c, alpha) 2606 self.lw(0.1) 2607 2608 cgpts = self.vertices - (0.5, 0.5, 0.5) 2609 2610 x, y, z = cgpts.T 2611 x = x * (1 + x * x) / 2 2612 y = y * (1 + y * y) / 2 2613 z = z * (1 + z * z) / 2 2614 _, theta, phi = cart2spher(x, y, z) 2615 2616 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2617 self.vertices = pts 2618 2619 else: 2620 if utils.is_sequence(res): 2621 res_t, res_phi = res 2622 else: 2623 res_t, res_phi = 2 * res, res 2624 2625 ss = vtki.new("SphereSource") 2626 ss.SetRadius(r) 2627 ss.SetThetaResolution(res_t) 2628 ss.SetPhiResolution(res_phi) 2629 ss.Update() 2630 2631 super().__init__(ss.GetOutput(), c, alpha) 2632 2633 self.phong() 2634 self.pos(pos) 2635 self.name = "Sphere" 2636 2637 2638class Spheres(Mesh): 2639 """ 2640 Build a large set of spheres. 2641 """ 2642 2643 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2644 """ 2645 Build a (possibly large) set of spheres at `centers` of radius `r`. 2646 2647 Either `c` or `r` can be a list of RGB colors or radii. 2648 2649 Examples: 2650 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2651 2652 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2653 """ 2654 2655 if isinstance(centers, Points): 2656 centers = centers.vertices 2657 centers = np.asarray(centers, dtype=float) 2658 base = centers[0] 2659 2660 cisseq = False 2661 if utils.is_sequence(c): 2662 cisseq = True 2663 2664 if cisseq: 2665 if len(centers) != len(c): 2666 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2667 raise RuntimeError() 2668 2669 risseq = False 2670 if utils.is_sequence(r): 2671 risseq = True 2672 2673 if risseq: 2674 if len(centers) != len(r): 2675 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2676 raise RuntimeError() 2677 if cisseq and risseq: 2678 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2679 raise RuntimeError() 2680 2681 src = vtki.new("SphereSource") 2682 if not risseq: 2683 src.SetRadius(r) 2684 if utils.is_sequence(res): 2685 res_t, res_phi = res 2686 else: 2687 res_t, res_phi = 2 * res, res 2688 2689 src.SetThetaResolution(res_t) 2690 src.SetPhiResolution(res_phi) 2691 src.Update() 2692 2693 psrc = vtki.new("PointSource") 2694 psrc.SetNumberOfPoints(len(centers)) 2695 psrc.Update() 2696 pd = psrc.GetOutput() 2697 vpts = pd.GetPoints() 2698 2699 glyph = vtki.vtkGlyph3D() 2700 glyph.SetSourceConnection(src.GetOutputPort()) 2701 2702 if cisseq: 2703 glyph.SetColorModeToColorByScalar() 2704 ucols = vtki.vtkUnsignedCharArray() 2705 ucols.SetNumberOfComponents(3) 2706 ucols.SetName("Colors") 2707 for acol in c: 2708 cx, cy, cz = get_color(acol) 2709 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2710 pd.GetPointData().AddArray(ucols) 2711 pd.GetPointData().SetActiveScalars("Colors") 2712 glyph.ScalingOff() 2713 elif risseq: 2714 glyph.SetScaleModeToScaleByScalar() 2715 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2716 urads.SetName("Radii") 2717 pd.GetPointData().AddArray(urads) 2718 pd.GetPointData().SetActiveScalars("Radii") 2719 2720 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2721 2722 glyph.SetInputData(pd) 2723 glyph.Update() 2724 2725 super().__init__(glyph.GetOutput(), alpha=alpha) 2726 self.pos(base) 2727 self.phong() 2728 if cisseq: 2729 self.mapper.ScalarVisibilityOn() 2730 else: 2731 self.mapper.ScalarVisibilityOff() 2732 self.c(c) 2733 self.name = "Spheres" 2734 2735 2736class Earth(Mesh): 2737 """ 2738 Build a textured mesh representing the Earth. 2739 """ 2740 2741 def __init__(self, style=1, r=1.0) -> None: 2742 """ 2743 Build a textured mesh representing the Earth. 2744 2745 Example: 2746 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2747 2748 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2749 """ 2750 tss = vtki.new("TexturedSphereSource") 2751 tss.SetRadius(r) 2752 tss.SetThetaResolution(72) 2753 tss.SetPhiResolution(36) 2754 tss.Update() 2755 super().__init__(tss.GetOutput(), c="w") 2756 atext = vtki.vtkTexture() 2757 pnm_reader = vtki.new("JPEGReader") 2758 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2759 pnm_reader.SetFileName(fn) 2760 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2761 atext.InterpolateOn() 2762 self.texture(atext) 2763 self.name = "Earth" 2764 2765 2766class Ellipsoid(Mesh): 2767 """Build a 3D ellipsoid.""" 2768 def __init__( 2769 self, 2770 pos=(0, 0, 0), 2771 axis1=(0.5, 0, 0), 2772 axis2=(0, 1, 0), 2773 axis3=(0, 0, 1.5), 2774 res=24, 2775 c="cyan4", 2776 alpha=1.0, 2777 ) -> None: 2778 """ 2779 Build a 3D ellipsoid centered at position `pos`. 2780 2781 Arguments: 2782 axis1 : (list) 2783 First axis. Length corresponds to semi-axis. 2784 axis2 : (list) 2785 Second axis. Length corresponds to semi-axis. 2786 axis3 : (list) 2787 Third axis. Length corresponds to semi-axis. 2788 """ 2789 self.center = utils.make3d(pos) 2790 2791 self.axis1 = utils.make3d(axis1) 2792 self.axis2 = utils.make3d(axis2) 2793 self.axis3 = utils.make3d(axis3) 2794 2795 self.va = np.linalg.norm(self.axis1) 2796 self.vb = np.linalg.norm(self.axis2) 2797 self.vc = np.linalg.norm(self.axis3) 2798 2799 self.va_error = 0 2800 self.vb_error = 0 2801 self.vc_error = 0 2802 2803 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2804 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2805 2806 if utils.is_sequence(res): 2807 res_t, res_phi = res 2808 else: 2809 res_t, res_phi = 2 * res, res 2810 2811 elli_source = vtki.new("SphereSource") 2812 elli_source.SetRadius(1) 2813 elli_source.SetThetaResolution(res_t) 2814 elli_source.SetPhiResolution(res_phi) 2815 elli_source.Update() 2816 2817 super().__init__(elli_source.GetOutput(), c, alpha) 2818 2819 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2820 lt = LinearTransform(matrix).translate(pos) 2821 self.apply_transform(lt) 2822 self.name = "Ellipsoid" 2823 2824 def asphericity(self) -> float: 2825 """ 2826 Return a measure of how different an ellipsoid is from a sphere. 2827 Values close to zero correspond to a spheric object. 2828 """ 2829 a, b, c = self.va, self.vb, self.vc 2830 asp = ( ((a-b)/(a+b))**2 2831 + ((a-c)/(a+c))**2 2832 + ((b-c)/(b+c))**2 ) / 3. * 4. 2833 return float(asp) 2834 2835 def asphericity_error(self) -> float: 2836 """ 2837 Calculate statistical error on the asphericity value. 2838 2839 Errors on the main axes are stored in 2840 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2841 """ 2842 a, b, c = self.va, self.vb, self.vc 2843 sqrtn = np.sqrt(self.nr_of_points) 2844 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2845 2846 # from sympy import * 2847 # init_printing(use_unicode=True) 2848 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2849 # L = ( 2850 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2851 # / 3 * 4) 2852 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2853 # print(dl2) 2854 # exit() 2855 2856 dL2 = ( 2857 ea ** 2 2858 * ( 2859 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2860 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2861 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2862 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2863 ) ** 2 2864 + eb ** 2 2865 * ( 2866 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2867 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2868 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2869 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2870 ) ** 2 2871 + ec ** 2 2872 * ( 2873 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2874 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2875 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2876 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2877 ) ** 2 2878 ) 2879 err = np.sqrt(dL2) 2880 self.va_error = ea 2881 self.vb_error = eb 2882 self.vc_error = ec 2883 return err 2884 2885 2886class Grid(Mesh): 2887 """ 2888 An even or uneven 2D grid. 2889 """ 2890 2891 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2892 """ 2893 Create an even or uneven 2D grid. 2894 Can also be created from a `np.mgrid` object (see example). 2895 2896 Arguments: 2897 pos : (list, Points, Mesh) 2898 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2899 s : (float, list) 2900 if a float is provided it is interpreted as the total size along x and y, 2901 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2902 In this case keyword `res` is ignored (see example below). 2903 res : (list) 2904 resolutions along x and y, e.i. the number of subdivisions 2905 lw : (int) 2906 line width 2907 2908 Example: 2909 ```python 2910 from vedo import * 2911 xcoords = np.arange(0, 2, 0.2) 2912 ycoords = np.arange(0, 1, 0.2) 2913 sqrtx = sqrt(xcoords) 2914 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2915 grid.show(axes=8).close() 2916 2917 # Can also create a grid from a np.mgrid: 2918 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2919 vgrid = Grid(s=(X[:,0], Y[0])) 2920 vgrid.show(axes=8).close() 2921 ``` 2922 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2923 """ 2924 resx, resy = res 2925 sx, sy = s 2926 2927 try: 2928 bb = pos.bounds() 2929 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2930 sx = bb[1] - bb[0] 2931 sy = bb[3] - bb[2] 2932 except AttributeError: 2933 pass 2934 2935 if len(pos) == 2: 2936 pos = (pos[0], pos[1], 0) 2937 elif len(pos) in [4,6]: # passing a bounding box 2938 bb = pos 2939 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2940 sx = bb[1] - bb[0] 2941 sy = bb[3] - bb[2] 2942 if len(pos)==6: 2943 pos[2] = bb[4] - bb[5] 2944 2945 if utils.is_sequence(sx) and utils.is_sequence(sy): 2946 verts = [] 2947 for y in sy: 2948 for x in sx: 2949 verts.append([x, y, 0]) 2950 faces = [] 2951 n = len(sx) 2952 m = len(sy) 2953 for j in range(m - 1): 2954 j1n = (j + 1) * n 2955 for i in range(n - 1): 2956 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2957 2958 super().__init__([verts, faces], c, alpha) 2959 2960 else: 2961 ps = vtki.new("PlaneSource") 2962 ps.SetResolution(resx, resy) 2963 ps.Update() 2964 2965 t = vtki.vtkTransform() 2966 t.Translate(pos) 2967 t.Scale(sx, sy, 1) 2968 2969 tf = vtki.new("TransformPolyDataFilter") 2970 tf.SetInputData(ps.GetOutput()) 2971 tf.SetTransform(t) 2972 tf.Update() 2973 2974 super().__init__(tf.GetOutput(), c, alpha) 2975 2976 self.wireframe().lw(lw) 2977 self.properties.LightingOff() 2978 self.name = "Grid" 2979 2980 2981class Plane(Mesh): 2982 """Create a plane in space.""" 2983 2984 def __init__( 2985 self, 2986 pos=(0, 0, 0), 2987 normal=(0, 0, 1), 2988 s=(1, 1), 2989 res=(1, 1), 2990 c="gray5", alpha=1.0, 2991 ) -> None: 2992 """ 2993 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 2994 to vector `normal` so that it passes through point `pos`. 2995 2996 Arguments: 2997 pos : (list) 2998 position of the plane center 2999 normal : (list) 3000 normal vector to the plane 3001 s : (list) 3002 size of the plane along x and y 3003 res : (list) 3004 resolution of the plane along x and y 3005 """ 3006 if isinstance(pos, vtki.vtkPolyData): 3007 super().__init__(pos, c, alpha) 3008 # self.transform = LinearTransform().translate(pos) 3009 3010 else: 3011 ps = vtki.new("PlaneSource") 3012 ps.SetResolution(res[0], res[1]) 3013 tri = vtki.new("TriangleFilter") 3014 tri.SetInputConnection(ps.GetOutputPort()) 3015 tri.Update() 3016 3017 super().__init__(tri.GetOutput(), c, alpha) 3018 3019 pos = utils.make3d(pos) 3020 normal = np.asarray(normal, dtype=float) 3021 axis = normal / np.linalg.norm(normal) 3022 theta = np.arccos(axis[2]) 3023 phi = np.arctan2(axis[1], axis[0]) 3024 3025 t = LinearTransform() 3026 t.scale([s[0], s[1], 1]) 3027 t.rotate_y(np.rad2deg(theta)) 3028 t.rotate_z(np.rad2deg(phi)) 3029 t.translate(pos) 3030 self.apply_transform(t) 3031 3032 self.lighting("off") 3033 self.name = "Plane" 3034 self.variance = 0 3035 3036 def clone(self, deep=True) -> "Plane": 3037 newplane = Plane() 3038 if deep: 3039 newplane.dataset.DeepCopy(self.dataset) 3040 else: 3041 newplane.dataset.ShallowCopy(self.dataset) 3042 newplane.copy_properties_from(self) 3043 newplane.transform = self.transform.clone() 3044 newplane.variance = 0 3045 return newplane 3046 3047 @property 3048 def normal(self) -> np.ndarray: 3049 pts = self.vertices 3050 # this is necessary because plane can have high resolution 3051 # p0, p1 = pts[0], pts[1] 3052 # AB = p1 - p0 3053 # AB /= np.linalg.norm(AB) 3054 # for pt in pts[2:]: 3055 # AC = pt - p0 3056 # AC /= np.linalg.norm(AC) 3057 # cosine_angle = np.dot(AB, AC) 3058 # if abs(cosine_angle) < 0.99: 3059 # normal = np.cross(AB, AC) 3060 # return normal / np.linalg.norm(normal) 3061 p0, p1, p2 = pts[0], pts[1], pts[int(len(pts)/2 +0.5)] 3062 AB = p1 - p0 3063 AB /= np.linalg.norm(AB) 3064 AC = p2 - p0 3065 AC /= np.linalg.norm(AC) 3066 normal = np.cross(AB, AC) 3067 return normal / np.linalg.norm(normal) 3068 3069 @property 3070 def center(self) -> np.ndarray: 3071 pts = self.vertices 3072 return np.mean(pts, axis=0) 3073 3074 def contains(self, points, tol=0) -> np.ndarray: 3075 """ 3076 Check if each of the provided point lies on this plane. 3077 `points` is an array of shape (n, 3). 3078 """ 3079 points = np.array(points, dtype=float) 3080 bounds = self.vertices 3081 3082 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3083 3084 for i in [1, 3]: 3085 AB = bounds[i] - bounds[0] 3086 AP = points - bounds[0] 3087 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3088 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3089 mask = np.logical_and(mask, mask_l) 3090 mask = np.logical_and(mask, mask_g) 3091 return mask 3092 3093 3094class Rectangle(Mesh): 3095 """ 3096 Build a rectangle in the xy plane. 3097 """ 3098 3099 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3100 """ 3101 Build a rectangle in the xy plane identified by any two corner points. 3102 3103 Arguments: 3104 p1 : (list) 3105 bottom-left position of the corner 3106 p2 : (list) 3107 top-right position of the corner 3108 radius : (float, list) 3109 smoothing radius of the corner in world units. 3110 A list can be passed with 4 individual values. 3111 """ 3112 if len(p1) == 2: 3113 p1 = np.array([p1[0], p1[1], 0.0]) 3114 else: 3115 p1 = np.array(p1, dtype=float) 3116 if len(p2) == 2: 3117 p2 = np.array([p2[0], p2[1], 0.0]) 3118 else: 3119 p2 = np.array(p2, dtype=float) 3120 3121 self.corner1 = p1 3122 self.corner2 = p2 3123 3124 color = c 3125 smoothr = False 3126 risseq = False 3127 if utils.is_sequence(radius): 3128 risseq = True 3129 smoothr = True 3130 if max(radius) == 0: 3131 smoothr = False 3132 elif radius: 3133 smoothr = True 3134 3135 if not smoothr: 3136 radius = None 3137 self.radius = radius 3138 3139 if smoothr: 3140 r = radius 3141 if not risseq: 3142 r = [r, r, r, r] 3143 rd, ra, rb, rc = r 3144 3145 if p1[0] > p2[0]: # flip p1 - p2 3146 p1, p2 = p2, p1 3147 if p1[1] > p2[1]: # flip p1y - p2y 3148 p1[1], p2[1] = p2[1], p1[1] 3149 3150 px, py, _ = p2 - p1 3151 k = min(px / 2, py / 2) 3152 ra = min(abs(ra), k) 3153 rb = min(abs(rb), k) 3154 rc = min(abs(rc), k) 3155 rd = min(abs(rd), k) 3156 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3157 betas = np.split(beta, 4) 3158 rrx = np.cos(betas) 3159 rry = np.sin(betas) 3160 3161 q1 = (rd, 0) 3162 # q2 = (px-ra, 0) 3163 q3 = (px, ra) 3164 # q4 = (px, py-rb) 3165 q5 = (px - rb, py) 3166 # q6 = (rc, py) 3167 q7 = (0, py - rc) 3168 # q8 = (0, rd) 3169 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3170 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3171 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3172 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3173 3174 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3175 faces = [list(range(len(pts)))] 3176 else: 3177 p1r = np.array([p2[0], p1[1], 0.0]) 3178 p2l = np.array([p1[0], p2[1], 0.0]) 3179 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3180 faces = [(0, 1, 2, 3)] 3181 3182 super().__init__([pts, faces], color, alpha) 3183 self.pos(p1) 3184 self.properties.LightingOff() 3185 self.name = "Rectangle" 3186 3187 3188class Box(Mesh): 3189 """ 3190 Build a box of specified dimensions. 3191 """ 3192 3193 def __init__( 3194 self, pos=(0, 0, 0), 3195 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3196 """ 3197 Build a box of dimensions `x=length, y=width and z=height`. 3198 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3199 3200 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3201 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3202 3203 Note that the shape polygonal data contains duplicated vertices. This is to allow 3204 each face to have its own normal, which is essential for some operations. 3205 Use the `clean()` method to remove duplicate points. 3206 3207 Examples: 3208 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3209 3210 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3211 """ 3212 src = vtki.new("CubeSource") 3213 3214 if len(pos) == 2: 3215 pos = (pos[0], pos[1], 0) 3216 3217 if len(pos) == 6: 3218 src.SetBounds(pos) 3219 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3220 elif len(size) == 3: 3221 length, width, height = size 3222 src.SetXLength(length) 3223 src.SetYLength(width) 3224 src.SetZLength(height) 3225 src.SetCenter(pos) 3226 else: 3227 src.SetXLength(length) 3228 src.SetYLength(width) 3229 src.SetZLength(height) 3230 src.SetCenter(pos) 3231 3232 src.Update() 3233 pd = src.GetOutput() 3234 3235 tc = [ 3236 [0.0, 0.0], 3237 [1.0, 0.0], 3238 [0.0, 1.0], 3239 [1.0, 1.0], 3240 [1.0, 0.0], 3241 [0.0, 0.0], 3242 [1.0, 1.0], 3243 [0.0, 1.0], 3244 [1.0, 1.0], 3245 [1.0, 0.0], 3246 [0.0, 1.0], 3247 [0.0, 0.0], 3248 [0.0, 1.0], 3249 [0.0, 0.0], 3250 [1.0, 1.0], 3251 [1.0, 0.0], 3252 [1.0, 0.0], 3253 [0.0, 0.0], 3254 [1.0, 1.0], 3255 [0.0, 1.0], 3256 [0.0, 0.0], 3257 [1.0, 0.0], 3258 [0.0, 1.0], 3259 [1.0, 1.0], 3260 ] 3261 vtc = utils.numpy2vtk(tc) 3262 pd.GetPointData().SetTCoords(vtc) 3263 super().__init__(pd, c, alpha) 3264 self.transform = LinearTransform().translate(pos) 3265 self.name = "Box" 3266 3267 3268class Cube(Box): 3269 """ 3270 Build a cube shape. 3271 3272 Note that the shape polygonal data contains duplicated vertices. This is to allow 3273 each face to have its own normal, which is essential for some operations. 3274 Use the `clean()` method to remove duplicate points. 3275 """ 3276 3277 def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None: 3278 """Build a cube of size `side`.""" 3279 super().__init__(pos, side, side, side, (), c, alpha) 3280 self.name = "Cube" 3281 3282 3283class TessellatedBox(Mesh): 3284 """ 3285 Build a cubic `Mesh` made of quads. 3286 """ 3287 3288 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3289 """ 3290 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3291 3292 Arguments: 3293 pos : (list) 3294 position of the left bottom corner 3295 n : (int, list) 3296 number of subdivisions along each side 3297 spacing : (float) 3298 size of the side of the single quad in the 3 directions 3299 """ 3300 if utils.is_sequence(n): # slow 3301 img = vtki.vtkImageData() 3302 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3303 img.SetSpacing(spacing) 3304 gf = vtki.new("GeometryFilter") 3305 gf.SetInputData(img) 3306 gf.Update() 3307 poly = gf.GetOutput() 3308 else: # fast 3309 n -= 1 3310 tbs = vtki.new("TessellatedBoxSource") 3311 tbs.SetLevel(n) 3312 if len(bounds): 3313 tbs.SetBounds(bounds) 3314 else: 3315 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3316 tbs.QuadsOn() 3317 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3318 tbs.Update() 3319 poly = tbs.GetOutput() 3320 super().__init__(poly, c=c, alpha=alpha) 3321 self.pos(pos) 3322 self.lw(1).lighting("off") 3323 self.name = "TessellatedBox" 3324 3325 3326class Spring(Mesh): 3327 """ 3328 Build a spring model. 3329 """ 3330 3331 def __init__( 3332 self, 3333 start_pt=(0, 0, 0), 3334 end_pt=(1, 0, 0), 3335 coils=20, 3336 r1=0.1, 3337 r2=None, 3338 thickness=None, 3339 c="gray5", 3340 alpha=1.0, 3341 ) -> None: 3342 """ 3343 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3344 3345 Arguments: 3346 coils : (int) 3347 number of coils 3348 r1 : (float) 3349 radius at start point 3350 r2 : (float) 3351 radius at end point 3352 thickness : (float) 3353 thickness of the coil section 3354 """ 3355 start_pt = utils.make3d(start_pt) 3356 end_pt = utils.make3d(end_pt) 3357 3358 diff = end_pt - start_pt 3359 length = np.linalg.norm(diff) 3360 if not length: 3361 return 3362 if not r1: 3363 r1 = length / 20 3364 trange = np.linspace(0, length, num=50 * coils) 3365 om = 6.283 * (coils - 0.5) / length 3366 if not r2: 3367 r2 = r1 3368 pts = [] 3369 for t in trange: 3370 f = (length - t) / length 3371 rd = r1 * f + r2 * (1 - f) 3372 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3373 3374 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3375 diff = diff / length 3376 theta = np.arccos(diff[2]) 3377 phi = np.arctan2(diff[1], diff[0]) 3378 sp = Line(pts) 3379 3380 t = vtki.vtkTransform() 3381 t.Translate(start_pt) 3382 t.RotateZ(np.rad2deg(phi)) 3383 t.RotateY(np.rad2deg(theta)) 3384 3385 tf = vtki.new("TransformPolyDataFilter") 3386 tf.SetInputData(sp.dataset) 3387 tf.SetTransform(t) 3388 tf.Update() 3389 3390 tuf = vtki.new("TubeFilter") 3391 tuf.SetNumberOfSides(12) 3392 tuf.CappingOn() 3393 tuf.SetInputData(tf.GetOutput()) 3394 if not thickness: 3395 thickness = r1 / 10 3396 tuf.SetRadius(thickness) 3397 tuf.Update() 3398 3399 super().__init__(tuf.GetOutput(), c, alpha) 3400 3401 self.phong() 3402 self.base = np.array(start_pt, dtype=float) 3403 self.top = np.array(end_pt, dtype=float) 3404 self.name = "Spring" 3405 3406 3407class Cylinder(Mesh): 3408 """ 3409 Build a cylinder of specified height and radius. 3410 """ 3411 3412 def __init__( 3413 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3414 cap=True, res=24, c="teal3", alpha=1.0 3415 ) -> None: 3416 """ 3417 Build a cylinder of specified height and radius `r`, centered at `pos`. 3418 3419 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3420 centered at `v1` and top at `v2`. 3421 3422 Arguments: 3423 cap : (bool) 3424 enable/disable the caps of the cylinder 3425 res : (int) 3426 resolution of the cylinder sides 3427 3428 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3429 """ 3430 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3431 base = np.array(pos[0], dtype=float) 3432 top = np.array(pos[1], dtype=float) 3433 pos = (base + top) / 2 3434 height = np.linalg.norm(top - base) 3435 axis = top - base 3436 axis = utils.versor(axis) 3437 else: 3438 axis = utils.versor(axis) 3439 base = pos - axis * height / 2 3440 top = pos + axis * height / 2 3441 3442 cyl = vtki.new("CylinderSource") 3443 cyl.SetResolution(res) 3444 cyl.SetRadius(r) 3445 cyl.SetHeight(height) 3446 cyl.SetCapping(cap) 3447 cyl.Update() 3448 3449 theta = np.arccos(axis[2]) 3450 phi = np.arctan2(axis[1], axis[0]) 3451 t = vtki.vtkTransform() 3452 t.PostMultiply() 3453 t.RotateX(90) # put it along Z 3454 t.RotateY(np.rad2deg(theta)) 3455 t.RotateZ(np.rad2deg(phi)) 3456 t.Translate(pos) 3457 3458 tf = vtki.new("TransformPolyDataFilter") 3459 tf.SetInputData(cyl.GetOutput()) 3460 tf.SetTransform(t) 3461 tf.Update() 3462 3463 super().__init__(tf.GetOutput(), c, alpha) 3464 3465 self.phong() 3466 self.base = base 3467 self.top = top 3468 self.transform = LinearTransform().translate(pos) 3469 self.name = "Cylinder" 3470 3471 3472class Cone(Mesh): 3473 """Build a cone of specified radius and height.""" 3474 3475 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3476 res=48, c="green3", alpha=1.0) -> None: 3477 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3478 con = vtki.new("ConeSource") 3479 con.SetResolution(res) 3480 con.SetRadius(r) 3481 con.SetHeight(height) 3482 con.SetDirection(axis) 3483 con.Update() 3484 super().__init__(con.GetOutput(), c, alpha) 3485 self.phong() 3486 if len(pos) == 2: 3487 pos = (pos[0], pos[1], 0) 3488 self.pos(pos) 3489 v = utils.versor(axis) * height / 2 3490 self.base = pos - v 3491 self.top = pos + v 3492 self.name = "Cone" 3493 3494 3495class Pyramid(Cone): 3496 """Build a pyramidal shape.""" 3497 3498 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3499 c="green3", alpha=1) -> None: 3500 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3501 super().__init__(pos, s, height, axis, 4, c, alpha) 3502 self.name = "Pyramid" 3503 3504 3505class Torus(Mesh): 3506 """ 3507 Build a toroidal shape. 3508 """ 3509 3510 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3511 """ 3512 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3513 If `quad=True` a quad-mesh is generated. 3514 """ 3515 if utils.is_sequence(res): 3516 res_u, res_v = res 3517 else: 3518 res_u, res_v = 3 * res, res 3519 3520 if quads: 3521 # https://github.com/marcomusy/vedo/issues/710 3522 3523 n = res_v 3524 m = res_u 3525 3526 theta = np.linspace(0, 2.0 * np.pi, n) 3527 phi = np.linspace(0, 2.0 * np.pi, m) 3528 theta, phi = np.meshgrid(theta, phi) 3529 t = r1 + r2 * np.cos(theta) 3530 x = t * np.cos(phi) 3531 y = t * np.sin(phi) 3532 z = r2 * np.sin(theta) 3533 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3534 3535 faces = [] 3536 for j in range(m - 1): 3537 j1n = (j + 1) * n 3538 for i in range(n - 1): 3539 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3540 3541 super().__init__([pts, faces], c, alpha) 3542 3543 else: 3544 rs = vtki.new("ParametricTorus") 3545 rs.SetRingRadius(r1) 3546 rs.SetCrossSectionRadius(r2) 3547 pfs = vtki.new("ParametricFunctionSource") 3548 pfs.SetParametricFunction(rs) 3549 pfs.SetUResolution(res_u) 3550 pfs.SetVResolution(res_v) 3551 pfs.Update() 3552 3553 super().__init__(pfs.GetOutput(), c, alpha) 3554 3555 self.phong() 3556 if len(pos) == 2: 3557 pos = (pos[0], pos[1], 0) 3558 self.pos(pos) 3559 self.name = "Torus" 3560 3561 3562class Paraboloid(Mesh): 3563 """ 3564 Build a paraboloid. 3565 """ 3566 3567 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3568 """ 3569 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3570 3571 Full volumetric expression is: 3572 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3573 3574 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3575 """ 3576 quadric = vtki.new("Quadric") 3577 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3578 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3579 # + a3*x*y + a4*y*z + a5*x*z 3580 # + a6*x + a7*y + a8*z +a9 3581 sample = vtki.new("SampleFunction") 3582 sample.SetSampleDimensions(res, res, res) 3583 sample.SetImplicitFunction(quadric) 3584 3585 contours = vtki.new("ContourFilter") 3586 contours.SetInputConnection(sample.GetOutputPort()) 3587 contours.GenerateValues(1, 0.01, 0.01) 3588 contours.Update() 3589 3590 super().__init__(contours.GetOutput(), c, alpha) 3591 self.compute_normals().phong() 3592 self.mapper.ScalarVisibilityOff() 3593 self.pos(pos) 3594 self.name = "Paraboloid" 3595 3596 3597class Hyperboloid(Mesh): 3598 """ 3599 Build a hyperboloid. 3600 """ 3601 3602 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3603 """ 3604 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3605 3606 Full volumetric expression is: 3607 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3608 """ 3609 q = vtki.new("Quadric") 3610 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3611 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3612 # + a3*x*y + a4*y*z + a5*x*z 3613 # + a6*x + a7*y + a8*z +a9 3614 sample = vtki.new("SampleFunction") 3615 sample.SetSampleDimensions(res, res, res) 3616 sample.SetImplicitFunction(q) 3617 3618 contours = vtki.new("ContourFilter") 3619 contours.SetInputConnection(sample.GetOutputPort()) 3620 contours.GenerateValues(1, value, value) 3621 contours.Update() 3622 3623 super().__init__(contours.GetOutput(), c, alpha) 3624 self.compute_normals().phong() 3625 self.mapper.ScalarVisibilityOff() 3626 self.pos(pos) 3627 self.name = "Hyperboloid" 3628 3629 3630def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True) -> Any: 3631 """ 3632 Generate a marker shape. Typically used in association with `Glyph`. 3633 """ 3634 if isinstance(symbol, Mesh): 3635 return symbol.c(c).alpha(alpha).lighting("off") 3636 3637 if isinstance(symbol, int): 3638 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 3639 symbol = symbol % len(symbs) 3640 symbol = symbs[symbol] 3641 3642 if symbol == ".": 3643 mesh = Polygon(nsides=24, r=s * 0.6) 3644 elif symbol == "o": 3645 mesh = Polygon(nsides=24, r=s * 0.75) 3646 elif symbol == "O": 3647 mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3648 elif symbol == "0": 3649 m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3650 m2 = Circle(r=s * 0.36).reverse() 3651 mesh = merge(m1, m2) 3652 elif symbol == "p": 3653 mesh = Polygon(nsides=5, r=s) 3654 elif symbol == "*": 3655 mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled) 3656 elif symbol == "h": 3657 mesh = Polygon(nsides=6, r=s) 3658 elif symbol == "D": 3659 mesh = Polygon(nsides=4, r=s) 3660 elif symbol == "d": 3661 mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1]) 3662 elif symbol == "v": 3663 mesh = Polygon(nsides=3, r=s).rotate_z(180) 3664 elif symbol == "^": 3665 mesh = Polygon(nsides=3, r=s) 3666 elif symbol == ">": 3667 mesh = Polygon(nsides=3, r=s).rotate_z(-90) 3668 elif symbol == "<": 3669 mesh = Polygon(nsides=3, r=s).rotate_z(90) 3670 elif symbol == "s": 3671 mesh = Mesh( 3672 [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]] 3673 ).scale(s / 1.4) 3674 elif symbol == "x": 3675 mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3676 # mesh.rotate_z(45) 3677 elif symbol == "a": 3678 mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3679 else: 3680 mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0) 3681 mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha) 3682 if len(pos) == 2: 3683 pos = (pos[0], pos[1], 0) 3684 mesh.pos(pos) 3685 mesh.name = "Marker" 3686 return mesh 3687 3688 3689class Brace(Mesh): 3690 """ 3691 Create a brace (bracket) shape. 3692 """ 3693 3694 def __init__( 3695 self, 3696 q1, 3697 q2, 3698 style="}", 3699 padding1=0.0, 3700 font="Theemim", 3701 comment="", 3702 justify=None, 3703 angle=0.0, 3704 padding2=0.2, 3705 s=1.0, 3706 italic=0, 3707 c="k1", 3708 alpha=1.0, 3709 ) -> None: 3710 """ 3711 Create a brace (bracket) shape which spans from point q1 to point q2. 3712 3713 Arguments: 3714 q1 : (list) 3715 point 1. 3716 q2 : (list) 3717 point 2. 3718 style : (str) 3719 style of the bracket, eg. `{}, [], (), <>`. 3720 padding1 : (float) 3721 padding space in percent form the input points. 3722 font : (str) 3723 font type 3724 comment : (str) 3725 additional text to appear next to the brace symbol. 3726 justify : (str) 3727 specify the anchor point to justify text comment, e.g. "top-left". 3728 italic : float 3729 italicness of the text comment (can be a positive or negative number) 3730 angle : (float) 3731 rotation angle of text. Use `None` to keep it horizontal. 3732 padding2 : (float) 3733 padding space in percent form brace to text comment. 3734 s : (float) 3735 scale factor for the comment 3736 3737 Examples: 3738 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3739 3740 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3741 """ 3742 if isinstance(q1, vtki.vtkActor): 3743 q1 = q1.GetPosition() 3744 if isinstance(q2, vtki.vtkActor): 3745 q2 = q2.GetPosition() 3746 if len(q1) == 2: 3747 q1 = [q1[0], q1[1], 0.0] 3748 if len(q2) == 2: 3749 q2 = [q2[0], q2[1], 0.0] 3750 q1 = np.array(q1, dtype=float) 3751 q2 = np.array(q2, dtype=float) 3752 mq = (q1 + q2) / 2 3753 q1 = q1 - mq 3754 q2 = q2 - mq 3755 d = np.linalg.norm(q2 - q1) 3756 q2[2] = q1[2] 3757 3758 if style not in "{}[]()<>|I": 3759 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3760 style = "}" 3761 3762 flip = False 3763 if style in ["{", "[", "(", "<"]: 3764 flip = True 3765 i = ["{", "[", "(", "<"].index(style) 3766 style = ["}", "]", ")", ">"][i] 3767 3768 br = Text3D(style, font="Theemim", justify="center-left") 3769 br.scale([0.4, 1, 1]) 3770 3771 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3772 if flip: 3773 angler += 180 3774 3775 _, x1, y0, y1, _, _ = br.bounds() 3776 if comment: 3777 just = "center-top" 3778 if angle is None: 3779 angle = -angler + 90 3780 if not flip: 3781 angle += 180 3782 3783 if flip: 3784 angle += 180 3785 just = "center-bottom" 3786 if justify is not None: 3787 just = justify 3788 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3789 cx0, cx1 = cmt.xbounds() 3790 cmt.rotate_z(90 + angle) 3791 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3792 cmt.shift(x1 * (1 + padding2), 0, 0) 3793 poly = merge(br, cmt).dataset 3794 3795 else: 3796 poly = br.dataset 3797 3798 tr = vtki.vtkTransform() 3799 tr.Translate(mq) 3800 tr.RotateZ(angler) 3801 tr.Translate(padding1 * d, 0, 0) 3802 pscale = 1 3803 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3804 3805 tf = vtki.new("TransformPolyDataFilter") 3806 tf.SetInputData(poly) 3807 tf.SetTransform(tr) 3808 tf.Update() 3809 poly = tf.GetOutput() 3810 3811 super().__init__(poly, c, alpha) 3812 3813 self.base = q1 3814 self.top = q2 3815 self.name = "Brace" 3816 3817 3818class Star3D(Mesh): 3819 """ 3820 Build a 3D starred shape. 3821 """ 3822 3823 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3824 """ 3825 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3826 """ 3827 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3828 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3829 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3830 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3831 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3832 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3833 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3834 [10,1, 0],[10,11, 9]] 3835 3836 super().__init__([pts, fcs], c, alpha) 3837 self.rotate_x(90) 3838 self.scale(r).lighting("shiny") 3839 3840 if len(pos) == 2: 3841 pos = (pos[0], pos[1], 0) 3842 self.pos(pos) 3843 self.name = "Star3D" 3844 3845 3846class Cross3D(Mesh): 3847 """ 3848 Build a 3D cross shape. 3849 """ 3850 3851 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3852 """ 3853 Build a 3D cross shape, mainly useful as a 3D marker. 3854 """ 3855 if len(pos) == 2: 3856 pos = (pos[0], pos[1], 0) 3857 3858 c1 = Cylinder(r=thickness * s, height=2 * s) 3859 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3860 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3861 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3862 super().__init__(poly, c, alpha) 3863 self.name = "Cross3D" 3864 3865 3866class ParametricShape(Mesh): 3867 """ 3868 A set of built-in shapes mainly for illustration purposes. 3869 """ 3870 3871 def __init__(self, name, res=51, n=25, seed=1): 3872 """ 3873 A set of built-in shapes mainly for illustration purposes. 3874 3875 Name can be an integer or a string in this list: 3876 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3877 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3878 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3879 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3880 3881 Example: 3882 ```python 3883 from vedo import * 3884 settings.immediate_rendering = False 3885 plt = Plotter(N=18) 3886 for i in range(18): 3887 ps = ParametricShape(i).color(i) 3888 plt.at(i).show(ps, ps.name) 3889 plt.interactive().close() 3890 ``` 3891 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3892 """ 3893 3894 shapes = [ 3895 "Boy", 3896 "ConicSpiral", 3897 "CrossCap", 3898 "Enneper", 3899 "Figure8Klein", 3900 "Klein", 3901 "Dini", 3902 "Mobius", 3903 "RandomHills", 3904 "Roman", 3905 "SuperEllipsoid", 3906 "BohemianDome", 3907 "Bour", 3908 "CatalanMinimal", 3909 "Henneberg", 3910 "Kuen", 3911 "PluckerConoid", 3912 "Pseudosphere", 3913 ] 3914 3915 if isinstance(name, int): 3916 name = name % len(shapes) 3917 name = shapes[name] 3918 3919 if name == "Boy": 3920 ps = vtki.new("ParametricBoy") 3921 elif name == "ConicSpiral": 3922 ps = vtki.new("ParametricConicSpiral") 3923 elif name == "CrossCap": 3924 ps = vtki.new("ParametricCrossCap") 3925 elif name == "Dini": 3926 ps = vtki.new("ParametricDini") 3927 elif name == "Enneper": 3928 ps = vtki.new("ParametricEnneper") 3929 elif name == "Figure8Klein": 3930 ps = vtki.new("ParametricFigure8Klein") 3931 elif name == "Klein": 3932 ps = vtki.new("ParametricKlein") 3933 elif name == "Mobius": 3934 ps = vtki.new("ParametricMobius") 3935 ps.SetRadius(2.0) 3936 ps.SetMinimumV(-0.5) 3937 ps.SetMaximumV(0.5) 3938 elif name == "RandomHills": 3939 ps = vtki.new("ParametricRandomHills") 3940 ps.AllowRandomGenerationOn() 3941 ps.SetRandomSeed(seed) 3942 ps.SetNumberOfHills(n) 3943 elif name == "Roman": 3944 ps = vtki.new("ParametricRoman") 3945 elif name == "SuperEllipsoid": 3946 ps = vtki.new("ParametricSuperEllipsoid") 3947 ps.SetN1(0.5) 3948 ps.SetN2(0.4) 3949 elif name == "BohemianDome": 3950 ps = vtki.new("ParametricBohemianDome") 3951 ps.SetA(5.0) 3952 ps.SetB(1.0) 3953 ps.SetC(2.0) 3954 elif name == "Bour": 3955 ps = vtki.new("ParametricBour") 3956 elif name == "CatalanMinimal": 3957 ps = vtki.new("ParametricCatalanMinimal") 3958 elif name == "Henneberg": 3959 ps = vtki.new("ParametricHenneberg") 3960 elif name == "Kuen": 3961 ps = vtki.new("ParametricKuen") 3962 ps.SetDeltaV0(0.001) 3963 elif name == "PluckerConoid": 3964 ps = vtki.new("ParametricPluckerConoid") 3965 elif name == "Pseudosphere": 3966 ps = vtki.new("ParametricPseudosphere") 3967 else: 3968 vedo.logger.error(f"unknown ParametricShape {name}") 3969 return 3970 3971 pfs = vtki.new("ParametricFunctionSource") 3972 pfs.SetParametricFunction(ps) 3973 pfs.SetUResolution(res) 3974 pfs.SetVResolution(res) 3975 pfs.SetWResolution(res) 3976 pfs.SetScalarModeToZ() 3977 pfs.Update() 3978 3979 super().__init__(pfs.GetOutput()) 3980 3981 if name == "RandomHills": self.shift([0,-10,-2.25]) 3982 if name != 'Kuen': self.normalize() 3983 if name == 'Dini': self.scale(0.4) 3984 if name == 'Enneper': self.scale(0.4) 3985 if name == 'ConicSpiral': self.bc('tomato') 3986 self.name = name 3987 3988 3989@lru_cache(None) 3990def _load_font(font) -> np.ndarray: 3991 # print('_load_font()', font) 3992 3993 if utils.is_number(font): 3994 font = list(settings.font_parameters.keys())[int(font)] 3995 3996 if font.endswith(".npz"): # user passed font as a local path 3997 fontfile = font 3998 font = os.path.basename(font).split(".")[0] 3999 4000 elif font.startswith("https"): # user passed URL link, make it a path 4001 try: 4002 fontfile = vedo.file_io.download(font, verbose=False, force=False) 4003 font = os.path.basename(font).split(".")[0] 4004 except: 4005 vedo.logger.warning(f"font {font} not found") 4006 font = settings.default_font 4007 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4008 4009 else: # user passed font by its standard name 4010 font = font[:1].upper() + font[1:] # capitalize first letter only 4011 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4012 4013 if font not in settings.font_parameters.keys(): 4014 font = "Normografo" 4015 vedo.logger.warning( 4016 f"Unknown font: {font}\n" 4017 f"Available 3D fonts are: " 4018 f"{list(settings.font_parameters.keys())}\n" 4019 f"Using font {font} instead." 4020 ) 4021 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4022 4023 if not settings.font_parameters[font]["islocal"]: 4024 font = "https://vedo.embl.es/fonts/" + font + ".npz" 4025 try: 4026 fontfile = vedo.file_io.download(font, verbose=False, force=False) 4027 font = os.path.basename(font).split(".")[0] 4028 except: 4029 vedo.logger.warning(f"font {font} not found") 4030 font = settings.default_font 4031 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4032 4033 ##### 4034 try: 4035 font_meshes = np.load(fontfile, allow_pickle=True)["font"][0] 4036 except: 4037 vedo.logger.warning(f"font name {font} not found.") 4038 raise RuntimeError 4039 return font_meshes 4040 4041 4042@lru_cache(None) 4043def _get_font_letter(font, letter): 4044 # print("_get_font_letter", font, letter) 4045 font_meshes = _load_font(font) 4046 try: 4047 pts, faces = font_meshes[letter] 4048 return utils.buildPolyData(pts.astype(float), faces) 4049 except KeyError: 4050 return None 4051 4052 4053class Text3D(Mesh): 4054 """ 4055 Generate a 3D polygonal Mesh to represent a text string. 4056 """ 4057 4058 def __init__( 4059 self, 4060 txt, 4061 pos=(0, 0, 0), 4062 s=1.0, 4063 font="", 4064 hspacing=1.15, 4065 vspacing=2.15, 4066 depth=0.0, 4067 italic=False, 4068 justify="bottom-left", 4069 literal=False, 4070 c=None, 4071 alpha=1.0, 4072 ) -> None: 4073 """ 4074 Generate a 3D polygonal `Mesh` representing a text string. 4075 4076 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4077 Most Latex symbols are also supported. 4078 4079 Symbols `~ ^ _` are reserved modifiers: 4080 - use ~ to add a short space, 1/4 of the default empty space, 4081 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4082 4083 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4084 4085 More fonts at: https://vedo.embl.es/fonts/ 4086 4087 Arguments: 4088 pos : (list) 4089 position coordinates in 3D space 4090 s : (float) 4091 vertical size of the text (as scaling factor) 4092 depth : (float) 4093 text thickness (along z) 4094 italic : (bool), float 4095 italic font type (can be a signed float too) 4096 justify : (str) 4097 text justification as centering of the bounding box 4098 (bottom-left, bottom-right, top-left, top-right, centered) 4099 font : (str, int) 4100 some of the available 3D-polygonized fonts are: 4101 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4102 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4103 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4104 4105 Check for more at https://vedo.embl.es/fonts/ 4106 4107 Or type in your terminal `vedo --run fonts`. 4108 4109 Default is Normografo, which can be changed using `settings.default_font`. 4110 4111 hspacing : (float) 4112 horizontal spacing of the font 4113 vspacing : (float) 4114 vertical spacing of the font for multiple lines text 4115 literal : (bool) 4116 if set to True will ignore modifiers like _ or ^ 4117 4118 Examples: 4119 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4120 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4121 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4122 4123 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4124 4125 .. note:: Type `vedo -r fonts` for a demo. 4126 """ 4127 if len(pos) == 2: 4128 pos = (pos[0], pos[1], 0) 4129 4130 if c is None: # automatic black or white 4131 pli = vedo.plotter_instance 4132 if pli and pli.renderer: 4133 c = (0.9, 0.9, 0.9) 4134 if pli.renderer.GetGradientBackground(): 4135 bgcol = pli.renderer.GetBackground2() 4136 else: 4137 bgcol = pli.renderer.GetBackground() 4138 if np.sum(bgcol) > 1.5: 4139 c = (0.1, 0.1, 0.1) 4140 else: 4141 c = (0.6, 0.6, 0.6) 4142 4143 tpoly = self._get_text3d_poly( 4144 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4145 ) 4146 4147 super().__init__(tpoly, c, alpha) 4148 4149 self.pos(pos) 4150 self.lighting("off") 4151 4152 self.actor.PickableOff() 4153 self.actor.DragableOff() 4154 self.init_scale = s 4155 self.name = "Text3D" 4156 self.txt = txt 4157 self.justify = justify 4158 4159 def text( 4160 self, 4161 txt=None, 4162 s=1, 4163 font="", 4164 hspacing=1.15, 4165 vspacing=2.15, 4166 depth=0, 4167 italic=False, 4168 justify="", 4169 literal=False, 4170 ) -> "Text3D": 4171 """ 4172 Update the text and some of its properties. 4173 4174 Check [available fonts here](https://vedo.embl.es/fonts). 4175 """ 4176 if txt is None: 4177 return self.txt 4178 if not justify: 4179 justify = self.justify 4180 4181 poly = self._get_text3d_poly( 4182 txt, self.init_scale * s, font, hspacing, vspacing, 4183 depth, italic, justify, literal 4184 ) 4185 4186 # apply the current transformation to the new polydata 4187 tf = vtki.new("TransformPolyDataFilter") 4188 tf.SetInputData(poly) 4189 tf.SetTransform(self.transform.T) 4190 tf.Update() 4191 tpoly = tf.GetOutput() 4192 4193 self._update(tpoly) 4194 self.txt = txt 4195 return self 4196 4197 def _get_text3d_poly( 4198 self, 4199 txt, 4200 s=1, 4201 font="", 4202 hspacing=1.15, 4203 vspacing=2.15, 4204 depth=0, 4205 italic=False, 4206 justify="bottom-left", 4207 literal=False, 4208 ) -> vtki.vtkPolyData: 4209 if not font: 4210 font = settings.default_font 4211 4212 txt = str(txt) 4213 4214 if font == "VTK": ####################################### 4215 vtt = vtki.new("VectorText") 4216 vtt.SetText(txt) 4217 vtt.Update() 4218 tpoly = vtt.GetOutput() 4219 4220 else: ################################################### 4221 4222 stxt = set(txt) # check here if null or only spaces 4223 if not txt or (len(stxt) == 1 and " " in stxt): 4224 return vtki.vtkPolyData() 4225 4226 if italic is True: 4227 italic = 1 4228 4229 if isinstance(font, int): 4230 lfonts = list(settings.font_parameters.keys()) 4231 font = font % len(lfonts) 4232 font = lfonts[font] 4233 4234 if font not in settings.font_parameters.keys(): 4235 fpars = settings.font_parameters["Normografo"] 4236 else: 4237 fpars = settings.font_parameters[font] 4238 4239 # ad hoc adjustments 4240 mono = fpars["mono"] 4241 lspacing = fpars["lspacing"] 4242 hspacing *= fpars["hspacing"] 4243 fscale = fpars["fscale"] 4244 dotsep = fpars["dotsep"] 4245 4246 # replacements 4247 if ":" in txt: 4248 for r in _reps: 4249 txt = txt.replace(r[0], r[1]) 4250 4251 if not literal: 4252 reps2 = [ 4253 (r"\_", "┭"), # trick to protect ~ _ and ^ chars 4254 (r"\^", "┮"), # 4255 (r"\~", "┯"), # 4256 ("**", "^"), # order matters 4257 ("e+0", dotsep + "10^"), 4258 ("e-0", dotsep + "10^-"), 4259 ("E+0", dotsep + "10^"), 4260 ("E-0", dotsep + "10^-"), 4261 ("e+", dotsep + "10^"), 4262 ("e-", dotsep + "10^-"), 4263 ("E+", dotsep + "10^"), 4264 ("E-", dotsep + "10^-"), 4265 ] 4266 for r in reps2: 4267 txt = txt.replace(r[0], r[1]) 4268 4269 xmax, ymax, yshift, scale = 0.0, 0.0, 0.0, 1.0 4270 save_xmax = 0.0 4271 4272 notfounds = set() 4273 polyletters = [] 4274 ntxt = len(txt) 4275 for i, t in enumerate(txt): 4276 ########## 4277 if t == "┭": 4278 t = "_" 4279 elif t == "┮": 4280 t = "^" 4281 elif t == "┯": 4282 t = "~" 4283 elif t == "^" and not literal: 4284 if yshift < 0: 4285 xmax = save_xmax 4286 yshift = 0.9 * fscale 4287 scale = 0.5 4288 continue 4289 elif t == "_" and not literal: 4290 if yshift > 0: 4291 xmax = save_xmax 4292 yshift = -0.3 * fscale 4293 scale = 0.5 4294 continue 4295 elif (t in (" ", "\\n")) and yshift: 4296 yshift = 0.0 4297 scale = 1.0 4298 save_xmax = xmax 4299 if t == " ": 4300 continue 4301 elif t == "~": 4302 if i < ntxt - 1 and txt[i + 1] == "_": 4303 continue 4304 xmax += hspacing * scale * fscale / 4 4305 continue 4306 4307 ############ 4308 if t == " ": 4309 xmax += hspacing * scale * fscale 4310 4311 elif t == "\n": 4312 xmax = 0.0 4313 save_xmax = 0.0 4314 ymax -= vspacing 4315 4316 else: 4317 poly = _get_font_letter(font, t) 4318 if not poly: 4319 notfounds.add(t) 4320 xmax += hspacing * scale * fscale 4321 continue 4322 4323 if poly.GetNumberOfPoints() == 0: 4324 continue 4325 4326 tr = vtki.vtkTransform() 4327 tr.Translate(xmax, ymax + yshift, 0) 4328 pscale = scale * fscale / 1000 4329 tr.Scale(pscale, pscale, pscale) 4330 if italic: 4331 tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 4332 tf = vtki.new("TransformPolyDataFilter") 4333 tf.SetInputData(poly) 4334 tf.SetTransform(tr) 4335 tf.Update() 4336 poly = tf.GetOutput() 4337 polyletters.append(poly) 4338 4339 bx = poly.GetBounds() 4340 if mono: 4341 xmax += hspacing * scale * fscale 4342 else: 4343 xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing 4344 if yshift == 0: 4345 save_xmax = xmax 4346 4347 if len(polyletters) == 1: 4348 tpoly = polyletters[0] 4349 else: 4350 polyapp = vtki.new("AppendPolyData") 4351 for polyd in polyletters: 4352 polyapp.AddInputData(polyd) 4353 polyapp.Update() 4354 tpoly = polyapp.GetOutput() 4355 4356 if notfounds: 4357 wmsg = f"unavailable characters in font name '{font}': {notfounds}." 4358 wmsg += '\nType "vedo -r fonts" for a demo.' 4359 vedo.logger.warning(wmsg) 4360 4361 bb = tpoly.GetBounds() 4362 dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s 4363 shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2 4364 if "bottom" in justify: shift += np.array([ 0, dy, 0.]) 4365 if "top" in justify: shift += np.array([ 0,-dy, 0.]) 4366 if "left" in justify: shift += np.array([ dx, 0, 0.]) 4367 if "right" in justify: shift += np.array([-dx, 0, 0.]) 4368 4369 if tpoly.GetNumberOfPoints(): 4370 t = vtki.vtkTransform() 4371 t.PostMultiply() 4372 t.Scale(s, s, s) 4373 t.Translate(shift) 4374 tf = vtki.new("TransformPolyDataFilter") 4375 tf.SetInputData(tpoly) 4376 tf.SetTransform(t) 4377 tf.Update() 4378 tpoly = tf.GetOutput() 4379 4380 if depth: 4381 extrude = vtki.new("LinearExtrusionFilter") 4382 extrude.SetInputData(tpoly) 4383 extrude.SetExtrusionTypeToVectorExtrusion() 4384 extrude.SetVector(0, 0, 1) 4385 extrude.SetScaleFactor(depth * dy) 4386 extrude.Update() 4387 tpoly = extrude.GetOutput() 4388 4389 return tpoly 4390 4391 4392class TextBase: 4393 "Base class." 4394 4395 def __init__(self): 4396 "Do not instantiate this base class." 4397 4398 self.rendered_at = set() 4399 self.properties = None 4400 4401 self.name = "Text" 4402 self.filename = "" 4403 self.time = 0 4404 self.info = {} 4405 4406 if isinstance(settings.default_font, int): 4407 lfonts = list(settings.font_parameters.keys()) 4408 font = settings.default_font % len(lfonts) 4409 self.fontname = lfonts[font] 4410 else: 4411 self.fontname = settings.default_font 4412 4413 def angle(self, value: float): 4414 """Orientation angle in degrees""" 4415 self.properties.SetOrientation(value) 4416 return self 4417 4418 def line_spacing(self, value: float): 4419 """Set the extra spacing between lines 4420 expressed as a text height multiplicative factor.""" 4421 self.properties.SetLineSpacing(value) 4422 return self 4423 4424 def line_offset(self, value: float): 4425 """Set/Get the vertical offset (measured in pixels).""" 4426 self.properties.SetLineOffset(value) 4427 return self 4428 4429 def bold(self, value=True): 4430 """Set bold face""" 4431 self.properties.SetBold(value) 4432 return self 4433 4434 def italic(self, value=True): 4435 """Set italic face""" 4436 self.properties.SetItalic(value) 4437 return self 4438 4439 def shadow(self, offset=(1, -1)): 4440 """Text shadowing. Set to `None` to disable it.""" 4441 if offset is None: 4442 self.properties.ShadowOff() 4443 else: 4444 self.properties.ShadowOn() 4445 self.properties.SetShadowOffset(offset) 4446 return self 4447 4448 def color(self, c=None): 4449 """Set the text color""" 4450 if c is None: 4451 return get_color(self.properties.GetColor()) 4452 self.properties.SetColor(get_color(c)) 4453 return self 4454 4455 def c(self, color=None): 4456 """Set the text color""" 4457 if color is None: 4458 return get_color(self.properties.GetColor()) 4459 return self.color(color) 4460 4461 def alpha(self, value: float): 4462 """Set the text opacity""" 4463 self.properties.SetBackgroundOpacity(value) 4464 return self 4465 4466 def background(self, color="k9", alpha=1.0): 4467 """Text background. Set to `None` to disable it.""" 4468 bg = get_color(color) 4469 if color is None: 4470 self.properties.SetBackgroundOpacity(0) 4471 else: 4472 self.properties.SetBackgroundColor(bg) 4473 if alpha: 4474 self.properties.SetBackgroundOpacity(alpha) 4475 return self 4476 4477 def frame(self, color="k1", lw=2): 4478 """Border color and width""" 4479 if color is None: 4480 self.properties.FrameOff() 4481 else: 4482 c = get_color(color) 4483 self.properties.FrameOn() 4484 self.properties.SetFrameColor(c) 4485 self.properties.SetFrameWidth(lw) 4486 return self 4487 4488 def font(self, font: str): 4489 """Text font face""" 4490 if isinstance(font, int): 4491 lfonts = list(settings.font_parameters.keys()) 4492 n = font % len(lfonts) 4493 font = lfonts[n] 4494 self.fontname = font 4495 4496 if not font: # use default font 4497 font = self.fontname 4498 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4499 elif font.startswith("https"): # user passed URL link, make it a path 4500 fpath = vedo.file_io.download(font, verbose=False, force=False) 4501 elif font.endswith(".ttf"): # user passing a local path to font file 4502 fpath = font 4503 else: # user passing name of preset font 4504 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4505 4506 if font == "Courier": self.properties.SetFontFamilyToCourier() 4507 elif font == "Times": self.properties.SetFontFamilyToTimes() 4508 elif font == "Arial": self.properties.SetFontFamilyToArial() 4509 else: 4510 fpath = utils.get_font_path(font) 4511 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4512 self.properties.SetFontFile(fpath) 4513 self.fontname = font # io.tonumpy() uses it 4514 4515 return self 4516 4517 def on(self): 4518 """Make text visible""" 4519 self.actor.SetVisibility(True) 4520 return self 4521 4522 def off(self): 4523 """Make text invisible""" 4524 self.actor.SetVisibility(False) 4525 return self 4526 4527class Text2D(TextBase, vedo.visual.Actor2D): 4528 """ 4529 Create a 2D text object. 4530 """ 4531 def __init__( 4532 self, 4533 txt="", 4534 pos="top-left", 4535 s=1.0, 4536 bg=None, 4537 font="", 4538 justify="", 4539 bold=False, 4540 italic=False, 4541 c=None, 4542 alpha=0.5, 4543 ) -> None: 4544 """ 4545 Create a 2D text object. 4546 4547 All properties of the text, and the text itself, can be changed after creation 4548 (which is especially useful in loops). 4549 4550 Arguments: 4551 pos : (str) 4552 text is placed in one of the 8 positions: 4553 - bottom-left 4554 - bottom-right 4555 - top-left 4556 - top-right 4557 - bottom-middle 4558 - middle-right 4559 - middle-left 4560 - top-middle 4561 4562 If a pair (x,y) is passed as input the 2D text is place at that 4563 position in the coordinate system of the 2D screen (with the 4564 origin sitting at the bottom left). 4565 4566 s : (float) 4567 size of text 4568 bg : (color) 4569 background color 4570 alpha : (float) 4571 background opacity 4572 justify : (str) 4573 text justification 4574 4575 font : (str) 4576 built-in available fonts are: 4577 - Antares 4578 - Arial 4579 - Bongas 4580 - Calco 4581 - Comae 4582 - ComicMono 4583 - Courier 4584 - Glasgo 4585 - Kanopus 4586 - LogoType 4587 - Normografo 4588 - Quikhand 4589 - SmartCouric 4590 - Theemim 4591 - Times 4592 - VictorMono 4593 - More fonts at: https://vedo.embl.es/fonts/ 4594 4595 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4596 4597 Examples: 4598 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4599 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4600 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4601 4602 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4603 """ 4604 super().__init__() 4605 self.name = "Text2D" 4606 4607 self.mapper = vtki.new("TextMapper") 4608 self.SetMapper(self.mapper) 4609 4610 self.properties = self.mapper.GetTextProperty() 4611 self.actor = self 4612 self.actor.retrieve_object = weak_ref_to(self) 4613 4614 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4615 4616 # automatic black or white 4617 if c is None: 4618 c = (0.1, 0.1, 0.1) 4619 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4620 if vedo.plotter_instance.renderer.GetGradientBackground(): 4621 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4622 else: 4623 bgcol = vedo.plotter_instance.renderer.GetBackground() 4624 c = (0.9, 0.9, 0.9) 4625 if np.sum(bgcol) > 1.5: 4626 c = (0.1, 0.1, 0.1) 4627 4628 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4629 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4630 self.PickableOff() 4631 4632 def pos(self, pos="top-left", justify=""): 4633 """ 4634 Set position of the text to draw. Keyword `pos` can be a string 4635 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4636 """ 4637 ajustify = "top-left" # autojustify 4638 if isinstance(pos, str): # corners 4639 ajustify = pos 4640 if "top" in pos: 4641 if "left" in pos: 4642 pos = (0.008, 0.994) 4643 elif "right" in pos: 4644 pos = (0.994, 0.994) 4645 elif "mid" in pos or "cent" in pos: 4646 pos = (0.5, 0.994) 4647 elif "bottom" in pos: 4648 if "left" in pos: 4649 pos = (0.008, 0.008) 4650 elif "right" in pos: 4651 pos = (0.994, 0.008) 4652 elif "mid" in pos or "cent" in pos: 4653 pos = (0.5, 0.008) 4654 elif "mid" in pos or "cent" in pos: 4655 if "left" in pos: 4656 pos = (0.008, 0.5) 4657 elif "right" in pos: 4658 pos = (0.994, 0.5) 4659 else: 4660 pos = (0.5, 0.5) 4661 4662 else: 4663 vedo.logger.warning(f"cannot understand text position {pos}") 4664 pos = (0.008, 0.994) 4665 ajustify = "top-left" 4666 4667 elif len(pos) != 2: 4668 vedo.logger.error("pos must be of length 2 or integer value or string") 4669 raise RuntimeError() 4670 4671 if not justify: 4672 justify = ajustify 4673 4674 self.properties.SetJustificationToLeft() 4675 if "top" in justify: 4676 self.properties.SetVerticalJustificationToTop() 4677 if "bottom" in justify: 4678 self.properties.SetVerticalJustificationToBottom() 4679 if "cent" in justify or "mid" in justify: 4680 self.properties.SetJustificationToCentered() 4681 if "left" in justify: 4682 self.properties.SetJustificationToLeft() 4683 if "right" in justify: 4684 self.properties.SetJustificationToRight() 4685 4686 self.SetPosition(pos) 4687 return self 4688 4689 def text(self, txt=None): 4690 """Set/get the input text string.""" 4691 if txt is None: 4692 return self.mapper.GetInput() 4693 4694 if ":" in txt: 4695 for r in _reps: 4696 txt = txt.replace(r[0], r[1]) 4697 else: 4698 txt = str(txt) 4699 4700 self.mapper.SetInput(txt) 4701 return self 4702 4703 def size(self, s): 4704 """Set the font size.""" 4705 self.properties.SetFontSize(int(s * 22.5)) 4706 return self 4707 4708 4709class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation): 4710 # PROBABLY USELESS given that Text2D does pretty much the same ... 4711 """ 4712 Annotate the window corner with 2D text. 4713 4714 See `Text2D` description as the basic functionality is very similar. 4715 4716 The added value of this class is the possibility to manage with one single 4717 object the all corner annotations (instead of creating 4 `Text2D` instances). 4718 4719 Examples: 4720 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 4721 """ 4722 4723 def __init__(self, c=None) -> None: 4724 4725 super().__init__() 4726 4727 self.properties = self.GetTextProperty() 4728 4729 # automatic black or white 4730 if c is None: 4731 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4732 c = (0.9, 0.9, 0.9) 4733 if vedo.plotter_instance.renderer.GetGradientBackground(): 4734 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4735 else: 4736 bgcol = vedo.plotter_instance.renderer.GetBackground() 4737 if np.sum(bgcol) > 1.5: 4738 c = (0.1, 0.1, 0.1) 4739 else: 4740 c = (0.5, 0.5, 0.5) 4741 4742 self.SetNonlinearFontScaleFactor(1 / 2.75) 4743 self.PickableOff() 4744 self.properties.SetColor(get_color(c)) 4745 self.properties.SetBold(False) 4746 self.properties.SetItalic(False) 4747 4748 def size(self, s:float, linear=False) -> "CornerAnnotation": 4749 """ 4750 The font size is calculated as the largest possible value such that the annotations 4751 for the given viewport do not overlap. 4752 4753 This font size can be scaled non-linearly with the viewport size, to maintain an 4754 acceptable readable size at larger viewport sizes, without being too big. 4755 `f' = linearScale * pow(f,nonlinearScale)` 4756 """ 4757 if linear: 4758 self.SetLinearFontScaleFactor(s * 5.5) 4759 else: 4760 self.SetNonlinearFontScaleFactor(s / 2.75) 4761 return self 4762 4763 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4764 """Set text at the assigned position""" 4765 4766 if isinstance(pos, str): # corners 4767 if "top" in pos: 4768 if "left" in pos: pos = 2 4769 elif "right" in pos: pos = 3 4770 elif "mid" in pos or "cent" in pos: pos = 7 4771 elif "bottom" in pos: 4772 if "left" in pos: pos = 0 4773 elif "right" in pos: pos = 1 4774 elif "mid" in pos or "cent" in pos: pos = 4 4775 else: 4776 if "left" in pos: pos = 6 4777 elif "right" in pos: pos = 5 4778 else: pos = 2 4779 4780 if "\\" in repr(txt): 4781 for r in _reps: 4782 txt = txt.replace(r[0], r[1]) 4783 else: 4784 txt = str(txt) 4785 4786 self.SetText(pos, txt) 4787 return self 4788 4789 def clear(self) -> "CornerAnnotation": 4790 """Remove all text from all corners""" 4791 self.ClearAllTexts() 4792 return self 4793 4794 4795class Latex(Image): 4796 """ 4797 Render Latex text and formulas. 4798 """ 4799 4800 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4801 """ 4802 Render Latex text and formulas. 4803 4804 Arguments: 4805 formula : (str) 4806 latex text string 4807 pos : (list) 4808 position coordinates in space 4809 bg : (color) 4810 background color box 4811 res : (int) 4812 dpi resolution 4813 usetex : (bool) 4814 use latex compiler of matplotlib if available 4815 4816 You can access the latex formula in `Latex.formula`. 4817 4818 Examples: 4819 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4820 4821 ![](https://vedo.embl.es/images/pyplot/latex.png) 4822 """ 4823 from tempfile import NamedTemporaryFile 4824 import matplotlib.pyplot as mpltib 4825 4826 def build_img_plt(formula, tfile): 4827 4828 mpltib.rc("text", usetex=usetex) 4829 4830 formula1 = "$" + formula + "$" 4831 mpltib.axis("off") 4832 col = get_color(c) 4833 if bg: 4834 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4835 else: 4836 bx = None 4837 mpltib.text( 4838 0.5, 4839 0.5, 4840 formula1, 4841 size=res, 4842 color=col, 4843 alpha=alpha, 4844 ha="center", 4845 va="center", 4846 bbox=bx, 4847 ) 4848 mpltib.savefig( 4849 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4850 ) 4851 mpltib.close() 4852 4853 if len(pos) == 2: 4854 pos = (pos[0], pos[1], 0) 4855 4856 tmp_file = NamedTemporaryFile(delete=True) 4857 tmp_file.name = tmp_file.name + ".png" 4858 4859 build_img_plt(formula, tmp_file.name) 4860 4861 super().__init__(tmp_file.name, channels=4) 4862 self.alpha(alpha) 4863 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4864 self.pos(pos) 4865 self.name = "Latex" 4866 self.formula = formula 4867 4868 # except: 4869 # printc("Error in Latex()\n", formula, c="r") 4870 # printc(" latex or dvipng not installed?", c="r") 4871 # printc(" Try: usetex=False", c="r") 4872 # printc(" Try: sudo apt install dvipng", c="r") 4873 4874 4875class ConvexHull(Mesh): 4876 """ 4877 Create the 2D/3D convex hull from a set of points. 4878 """ 4879 4880 def __init__(self, pts) -> None: 4881 """ 4882 Create the 2D/3D convex hull from a set of input points or input Mesh. 4883 4884 Examples: 4885 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4886 4887 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4888 """ 4889 if utils.is_sequence(pts): 4890 pts = utils.make3d(pts).astype(float) 4891 mesh = Points(pts) 4892 else: 4893 mesh = pts 4894 apoly = mesh.clean().dataset 4895 4896 # Create the convex hull of the pointcloud 4897 z0, z1 = mesh.zbounds() 4898 d = mesh.diagonal_size() 4899 if (z1 - z0) / d > 0.0001: 4900 delaunay = vtki.new("Delaunay3D") 4901 delaunay.SetInputData(apoly) 4902 delaunay.Update() 4903 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4904 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4905 surfaceFilter.Update() 4906 out = surfaceFilter.GetOutput() 4907 else: 4908 delaunay = vtki.new("Delaunay2D") 4909 delaunay.SetInputData(apoly) 4910 delaunay.Update() 4911 fe = vtki.new("FeatureEdges") 4912 fe.SetInputConnection(delaunay.GetOutputPort()) 4913 fe.BoundaryEdgesOn() 4914 fe.Update() 4915 out = fe.GetOutput() 4916 4917 super().__init__(out, c=mesh.color(), alpha=0.75) 4918 self.flat() 4919 self.name = "ConvexHull" 4920 4921 4922def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True) -> "vedo.Assembly": 4923 """ 4924 Create the 3D vedo logo. 4925 4926 Arguments: 4927 distance : (float) 4928 send back logo by this distance from camera 4929 version : (bool) 4930 add version text to the right end of the logo 4931 bc : (color) 4932 text back face color 4933 """ 4934 if c is None: 4935 c = (0, 0, 0) 4936 if vedo.plotter_instance: 4937 if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5: 4938 c = [0, 0, 0] 4939 else: 4940 c = "linen" 4941 4942 font = "Comae" 4943 vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8) 4944 vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc) 4945 vlogo.properties.LightingOn() 4946 4947 vr, rul = None, None 4948 if version: 4949 vr = Text3D( 4950 vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1 4951 ).scale([1, 0.7, 1]) 4952 vr.rotate_z(90).pos(2450, 50, 80) 4953 vr.bc(bc).pickable(False) 4954 elif frame: 4955 rul = vedo.RulerAxes( 4956 (-2600, 2110, 0, 1650, 0, 0), 4957 xlabel="European Molecular Biology Laboratory", 4958 ylabel=vedo.__version__, 4959 font=font, 4960 xpadding=0.09, 4961 ypadding=0.04, 4962 ) 4963 fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0) 4964 return vedo.Assembly([vlogo, vr, fakept, rul]).scale(1 / 1725)
3631def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True) -> Any: 3632 """ 3633 Generate a marker shape. Typically used in association with `Glyph`. 3634 """ 3635 if isinstance(symbol, Mesh): 3636 return symbol.c(c).alpha(alpha).lighting("off") 3637 3638 if isinstance(symbol, int): 3639 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 3640 symbol = symbol % len(symbs) 3641 symbol = symbs[symbol] 3642 3643 if symbol == ".": 3644 mesh = Polygon(nsides=24, r=s * 0.6) 3645 elif symbol == "o": 3646 mesh = Polygon(nsides=24, r=s * 0.75) 3647 elif symbol == "O": 3648 mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3649 elif symbol == "0": 3650 m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3651 m2 = Circle(r=s * 0.36).reverse() 3652 mesh = merge(m1, m2) 3653 elif symbol == "p": 3654 mesh = Polygon(nsides=5, r=s) 3655 elif symbol == "*": 3656 mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled) 3657 elif symbol == "h": 3658 mesh = Polygon(nsides=6, r=s) 3659 elif symbol == "D": 3660 mesh = Polygon(nsides=4, r=s) 3661 elif symbol == "d": 3662 mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1]) 3663 elif symbol == "v": 3664 mesh = Polygon(nsides=3, r=s).rotate_z(180) 3665 elif symbol == "^": 3666 mesh = Polygon(nsides=3, r=s) 3667 elif symbol == ">": 3668 mesh = Polygon(nsides=3, r=s).rotate_z(-90) 3669 elif symbol == "<": 3670 mesh = Polygon(nsides=3, r=s).rotate_z(90) 3671 elif symbol == "s": 3672 mesh = Mesh( 3673 [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]] 3674 ).scale(s / 1.4) 3675 elif symbol == "x": 3676 mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3677 # mesh.rotate_z(45) 3678 elif symbol == "a": 3679 mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3680 else: 3681 mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0) 3682 mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha) 3683 if len(pos) == 2: 3684 pos = (pos[0], pos[1], 0) 3685 mesh.pos(pos) 3686 mesh.name = "Marker" 3687 return mesh
Generate a marker shape. Typically used in association with Glyph
.
395class Line(Mesh): 396 """ 397 Build the line segment between point `p0` and point `p1`. 398 399 If `p0` is already a list of points, return the line connecting them. 400 401 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 402 """ 403 404 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None: 405 """ 406 Arguments: 407 closed : (bool) 408 join last to first point 409 res : (int) 410 resolution, number of points along the line 411 (only relevant if only 2 points are specified) 412 lw : (int) 413 line width in pixel units 414 """ 415 416 if isinstance(p1, Points): 417 p1 = p1.pos() 418 if isinstance(p0, Points): 419 p0 = p0.pos() 420 try: 421 p0 = p0.dataset 422 except AttributeError: 423 pass 424 425 if isinstance(p0, vtki.vtkPolyData): 426 poly = p0 427 top = np.array([0,0,1]) 428 base = np.array([0,0,0]) 429 430 elif utils.is_sequence(p0[0]): # detect if user is passing a list of points 431 432 p0 = utils.make3d(p0) 433 ppoints = vtki.vtkPoints() # Generate the polyline 434 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 435 lines = vtki.vtkCellArray() 436 npt = len(p0) 437 if closed: 438 lines.InsertNextCell(npt + 1) 439 else: 440 lines.InsertNextCell(npt) 441 for i in range(npt): 442 lines.InsertCellPoint(i) 443 if closed: 444 lines.InsertCellPoint(0) 445 poly = vtki.vtkPolyData() 446 poly.SetPoints(ppoints) 447 poly.SetLines(lines) 448 top = p0[-1] 449 base = p0[0] 450 if res != 2: 451 printc(f"Warning: calling Line(res={res}), try remove []?", c='y') 452 res = 2 453 454 else: # or just 2 points to link 455 456 line_source = vtki.new("LineSource") 457 p0 = utils.make3d(p0) 458 p1 = utils.make3d(p1) 459 line_source.SetPoint1(p0) 460 line_source.SetPoint2(p1) 461 line_source.SetResolution(res - 1) 462 line_source.Update() 463 poly = line_source.GetOutput() 464 top = np.asarray(p1, dtype=float) 465 base = np.asarray(p0, dtype=float) 466 467 super().__init__(poly, c, alpha) 468 469 self.slope: List[float] = [] # populated by analysis.fit_line 470 self.center: List[float] = [] 471 self.variances: List[float] = [] 472 473 self.coefficients: List[float] = [] # populated by pyplot.fit() 474 self.covariance_matrix: List[float] = [] 475 self.coefficient_errors: List[float] = [] 476 self.monte_carlo_coefficients: List[float] = [] 477 self.reduced_chi2 = -1 478 self.ndof = 0 479 self.data_sigma = 0 480 self.error_lines: List[Any] = [] 481 self.error_band = None 482 self.res = res 483 484 self.lw(lw) 485 self.properties.LightingOff() 486 self.actor.PickableOff() 487 self.actor.DragableOff() 488 self.base = base 489 self.top = top 490 self.name = "Line" 491 492 def clone(self, deep=True) -> "Line": 493 """ 494 Return a copy of the ``Line`` object. 495 496 Example: 497 ```python 498 from vedo import * 499 ln1 = Line([1,1,1], [2,2,2], lw=3).print() 500 ln2 = ln1.clone().shift(0,0,1).c('red').print() 501 show(ln1, ln2, axes=1, viewup='z').close() 502 ``` 503 ![](https://vedo.embl.es/images/feats/line_clone.png) 504 """ 505 poly = vtki.vtkPolyData() 506 if deep: 507 poly.DeepCopy(self.dataset) 508 else: 509 poly.ShallowCopy(self.dataset) 510 ln = Line(poly) 511 ln.copy_properties_from(self) 512 ln.transform = self.transform.clone() 513 ln.name = self.name 514 ln.base = self.base 515 ln.top = self.top 516 ln.pipeline = utils.OperationNode( 517 "clone", parents=[self], shape="diamond", c="#edede9") 518 return ln 519 520 def linecolor(self, lc=None) -> "Line": 521 """Assign a color to the line""" 522 # overrides mesh.linecolor which would have no effect here 523 return self.color(lc) 524 525 def eval(self, x: float) -> np.ndarray: 526 """ 527 Calculate the position of an intermediate point 528 as a fraction of the length of the line, 529 being x=0 the first point and x=1 the last point. 530 This corresponds to an imaginary point that travels along the line 531 at constant speed. 532 533 Can be used in conjunction with `lin_interpolate()` 534 to map any range to the [0,1] range. 535 """ 536 distance1 = 0.0 537 length = self.length() 538 pts = self.vertices 539 for i in range(1, len(pts)): 540 p0 = pts[i - 1] 541 p1 = pts[i] 542 seg = p1 - p0 543 distance0 = distance1 544 distance1 += np.linalg.norm(seg) 545 w1 = distance1 / length 546 if w1 >= x: 547 break 548 w0 = distance0 / length 549 v = p0 + seg * (x - w0) / (w1 - w0) 550 return v 551 552 def find_index_at_position(self, p) -> float: 553 """ 554 Find the index of the line vertex that is closest to the point `p`. 555 Note that the returned index can be fractional if `p` is not exactly 556 one of the vertices of the line. 557 """ 558 q = self.closest_point(p) 559 a, b = sorted(self.closest_point(q, n=2, return_point_id=True)) 560 pts = self.vertices 561 d = np.linalg.norm(pts[a] - pts[b]) 562 t = a + np.linalg.norm(pts[a] - q) / d 563 return t 564 565 def pattern(self, stipple, repeats=10) -> "Line": 566 """ 567 Define a stipple pattern for dashing the line. 568 Pass the stipple pattern as a string like `'- - -'`. 569 Repeats controls the number of times the pattern repeats in a single segment. 570 571 Examples are: `'- -', '-- - --'`, etc. 572 573 The resolution of the line (nr of points) can affect how pattern will show up. 574 575 Example: 576 ```python 577 from vedo import Line 578 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 579 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 580 ln.show(axes=1).close() 581 ``` 582 ![](https://vedo.embl.es/images/feats/line_pattern.png) 583 """ 584 stipple = str(stipple) * int(2 * repeats) 585 dimension = len(stipple) 586 587 image = vtki.vtkImageData() 588 image.SetDimensions(dimension, 1, 1) 589 image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4) 590 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 591 i_dim = 0 592 while i_dim < dimension: 593 for i in range(dimension): 594 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 595 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 596 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 597 if stipple[i] == " ": 598 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 599 else: 600 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 601 i_dim += 1 602 603 poly = self.dataset 604 605 # Create texture coordinates 606 tcoords = vtki.vtkDoubleArray() 607 tcoords.SetName("TCoordsStippledLine") 608 tcoords.SetNumberOfComponents(1) 609 tcoords.SetNumberOfTuples(poly.GetNumberOfPoints()) 610 for i in range(poly.GetNumberOfPoints()): 611 tcoords.SetTypedTuple(i, [i / 2]) 612 poly.GetPointData().SetTCoords(tcoords) 613 poly.GetPointData().Modified() 614 texture = vtki.vtkTexture() 615 texture.SetInputData(image) 616 texture.InterpolateOff() 617 texture.RepeatOn() 618 self.actor.SetTexture(texture) 619 return self 620 621 def length(self) -> float: 622 """Calculate length of the line.""" 623 distance = 0.0 624 pts = self.vertices 625 for i in range(1, len(pts)): 626 distance += np.linalg.norm(pts[i] - pts[i - 1]) 627 return distance 628 629 def tangents(self) -> np.ndarray: 630 """ 631 Compute the tangents of a line in space. 632 633 Example: 634 ```python 635 from vedo import * 636 shape = Assembly(dataurl+"timecourse1d.npy")[58] 637 pts = shape.rotate_x(30).vertices 638 tangents = Line(pts).tangents() 639 arrs = Arrows(pts, pts+tangents, c='blue9') 640 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 641 ``` 642 ![](https://vedo.embl.es/images/feats/line_tangents.png) 643 """ 644 v = np.gradient(self.vertices)[0] 645 ds_dt = np.linalg.norm(v, axis=1) 646 tangent = np.array([1 / ds_dt] * 3).transpose() * v 647 return tangent 648 649 def curvature(self) -> np.ndarray: 650 """ 651 Compute the signed curvature of a line in space. 652 The signed is computed assuming the line is about coplanar to the xy plane. 653 654 Example: 655 ```python 656 from vedo import * 657 from vedo.pyplot import plot 658 shape = Assembly(dataurl+"timecourse1d.npy")[55] 659 curvs = Line(shape.vertices).curvature() 660 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 661 shape.render_lines_as_tubes().lw(12) 662 pp = plot(curvs, ac='white', lc='yellow5') 663 show(shape, pp, N=2, bg='bb', sharecam=False).close() 664 ``` 665 ![](https://vedo.embl.es/images/feats/line_curvature.png) 666 """ 667 v = np.gradient(self.vertices)[0] 668 a = np.gradient(v)[0] 669 av = np.cross(a, v) 670 mav = np.linalg.norm(av, axis=1) 671 mv = utils.mag2(v) 672 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 673 val[0] = val[1] 674 val[-1] = val[-2] 675 return val 676 677 def compute_curvature(self, method=0) -> "Line": 678 """ 679 Add a pointdata array named 'Curvatures' which contains 680 the curvature value at each point. 681 682 NB: keyword `method` is overridden in Mesh and has no effect here. 683 """ 684 # overrides mesh.compute_curvature 685 curvs = self.curvature() 686 vmin, vmax = np.min(curvs), np.max(curvs) 687 if vmin < 0 and vmax > 0: 688 v = max(-vmin, vmax) 689 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 690 else: 691 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 692 return self 693 694 def plot_scalar( 695 self, 696 radius=0.0, 697 height=1.1, 698 normal=(), 699 camera=None, 700 ) -> "Line": 701 """ 702 Generate a new `Line` which plots the active scalar along the line. 703 704 Arguments: 705 radius : (float) 706 distance radius to the line 707 height: (float) 708 height of the plot 709 normal: (list) 710 normal vector to the plane of the plot 711 camera: (vtkCamera) 712 camera object to use for the plot orientation 713 714 Example: 715 ```python 716 from vedo import * 717 circle = Circle(res=360).rotate_y(20) 718 pts = circle.vertices 719 bore = Line(pts).lw(5) 720 values = np.arctan2(pts[:,1], pts[:,0]) 721 bore.pointdata["scalars"] = values + np.random.randn(360)/5 722 vap = bore.plot_scalar(radius=0, height=1) 723 show(bore, vap, axes=1, viewup='z').close() 724 ``` 725 ![](https://vedo.embl.es/images/feats/line_plot_scalar.png) 726 """ 727 ap = vtki.new("ArcPlotter") 728 ap.SetInputData(self.dataset) 729 ap.SetCamera(camera) 730 ap.SetRadius(radius) 731 ap.SetHeight(height) 732 if len(normal)>0: 733 ap.UseDefaultNormalOn() 734 ap.SetDefaultNormal(normal) 735 ap.Update() 736 vap = Line(ap.GetOutput()) 737 vap.linewidth(3).lighting('off') 738 vap.name = "ArcPlot" 739 return vap 740 741 def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh": 742 """ 743 Sweep the `Line` along the specified vector direction. 744 745 Returns a `Mesh` surface. 746 Line position is updated to allow for additional sweepings. 747 748 Example: 749 ```python 750 from vedo import Line, show 751 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 752 surf1 = aline.sweep((1,0.2,0), res=3) 753 surf2 = aline.sweep((0.2,0,1)).alpha(0.5) 754 aline.color('r').linewidth(4) 755 show(surf1, surf2, aline, axes=1).close() 756 ``` 757 ![](https://vedo.embl.es/images/feats/sweepline.png) 758 """ 759 line = self.dataset 760 rows = line.GetNumberOfPoints() 761 762 spacing = 1 / res 763 surface = vtki.vtkPolyData() 764 765 res += 1 766 npts = rows * res 767 npolys = (rows - 1) * (res - 1) 768 points = vtki.vtkPoints() 769 points.Allocate(npts) 770 771 cnt = 0 772 x = [0.0, 0.0, 0.0] 773 for row in range(rows): 774 for col in range(res): 775 p = [0.0, 0.0, 0.0] 776 line.GetPoint(row, p) 777 x[0] = p[0] + direction[0] * col * spacing 778 x[1] = p[1] + direction[1] * col * spacing 779 x[2] = p[2] + direction[2] * col * spacing 780 points.InsertPoint(cnt, x) 781 cnt += 1 782 783 # Generate the quads 784 polys = vtki.vtkCellArray() 785 polys.Allocate(npolys * 4) 786 pts = [0, 0, 0, 0] 787 for row in range(rows - 1): 788 for col in range(res - 1): 789 pts[0] = col + row * res 790 pts[1] = pts[0] + 1 791 pts[2] = pts[0] + res + 1 792 pts[3] = pts[0] + res 793 polys.InsertNextCell(4, pts) 794 surface.SetPoints(points) 795 surface.SetPolys(polys) 796 asurface = Mesh(surface) 797 asurface.copy_properties_from(self) 798 asurface.lighting("default") 799 self.vertices = self.vertices + direction 800 return asurface 801 802 def reverse(self): 803 """Reverse the points sequence order.""" 804 pts = np.flip(self.vertices, axis=0) 805 self.vertices = pts 806 return self
Build the line segment between point p0
and point p1
.
If p0
is already a list of points, return the line connecting them.
A 2D set of coords can also be passed as p0=[x..], p1=[y..]
.
404 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None: 405 """ 406 Arguments: 407 closed : (bool) 408 join last to first point 409 res : (int) 410 resolution, number of points along the line 411 (only relevant if only 2 points are specified) 412 lw : (int) 413 line width in pixel units 414 """ 415 416 if isinstance(p1, Points): 417 p1 = p1.pos() 418 if isinstance(p0, Points): 419 p0 = p0.pos() 420 try: 421 p0 = p0.dataset 422 except AttributeError: 423 pass 424 425 if isinstance(p0, vtki.vtkPolyData): 426 poly = p0 427 top = np.array([0,0,1]) 428 base = np.array([0,0,0]) 429 430 elif utils.is_sequence(p0[0]): # detect if user is passing a list of points 431 432 p0 = utils.make3d(p0) 433 ppoints = vtki.vtkPoints() # Generate the polyline 434 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 435 lines = vtki.vtkCellArray() 436 npt = len(p0) 437 if closed: 438 lines.InsertNextCell(npt + 1) 439 else: 440 lines.InsertNextCell(npt) 441 for i in range(npt): 442 lines.InsertCellPoint(i) 443 if closed: 444 lines.InsertCellPoint(0) 445 poly = vtki.vtkPolyData() 446 poly.SetPoints(ppoints) 447 poly.SetLines(lines) 448 top = p0[-1] 449 base = p0[0] 450 if res != 2: 451 printc(f"Warning: calling Line(res={res}), try remove []?", c='y') 452 res = 2 453 454 else: # or just 2 points to link 455 456 line_source = vtki.new("LineSource") 457 p0 = utils.make3d(p0) 458 p1 = utils.make3d(p1) 459 line_source.SetPoint1(p0) 460 line_source.SetPoint2(p1) 461 line_source.SetResolution(res - 1) 462 line_source.Update() 463 poly = line_source.GetOutput() 464 top = np.asarray(p1, dtype=float) 465 base = np.asarray(p0, dtype=float) 466 467 super().__init__(poly, c, alpha) 468 469 self.slope: List[float] = [] # populated by analysis.fit_line 470 self.center: List[float] = [] 471 self.variances: List[float] = [] 472 473 self.coefficients: List[float] = [] # populated by pyplot.fit() 474 self.covariance_matrix: List[float] = [] 475 self.coefficient_errors: List[float] = [] 476 self.monte_carlo_coefficients: List[float] = [] 477 self.reduced_chi2 = -1 478 self.ndof = 0 479 self.data_sigma = 0 480 self.error_lines: List[Any] = [] 481 self.error_band = None 482 self.res = res 483 484 self.lw(lw) 485 self.properties.LightingOff() 486 self.actor.PickableOff() 487 self.actor.DragableOff() 488 self.base = base 489 self.top = top 490 self.name = "Line"
Arguments:
- closed : (bool) join last to first point
- res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
- lw : (int) line width in pixel units
492 def clone(self, deep=True) -> "Line": 493 """ 494 Return a copy of the ``Line`` object. 495 496 Example: 497 ```python 498 from vedo import * 499 ln1 = Line([1,1,1], [2,2,2], lw=3).print() 500 ln2 = ln1.clone().shift(0,0,1).c('red').print() 501 show(ln1, ln2, axes=1, viewup='z').close() 502 ``` 503 ![](https://vedo.embl.es/images/feats/line_clone.png) 504 """ 505 poly = vtki.vtkPolyData() 506 if deep: 507 poly.DeepCopy(self.dataset) 508 else: 509 poly.ShallowCopy(self.dataset) 510 ln = Line(poly) 511 ln.copy_properties_from(self) 512 ln.transform = self.transform.clone() 513 ln.name = self.name 514 ln.base = self.base 515 ln.top = self.top 516 ln.pipeline = utils.OperationNode( 517 "clone", parents=[self], shape="diamond", c="#edede9") 518 return ln
Return a copy of the Line
object.
Example:
from vedo import * ln1 = Line([1,1,1], [2,2,2], lw=3).print() ln2 = ln1.clone().shift(0,0,1).c('red').print() show(ln1, ln2, axes=1, viewup='z').close()
520 def linecolor(self, lc=None) -> "Line": 521 """Assign a color to the line""" 522 # overrides mesh.linecolor which would have no effect here 523 return self.color(lc)
Assign a color to the line
525 def eval(self, x: float) -> np.ndarray: 526 """ 527 Calculate the position of an intermediate point 528 as a fraction of the length of the line, 529 being x=0 the first point and x=1 the last point. 530 This corresponds to an imaginary point that travels along the line 531 at constant speed. 532 533 Can be used in conjunction with `lin_interpolate()` 534 to map any range to the [0,1] range. 535 """ 536 distance1 = 0.0 537 length = self.length() 538 pts = self.vertices 539 for i in range(1, len(pts)): 540 p0 = pts[i - 1] 541 p1 = pts[i] 542 seg = p1 - p0 543 distance0 = distance1 544 distance1 += np.linalg.norm(seg) 545 w1 = distance1 / length 546 if w1 >= x: 547 break 548 w0 = distance0 / length 549 v = p0 + seg * (x - w0) / (w1 - w0) 550 return v
Calculate the position of an intermediate point as a fraction of the length of the line, being x=0 the first point and x=1 the last point. This corresponds to an imaginary point that travels along the line at constant speed.
Can be used in conjunction with lin_interpolate()
to map any range to the [0,1] range.
552 def find_index_at_position(self, p) -> float: 553 """ 554 Find the index of the line vertex that is closest to the point `p`. 555 Note that the returned index can be fractional if `p` is not exactly 556 one of the vertices of the line. 557 """ 558 q = self.closest_point(p) 559 a, b = sorted(self.closest_point(q, n=2, return_point_id=True)) 560 pts = self.vertices 561 d = np.linalg.norm(pts[a] - pts[b]) 562 t = a + np.linalg.norm(pts[a] - q) / d 563 return t
Find the index of the line vertex that is closest to the point p
.
Note that the returned index can be fractional if p
is not exactly
one of the vertices of the line.
565 def pattern(self, stipple, repeats=10) -> "Line": 566 """ 567 Define a stipple pattern for dashing the line. 568 Pass the stipple pattern as a string like `'- - -'`. 569 Repeats controls the number of times the pattern repeats in a single segment. 570 571 Examples are: `'- -', '-- - --'`, etc. 572 573 The resolution of the line (nr of points) can affect how pattern will show up. 574 575 Example: 576 ```python 577 from vedo import Line 578 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 579 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 580 ln.show(axes=1).close() 581 ``` 582 ![](https://vedo.embl.es/images/feats/line_pattern.png) 583 """ 584 stipple = str(stipple) * int(2 * repeats) 585 dimension = len(stipple) 586 587 image = vtki.vtkImageData() 588 image.SetDimensions(dimension, 1, 1) 589 image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4) 590 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 591 i_dim = 0 592 while i_dim < dimension: 593 for i in range(dimension): 594 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 595 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 596 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 597 if stipple[i] == " ": 598 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 599 else: 600 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 601 i_dim += 1 602 603 poly = self.dataset 604 605 # Create texture coordinates 606 tcoords = vtki.vtkDoubleArray() 607 tcoords.SetName("TCoordsStippledLine") 608 tcoords.SetNumberOfComponents(1) 609 tcoords.SetNumberOfTuples(poly.GetNumberOfPoints()) 610 for i in range(poly.GetNumberOfPoints()): 611 tcoords.SetTypedTuple(i, [i / 2]) 612 poly.GetPointData().SetTCoords(tcoords) 613 poly.GetPointData().Modified() 614 texture = vtki.vtkTexture() 615 texture.SetInputData(image) 616 texture.InterpolateOff() 617 texture.RepeatOn() 618 self.actor.SetTexture(texture) 619 return self
Define a stipple pattern for dashing the line.
Pass the stipple pattern as a string like '- - -'
.
Repeats controls the number of times the pattern repeats in a single segment.
Examples are: '- -', '-- - --'
, etc.
The resolution of the line (nr of points) can affect how pattern will show up.
Example:
from vedo import Line pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) ln.show(axes=1).close()
621 def length(self) -> float: 622 """Calculate length of the line.""" 623 distance = 0.0 624 pts = self.vertices 625 for i in range(1, len(pts)): 626 distance += np.linalg.norm(pts[i] - pts[i - 1]) 627 return distance
Calculate length of the line.
629 def tangents(self) -> np.ndarray: 630 """ 631 Compute the tangents of a line in space. 632 633 Example: 634 ```python 635 from vedo import * 636 shape = Assembly(dataurl+"timecourse1d.npy")[58] 637 pts = shape.rotate_x(30).vertices 638 tangents = Line(pts).tangents() 639 arrs = Arrows(pts, pts+tangents, c='blue9') 640 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 641 ``` 642 ![](https://vedo.embl.es/images/feats/line_tangents.png) 643 """ 644 v = np.gradient(self.vertices)[0] 645 ds_dt = np.linalg.norm(v, axis=1) 646 tangent = np.array([1 / ds_dt] * 3).transpose() * v 647 return tangent
Compute the tangents of a line in space.
Example:
from vedo import * shape = Assembly(dataurl+"timecourse1d.npy")[58] pts = shape.rotate_x(30).vertices tangents = Line(pts).tangents() arrs = Arrows(pts, pts+tangents, c='blue9') show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()
649 def curvature(self) -> np.ndarray: 650 """ 651 Compute the signed curvature of a line in space. 652 The signed is computed assuming the line is about coplanar to the xy plane. 653 654 Example: 655 ```python 656 from vedo import * 657 from vedo.pyplot import plot 658 shape = Assembly(dataurl+"timecourse1d.npy")[55] 659 curvs = Line(shape.vertices).curvature() 660 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 661 shape.render_lines_as_tubes().lw(12) 662 pp = plot(curvs, ac='white', lc='yellow5') 663 show(shape, pp, N=2, bg='bb', sharecam=False).close() 664 ``` 665 ![](https://vedo.embl.es/images/feats/line_curvature.png) 666 """ 667 v = np.gradient(self.vertices)[0] 668 a = np.gradient(v)[0] 669 av = np.cross(a, v) 670 mav = np.linalg.norm(av, axis=1) 671 mv = utils.mag2(v) 672 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 673 val[0] = val[1] 674 val[-1] = val[-2] 675 return val
Compute the signed curvature of a line in space. The signed is computed assuming the line is about coplanar to the xy plane.
Example:
from vedo import * from vedo.pyplot import plot shape = Assembly(dataurl+"timecourse1d.npy")[55] curvs = Line(shape.vertices).curvature() shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') shape.render_lines_as_tubes().lw(12) pp = plot(curvs, ac='white', lc='yellow5') show(shape, pp, N=2, bg='bb', sharecam=False).close()
677 def compute_curvature(self, method=0) -> "Line": 678 """ 679 Add a pointdata array named 'Curvatures' which contains 680 the curvature value at each point. 681 682 NB: keyword `method` is overridden in Mesh and has no effect here. 683 """ 684 # overrides mesh.compute_curvature 685 curvs = self.curvature() 686 vmin, vmax = np.min(curvs), np.max(curvs) 687 if vmin < 0 and vmax > 0: 688 v = max(-vmin, vmax) 689 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 690 else: 691 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 692 return self
Add a pointdata array named 'Curvatures' which contains the curvature value at each point.
NB: keyword method
is overridden in Mesh and has no effect here.
694 def plot_scalar( 695 self, 696 radius=0.0, 697 height=1.1, 698 normal=(), 699 camera=None, 700 ) -> "Line": 701 """ 702 Generate a new `Line` which plots the active scalar along the line. 703 704 Arguments: 705 radius : (float) 706 distance radius to the line 707 height: (float) 708 height of the plot 709 normal: (list) 710 normal vector to the plane of the plot 711 camera: (vtkCamera) 712 camera object to use for the plot orientation 713 714 Example: 715 ```python 716 from vedo import * 717 circle = Circle(res=360).rotate_y(20) 718 pts = circle.vertices 719 bore = Line(pts).lw(5) 720 values = np.arctan2(pts[:,1], pts[:,0]) 721 bore.pointdata["scalars"] = values + np.random.randn(360)/5 722 vap = bore.plot_scalar(radius=0, height=1) 723 show(bore, vap, axes=1, viewup='z').close() 724 ``` 725 ![](https://vedo.embl.es/images/feats/line_plot_scalar.png) 726 """ 727 ap = vtki.new("ArcPlotter") 728 ap.SetInputData(self.dataset) 729 ap.SetCamera(camera) 730 ap.SetRadius(radius) 731 ap.SetHeight(height) 732 if len(normal)>0: 733 ap.UseDefaultNormalOn() 734 ap.SetDefaultNormal(normal) 735 ap.Update() 736 vap = Line(ap.GetOutput()) 737 vap.linewidth(3).lighting('off') 738 vap.name = "ArcPlot" 739 return vap
Generate a new Line
which plots the active scalar along the line.
Arguments:
- radius : (float) distance radius to the line
- height: (float) height of the plot
- normal: (list) normal vector to the plane of the plot
- camera: (vtkCamera) camera object to use for the plot orientation
Example:
from vedo import * circle = Circle(res=360).rotate_y(20) pts = circle.vertices bore = Line(pts).lw(5) values = np.arctan2(pts[:,1], pts[:,0]) bore.pointdata["scalars"] = values + np.random.randn(360)/5 vap = bore.plot_scalar(radius=0, height=1) show(bore, vap, axes=1, viewup='z').close()
741 def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh": 742 """ 743 Sweep the `Line` along the specified vector direction. 744 745 Returns a `Mesh` surface. 746 Line position is updated to allow for additional sweepings. 747 748 Example: 749 ```python 750 from vedo import Line, show 751 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 752 surf1 = aline.sweep((1,0.2,0), res=3) 753 surf2 = aline.sweep((0.2,0,1)).alpha(0.5) 754 aline.color('r').linewidth(4) 755 show(surf1, surf2, aline, axes=1).close() 756 ``` 757 ![](https://vedo.embl.es/images/feats/sweepline.png) 758 """ 759 line = self.dataset 760 rows = line.GetNumberOfPoints() 761 762 spacing = 1 / res 763 surface = vtki.vtkPolyData() 764 765 res += 1 766 npts = rows * res 767 npolys = (rows - 1) * (res - 1) 768 points = vtki.vtkPoints() 769 points.Allocate(npts) 770 771 cnt = 0 772 x = [0.0, 0.0, 0.0] 773 for row in range(rows): 774 for col in range(res): 775 p = [0.0, 0.0, 0.0] 776 line.GetPoint(row, p) 777 x[0] = p[0] + direction[0] * col * spacing 778 x[1] = p[1] + direction[1] * col * spacing 779 x[2] = p[2] + direction[2] * col * spacing 780 points.InsertPoint(cnt, x) 781 cnt += 1 782 783 # Generate the quads 784 polys = vtki.vtkCellArray() 785 polys.Allocate(npolys * 4) 786 pts = [0, 0, 0, 0] 787 for row in range(rows - 1): 788 for col in range(res - 1): 789 pts[0] = col + row * res 790 pts[1] = pts[0] + 1 791 pts[2] = pts[0] + res + 1 792 pts[3] = pts[0] + res 793 polys.InsertNextCell(4, pts) 794 surface.SetPoints(points) 795 surface.SetPolys(polys) 796 asurface = Mesh(surface) 797 asurface.copy_properties_from(self) 798 asurface.lighting("default") 799 self.vertices = self.vertices + direction 800 return asurface
Sweep the Line
along the specified vector direction.
Returns a Mesh
surface.
Line position is updated to allow for additional sweepings.
Example:
from vedo import Line, show aline = Line([(0,0,0),(1,3,0),(2,4,0)]) surf1 = aline.sweep((1,0.2,0), res=3) surf2 = aline.sweep((0.2,0,1)).alpha(0.5) aline.color('r').linewidth(4) show(surf1, surf2, aline, axes=1).close()
809class DashedLine(Mesh): 810 """ 811 Consider using `Line.pattern()` instead. 812 813 Build a dashed line segment between points `p0` and `p1`. 814 If `p0` is a list of points returns the line connecting them. 815 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 816 """ 817 818 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None: 819 """ 820 Arguments: 821 closed : (bool) 822 join last to first point 823 spacing : (float) 824 relative size of the dash 825 lw : (int) 826 line width in pixels 827 """ 828 if isinstance(p1, vtki.vtkActor): 829 p1 = p1.GetPosition() 830 if isinstance(p0, vtki.vtkActor): 831 p0 = p0.GetPosition() 832 if isinstance(p0, Points): 833 p0 = p0.vertices 834 835 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 836 if len(p0) > 3: 837 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 838 # assume input is 2D xlist, ylist 839 p0 = np.stack((p0, p1), axis=1) 840 p1 = None 841 p0 = utils.make3d(p0) 842 if closed: 843 p0 = np.append(p0, [p0[0]], axis=0) 844 845 if p1 is not None: # assume passing p0=[x,y] 846 if len(p0) == 2 and not utils.is_sequence(p0[0]): 847 p0 = (p0[0], p0[1], 0) 848 if len(p1) == 2 and not utils.is_sequence(p1[0]): 849 p1 = (p1[0], p1[1], 0) 850 851 # detect if user is passing a list of points: 852 if utils.is_sequence(p0[0]): 853 listp = p0 854 else: # or just 2 points to link 855 listp = [p0, p1] 856 857 listp = np.array(listp) 858 if listp.shape[1] == 2: 859 listp = np.c_[listp, np.zeros(listp.shape[0])] 860 861 xmn = np.min(listp, axis=0) 862 xmx = np.max(listp, axis=0) 863 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 864 if not dlen: 865 super().__init__(vtki.vtkPolyData(), c, alpha) 866 self.name = "DashedLine (void)" 867 return 868 869 qs = [] 870 for ipt in range(len(listp) - 1): 871 p0 = listp[ipt] 872 p1 = listp[ipt + 1] 873 v = p1 - p0 874 vdist = np.linalg.norm(v) 875 n1 = int(vdist / dlen) 876 if not n1: 877 continue 878 879 res = 0.0 880 for i in range(n1 + 2): 881 ist = (i - 0.5) / n1 882 ist = max(ist, 0) 883 qi = p0 + v * (ist - res / vdist) 884 if ist > 1: 885 qi = p1 886 res = np.linalg.norm(qi - p1) 887 qs.append(qi) 888 break 889 qs.append(qi) 890 891 polylns = vtki.new("AppendPolyData") 892 for i, q1 in enumerate(qs): 893 if not i % 2: 894 continue 895 q0 = qs[i - 1] 896 line_source = vtki.new("LineSource") 897 line_source.SetPoint1(q0) 898 line_source.SetPoint2(q1) 899 line_source.Update() 900 polylns.AddInputData(line_source.GetOutput()) 901 polylns.Update() 902 903 super().__init__(polylns.GetOutput(), c, alpha) 904 self.lw(lw).lighting("off") 905 self.base = listp[0] 906 if closed: 907 self.top = listp[-2] 908 else: 909 self.top = listp[-1] 910 self.name = "DashedLine"
Consider using Line.pattern()
instead.
Build a dashed line segment between points p0
and p1
.
If p0
is a list of points returns the line connecting them.
A 2D set of coords can also be passed as p0=[x..], p1=[y..]
.
818 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None: 819 """ 820 Arguments: 821 closed : (bool) 822 join last to first point 823 spacing : (float) 824 relative size of the dash 825 lw : (int) 826 line width in pixels 827 """ 828 if isinstance(p1, vtki.vtkActor): 829 p1 = p1.GetPosition() 830 if isinstance(p0, vtki.vtkActor): 831 p0 = p0.GetPosition() 832 if isinstance(p0, Points): 833 p0 = p0.vertices 834 835 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 836 if len(p0) > 3: 837 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 838 # assume input is 2D xlist, ylist 839 p0 = np.stack((p0, p1), axis=1) 840 p1 = None 841 p0 = utils.make3d(p0) 842 if closed: 843 p0 = np.append(p0, [p0[0]], axis=0) 844 845 if p1 is not None: # assume passing p0=[x,y] 846 if len(p0) == 2 and not utils.is_sequence(p0[0]): 847 p0 = (p0[0], p0[1], 0) 848 if len(p1) == 2 and not utils.is_sequence(p1[0]): 849 p1 = (p1[0], p1[1], 0) 850 851 # detect if user is passing a list of points: 852 if utils.is_sequence(p0[0]): 853 listp = p0 854 else: # or just 2 points to link 855 listp = [p0, p1] 856 857 listp = np.array(listp) 858 if listp.shape[1] == 2: 859 listp = np.c_[listp, np.zeros(listp.shape[0])] 860 861 xmn = np.min(listp, axis=0) 862 xmx = np.max(listp, axis=0) 863 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 864 if not dlen: 865 super().__init__(vtki.vtkPolyData(), c, alpha) 866 self.name = "DashedLine (void)" 867 return 868 869 qs = [] 870 for ipt in range(len(listp) - 1): 871 p0 = listp[ipt] 872 p1 = listp[ipt + 1] 873 v = p1 - p0 874 vdist = np.linalg.norm(v) 875 n1 = int(vdist / dlen) 876 if not n1: 877 continue 878 879 res = 0.0 880 for i in range(n1 + 2): 881 ist = (i - 0.5) / n1 882 ist = max(ist, 0) 883 qi = p0 + v * (ist - res / vdist) 884 if ist > 1: 885 qi = p1 886 res = np.linalg.norm(qi - p1) 887 qs.append(qi) 888 break 889 qs.append(qi) 890 891 polylns = vtki.new("AppendPolyData") 892 for i, q1 in enumerate(qs): 893 if not i % 2: 894 continue 895 q0 = qs[i - 1] 896 line_source = vtki.new("LineSource") 897 line_source.SetPoint1(q0) 898 line_source.SetPoint2(q1) 899 line_source.Update() 900 polylns.AddInputData(line_source.GetOutput()) 901 polylns.Update() 902 903 super().__init__(polylns.GetOutput(), c, alpha) 904 self.lw(lw).lighting("off") 905 self.base = listp[0] 906 if closed: 907 self.top = listp[-2] 908 else: 909 self.top = listp[-1] 910 self.name = "DashedLine"
Arguments:
- closed : (bool) join last to first point
- spacing : (float) relative size of the dash
- lw : (int) line width in pixels
913class RoundedLine(Mesh): 914 """ 915 Create a 2D line of specified thickness (in absolute units) passing through 916 a list of input points. Borders of the line are rounded. 917 """ 918 919 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None: 920 """ 921 Arguments: 922 pts : (list) 923 a list of points in 2D or 3D (z will be ignored). 924 lw : (float) 925 thickness of the line. 926 res : (int) 927 resolution of the rounded regions 928 929 Example: 930 ```python 931 from vedo import * 932 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 933 ln = Line(pts).z(0.01) 934 ln.color("red5").linewidth(2) 935 rl = RoundedLine(pts, 0.6) 936 show(Points(pts), ln, rl, axes=1).close() 937 ``` 938 ![](https://vedo.embl.es/images/feats/rounded_line.png) 939 """ 940 pts = utils.make3d(pts) 941 942 def _getpts(pts, revd=False): 943 944 if revd: 945 pts = list(reversed(pts)) 946 947 if len(pts) == 2: 948 p0, p1 = pts 949 v = p1 - p0 950 dv = np.linalg.norm(v) 951 nv = np.cross(v, (0, 0, -1)) 952 nv = nv / np.linalg.norm(nv) * lw 953 return [p0 + nv, p1 + nv] 954 955 ptsnew = [] 956 for k in range(len(pts) - 2): 957 p0 = pts[k] 958 p1 = pts[k + 1] 959 p2 = pts[k + 2] 960 v = p1 - p0 961 u = p2 - p1 962 du = np.linalg.norm(u) 963 dv = np.linalg.norm(v) 964 nv = np.cross(v, (0, 0, -1)) 965 nv = nv / np.linalg.norm(nv) * lw 966 nu = np.cross(u, (0, 0, -1)) 967 nu = nu / np.linalg.norm(nu) * lw 968 uv = np.cross(u, v) 969 if k == 0: 970 ptsnew.append(p0 + nv) 971 if uv[2] <= 0: 972 # the following computation can return a value 973 # ever so slightly > 1.0 causing arccos to fail. 974 uv_arg = np.dot(u, v) / du / dv 975 if uv_arg > 1.0: 976 # since the argument to arcos is 1, simply 977 # assign alpha to 0.0 without calculating the 978 # arccos 979 alpha = 0.0 980 else: 981 alpha = np.arccos(uv_arg) 982 db = lw * np.tan(alpha / 2) 983 p1new = p1 + nv - v / dv * db 984 ptsnew.append(p1new) 985 else: 986 p1a = p1 + nv 987 p1b = p1 + nu 988 for i in range(0, res + 1): 989 pab = p1a * (res - i) / res + p1b * i / res 990 vpab = pab - p1 991 vpab = vpab / np.linalg.norm(vpab) * lw 992 ptsnew.append(p1 + vpab) 993 if k == len(pts) - 3: 994 ptsnew.append(p2 + nu) 995 if revd: 996 ptsnew.append(p2 - nu) 997 return ptsnew 998 999 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 1000 1001 ppoints = vtki.vtkPoints() # Generate the polyline 1002 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 1003 lines = vtki.vtkCellArray() 1004 npt = len(ptsnew) 1005 lines.InsertNextCell(npt) 1006 for i in range(npt): 1007 lines.InsertCellPoint(i) 1008 poly = vtki.vtkPolyData() 1009 poly.SetPoints(ppoints) 1010 poly.SetLines(lines) 1011 vct = vtki.new("ContourTriangulator") 1012 vct.SetInputData(poly) 1013 vct.Update() 1014 1015 super().__init__(vct.GetOutput(), c, alpha) 1016 self.flat() 1017 self.properties.LightingOff() 1018 self.name = "RoundedLine" 1019 self.base = ptsnew[0] 1020 self.top = ptsnew[-1]
Create a 2D line of specified thickness (in absolute units) passing through a list of input points. Borders of the line are rounded.
919 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None: 920 """ 921 Arguments: 922 pts : (list) 923 a list of points in 2D or 3D (z will be ignored). 924 lw : (float) 925 thickness of the line. 926 res : (int) 927 resolution of the rounded regions 928 929 Example: 930 ```python 931 from vedo import * 932 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 933 ln = Line(pts).z(0.01) 934 ln.color("red5").linewidth(2) 935 rl = RoundedLine(pts, 0.6) 936 show(Points(pts), ln, rl, axes=1).close() 937 ``` 938 ![](https://vedo.embl.es/images/feats/rounded_line.png) 939 """ 940 pts = utils.make3d(pts) 941 942 def _getpts(pts, revd=False): 943 944 if revd: 945 pts = list(reversed(pts)) 946 947 if len(pts) == 2: 948 p0, p1 = pts 949 v = p1 - p0 950 dv = np.linalg.norm(v) 951 nv = np.cross(v, (0, 0, -1)) 952 nv = nv / np.linalg.norm(nv) * lw 953 return [p0 + nv, p1 + nv] 954 955 ptsnew = [] 956 for k in range(len(pts) - 2): 957 p0 = pts[k] 958 p1 = pts[k + 1] 959 p2 = pts[k + 2] 960 v = p1 - p0 961 u = p2 - p1 962 du = np.linalg.norm(u) 963 dv = np.linalg.norm(v) 964 nv = np.cross(v, (0, 0, -1)) 965 nv = nv / np.linalg.norm(nv) * lw 966 nu = np.cross(u, (0, 0, -1)) 967 nu = nu / np.linalg.norm(nu) * lw 968 uv = np.cross(u, v) 969 if k == 0: 970 ptsnew.append(p0 + nv) 971 if uv[2] <= 0: 972 # the following computation can return a value 973 # ever so slightly > 1.0 causing arccos to fail. 974 uv_arg = np.dot(u, v) / du / dv 975 if uv_arg > 1.0: 976 # since the argument to arcos is 1, simply 977 # assign alpha to 0.0 without calculating the 978 # arccos 979 alpha = 0.0 980 else: 981 alpha = np.arccos(uv_arg) 982 db = lw * np.tan(alpha / 2) 983 p1new = p1 + nv - v / dv * db 984 ptsnew.append(p1new) 985 else: 986 p1a = p1 + nv 987 p1b = p1 + nu 988 for i in range(0, res + 1): 989 pab = p1a * (res - i) / res + p1b * i / res 990 vpab = pab - p1 991 vpab = vpab / np.linalg.norm(vpab) * lw 992 ptsnew.append(p1 + vpab) 993 if k == len(pts) - 3: 994 ptsnew.append(p2 + nu) 995 if revd: 996 ptsnew.append(p2 - nu) 997 return ptsnew 998 999 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 1000 1001 ppoints = vtki.vtkPoints() # Generate the polyline 1002 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 1003 lines = vtki.vtkCellArray() 1004 npt = len(ptsnew) 1005 lines.InsertNextCell(npt) 1006 for i in range(npt): 1007 lines.InsertCellPoint(i) 1008 poly = vtki.vtkPolyData() 1009 poly.SetPoints(ppoints) 1010 poly.SetLines(lines) 1011 vct = vtki.new("ContourTriangulator") 1012 vct.SetInputData(poly) 1013 vct.Update() 1014 1015 super().__init__(vct.GetOutput(), c, alpha) 1016 self.flat() 1017 self.properties.LightingOff() 1018 self.name = "RoundedLine" 1019 self.base = ptsnew[0] 1020 self.top = ptsnew[-1]
Arguments:
- pts : (list) a list of points in 2D or 3D (z will be ignored).
- lw : (float) thickness of the line.
- res : (int) resolution of the rounded regions
Example:
from vedo import * pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] ln = Line(pts).z(0.01) ln.color("red5").linewidth(2) rl = RoundedLine(pts, 0.6) show(Points(pts), ln, rl, axes=1).close()
1463class Tube(Mesh): 1464 """ 1465 Build a tube along the line defined by a set of points. 1466 """ 1467 1468 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None: 1469 """ 1470 Arguments: 1471 r : (float, list) 1472 constant radius or list of radii. 1473 res : (int) 1474 resolution, number of the sides of the tube 1475 c : (color) 1476 constant color or list of colors for each point. 1477 1478 Example: 1479 Create a tube along a line, with data associated to each point: 1480 1481 ```python 1482 from vedo import * 1483 line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5) 1484 scalars = np.array([0, 1, 2, 3]) 1485 line.pointdata["myscalars"] = scalars 1486 tube = Tube(line, r=0.1).lw(1) 1487 tube.cmap('viridis', "myscalars").add_scalarbar3d() 1488 show(line, tube, axes=1).close() 1489 ``` 1490 1491 Examples: 1492 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1493 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1494 1495 ![](https://vedo.embl.es/images/basic/tube.png) 1496 """ 1497 if utils.is_sequence(points): 1498 vpoints = vtki.vtkPoints() 1499 idx = len(points) 1500 for p in points: 1501 vpoints.InsertNextPoint(p) 1502 line = vtki.new("PolyLine") 1503 line.GetPointIds().SetNumberOfIds(idx) 1504 for i in range(idx): 1505 line.GetPointIds().SetId(i, i) 1506 lines = vtki.vtkCellArray() 1507 lines.InsertNextCell(line) 1508 polyln = vtki.vtkPolyData() 1509 polyln.SetPoints(vpoints) 1510 polyln.SetLines(lines) 1511 self.base = np.asarray(points[0], dtype=float) 1512 self.top = np.asarray(points[-1], dtype=float) 1513 1514 elif isinstance(points, Mesh): 1515 polyln = points.dataset 1516 n = polyln.GetNumberOfPoints() 1517 self.base = np.array(polyln.GetPoint(0)) 1518 self.top = np.array(polyln.GetPoint(n - 1)) 1519 1520 # from vtkmodules.vtkFiltersCore import vtkTubeBender 1521 # bender = vtkTubeBender() 1522 # bender.SetInputData(polyln) 1523 # bender.SetRadius(r) 1524 # bender.Update() 1525 # polyln = bender.GetOutput() 1526 1527 tuf = vtki.new("TubeFilter") 1528 tuf.SetCapping(cap) 1529 tuf.SetNumberOfSides(res) 1530 tuf.SetInputData(polyln) 1531 if utils.is_sequence(r): 1532 arr = utils.numpy2vtk(r, dtype=float) 1533 arr.SetName("TubeRadius") 1534 polyln.GetPointData().AddArray(arr) 1535 polyln.GetPointData().SetActiveScalars("TubeRadius") 1536 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1537 else: 1538 tuf.SetRadius(r) 1539 1540 usingColScals = False 1541 if utils.is_sequence(c): 1542 usingColScals = True 1543 cc = vtki.vtkUnsignedCharArray() 1544 cc.SetName("TubeColors") 1545 cc.SetNumberOfComponents(3) 1546 cc.SetNumberOfTuples(len(c)) 1547 for i, ic in enumerate(c): 1548 r, g, b = get_color(ic) 1549 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1550 polyln.GetPointData().AddArray(cc) 1551 c = None 1552 tuf.Update() 1553 1554 super().__init__(tuf.GetOutput(), c, alpha) 1555 self.phong() 1556 if usingColScals: 1557 self.mapper.SetScalarModeToUsePointFieldData() 1558 self.mapper.ScalarVisibilityOn() 1559 self.mapper.SelectColorArray("TubeColors") 1560 self.mapper.Modified() 1561 self.name = "Tube"
Build a tube along the line defined by a set of points.
1468 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None: 1469 """ 1470 Arguments: 1471 r : (float, list) 1472 constant radius or list of radii. 1473 res : (int) 1474 resolution, number of the sides of the tube 1475 c : (color) 1476 constant color or list of colors for each point. 1477 1478 Example: 1479 Create a tube along a line, with data associated to each point: 1480 1481 ```python 1482 from vedo import * 1483 line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5) 1484 scalars = np.array([0, 1, 2, 3]) 1485 line.pointdata["myscalars"] = scalars 1486 tube = Tube(line, r=0.1).lw(1) 1487 tube.cmap('viridis', "myscalars").add_scalarbar3d() 1488 show(line, tube, axes=1).close() 1489 ``` 1490 1491 Examples: 1492 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1493 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1494 1495 ![](https://vedo.embl.es/images/basic/tube.png) 1496 """ 1497 if utils.is_sequence(points): 1498 vpoints = vtki.vtkPoints() 1499 idx = len(points) 1500 for p in points: 1501 vpoints.InsertNextPoint(p) 1502 line = vtki.new("PolyLine") 1503 line.GetPointIds().SetNumberOfIds(idx) 1504 for i in range(idx): 1505 line.GetPointIds().SetId(i, i) 1506 lines = vtki.vtkCellArray() 1507 lines.InsertNextCell(line) 1508 polyln = vtki.vtkPolyData() 1509 polyln.SetPoints(vpoints) 1510 polyln.SetLines(lines) 1511 self.base = np.asarray(points[0], dtype=float) 1512 self.top = np.asarray(points[-1], dtype=float) 1513 1514 elif isinstance(points, Mesh): 1515 polyln = points.dataset 1516 n = polyln.GetNumberOfPoints() 1517 self.base = np.array(polyln.GetPoint(0)) 1518 self.top = np.array(polyln.GetPoint(n - 1)) 1519 1520 # from vtkmodules.vtkFiltersCore import vtkTubeBender 1521 # bender = vtkTubeBender() 1522 # bender.SetInputData(polyln) 1523 # bender.SetRadius(r) 1524 # bender.Update() 1525 # polyln = bender.GetOutput() 1526 1527 tuf = vtki.new("TubeFilter") 1528 tuf.SetCapping(cap) 1529 tuf.SetNumberOfSides(res) 1530 tuf.SetInputData(polyln) 1531 if utils.is_sequence(r): 1532 arr = utils.numpy2vtk(r, dtype=float) 1533 arr.SetName("TubeRadius") 1534 polyln.GetPointData().AddArray(arr) 1535 polyln.GetPointData().SetActiveScalars("TubeRadius") 1536 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1537 else: 1538 tuf.SetRadius(r) 1539 1540 usingColScals = False 1541 if utils.is_sequence(c): 1542 usingColScals = True 1543 cc = vtki.vtkUnsignedCharArray() 1544 cc.SetName("TubeColors") 1545 cc.SetNumberOfComponents(3) 1546 cc.SetNumberOfTuples(len(c)) 1547 for i, ic in enumerate(c): 1548 r, g, b = get_color(ic) 1549 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1550 polyln.GetPointData().AddArray(cc) 1551 c = None 1552 tuf.Update() 1553 1554 super().__init__(tuf.GetOutput(), c, alpha) 1555 self.phong() 1556 if usingColScals: 1557 self.mapper.SetScalarModeToUsePointFieldData() 1558 self.mapper.ScalarVisibilityOn() 1559 self.mapper.SelectColorArray("TubeColors") 1560 self.mapper.Modified() 1561 self.name = "Tube"
Arguments:
- r : (float, list) constant radius or list of radii.
- res : (int) resolution, number of the sides of the tube
- c : (color) constant color or list of colors for each point.
Example:
Create a tube along a line, with data associated to each point:
from vedo import * line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5) scalars = np.array([0, 1, 2, 3]) line.pointdata["myscalars"] = scalars tube = Tube(line, r=0.1).lw(1) tube.cmap('viridis', "myscalars").add_scalarbar3d() show(line, tube, axes=1).close()
Examples:
1615class Tubes(Mesh): 1616 """ 1617 Build tubes around a `Lines` object. 1618 """ 1619 def __init__( 1620 self, 1621 lines, 1622 r=1, 1623 vary_radius_by_scalar=False, 1624 vary_radius_by_vector=False, 1625 vary_radius_by_vector_norm=False, 1626 vary_radius_by_absolute_scalar=False, 1627 max_radius_factor=100, 1628 cap=True, 1629 res=12 1630 ) -> None: 1631 """ 1632 Wrap tubes around the input `Lines` object. 1633 1634 Arguments: 1635 lines : (Lines) 1636 input Lines object. 1637 r : (float) 1638 constant radius 1639 vary_radius_by_scalar : (bool) 1640 use scalar array to control radius 1641 vary_radius_by_vector : (bool) 1642 use vector array to control radius 1643 vary_radius_by_vector_norm : (bool) 1644 use vector norm to control radius 1645 vary_radius_by_absolute_scalar : (bool) 1646 use absolute scalar value to control radius 1647 max_radius_factor : (float) 1648 max tube radius as a multiple of the min radius 1649 cap : (bool) 1650 capping of the tube 1651 res : (int) 1652 resolution, number of the sides of the tube 1653 c : (color) 1654 constant color or list of colors for each point. 1655 1656 Examples: 1657 - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py) 1658 """ 1659 plines = lines.dataset 1660 if plines.GetNumberOfLines() == 0: 1661 vedo.logger.warning("Tubes(): input Lines is empty.") 1662 1663 tuf = vtki.new("TubeFilter") 1664 if vary_radius_by_scalar: 1665 tuf.SetVaryRadiusToVaryRadiusByScalar() 1666 elif vary_radius_by_vector: 1667 tuf.SetVaryRadiusToVaryRadiusByVector() 1668 elif vary_radius_by_vector_norm: 1669 tuf.SetVaryRadiusToVaryRadiusByVectorNorm() 1670 elif vary_radius_by_absolute_scalar: 1671 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1672 tuf.SetRadius(r) 1673 tuf.SetCapping(cap) 1674 tuf.SetGenerateTCoords(0) 1675 tuf.SetSidesShareVertices(1) 1676 tuf.SetRadiusFactor(max_radius_factor) 1677 tuf.SetNumberOfSides(res) 1678 tuf.SetInputData(plines) 1679 tuf.Update() 1680 1681 super().__init__(tuf.GetOutput()) 1682 self.name = "Tubes"
Build tubes around a Lines
object.
1619 def __init__( 1620 self, 1621 lines, 1622 r=1, 1623 vary_radius_by_scalar=False, 1624 vary_radius_by_vector=False, 1625 vary_radius_by_vector_norm=False, 1626 vary_radius_by_absolute_scalar=False, 1627 max_radius_factor=100, 1628 cap=True, 1629 res=12 1630 ) -> None: 1631 """ 1632 Wrap tubes around the input `Lines` object. 1633 1634 Arguments: 1635 lines : (Lines) 1636 input Lines object. 1637 r : (float) 1638 constant radius 1639 vary_radius_by_scalar : (bool) 1640 use scalar array to control radius 1641 vary_radius_by_vector : (bool) 1642 use vector array to control radius 1643 vary_radius_by_vector_norm : (bool) 1644 use vector norm to control radius 1645 vary_radius_by_absolute_scalar : (bool) 1646 use absolute scalar value to control radius 1647 max_radius_factor : (float) 1648 max tube radius as a multiple of the min radius 1649 cap : (bool) 1650 capping of the tube 1651 res : (int) 1652 resolution, number of the sides of the tube 1653 c : (color) 1654 constant color or list of colors for each point. 1655 1656 Examples: 1657 - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py) 1658 """ 1659 plines = lines.dataset 1660 if plines.GetNumberOfLines() == 0: 1661 vedo.logger.warning("Tubes(): input Lines is empty.") 1662 1663 tuf = vtki.new("TubeFilter") 1664 if vary_radius_by_scalar: 1665 tuf.SetVaryRadiusToVaryRadiusByScalar() 1666 elif vary_radius_by_vector: 1667 tuf.SetVaryRadiusToVaryRadiusByVector() 1668 elif vary_radius_by_vector_norm: 1669 tuf.SetVaryRadiusToVaryRadiusByVectorNorm() 1670 elif vary_radius_by_absolute_scalar: 1671 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1672 tuf.SetRadius(r) 1673 tuf.SetCapping(cap) 1674 tuf.SetGenerateTCoords(0) 1675 tuf.SetSidesShareVertices(1) 1676 tuf.SetRadiusFactor(max_radius_factor) 1677 tuf.SetNumberOfSides(res) 1678 tuf.SetInputData(plines) 1679 tuf.Update() 1680 1681 super().__init__(tuf.GetOutput()) 1682 self.name = "Tubes"
Wrap tubes around the input Lines
object.
Arguments:
- lines : (Lines) input Lines object.
- r : (float) constant radius
- vary_radius_by_scalar : (bool) use scalar array to control radius
- vary_radius_by_vector : (bool) use vector array to control radius
- vary_radius_by_vector_norm : (bool) use vector norm to control radius
- vary_radius_by_absolute_scalar : (bool) use absolute scalar value to control radius
- max_radius_factor : (float) max tube radius as a multiple of the min radius
- cap : (bool) capping of the tube
- res : (int) resolution, number of the sides of the tube
- c : (color) constant color or list of colors for each point.
Examples:
1564def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0) -> Union["Mesh", None]: 1565 """ 1566 Create a tube with a thickness along a line of points. 1567 1568 Example: 1569 ```python 1570 from vedo import * 1571 pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)] 1572 vline = Line(pts, lw=5, c='red5') 1573 thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1) 1574 show(vline, thick_tube, axes=1).close() 1575 ``` 1576 ![](https://vedo.embl.es/images/feats/thick_tube.png) 1577 """ 1578 1579 def make_cap(t1, t2): 1580 newpoints = t1.vertices.tolist() + t2.vertices.tolist() 1581 newfaces = [] 1582 for i in range(n - 1): 1583 newfaces.append([i, i + 1, i + n]) 1584 newfaces.append([i + n, i + 1, i + n + 1]) 1585 newfaces.append([2 * n - 1, 0, n]) 1586 newfaces.append([2 * n - 1, n - 1, 0]) 1587 capm = utils.buildPolyData(newpoints, newfaces) 1588 return capm 1589 1590 assert r1 < r2 1591 1592 t1 = Tube(pts, r=r1, cap=False, res=res) 1593 t2 = Tube(pts, r=r2, cap=False, res=res) 1594 1595 tc1a, tc1b = t1.boundaries().split() 1596 tc2a, tc2b = t2.boundaries().split() 1597 n = tc1b.npoints 1598 1599 tc1b.join(reset=True).clean() # needed because indices are flipped 1600 tc2b.join(reset=True).clean() 1601 1602 capa = make_cap(tc1a, tc2a) 1603 capb = make_cap(tc1b, tc2b) 1604 1605 thick_tube = merge(t1, t2, capa, capb) 1606 if thick_tube: 1607 thick_tube.c(c).alpha(alpha) 1608 thick_tube.base = t1.base 1609 thick_tube.top = t1.top 1610 thick_tube.name = "ThickTube" 1611 return thick_tube 1612 return None
Create a tube with a thickness along a line of points.
Example:
from vedo import *
pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)]
vline = Line(pts, lw=5, c='red5')
thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1)
show(vline, thick_tube, axes=1).close()
1023class Lines(Mesh): 1024 """ 1025 Build the line segments between two lists of points `start_pts` and `end_pts`. 1026 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1027 """ 1028 1029 def __init__( 1030 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 1031 ) -> None: 1032 """ 1033 Arguments: 1034 scale : (float) 1035 apply a rescaling factor to the lengths. 1036 c : (color, int, str, list) 1037 color name, number, or list of [R,G,B] colors 1038 alpha : (float) 1039 opacity in range [0,1] 1040 lw : (int) 1041 line width in pixel units 1042 dotted : (bool) 1043 draw a dotted line 1044 res : (int) 1045 resolution, number of points along the line 1046 (only relevant if only 2 points are specified) 1047 1048 Examples: 1049 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 1050 1051 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 1052 """ 1053 1054 if isinstance(start_pts, vtki.vtkPolyData):######## 1055 super().__init__(start_pts, c, alpha) 1056 self.lw(lw).lighting("off") 1057 self.name = "Lines" 1058 return ######################################## 1059 1060 if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line): 1061 # passing a list of Line, see tests/issues/issue_950.py 1062 polylns = vtki.new("AppendPolyData") 1063 for ln in start_pts: 1064 polylns.AddInputData(ln.dataset) 1065 polylns.Update() 1066 1067 super().__init__(polylns.GetOutput(), c, alpha) 1068 self.lw(lw).lighting("off") 1069 if dotted: 1070 self.properties.SetLineStipplePattern(0xF0F0) 1071 self.properties.SetLineStippleRepeatFactor(1) 1072 self.name = "Lines" 1073 return ######################################## 1074 1075 if isinstance(start_pts, Points): 1076 start_pts = start_pts.vertices 1077 if isinstance(end_pts, Points): 1078 end_pts = end_pts.vertices 1079 1080 if end_pts is not None: 1081 start_pts = np.stack((start_pts, end_pts), axis=1) 1082 1083 polylns = vtki.new("AppendPolyData") 1084 1085 if not utils.is_ragged(start_pts): 1086 1087 for twopts in start_pts: 1088 line_source = vtki.new("LineSource") 1089 line_source.SetResolution(res) 1090 if len(twopts[0]) == 2: 1091 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 1092 else: 1093 line_source.SetPoint1(twopts[0]) 1094 1095 if scale == 1: 1096 pt2 = twopts[1] 1097 else: 1098 vers = (np.array(twopts[1]) - twopts[0]) * scale 1099 pt2 = np.array(twopts[0]) + vers 1100 1101 if len(pt2) == 2: 1102 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1103 else: 1104 line_source.SetPoint2(pt2) 1105 polylns.AddInputConnection(line_source.GetOutputPort()) 1106 1107 else: 1108 1109 polylns = vtki.new("AppendPolyData") 1110 for t in start_pts: 1111 t = utils.make3d(t) 1112 ppoints = vtki.vtkPoints() # Generate the polyline 1113 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1114 lines = vtki.vtkCellArray() 1115 npt = len(t) 1116 lines.InsertNextCell(npt) 1117 for i in range(npt): 1118 lines.InsertCellPoint(i) 1119 poly = vtki.vtkPolyData() 1120 poly.SetPoints(ppoints) 1121 poly.SetLines(lines) 1122 polylns.AddInputData(poly) 1123 1124 polylns.Update() 1125 1126 super().__init__(polylns.GetOutput(), c, alpha) 1127 self.lw(lw).lighting("off") 1128 if dotted: 1129 self.properties.SetLineStipplePattern(0xF0F0) 1130 self.properties.SetLineStippleRepeatFactor(1) 1131 1132 self.name = "Lines"
Build the line segments between two lists of points start_pts
and end_pts
.
start_pts
can be also passed in the form [[point1, point2], ...]
.
1029 def __init__( 1030 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 1031 ) -> None: 1032 """ 1033 Arguments: 1034 scale : (float) 1035 apply a rescaling factor to the lengths. 1036 c : (color, int, str, list) 1037 color name, number, or list of [R,G,B] colors 1038 alpha : (float) 1039 opacity in range [0,1] 1040 lw : (int) 1041 line width in pixel units 1042 dotted : (bool) 1043 draw a dotted line 1044 res : (int) 1045 resolution, number of points along the line 1046 (only relevant if only 2 points are specified) 1047 1048 Examples: 1049 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 1050 1051 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 1052 """ 1053 1054 if isinstance(start_pts, vtki.vtkPolyData):######## 1055 super().__init__(start_pts, c, alpha) 1056 self.lw(lw).lighting("off") 1057 self.name = "Lines" 1058 return ######################################## 1059 1060 if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line): 1061 # passing a list of Line, see tests/issues/issue_950.py 1062 polylns = vtki.new("AppendPolyData") 1063 for ln in start_pts: 1064 polylns.AddInputData(ln.dataset) 1065 polylns.Update() 1066 1067 super().__init__(polylns.GetOutput(), c, alpha) 1068 self.lw(lw).lighting("off") 1069 if dotted: 1070 self.properties.SetLineStipplePattern(0xF0F0) 1071 self.properties.SetLineStippleRepeatFactor(1) 1072 self.name = "Lines" 1073 return ######################################## 1074 1075 if isinstance(start_pts, Points): 1076 start_pts = start_pts.vertices 1077 if isinstance(end_pts, Points): 1078 end_pts = end_pts.vertices 1079 1080 if end_pts is not None: 1081 start_pts = np.stack((start_pts, end_pts), axis=1) 1082 1083 polylns = vtki.new("AppendPolyData") 1084 1085 if not utils.is_ragged(start_pts): 1086 1087 for twopts in start_pts: 1088 line_source = vtki.new("LineSource") 1089 line_source.SetResolution(res) 1090 if len(twopts[0]) == 2: 1091 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 1092 else: 1093 line_source.SetPoint1(twopts[0]) 1094 1095 if scale == 1: 1096 pt2 = twopts[1] 1097 else: 1098 vers = (np.array(twopts[1]) - twopts[0]) * scale 1099 pt2 = np.array(twopts[0]) + vers 1100 1101 if len(pt2) == 2: 1102 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1103 else: 1104 line_source.SetPoint2(pt2) 1105 polylns.AddInputConnection(line_source.GetOutputPort()) 1106 1107 else: 1108 1109 polylns = vtki.new("AppendPolyData") 1110 for t in start_pts: 1111 t = utils.make3d(t) 1112 ppoints = vtki.vtkPoints() # Generate the polyline 1113 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1114 lines = vtki.vtkCellArray() 1115 npt = len(t) 1116 lines.InsertNextCell(npt) 1117 for i in range(npt): 1118 lines.InsertCellPoint(i) 1119 poly = vtki.vtkPolyData() 1120 poly.SetPoints(ppoints) 1121 poly.SetLines(lines) 1122 polylns.AddInputData(poly) 1123 1124 polylns.Update() 1125 1126 super().__init__(polylns.GetOutput(), c, alpha) 1127 self.lw(lw).lighting("off") 1128 if dotted: 1129 self.properties.SetLineStipplePattern(0xF0F0) 1130 self.properties.SetLineStippleRepeatFactor(1) 1131 1132 self.name = "Lines"
Arguments:
- scale : (float) apply a rescaling factor to the lengths.
- c : (color, int, str, list) color name, number, or list of [R,G,B] colors
- alpha : (float) opacity in range [0,1]
- lw : (int) line width in pixel units
- dotted : (bool) draw a dotted line
- res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
Examples:
1135class Spline(Line): 1136 """ 1137 Find the B-Spline curve through a set of points. This curve does not necessarily 1138 pass exactly through all the input points. Needs to import `scipy`. 1139 """ 1140 1141 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None: 1142 """ 1143 Arguments: 1144 smooth : (float) 1145 smoothing factor. 1146 - 0 = interpolate points exactly [default]. 1147 - 1 = average point positions. 1148 degree : (int) 1149 degree of the spline (between 1 and 5). 1150 easing : (str) 1151 control sensity of points along the spline. 1152 Available options are 1153 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1154 Can be used to create animations (move objects at varying speed). 1155 See e.g.: https://easings.net 1156 res : (int) 1157 number of points on the spline 1158 1159 See also: `CSpline` and `KSpline`. 1160 1161 Examples: 1162 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1163 1164 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1165 """ 1166 from scipy.interpolate import splprep, splev 1167 1168 if isinstance(points, Points): 1169 points = points.vertices 1170 1171 points = utils.make3d(points) 1172 1173 per = 0 1174 if closed: 1175 points = np.append(points, [points[0]], axis=0) 1176 per = 1 1177 1178 if res is None: 1179 res = len(points) * 10 1180 1181 points = np.array(points, dtype=float) 1182 1183 minx, miny, minz = np.min(points, axis=0) 1184 maxx, maxy, maxz = np.max(points, axis=0) 1185 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1186 smooth *= maxb / 2 # must be in absolute units 1187 1188 x = np.linspace(0.0, 1.0, res) 1189 if easing: 1190 if easing == "InSine": 1191 x = 1.0 - np.cos((x * np.pi) / 2) 1192 elif easing == "OutSine": 1193 x = np.sin((x * np.pi) / 2) 1194 elif easing == "Sine": 1195 x = -(np.cos(np.pi * x) - 1) / 2 1196 elif easing == "InQuad": 1197 x = x * x 1198 elif easing == "OutQuad": 1199 x = 1.0 - (1 - x) * (1 - x) 1200 elif easing == "InCubic": 1201 x = x * x 1202 elif easing == "OutCubic": 1203 x = 1.0 - np.power(1 - x, 3) 1204 elif easing == "InQuart": 1205 x = x * x * x * x 1206 elif easing == "OutQuart": 1207 x = 1.0 - np.power(1 - x, 4) 1208 elif easing == "InCirc": 1209 x = 1.0 - np.sqrt(1 - np.power(x, 2)) 1210 elif easing == "OutCirc": 1211 x = np.sqrt(1.0 - np.power(x - 1, 2)) 1212 else: 1213 vedo.logger.error(f"unknown ease mode {easing}") 1214 1215 # find the knots 1216 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1217 # evaluate spLine, including interpolated points: 1218 xnew, ynew, znew = splev(x, tckp) 1219 1220 super().__init__(np.c_[xnew, ynew, znew], lw=2) 1221 self.name = "Spline"
Find the B-Spline curve through a set of points. This curve does not necessarily
pass exactly through all the input points. Needs to import scipy
.
1141 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None: 1142 """ 1143 Arguments: 1144 smooth : (float) 1145 smoothing factor. 1146 - 0 = interpolate points exactly [default]. 1147 - 1 = average point positions. 1148 degree : (int) 1149 degree of the spline (between 1 and 5). 1150 easing : (str) 1151 control sensity of points along the spline. 1152 Available options are 1153 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1154 Can be used to create animations (move objects at varying speed). 1155 See e.g.: https://easings.net 1156 res : (int) 1157 number of points on the spline 1158 1159 See also: `CSpline` and `KSpline`. 1160 1161 Examples: 1162 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1163 1164 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1165 """ 1166 from scipy.interpolate import splprep, splev 1167 1168 if isinstance(points, Points): 1169 points = points.vertices 1170 1171 points = utils.make3d(points) 1172 1173 per = 0 1174 if closed: 1175 points = np.append(points, [points[0]], axis=0) 1176 per = 1 1177 1178 if res is None: 1179 res = len(points) * 10 1180 1181 points = np.array(points, dtype=float) 1182 1183 minx, miny, minz = np.min(points, axis=0) 1184 maxx, maxy, maxz = np.max(points, axis=0) 1185 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1186 smooth *= maxb / 2 # must be in absolute units 1187 1188 x = np.linspace(0.0, 1.0, res) 1189 if easing: 1190 if easing == "InSine": 1191 x = 1.0 - np.cos((x * np.pi) / 2) 1192 elif easing == "OutSine": 1193 x = np.sin((x * np.pi) / 2) 1194 elif easing == "Sine": 1195 x = -(np.cos(np.pi * x) - 1) / 2 1196 elif easing == "InQuad": 1197 x = x * x 1198 elif easing == "OutQuad": 1199 x = 1.0 - (1 - x) * (1 - x) 1200 elif easing == "InCubic": 1201 x = x * x 1202 elif easing == "OutCubic": 1203 x = 1.0 - np.power(1 - x, 3) 1204 elif easing == "InQuart": 1205 x = x * x * x * x 1206 elif easing == "OutQuart": 1207 x = 1.0 - np.power(1 - x, 4) 1208 elif easing == "InCirc": 1209 x = 1.0 - np.sqrt(1 - np.power(x, 2)) 1210 elif easing == "OutCirc": 1211 x = np.sqrt(1.0 - np.power(x - 1, 2)) 1212 else: 1213 vedo.logger.error(f"unknown ease mode {easing}") 1214 1215 # find the knots 1216 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1217 # evaluate spLine, including interpolated points: 1218 xnew, ynew, znew = splev(x, tckp) 1219 1220 super().__init__(np.c_[xnew, ynew, znew], lw=2) 1221 self.name = "Spline"
Arguments:
- smooth : (float)
smoothing factor.
- 0 = interpolate points exactly [default].
- 1 = average point positions.
- degree : (int) degree of the spline (between 1 and 5).
- easing : (str)
control sensity of points along the spline.
Available options are
[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].
Can be used to create animations (move objects at varying speed). See e.g.: https://easings.net - res : (int) number of points on the spline
See also: CSpline
and KSpline
.
Examples:
1224class KSpline(Line): 1225 """ 1226 Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline) 1227 which runs exactly through all the input points. 1228 """ 1229 1230 def __init__(self, points, 1231 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None: 1232 """ 1233 Arguments: 1234 continuity : (float) 1235 changes the sharpness in change between tangents 1236 tension : (float) 1237 changes the length of the tangent vector 1238 bias : (float) 1239 changes the direction of the tangent vector 1240 closed : (bool) 1241 join last to first point to produce a closed curve 1242 res : (int) 1243 approximate resolution of the output line. 1244 Default is 20 times the number of input points. 1245 1246 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1247 1248 Warning: 1249 This class is not necessarily generating the exact number of points 1250 as requested by `res`. Some points may be concident and removed. 1251 1252 See also: `Spline` and `CSpline`. 1253 """ 1254 if isinstance(points, Points): 1255 points = points.vertices 1256 1257 if not res: 1258 res = len(points) * 20 1259 1260 points = utils.make3d(points).astype(float) 1261 1262 vtkKochanekSpline = vtki.get_class("KochanekSpline") 1263 xspline = vtkKochanekSpline() 1264 yspline = vtkKochanekSpline() 1265 zspline = vtkKochanekSpline() 1266 for s in [xspline, yspline, zspline]: 1267 if bias: 1268 s.SetDefaultBias(bias) 1269 if tension: 1270 s.SetDefaultTension(tension) 1271 if continuity: 1272 s.SetDefaultContinuity(continuity) 1273 s.SetClosed(closed) 1274 1275 lenp = len(points[0]) > 2 1276 1277 for i, p in enumerate(points): 1278 xspline.AddPoint(i, p[0]) 1279 yspline.AddPoint(i, p[1]) 1280 if lenp: 1281 zspline.AddPoint(i, p[2]) 1282 1283 ln = [] 1284 for pos in np.linspace(0, len(points), res): 1285 x = xspline.Evaluate(pos) 1286 y = yspline.Evaluate(pos) 1287 z = 0 1288 if lenp: 1289 z = zspline.Evaluate(pos) 1290 ln.append((x, y, z)) 1291 1292 super().__init__(ln, lw=2) 1293 self.clean() 1294 self.lighting("off") 1295 self.name = "KSpline" 1296 self.base = np.array(points[0], dtype=float) 1297 self.top = np.array(points[-1], dtype=float)
Return a Kochanek spline which runs exactly through all the input points.
1230 def __init__(self, points, 1231 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None: 1232 """ 1233 Arguments: 1234 continuity : (float) 1235 changes the sharpness in change between tangents 1236 tension : (float) 1237 changes the length of the tangent vector 1238 bias : (float) 1239 changes the direction of the tangent vector 1240 closed : (bool) 1241 join last to first point to produce a closed curve 1242 res : (int) 1243 approximate resolution of the output line. 1244 Default is 20 times the number of input points. 1245 1246 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1247 1248 Warning: 1249 This class is not necessarily generating the exact number of points 1250 as requested by `res`. Some points may be concident and removed. 1251 1252 See also: `Spline` and `CSpline`. 1253 """ 1254 if isinstance(points, Points): 1255 points = points.vertices 1256 1257 if not res: 1258 res = len(points) * 20 1259 1260 points = utils.make3d(points).astype(float) 1261 1262 vtkKochanekSpline = vtki.get_class("KochanekSpline") 1263 xspline = vtkKochanekSpline() 1264 yspline = vtkKochanekSpline() 1265 zspline = vtkKochanekSpline() 1266 for s in [xspline, yspline, zspline]: 1267 if bias: 1268 s.SetDefaultBias(bias) 1269 if tension: 1270 s.SetDefaultTension(tension) 1271 if continuity: 1272 s.SetDefaultContinuity(continuity) 1273 s.SetClosed(closed) 1274 1275 lenp = len(points[0]) > 2 1276 1277 for i, p in enumerate(points): 1278 xspline.AddPoint(i, p[0]) 1279 yspline.AddPoint(i, p[1]) 1280 if lenp: 1281 zspline.AddPoint(i, p[2]) 1282 1283 ln = [] 1284 for pos in np.linspace(0, len(points), res): 1285 x = xspline.Evaluate(pos) 1286 y = yspline.Evaluate(pos) 1287 z = 0 1288 if lenp: 1289 z = zspline.Evaluate(pos) 1290 ln.append((x, y, z)) 1291 1292 super().__init__(ln, lw=2) 1293 self.clean() 1294 self.lighting("off") 1295 self.name = "KSpline" 1296 self.base = np.array(points[0], dtype=float) 1297 self.top = np.array(points[-1], dtype=float)
Arguments:
- continuity : (float) changes the sharpness in change between tangents
- tension : (float) changes the length of the tangent vector
- bias : (float) changes the direction of the tangent vector
- closed : (bool) join last to first point to produce a closed curve
- res : (int) approximate resolution of the output line. Default is 20 times the number of input points.
Warning:
This class is not necessarily generating the exact number of points as requested by
res
. Some points may be concident and removed.
1300class CSpline(Line): 1301 """ 1302 Return a Cardinal spline which runs exactly through all the input points. 1303 """ 1304 1305 def __init__(self, points, closed=False, res=None) -> None: 1306 """ 1307 Arguments: 1308 closed : (bool) 1309 join last to first point to produce a closed curve 1310 res : (int) 1311 approximate resolution of the output line. 1312 Default is 20 times the number of input points. 1313 1314 Warning: 1315 This class is not necessarily generating the exact number of points 1316 as requested by `res`. Some points may be concident and removed. 1317 1318 See also: `Spline` and `KSpline`. 1319 """ 1320 1321 if isinstance(points, Points): 1322 points = points.vertices 1323 1324 if not res: 1325 res = len(points) * 20 1326 1327 points = utils.make3d(points).astype(float) 1328 1329 vtkCardinalSpline = vtki.get_class("CardinalSpline") 1330 xspline = vtkCardinalSpline() 1331 yspline = vtkCardinalSpline() 1332 zspline = vtkCardinalSpline() 1333 for s in [xspline, yspline, zspline]: 1334 s.SetClosed(closed) 1335 1336 lenp = len(points[0]) > 2 1337 1338 for i, p in enumerate(points): 1339 xspline.AddPoint(i, p[0]) 1340 yspline.AddPoint(i, p[1]) 1341 if lenp: 1342 zspline.AddPoint(i, p[2]) 1343 1344 ln = [] 1345 for pos in np.linspace(0, len(points), res): 1346 x = xspline.Evaluate(pos) 1347 y = yspline.Evaluate(pos) 1348 z = 0 1349 if lenp: 1350 z = zspline.Evaluate(pos) 1351 ln.append((x, y, z)) 1352 1353 super().__init__(ln, lw=2) 1354 self.clean() 1355 self.lighting("off") 1356 self.name = "CSpline" 1357 self.base = points[0] 1358 self.top = points[-1]
Return a Cardinal spline which runs exactly through all the input points.
1305 def __init__(self, points, closed=False, res=None) -> None: 1306 """ 1307 Arguments: 1308 closed : (bool) 1309 join last to first point to produce a closed curve 1310 res : (int) 1311 approximate resolution of the output line. 1312 Default is 20 times the number of input points. 1313 1314 Warning: 1315 This class is not necessarily generating the exact number of points 1316 as requested by `res`. Some points may be concident and removed. 1317 1318 See also: `Spline` and `KSpline`. 1319 """ 1320 1321 if isinstance(points, Points): 1322 points = points.vertices 1323 1324 if not res: 1325 res = len(points) * 20 1326 1327 points = utils.make3d(points).astype(float) 1328 1329 vtkCardinalSpline = vtki.get_class("CardinalSpline") 1330 xspline = vtkCardinalSpline() 1331 yspline = vtkCardinalSpline() 1332 zspline = vtkCardinalSpline() 1333 for s in [xspline, yspline, zspline]: 1334 s.SetClosed(closed) 1335 1336 lenp = len(points[0]) > 2 1337 1338 for i, p in enumerate(points): 1339 xspline.AddPoint(i, p[0]) 1340 yspline.AddPoint(i, p[1]) 1341 if lenp: 1342 zspline.AddPoint(i, p[2]) 1343 1344 ln = [] 1345 for pos in np.linspace(0, len(points), res): 1346 x = xspline.Evaluate(pos) 1347 y = yspline.Evaluate(pos) 1348 z = 0 1349 if lenp: 1350 z = zspline.Evaluate(pos) 1351 ln.append((x, y, z)) 1352 1353 super().__init__(ln, lw=2) 1354 self.clean() 1355 self.lighting("off") 1356 self.name = "CSpline" 1357 self.base = points[0] 1358 self.top = points[-1]
Arguments:
- closed : (bool) join last to first point to produce a closed curve
- res : (int) approximate resolution of the output line. Default is 20 times the number of input points.
Warning:
This class is not necessarily generating the exact number of points as requested by
res
. Some points may be concident and removed.
1361class Bezier(Line): 1362 """ 1363 Generate the Bezier line that links the first to the last point. 1364 """ 1365 1366 def __init__(self, points, res=None) -> None: 1367 """ 1368 Example: 1369 ```python 1370 from vedo import * 1371 import numpy as np 1372 pts = np.random.randn(25,3) 1373 for i,p in enumerate(pts): 1374 p += [5*i, 15*sin(i/2), i*i*i/200] 1375 show(Points(pts), Bezier(pts), axes=1).close() 1376 ``` 1377 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1378 """ 1379 N = len(points) 1380 if res is None: 1381 res = 10 * N 1382 t = np.linspace(0, 1, num=res) 1383 bcurve = np.zeros((res, len(points[0]))) 1384 1385 def binom(n, k): 1386 b = 1 1387 for t in range(1, min(k, n - k) + 1): 1388 b *= n / t 1389 n -= 1 1390 return b 1391 1392 def bernstein(n, k): 1393 coeff = binom(n, k) 1394 1395 def _bpoly(x): 1396 return coeff * x ** k * (1 - x) ** (n - k) 1397 1398 return _bpoly 1399 1400 for ii in range(N): 1401 b = bernstein(N - 1, ii)(t) 1402 bcurve += np.outer(b, points[ii]) 1403 super().__init__(bcurve, lw=2) 1404 self.name = "BezierLine"
Generate the Bezier line that links the first to the last point.
1366 def __init__(self, points, res=None) -> None: 1367 """ 1368 Example: 1369 ```python 1370 from vedo import * 1371 import numpy as np 1372 pts = np.random.randn(25,3) 1373 for i,p in enumerate(pts): 1374 p += [5*i, 15*sin(i/2), i*i*i/200] 1375 show(Points(pts), Bezier(pts), axes=1).close() 1376 ``` 1377 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1378 """ 1379 N = len(points) 1380 if res is None: 1381 res = 10 * N 1382 t = np.linspace(0, 1, num=res) 1383 bcurve = np.zeros((res, len(points[0]))) 1384 1385 def binom(n, k): 1386 b = 1 1387 for t in range(1, min(k, n - k) + 1): 1388 b *= n / t 1389 n -= 1 1390 return b 1391 1392 def bernstein(n, k): 1393 coeff = binom(n, k) 1394 1395 def _bpoly(x): 1396 return coeff * x ** k * (1 - x) ** (n - k) 1397 1398 return _bpoly 1399 1400 for ii in range(N): 1401 b = bernstein(N - 1, ii)(t) 1402 bcurve += np.outer(b, points[ii]) 1403 super().__init__(bcurve, lw=2) 1404 self.name = "BezierLine"
Example:
from vedo import * import numpy as np pts = np.random.randn(25,3) for i,p in enumerate(pts): p += [5*i, 15*sin(i/2), i*i*i/200] show(Points(pts), Bezier(pts), axes=1).close()
3690class Brace(Mesh): 3691 """ 3692 Create a brace (bracket) shape. 3693 """ 3694 3695 def __init__( 3696 self, 3697 q1, 3698 q2, 3699 style="}", 3700 padding1=0.0, 3701 font="Theemim", 3702 comment="", 3703 justify=None, 3704 angle=0.0, 3705 padding2=0.2, 3706 s=1.0, 3707 italic=0, 3708 c="k1", 3709 alpha=1.0, 3710 ) -> None: 3711 """ 3712 Create a brace (bracket) shape which spans from point q1 to point q2. 3713 3714 Arguments: 3715 q1 : (list) 3716 point 1. 3717 q2 : (list) 3718 point 2. 3719 style : (str) 3720 style of the bracket, eg. `{}, [], (), <>`. 3721 padding1 : (float) 3722 padding space in percent form the input points. 3723 font : (str) 3724 font type 3725 comment : (str) 3726 additional text to appear next to the brace symbol. 3727 justify : (str) 3728 specify the anchor point to justify text comment, e.g. "top-left". 3729 italic : float 3730 italicness of the text comment (can be a positive or negative number) 3731 angle : (float) 3732 rotation angle of text. Use `None` to keep it horizontal. 3733 padding2 : (float) 3734 padding space in percent form brace to text comment. 3735 s : (float) 3736 scale factor for the comment 3737 3738 Examples: 3739 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3740 3741 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3742 """ 3743 if isinstance(q1, vtki.vtkActor): 3744 q1 = q1.GetPosition() 3745 if isinstance(q2, vtki.vtkActor): 3746 q2 = q2.GetPosition() 3747 if len(q1) == 2: 3748 q1 = [q1[0], q1[1], 0.0] 3749 if len(q2) == 2: 3750 q2 = [q2[0], q2[1], 0.0] 3751 q1 = np.array(q1, dtype=float) 3752 q2 = np.array(q2, dtype=float) 3753 mq = (q1 + q2) / 2 3754 q1 = q1 - mq 3755 q2 = q2 - mq 3756 d = np.linalg.norm(q2 - q1) 3757 q2[2] = q1[2] 3758 3759 if style not in "{}[]()<>|I": 3760 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3761 style = "}" 3762 3763 flip = False 3764 if style in ["{", "[", "(", "<"]: 3765 flip = True 3766 i = ["{", "[", "(", "<"].index(style) 3767 style = ["}", "]", ")", ">"][i] 3768 3769 br = Text3D(style, font="Theemim", justify="center-left") 3770 br.scale([0.4, 1, 1]) 3771 3772 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3773 if flip: 3774 angler += 180 3775 3776 _, x1, y0, y1, _, _ = br.bounds() 3777 if comment: 3778 just = "center-top" 3779 if angle is None: 3780 angle = -angler + 90 3781 if not flip: 3782 angle += 180 3783 3784 if flip: 3785 angle += 180 3786 just = "center-bottom" 3787 if justify is not None: 3788 just = justify 3789 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3790 cx0, cx1 = cmt.xbounds() 3791 cmt.rotate_z(90 + angle) 3792 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3793 cmt.shift(x1 * (1 + padding2), 0, 0) 3794 poly = merge(br, cmt).dataset 3795 3796 else: 3797 poly = br.dataset 3798 3799 tr = vtki.vtkTransform() 3800 tr.Translate(mq) 3801 tr.RotateZ(angler) 3802 tr.Translate(padding1 * d, 0, 0) 3803 pscale = 1 3804 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3805 3806 tf = vtki.new("TransformPolyDataFilter") 3807 tf.SetInputData(poly) 3808 tf.SetTransform(tr) 3809 tf.Update() 3810 poly = tf.GetOutput() 3811 3812 super().__init__(poly, c, alpha) 3813 3814 self.base = q1 3815 self.top = q2 3816 self.name = "Brace"
Create a brace (bracket) shape.
3695 def __init__( 3696 self, 3697 q1, 3698 q2, 3699 style="}", 3700 padding1=0.0, 3701 font="Theemim", 3702 comment="", 3703 justify=None, 3704 angle=0.0, 3705 padding2=0.2, 3706 s=1.0, 3707 italic=0, 3708 c="k1", 3709 alpha=1.0, 3710 ) -> None: 3711 """ 3712 Create a brace (bracket) shape which spans from point q1 to point q2. 3713 3714 Arguments: 3715 q1 : (list) 3716 point 1. 3717 q2 : (list) 3718 point 2. 3719 style : (str) 3720 style of the bracket, eg. `{}, [], (), <>`. 3721 padding1 : (float) 3722 padding space in percent form the input points. 3723 font : (str) 3724 font type 3725 comment : (str) 3726 additional text to appear next to the brace symbol. 3727 justify : (str) 3728 specify the anchor point to justify text comment, e.g. "top-left". 3729 italic : float 3730 italicness of the text comment (can be a positive or negative number) 3731 angle : (float) 3732 rotation angle of text. Use `None` to keep it horizontal. 3733 padding2 : (float) 3734 padding space in percent form brace to text comment. 3735 s : (float) 3736 scale factor for the comment 3737 3738 Examples: 3739 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3740 3741 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3742 """ 3743 if isinstance(q1, vtki.vtkActor): 3744 q1 = q1.GetPosition() 3745 if isinstance(q2, vtki.vtkActor): 3746 q2 = q2.GetPosition() 3747 if len(q1) == 2: 3748 q1 = [q1[0], q1[1], 0.0] 3749 if len(q2) == 2: 3750 q2 = [q2[0], q2[1], 0.0] 3751 q1 = np.array(q1, dtype=float) 3752 q2 = np.array(q2, dtype=float) 3753 mq = (q1 + q2) / 2 3754 q1 = q1 - mq 3755 q2 = q2 - mq 3756 d = np.linalg.norm(q2 - q1) 3757 q2[2] = q1[2] 3758 3759 if style not in "{}[]()<>|I": 3760 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3761 style = "}" 3762 3763 flip = False 3764 if style in ["{", "[", "(", "<"]: 3765 flip = True 3766 i = ["{", "[", "(", "<"].index(style) 3767 style = ["}", "]", ")", ">"][i] 3768 3769 br = Text3D(style, font="Theemim", justify="center-left") 3770 br.scale([0.4, 1, 1]) 3771 3772 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3773 if flip: 3774 angler += 180 3775 3776 _, x1, y0, y1, _, _ = br.bounds() 3777 if comment: 3778 just = "center-top" 3779 if angle is None: 3780 angle = -angler + 90 3781 if not flip: 3782 angle += 180 3783 3784 if flip: 3785 angle += 180 3786 just = "center-bottom" 3787 if justify is not None: 3788 just = justify 3789 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3790 cx0, cx1 = cmt.xbounds() 3791 cmt.rotate_z(90 + angle) 3792 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3793 cmt.shift(x1 * (1 + padding2), 0, 0) 3794 poly = merge(br, cmt).dataset 3795 3796 else: 3797 poly = br.dataset 3798 3799 tr = vtki.vtkTransform() 3800 tr.Translate(mq) 3801 tr.RotateZ(angler) 3802 tr.Translate(padding1 * d, 0, 0) 3803 pscale = 1 3804 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3805 3806 tf = vtki.new("TransformPolyDataFilter") 3807 tf.SetInputData(poly) 3808 tf.SetTransform(tr) 3809 tf.Update() 3810 poly = tf.GetOutput() 3811 3812 super().__init__(poly, c, alpha) 3813 3814 self.base = q1 3815 self.top = q2 3816 self.name = "Brace"
Create a brace (bracket) shape which spans from point q1 to point q2.
Arguments:
- q1 : (list) point 1.
- q2 : (list) point 2.
- style : (str)
style of the bracket, eg.
{}, [], (), <>
. - padding1 : (float) padding space in percent form the input points.
- font : (str) font type
- comment : (str) additional text to appear next to the brace symbol.
- justify : (str) specify the anchor point to justify text comment, e.g. "top-left".
- italic : float italicness of the text comment (can be a positive or negative number)
- angle : (float)
rotation angle of text. Use
None
to keep it horizontal. - padding2 : (float) padding space in percent form brace to text comment.
- s : (float) scale factor for the comment
Examples:
1407class NormalLines(Mesh): 1408 """ 1409 Build an `Glyph` to show the normals at cell centers or at mesh vertices. 1410 1411 Arguments: 1412 ratio : (int) 1413 show 1 normal every `ratio` cells. 1414 on : (str) 1415 either "cells" or "points". 1416 scale : (float) 1417 scale factor to control size. 1418 """ 1419 1420 def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None: 1421 1422 poly = msh.clone().dataset 1423 1424 if "cell" in on: 1425 centers = vtki.new("CellCenters") 1426 centers.SetInputData(poly) 1427 centers.Update() 1428 poly = centers.GetOutput() 1429 1430 mask_pts = vtki.new("MaskPoints") 1431 mask_pts.SetInputData(poly) 1432 mask_pts.SetOnRatio(ratio) 1433 mask_pts.RandomModeOff() 1434 mask_pts.Update() 1435 1436 ln = vtki.new("LineSource") 1437 ln.SetPoint1(0, 0, 0) 1438 ln.SetPoint2(1, 0, 0) 1439 ln.Update() 1440 glyph = vtki.vtkGlyph3D() 1441 glyph.SetSourceData(ln.GetOutput()) 1442 glyph.SetInputData(mask_pts.GetOutput()) 1443 glyph.SetVectorModeToUseNormal() 1444 1445 b = poly.GetBounds() 1446 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1447 glyph.SetScaleFactor(f) 1448 glyph.OrientOn() 1449 glyph.Update() 1450 1451 super().__init__(glyph.GetOutput()) 1452 1453 self.actor.PickableOff() 1454 prop = vtki.vtkProperty() 1455 prop.DeepCopy(msh.properties) 1456 self.actor.SetProperty(prop) 1457 self.properties = prop 1458 self.properties.LightingOff() 1459 self.mapper.ScalarVisibilityOff() 1460 self.name = "NormalLines"
Build an Glyph
to show the normals at cell centers or at mesh vertices.
Arguments:
- ratio : (int)
show 1 normal every
ratio
cells. - on : (str) either "cells" or "points".
- scale : (float) scale factor to control size.
1420 def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None: 1421 1422 poly = msh.clone().dataset 1423 1424 if "cell" in on: 1425 centers = vtki.new("CellCenters") 1426 centers.SetInputData(poly) 1427 centers.Update() 1428 poly = centers.GetOutput() 1429 1430 mask_pts = vtki.new("MaskPoints") 1431 mask_pts.SetInputData(poly) 1432 mask_pts.SetOnRatio(ratio) 1433 mask_pts.RandomModeOff() 1434 mask_pts.Update() 1435 1436 ln = vtki.new("LineSource") 1437 ln.SetPoint1(0, 0, 0) 1438 ln.SetPoint2(1, 0, 0) 1439 ln.Update() 1440 glyph = vtki.vtkGlyph3D() 1441 glyph.SetSourceData(ln.GetOutput()) 1442 glyph.SetInputData(mask_pts.GetOutput()) 1443 glyph.SetVectorModeToUseNormal() 1444 1445 b = poly.GetBounds() 1446 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1447 glyph.SetScaleFactor(f) 1448 glyph.OrientOn() 1449 glyph.Update() 1450 1451 super().__init__(glyph.GetOutput()) 1452 1453 self.actor.PickableOff() 1454 prop = vtki.vtkProperty() 1455 prop.DeepCopy(msh.properties) 1456 self.actor.SetProperty(prop) 1457 self.properties = prop 1458 self.properties.LightingOff() 1459 self.mapper.ScalarVisibilityOff() 1460 self.name = "NormalLines"
Initialize a Mesh
object.
Arguments:
- inputobj : (str, vtkPolyData, vtkActor, vedo.Mesh)
If inputobj is
None
an empty mesh is created. If inputobj is astr
then it is interpreted as the name of a file to load as mesh. If inputobj is anvtkPolyData
orvtkActor
orvedo.Mesh
then a shallow copy of it is created. If inputobj is avedo.Mesh
then a shallow copy of it is created.
Examples:
- buildmesh.py (and many others!)
1685class Ribbon(Mesh): 1686 """ 1687 Connect two lines to generate the surface inbetween. 1688 Set the mode by which to create the ruled surface. 1689 1690 It also works with a single line in input. In this case the ribbon 1691 is formed by following the local plane of the line in space. 1692 """ 1693 1694 def __init__( 1695 self, 1696 line1, 1697 line2=None, 1698 mode=0, 1699 closed=False, 1700 width=None, 1701 res=(200, 5), 1702 c="indigo3", 1703 alpha=1.0, 1704 ) -> None: 1705 """ 1706 Arguments: 1707 mode : (int) 1708 If mode=0, resample evenly the input lines (based on length) 1709 and generates triangle strips. 1710 1711 If mode=1, use the existing points and walks around the 1712 polyline using existing points. 1713 1714 closed : (bool) 1715 if True, join the last point with the first to form a closed surface 1716 1717 res : (list) 1718 ribbon resolutions along the line and perpendicularly to it. 1719 1720 Examples: 1721 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1722 1723 ![](https://vedo.embl.es/images/basic/ribbon.png) 1724 """ 1725 1726 if isinstance(line1, Points): 1727 line1 = line1.vertices 1728 1729 if isinstance(line2, Points): 1730 line2 = line2.vertices 1731 1732 elif line2 is None: 1733 ############################################# 1734 ribbon_filter = vtki.new("RibbonFilter") 1735 aline = Line(line1) 1736 ribbon_filter.SetInputData(aline.dataset) 1737 if width is None: 1738 width = aline.diagonal_size() / 20.0 1739 ribbon_filter.SetWidth(width) 1740 ribbon_filter.Update() 1741 # convert triangle strips to polygons 1742 tris = vtki.new("TriangleFilter") 1743 tris.SetInputData(ribbon_filter.GetOutput()) 1744 tris.Update() 1745 1746 super().__init__(tris.GetOutput(), c, alpha) 1747 self.name = "Ribbon" 1748 ############################################## 1749 return ###################################### 1750 ############################################## 1751 1752 line1 = np.asarray(line1) 1753 line2 = np.asarray(line2) 1754 1755 if closed: 1756 line1 = line1.tolist() 1757 line1 += [line1[0]] 1758 line2 = line2.tolist() 1759 line2 += [line2[0]] 1760 line1 = np.array(line1) 1761 line2 = np.array(line2) 1762 1763 if len(line1[0]) == 2: 1764 line1 = np.c_[line1, np.zeros(len(line1))] 1765 if len(line2[0]) == 2: 1766 line2 = np.c_[line2, np.zeros(len(line2))] 1767 1768 ppoints1 = vtki.vtkPoints() # Generate the polyline1 1769 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1770 lines1 = vtki.vtkCellArray() 1771 lines1.InsertNextCell(len(line1)) 1772 for i in range(len(line1)): 1773 lines1.InsertCellPoint(i) 1774 poly1 = vtki.vtkPolyData() 1775 poly1.SetPoints(ppoints1) 1776 poly1.SetLines(lines1) 1777 1778 ppoints2 = vtki.vtkPoints() # Generate the polyline2 1779 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1780 lines2 = vtki.vtkCellArray() 1781 lines2.InsertNextCell(len(line2)) 1782 for i in range(len(line2)): 1783 lines2.InsertCellPoint(i) 1784 poly2 = vtki.vtkPolyData() 1785 poly2.SetPoints(ppoints2) 1786 poly2.SetLines(lines2) 1787 1788 # build the lines 1789 lines1 = vtki.vtkCellArray() 1790 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1791 for i in range(poly1.GetNumberOfPoints()): 1792 lines1.InsertCellPoint(i) 1793 1794 polygon1 = vtki.vtkPolyData() 1795 polygon1.SetPoints(ppoints1) 1796 polygon1.SetLines(lines1) 1797 1798 lines2 = vtki.vtkCellArray() 1799 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1800 for i in range(poly2.GetNumberOfPoints()): 1801 lines2.InsertCellPoint(i) 1802 1803 polygon2 = vtki.vtkPolyData() 1804 polygon2.SetPoints(ppoints2) 1805 polygon2.SetLines(lines2) 1806 1807 merged_pd = vtki.new("AppendPolyData") 1808 merged_pd.AddInputData(polygon1) 1809 merged_pd.AddInputData(polygon2) 1810 merged_pd.Update() 1811 1812 rsf = vtki.new("RuledSurfaceFilter") 1813 rsf.CloseSurfaceOff() 1814 rsf.SetRuledMode(mode) 1815 rsf.SetResolution(res[0], res[1]) 1816 rsf.SetInputData(merged_pd.GetOutput()) 1817 rsf.Update() 1818 # convert triangle strips to polygons 1819 tris = vtki.new("TriangleFilter") 1820 tris.SetInputData(rsf.GetOutput()) 1821 tris.Update() 1822 out = tris.GetOutput() 1823 1824 super().__init__(out, c, alpha) 1825 1826 self.name = "Ribbon"
Connect two lines to generate the surface inbetween. Set the mode by which to create the ruled surface.
It also works with a single line in input. In this case the ribbon is formed by following the local plane of the line in space.
1694 def __init__( 1695 self, 1696 line1, 1697 line2=None, 1698 mode=0, 1699 closed=False, 1700 width=None, 1701 res=(200, 5), 1702 c="indigo3", 1703 alpha=1.0, 1704 ) -> None: 1705 """ 1706 Arguments: 1707 mode : (int) 1708 If mode=0, resample evenly the input lines (based on length) 1709 and generates triangle strips. 1710 1711 If mode=1, use the existing points and walks around the 1712 polyline using existing points. 1713 1714 closed : (bool) 1715 if True, join the last point with the first to form a closed surface 1716 1717 res : (list) 1718 ribbon resolutions along the line and perpendicularly to it. 1719 1720 Examples: 1721 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1722 1723 ![](https://vedo.embl.es/images/basic/ribbon.png) 1724 """ 1725 1726 if isinstance(line1, Points): 1727 line1 = line1.vertices 1728 1729 if isinstance(line2, Points): 1730 line2 = line2.vertices 1731 1732 elif line2 is None: 1733 ############################################# 1734 ribbon_filter = vtki.new("RibbonFilter") 1735 aline = Line(line1) 1736 ribbon_filter.SetInputData(aline.dataset) 1737 if width is None: 1738 width = aline.diagonal_size() / 20.0 1739 ribbon_filter.SetWidth(width) 1740 ribbon_filter.Update() 1741 # convert triangle strips to polygons 1742 tris = vtki.new("TriangleFilter") 1743 tris.SetInputData(ribbon_filter.GetOutput()) 1744 tris.Update() 1745 1746 super().__init__(tris.GetOutput(), c, alpha) 1747 self.name = "Ribbon" 1748 ############################################## 1749 return ###################################### 1750 ############################################## 1751 1752 line1 = np.asarray(line1) 1753 line2 = np.asarray(line2) 1754 1755 if closed: 1756 line1 = line1.tolist() 1757 line1 += [line1[0]] 1758 line2 = line2.tolist() 1759 line2 += [line2[0]] 1760 line1 = np.array(line1) 1761 line2 = np.array(line2) 1762 1763 if len(line1[0]) == 2: 1764 line1 = np.c_[line1, np.zeros(len(line1))] 1765 if len(line2[0]) == 2: 1766 line2 = np.c_[line2, np.zeros(len(line2))] 1767 1768 ppoints1 = vtki.vtkPoints() # Generate the polyline1 1769 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1770 lines1 = vtki.vtkCellArray() 1771 lines1.InsertNextCell(len(line1)) 1772 for i in range(len(line1)): 1773 lines1.InsertCellPoint(i) 1774 poly1 = vtki.vtkPolyData() 1775 poly1.SetPoints(ppoints1) 1776 poly1.SetLines(lines1) 1777 1778 ppoints2 = vtki.vtkPoints() # Generate the polyline2 1779 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1780 lines2 = vtki.vtkCellArray() 1781 lines2.InsertNextCell(len(line2)) 1782 for i in range(len(line2)): 1783 lines2.InsertCellPoint(i) 1784 poly2 = vtki.vtkPolyData() 1785 poly2.SetPoints(ppoints2) 1786 poly2.SetLines(lines2) 1787 1788 # build the lines 1789 lines1 = vtki.vtkCellArray() 1790 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1791 for i in range(poly1.GetNumberOfPoints()): 1792 lines1.InsertCellPoint(i) 1793 1794 polygon1 = vtki.vtkPolyData() 1795 polygon1.SetPoints(ppoints1) 1796 polygon1.SetLines(lines1) 1797 1798 lines2 = vtki.vtkCellArray() 1799 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1800 for i in range(poly2.GetNumberOfPoints()): 1801 lines2.InsertCellPoint(i) 1802 1803 polygon2 = vtki.vtkPolyData() 1804 polygon2.SetPoints(ppoints2) 1805 polygon2.SetLines(lines2) 1806 1807 merged_pd = vtki.new("AppendPolyData") 1808 merged_pd.AddInputData(polygon1) 1809 merged_pd.AddInputData(polygon2) 1810 merged_pd.Update() 1811 1812 rsf = vtki.new("RuledSurfaceFilter") 1813 rsf.CloseSurfaceOff() 1814 rsf.SetRuledMode(mode) 1815 rsf.SetResolution(res[0], res[1]) 1816 rsf.SetInputData(merged_pd.GetOutput()) 1817 rsf.Update() 1818 # convert triangle strips to polygons 1819 tris = vtki.new("TriangleFilter") 1820 tris.SetInputData(rsf.GetOutput()) 1821 tris.Update() 1822 out = tris.GetOutput() 1823 1824 super().__init__(out, c, alpha) 1825 1826 self.name = "Ribbon"
Arguments:
mode : (int) If mode=0, resample evenly the input lines (based on length) and generates triangle strips.
If mode=1, use the existing points and walks around the polyline using existing points.
- closed : (bool) if True, join the last point with the first to form a closed surface
- res : (list) ribbon resolutions along the line and perpendicularly to it.
Examples:
1829class Arrow(Mesh): 1830 """ 1831 Build a 3D arrow from `start_pt` to `end_pt` of section size `s`, 1832 expressed as the fraction of the window size. 1833 """ 1834 1835 def __init__( 1836 self, 1837 start_pt=(0, 0, 0), 1838 end_pt=(1, 0, 0), 1839 s=None, 1840 shaft_radius=None, 1841 head_radius=None, 1842 head_length=None, 1843 res=12, 1844 c="r4", 1845 alpha=1.0, 1846 ) -> None: 1847 """ 1848 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1849 from white to red. 1850 1851 .. note:: If `s=None` the arrow is scaled proportionally to its length 1852 1853 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1854 """ 1855 # in case user is passing meshs 1856 if isinstance(start_pt, vtki.vtkActor): 1857 start_pt = start_pt.GetPosition() 1858 if isinstance(end_pt, vtki.vtkActor): 1859 end_pt = end_pt.GetPosition() 1860 1861 axis = np.asarray(end_pt) - np.asarray(start_pt) 1862 length = float(np.linalg.norm(axis)) 1863 if length: 1864 axis = axis / length 1865 if len(axis) < 3: # its 2d 1866 theta = np.pi / 2 1867 start_pt = [start_pt[0], start_pt[1], 0.0] 1868 end_pt = [end_pt[0], end_pt[1], 0.0] 1869 else: 1870 theta = np.arccos(axis[2]) 1871 phi = np.arctan2(axis[1], axis[0]) 1872 self.source = vtki.new("ArrowSource") 1873 self.source.SetShaftResolution(res) 1874 self.source.SetTipResolution(res) 1875 1876 if s: 1877 sz = 0.02 1878 self.source.SetTipRadius(sz) 1879 self.source.SetShaftRadius(sz / 1.75) 1880 self.source.SetTipLength(sz * 15) 1881 1882 if head_length: 1883 self.source.SetTipLength(head_length) 1884 if head_radius: 1885 self.source.SetTipRadius(head_radius) 1886 if shaft_radius: 1887 self.source.SetShaftRadius(shaft_radius) 1888 1889 self.source.Update() 1890 1891 t = vtki.vtkTransform() 1892 t.Translate(start_pt) 1893 t.RotateZ(np.rad2deg(phi)) 1894 t.RotateY(np.rad2deg(theta)) 1895 t.RotateY(-90) # put it along Z 1896 if s: 1897 sz = 800 * s 1898 t.Scale(length, sz, sz) 1899 else: 1900 t.Scale(length, length, length) 1901 1902 tf = vtki.new("TransformPolyDataFilter") 1903 tf.SetInputData(self.source.GetOutput()) 1904 tf.SetTransform(t) 1905 tf.Update() 1906 1907 super().__init__(tf.GetOutput(), c, alpha) 1908 1909 self.transform = LinearTransform().translate(start_pt) 1910 1911 self.phong().lighting("plastic") 1912 self.actor.PickableOff() 1913 self.actor.DragableOff() 1914 self.base = np.array(start_pt, dtype=float) # used by pyplot 1915 self.top = np.array(end_pt, dtype=float) # used by pyplot 1916 self.top_index = self.source.GetTipResolution() * 4 1917 self.fill = True # used by pyplot.__iadd__() 1918 self.s = s if s is not None else 1 # used by pyplot.__iadd__() 1919 self.name = "Arrow" 1920 1921 def top_point(self): 1922 """Return the current coordinates of the tip of the Arrow.""" 1923 return self.transform.transform_point(self.top) 1924 1925 def base_point(self): 1926 """Return the current coordinates of the base of the Arrow.""" 1927 return self.transform.transform_point(self.base)
Build a 3D arrow from start_pt
to end_pt
of section size s
,
expressed as the fraction of the window size.
1835 def __init__( 1836 self, 1837 start_pt=(0, 0, 0), 1838 end_pt=(1, 0, 0), 1839 s=None, 1840 shaft_radius=None, 1841 head_radius=None, 1842 head_length=None, 1843 res=12, 1844 c="r4", 1845 alpha=1.0, 1846 ) -> None: 1847 """ 1848 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1849 from white to red. 1850 1851 .. note:: If `s=None` the arrow is scaled proportionally to its length 1852 1853 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1854 """ 1855 # in case user is passing meshs 1856 if isinstance(start_pt, vtki.vtkActor): 1857 start_pt = start_pt.GetPosition() 1858 if isinstance(end_pt, vtki.vtkActor): 1859 end_pt = end_pt.GetPosition() 1860 1861 axis = np.asarray(end_pt) - np.asarray(start_pt) 1862 length = float(np.linalg.norm(axis)) 1863 if length: 1864 axis = axis / length 1865 if len(axis) < 3: # its 2d 1866 theta = np.pi / 2 1867 start_pt = [start_pt[0], start_pt[1], 0.0] 1868 end_pt = [end_pt[0], end_pt[1], 0.0] 1869 else: 1870 theta = np.arccos(axis[2]) 1871 phi = np.arctan2(axis[1], axis[0]) 1872 self.source = vtki.new("ArrowSource") 1873 self.source.SetShaftResolution(res) 1874 self.source.SetTipResolution(res) 1875 1876 if s: 1877 sz = 0.02 1878 self.source.SetTipRadius(sz) 1879 self.source.SetShaftRadius(sz / 1.75) 1880 self.source.SetTipLength(sz * 15) 1881 1882 if head_length: 1883 self.source.SetTipLength(head_length) 1884 if head_radius: 1885 self.source.SetTipRadius(head_radius) 1886 if shaft_radius: 1887 self.source.SetShaftRadius(shaft_radius) 1888 1889 self.source.Update() 1890 1891 t = vtki.vtkTransform() 1892 t.Translate(start_pt) 1893 t.RotateZ(np.rad2deg(phi)) 1894 t.RotateY(np.rad2deg(theta)) 1895 t.RotateY(-90) # put it along Z 1896 if s: 1897 sz = 800 * s 1898 t.Scale(length, sz, sz) 1899 else: 1900 t.Scale(length, length, length) 1901 1902 tf = vtki.new("TransformPolyDataFilter") 1903 tf.SetInputData(self.source.GetOutput()) 1904 tf.SetTransform(t) 1905 tf.Update() 1906 1907 super().__init__(tf.GetOutput(), c, alpha) 1908 1909 self.transform = LinearTransform().translate(start_pt) 1910 1911 self.phong().lighting("plastic") 1912 self.actor.PickableOff() 1913 self.actor.DragableOff() 1914 self.base = np.array(start_pt, dtype=float) # used by pyplot 1915 self.top = np.array(end_pt, dtype=float) # used by pyplot 1916 self.top_index = self.source.GetTipResolution() * 4 1917 self.fill = True # used by pyplot.__iadd__() 1918 self.s = s if s is not None else 1 # used by pyplot.__iadd__() 1919 self.name = "Arrow"
If c
is a float
less than 1, the arrow is rendered as a in a color scale
from white to red.
If s=None
the arrow is scaled proportionally to its length
1929class Arrows(Glyph): 1930 """ 1931 Build arrows between two lists of points. 1932 """ 1933 1934 def __init__( 1935 self, 1936 start_pts, 1937 end_pts=None, 1938 s=None, 1939 shaft_radius=None, 1940 head_radius=None, 1941 head_length=None, 1942 thickness=1.0, 1943 res=6, 1944 c='k3', 1945 alpha=1.0, 1946 ) -> None: 1947 """ 1948 Build arrows between two lists of points `start_pts` and `end_pts`. 1949 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1950 1951 Color can be specified as a colormap which maps the size of the arrows. 1952 1953 Arguments: 1954 s : (float) 1955 fix aspect-ratio of the arrow and scale its cross section 1956 c : (color) 1957 color or color map name 1958 alpha : (float) 1959 set object opacity 1960 res : (int) 1961 set arrow resolution 1962 1963 Examples: 1964 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 1965 1966 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 1967 """ 1968 if isinstance(start_pts, Points): 1969 start_pts = start_pts.vertices 1970 if isinstance(end_pts, Points): 1971 end_pts = end_pts.vertices 1972 1973 start_pts = np.asarray(start_pts) 1974 if end_pts is None: 1975 strt = start_pts[:, 0] 1976 end_pts = start_pts[:, 1] 1977 start_pts = strt 1978 else: 1979 end_pts = np.asarray(end_pts) 1980 1981 start_pts = utils.make3d(start_pts) 1982 end_pts = utils.make3d(end_pts) 1983 1984 arr = vtki.new("ArrowSource") 1985 arr.SetShaftResolution(res) 1986 arr.SetTipResolution(res) 1987 1988 if s: 1989 sz = 0.02 * s 1990 arr.SetTipRadius(sz * 2) 1991 arr.SetShaftRadius(sz * thickness) 1992 arr.SetTipLength(sz * 10) 1993 1994 if head_radius: 1995 arr.SetTipRadius(head_radius) 1996 if shaft_radius: 1997 arr.SetShaftRadius(shaft_radius) 1998 if head_length: 1999 arr.SetTipLength(head_length) 2000 2001 arr.Update() 2002 out = arr.GetOutput() 2003 2004 orients = end_pts - start_pts 2005 2006 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2007 2008 super().__init__( 2009 start_pts, 2010 out, 2011 orientation_array=orients, 2012 scale_by_vector_size=True, 2013 color_by_vector_size=color_by_vector_size, 2014 c=c, 2015 alpha=alpha, 2016 ) 2017 self.lighting("off") 2018 if color_by_vector_size: 2019 vals = np.linalg.norm(orients, axis=1) 2020 self.mapper.SetScalarRange(vals.min(), vals.max()) 2021 else: 2022 self.c(c) 2023 self.name = "Arrows"
Build arrows between two lists of points.
1934 def __init__( 1935 self, 1936 start_pts, 1937 end_pts=None, 1938 s=None, 1939 shaft_radius=None, 1940 head_radius=None, 1941 head_length=None, 1942 thickness=1.0, 1943 res=6, 1944 c='k3', 1945 alpha=1.0, 1946 ) -> None: 1947 """ 1948 Build arrows between two lists of points `start_pts` and `end_pts`. 1949 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1950 1951 Color can be specified as a colormap which maps the size of the arrows. 1952 1953 Arguments: 1954 s : (float) 1955 fix aspect-ratio of the arrow and scale its cross section 1956 c : (color) 1957 color or color map name 1958 alpha : (float) 1959 set object opacity 1960 res : (int) 1961 set arrow resolution 1962 1963 Examples: 1964 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 1965 1966 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 1967 """ 1968 if isinstance(start_pts, Points): 1969 start_pts = start_pts.vertices 1970 if isinstance(end_pts, Points): 1971 end_pts = end_pts.vertices 1972 1973 start_pts = np.asarray(start_pts) 1974 if end_pts is None: 1975 strt = start_pts[:, 0] 1976 end_pts = start_pts[:, 1] 1977 start_pts = strt 1978 else: 1979 end_pts = np.asarray(end_pts) 1980 1981 start_pts = utils.make3d(start_pts) 1982 end_pts = utils.make3d(end_pts) 1983 1984 arr = vtki.new("ArrowSource") 1985 arr.SetShaftResolution(res) 1986 arr.SetTipResolution(res) 1987 1988 if s: 1989 sz = 0.02 * s 1990 arr.SetTipRadius(sz * 2) 1991 arr.SetShaftRadius(sz * thickness) 1992 arr.SetTipLength(sz * 10) 1993 1994 if head_radius: 1995 arr.SetTipRadius(head_radius) 1996 if shaft_radius: 1997 arr.SetShaftRadius(shaft_radius) 1998 if head_length: 1999 arr.SetTipLength(head_length) 2000 2001 arr.Update() 2002 out = arr.GetOutput() 2003 2004 orients = end_pts - start_pts 2005 2006 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2007 2008 super().__init__( 2009 start_pts, 2010 out, 2011 orientation_array=orients, 2012 scale_by_vector_size=True, 2013 color_by_vector_size=color_by_vector_size, 2014 c=c, 2015 alpha=alpha, 2016 ) 2017 self.lighting("off") 2018 if color_by_vector_size: 2019 vals = np.linalg.norm(orients, axis=1) 2020 self.mapper.SetScalarRange(vals.min(), vals.max()) 2021 else: 2022 self.c(c) 2023 self.name = "Arrows"
Build arrows between two lists of points start_pts
and end_pts
.
start_pts
can be also passed in the form [[point1, point2], ...]
.
Color can be specified as a colormap which maps the size of the arrows.
Arguments:
- s : (float) fix aspect-ratio of the arrow and scale its cross section
- c : (color) color or color map name
- alpha : (float) set object opacity
- res : (int) set arrow resolution
Examples:
2026class Arrow2D(Mesh): 2027 """ 2028 Build a 2D arrow. 2029 """ 2030 2031 def __init__( 2032 self, 2033 start_pt=(0, 0, 0), 2034 end_pt=(1, 0, 0), 2035 s=1, 2036 rotation=0.0, 2037 shaft_length=0.85, 2038 shaft_width=0.055, 2039 head_length=0.175, 2040 head_width=0.175, 2041 fill=True, 2042 c="red4", 2043 alpha=1.0, 2044 ) -> None: 2045 """ 2046 Build a 2D arrow from `start_pt` to `end_pt`. 2047 2048 Arguments: 2049 s : (float) 2050 a global multiplicative convenience factor controlling the arrow size 2051 shaft_length : (float) 2052 fractional shaft length 2053 shaft_width : (float) 2054 fractional shaft width 2055 head_length : (float) 2056 fractional head length 2057 head_width : (float) 2058 fractional head width 2059 fill : (bool) 2060 if False only generate the outline 2061 """ 2062 self.fill = fill ## needed by pyplot.__iadd() 2063 self.s = s ## needed by pyplot.__iadd() 2064 2065 if s != 1: 2066 shaft_width *= s 2067 head_width *= np.sqrt(s) 2068 2069 # in case user is passing meshs 2070 if isinstance(start_pt, vtki.vtkActor): 2071 start_pt = start_pt.GetPosition() 2072 if isinstance(end_pt, vtki.vtkActor): 2073 end_pt = end_pt.GetPosition() 2074 if len(start_pt) == 2: 2075 start_pt = [start_pt[0], start_pt[1], 0] 2076 if len(end_pt) == 2: 2077 end_pt = [end_pt[0], end_pt[1], 0] 2078 2079 headBase = 1 - head_length 2080 head_width = max(head_width, shaft_width) 2081 if head_length is None or headBase > shaft_length: 2082 headBase = shaft_length 2083 2084 verts = [] 2085 verts.append([0, -shaft_width / 2, 0]) 2086 verts.append([shaft_length, -shaft_width / 2, 0]) 2087 verts.append([headBase, -head_width / 2, 0]) 2088 verts.append([1, 0, 0]) 2089 verts.append([headBase, head_width / 2, 0]) 2090 verts.append([shaft_length, shaft_width / 2, 0]) 2091 verts.append([0, shaft_width / 2, 0]) 2092 if fill: 2093 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2094 poly = utils.buildPolyData(verts, faces) 2095 else: 2096 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2097 poly = utils.buildPolyData(verts, [], lines=lines) 2098 2099 axis = np.array(end_pt) - np.array(start_pt) 2100 length = float(np.linalg.norm(axis)) 2101 if length: 2102 axis = axis / length 2103 theta = 0 2104 if len(axis) > 2: 2105 theta = np.arccos(axis[2]) 2106 phi = np.arctan2(axis[1], axis[0]) 2107 2108 t = vtki.vtkTransform() 2109 t.Translate(start_pt) 2110 if phi: 2111 t.RotateZ(np.rad2deg(phi)) 2112 if theta: 2113 t.RotateY(np.rad2deg(theta)) 2114 t.RotateY(-90) # put it along Z 2115 if rotation: 2116 t.RotateX(rotation) 2117 t.Scale(length, length, length) 2118 2119 tf = vtki.new("TransformPolyDataFilter") 2120 tf.SetInputData(poly) 2121 tf.SetTransform(t) 2122 tf.Update() 2123 2124 super().__init__(tf.GetOutput(), c, alpha) 2125 2126 self.transform = LinearTransform().translate(start_pt) 2127 2128 self.lighting("off") 2129 self.actor.DragableOff() 2130 self.actor.PickableOff() 2131 self.base = np.array(start_pt, dtype=float) # used by pyplot 2132 self.top = np.array(end_pt, dtype=float) # used by pyplot 2133 self.name = "Arrow2D"
Build a 2D arrow.
2031 def __init__( 2032 self, 2033 start_pt=(0, 0, 0), 2034 end_pt=(1, 0, 0), 2035 s=1, 2036 rotation=0.0, 2037 shaft_length=0.85, 2038 shaft_width=0.055, 2039 head_length=0.175, 2040 head_width=0.175, 2041 fill=True, 2042 c="red4", 2043 alpha=1.0, 2044 ) -> None: 2045 """ 2046 Build a 2D arrow from `start_pt` to `end_pt`. 2047 2048 Arguments: 2049 s : (float) 2050 a global multiplicative convenience factor controlling the arrow size 2051 shaft_length : (float) 2052 fractional shaft length 2053 shaft_width : (float) 2054 fractional shaft width 2055 head_length : (float) 2056 fractional head length 2057 head_width : (float) 2058 fractional head width 2059 fill : (bool) 2060 if False only generate the outline 2061 """ 2062 self.fill = fill ## needed by pyplot.__iadd() 2063 self.s = s ## needed by pyplot.__iadd() 2064 2065 if s != 1: 2066 shaft_width *= s 2067 head_width *= np.sqrt(s) 2068 2069 # in case user is passing meshs 2070 if isinstance(start_pt, vtki.vtkActor): 2071 start_pt = start_pt.GetPosition() 2072 if isinstance(end_pt, vtki.vtkActor): 2073 end_pt = end_pt.GetPosition() 2074 if len(start_pt) == 2: 2075 start_pt = [start_pt[0], start_pt[1], 0] 2076 if len(end_pt) == 2: 2077 end_pt = [end_pt[0], end_pt[1], 0] 2078 2079 headBase = 1 - head_length 2080 head_width = max(head_width, shaft_width) 2081 if head_length is None or headBase > shaft_length: 2082 headBase = shaft_length 2083 2084 verts = [] 2085 verts.append([0, -shaft_width / 2, 0]) 2086 verts.append([shaft_length, -shaft_width / 2, 0]) 2087 verts.append([headBase, -head_width / 2, 0]) 2088 verts.append([1, 0, 0]) 2089 verts.append([headBase, head_width / 2, 0]) 2090 verts.append([shaft_length, shaft_width / 2, 0]) 2091 verts.append([0, shaft_width / 2, 0]) 2092 if fill: 2093 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2094 poly = utils.buildPolyData(verts, faces) 2095 else: 2096 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2097 poly = utils.buildPolyData(verts, [], lines=lines) 2098 2099 axis = np.array(end_pt) - np.array(start_pt) 2100 length = float(np.linalg.norm(axis)) 2101 if length: 2102 axis = axis / length 2103 theta = 0 2104 if len(axis) > 2: 2105 theta = np.arccos(axis[2]) 2106 phi = np.arctan2(axis[1], axis[0]) 2107 2108 t = vtki.vtkTransform() 2109 t.Translate(start_pt) 2110 if phi: 2111 t.RotateZ(np.rad2deg(phi)) 2112 if theta: 2113 t.RotateY(np.rad2deg(theta)) 2114 t.RotateY(-90) # put it along Z 2115 if rotation: 2116 t.RotateX(rotation) 2117 t.Scale(length, length, length) 2118 2119 tf = vtki.new("TransformPolyDataFilter") 2120 tf.SetInputData(poly) 2121 tf.SetTransform(t) 2122 tf.Update() 2123 2124 super().__init__(tf.GetOutput(), c, alpha) 2125 2126 self.transform = LinearTransform().translate(start_pt) 2127 2128 self.lighting("off") 2129 self.actor.DragableOff() 2130 self.actor.PickableOff() 2131 self.base = np.array(start_pt, dtype=float) # used by pyplot 2132 self.top = np.array(end_pt, dtype=float) # used by pyplot 2133 self.name = "Arrow2D"
Build a 2D arrow from start_pt
to end_pt
.
Arguments:
- s : (float) a global multiplicative convenience factor controlling the arrow size
- shaft_length : (float) fractional shaft length
- shaft_width : (float) fractional shaft width
- head_length : (float) fractional head length
- head_width : (float) fractional head width
- fill : (bool) if False only generate the outline
2136class Arrows2D(Glyph): 2137 """ 2138 Build 2D arrows between two lists of points. 2139 """ 2140 2141 def __init__( 2142 self, 2143 start_pts, 2144 end_pts=None, 2145 s=1.0, 2146 rotation=0.0, 2147 shaft_length=0.8, 2148 shaft_width=0.05, 2149 head_length=0.225, 2150 head_width=0.175, 2151 fill=True, 2152 c=None, 2153 alpha=1.0, 2154 ) -> None: 2155 """ 2156 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2157 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2158 2159 Color can be specified as a colormap which maps the size of the arrows. 2160 2161 Arguments: 2162 shaft_length : (float) 2163 fractional shaft length 2164 shaft_width : (float) 2165 fractional shaft width 2166 head_length : (float) 2167 fractional head length 2168 head_width : (float) 2169 fractional head width 2170 fill : (bool) 2171 if False only generate the outline 2172 """ 2173 if isinstance(start_pts, Points): 2174 start_pts = start_pts.vertices 2175 if isinstance(end_pts, Points): 2176 end_pts = end_pts.vertices 2177 2178 start_pts = np.asarray(start_pts, dtype=float) 2179 if end_pts is None: 2180 strt = start_pts[:, 0] 2181 end_pts = start_pts[:, 1] 2182 start_pts = strt 2183 else: 2184 end_pts = np.asarray(end_pts, dtype=float) 2185 2186 if head_length is None: 2187 head_length = 1 - shaft_length 2188 2189 arr = Arrow2D( 2190 (0, 0, 0), 2191 (1, 0, 0), 2192 s=s, 2193 rotation=rotation, 2194 shaft_length=shaft_length, 2195 shaft_width=shaft_width, 2196 head_length=head_length, 2197 head_width=head_width, 2198 fill=fill, 2199 ) 2200 2201 orients = end_pts - start_pts 2202 orients = utils.make3d(orients) 2203 2204 pts = Points(start_pts) 2205 super().__init__( 2206 pts, 2207 arr, 2208 orientation_array=orients, 2209 scale_by_vector_size=True, 2210 c=c, 2211 alpha=alpha, 2212 ) 2213 self.flat().lighting("off").pickable(False) 2214 if c is not None: 2215 self.color(c) 2216 self.name = "Arrows2D"
Build 2D arrows between two lists of points.
2141 def __init__( 2142 self, 2143 start_pts, 2144 end_pts=None, 2145 s=1.0, 2146 rotation=0.0, 2147 shaft_length=0.8, 2148 shaft_width=0.05, 2149 head_length=0.225, 2150 head_width=0.175, 2151 fill=True, 2152 c=None, 2153 alpha=1.0, 2154 ) -> None: 2155 """ 2156 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2157 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2158 2159 Color can be specified as a colormap which maps the size of the arrows. 2160 2161 Arguments: 2162 shaft_length : (float) 2163 fractional shaft length 2164 shaft_width : (float) 2165 fractional shaft width 2166 head_length : (float) 2167 fractional head length 2168 head_width : (float) 2169 fractional head width 2170 fill : (bool) 2171 if False only generate the outline 2172 """ 2173 if isinstance(start_pts, Points): 2174 start_pts = start_pts.vertices 2175 if isinstance(end_pts, Points): 2176 end_pts = end_pts.vertices 2177 2178 start_pts = np.asarray(start_pts, dtype=float) 2179 if end_pts is None: 2180 strt = start_pts[:, 0] 2181 end_pts = start_pts[:, 1] 2182 start_pts = strt 2183 else: 2184 end_pts = np.asarray(end_pts, dtype=float) 2185 2186 if head_length is None: 2187 head_length = 1 - shaft_length 2188 2189 arr = Arrow2D( 2190 (0, 0, 0), 2191 (1, 0, 0), 2192 s=s, 2193 rotation=rotation, 2194 shaft_length=shaft_length, 2195 shaft_width=shaft_width, 2196 head_length=head_length, 2197 head_width=head_width, 2198 fill=fill, 2199 ) 2200 2201 orients = end_pts - start_pts 2202 orients = utils.make3d(orients) 2203 2204 pts = Points(start_pts) 2205 super().__init__( 2206 pts, 2207 arr, 2208 orientation_array=orients, 2209 scale_by_vector_size=True, 2210 c=c, 2211 alpha=alpha, 2212 ) 2213 self.flat().lighting("off").pickable(False) 2214 if c is not None: 2215 self.color(c) 2216 self.name = "Arrows2D"
Build 2D arrows between two lists of points start_pts
and end_pts
.
start_pts
can be also passed in the form [[point1, point2], ...]
.
Color can be specified as a colormap which maps the size of the arrows.
Arguments:
- shaft_length : (float) fractional shaft length
- shaft_width : (float) fractional shaft width
- head_length : (float) fractional head length
- head_width : (float) fractional head width
- fill : (bool) if False only generate the outline
2219class FlatArrow(Ribbon): 2220 """ 2221 Build a 2D arrow in 3D space by joining two close lines. 2222 """ 2223 2224 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2225 """ 2226 Build a 2D arrow in 3D space by joining two close lines. 2227 2228 Examples: 2229 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2230 2231 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2232 """ 2233 if isinstance(line1, Points): 2234 line1 = line1.vertices 2235 if isinstance(line2, Points): 2236 line2 = line2.vertices 2237 2238 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2239 2240 v = (sm1 - sm2) / 3 * tip_width 2241 p1 = sm1 + v 2242 p2 = sm2 - v 2243 pm1 = (sm1 + sm2) / 2 2244 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2245 pm12 = pm1 - pm2 2246 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2247 2248 line1.append(p1) 2249 line1.append(tip) 2250 line2.append(p2) 2251 line2.append(tip) 2252 resm = max(100, len(line1)) 2253 2254 super().__init__(line1, line2, res=(resm, 1)) 2255 self.phong().lighting("off") 2256 self.actor.PickableOff() 2257 self.actor.DragableOff() 2258 self.name = "FlatArrow"
Build a 2D arrow in 3D space by joining two close lines.
2224 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2225 """ 2226 Build a 2D arrow in 3D space by joining two close lines. 2227 2228 Examples: 2229 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2230 2231 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2232 """ 2233 if isinstance(line1, Points): 2234 line1 = line1.vertices 2235 if isinstance(line2, Points): 2236 line2 = line2.vertices 2237 2238 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2239 2240 v = (sm1 - sm2) / 3 * tip_width 2241 p1 = sm1 + v 2242 p2 = sm2 - v 2243 pm1 = (sm1 + sm2) / 2 2244 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2245 pm12 = pm1 - pm2 2246 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2247 2248 line1.append(p1) 2249 line1.append(tip) 2250 line2.append(p2) 2251 line2.append(tip) 2252 resm = max(100, len(line1)) 2253 2254 super().__init__(line1, line2, res=(resm, 1)) 2255 self.phong().lighting("off") 2256 self.actor.PickableOff() 2257 self.actor.DragableOff() 2258 self.name = "FlatArrow"
2271class Polygon(Mesh): 2272 """ 2273 Build a polygon in the `xy` plane. 2274 """ 2275 2276 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2277 """ 2278 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2279 2280 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2281 """ 2282 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2283 pts = pol2cart(np.ones_like(t) * r, t).T 2284 faces = [list(range(nsides))] 2285 # do not use: vtkRegularPolygonSource 2286 super().__init__([pts, faces], c, alpha) 2287 if len(pos) == 2: 2288 pos = (pos[0], pos[1], 0) 2289 self.pos(pos) 2290 self.properties.LightingOff() 2291 self.name = "Polygon " + str(nsides)
Build a polygon in the xy
plane.
2276 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2277 """ 2278 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2279 2280 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2281 """ 2282 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2283 pts = pol2cart(np.ones_like(t) * r, t).T 2284 faces = [list(range(nsides))] 2285 # do not use: vtkRegularPolygonSource 2286 super().__init__([pts, faces], c, alpha) 2287 if len(pos) == 2: 2288 pos = (pos[0], pos[1], 0) 2289 self.pos(pos) 2290 self.properties.LightingOff() 2291 self.name = "Polygon " + str(nsides)
Build a polygon in the xy
plane of nsides
of radius r
.
2261class Triangle(Mesh): 2262 """Create a triangle from 3 points in space.""" 2263 2264 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2265 """Create a triangle from 3 points in space.""" 2266 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2267 self.properties.LightingOff() 2268 self.name = "Triangle"
Create a triangle from 3 points in space.
2264 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2265 """Create a triangle from 3 points in space.""" 2266 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2267 self.properties.LightingOff() 2268 self.name = "Triangle"
Create a triangle from 3 points in space.
3095class Rectangle(Mesh): 3096 """ 3097 Build a rectangle in the xy plane. 3098 """ 3099 3100 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3101 """ 3102 Build a rectangle in the xy plane identified by any two corner points. 3103 3104 Arguments: 3105 p1 : (list) 3106 bottom-left position of the corner 3107 p2 : (list) 3108 top-right position of the corner 3109 radius : (float, list) 3110 smoothing radius of the corner in world units. 3111 A list can be passed with 4 individual values. 3112 """ 3113 if len(p1) == 2: 3114 p1 = np.array([p1[0], p1[1], 0.0]) 3115 else: 3116 p1 = np.array(p1, dtype=float) 3117 if len(p2) == 2: 3118 p2 = np.array([p2[0], p2[1], 0.0]) 3119 else: 3120 p2 = np.array(p2, dtype=float) 3121 3122 self.corner1 = p1 3123 self.corner2 = p2 3124 3125 color = c 3126 smoothr = False 3127 risseq = False 3128 if utils.is_sequence(radius): 3129 risseq = True 3130 smoothr = True 3131 if max(radius) == 0: 3132 smoothr = False 3133 elif radius: 3134 smoothr = True 3135 3136 if not smoothr: 3137 radius = None 3138 self.radius = radius 3139 3140 if smoothr: 3141 r = radius 3142 if not risseq: 3143 r = [r, r, r, r] 3144 rd, ra, rb, rc = r 3145 3146 if p1[0] > p2[0]: # flip p1 - p2 3147 p1, p2 = p2, p1 3148 if p1[1] > p2[1]: # flip p1y - p2y 3149 p1[1], p2[1] = p2[1], p1[1] 3150 3151 px, py, _ = p2 - p1 3152 k = min(px / 2, py / 2) 3153 ra = min(abs(ra), k) 3154 rb = min(abs(rb), k) 3155 rc = min(abs(rc), k) 3156 rd = min(abs(rd), k) 3157 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3158 betas = np.split(beta, 4) 3159 rrx = np.cos(betas) 3160 rry = np.sin(betas) 3161 3162 q1 = (rd, 0) 3163 # q2 = (px-ra, 0) 3164 q3 = (px, ra) 3165 # q4 = (px, py-rb) 3166 q5 = (px - rb, py) 3167 # q6 = (rc, py) 3168 q7 = (0, py - rc) 3169 # q8 = (0, rd) 3170 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3171 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3172 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3173 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3174 3175 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3176 faces = [list(range(len(pts)))] 3177 else: 3178 p1r = np.array([p2[0], p1[1], 0.0]) 3179 p2l = np.array([p1[0], p2[1], 0.0]) 3180 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3181 faces = [(0, 1, 2, 3)] 3182 3183 super().__init__([pts, faces], color, alpha) 3184 self.pos(p1) 3185 self.properties.LightingOff() 3186 self.name = "Rectangle"
Build a rectangle in the xy plane.
3100 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3101 """ 3102 Build a rectangle in the xy plane identified by any two corner points. 3103 3104 Arguments: 3105 p1 : (list) 3106 bottom-left position of the corner 3107 p2 : (list) 3108 top-right position of the corner 3109 radius : (float, list) 3110 smoothing radius of the corner in world units. 3111 A list can be passed with 4 individual values. 3112 """ 3113 if len(p1) == 2: 3114 p1 = np.array([p1[0], p1[1], 0.0]) 3115 else: 3116 p1 = np.array(p1, dtype=float) 3117 if len(p2) == 2: 3118 p2 = np.array([p2[0], p2[1], 0.0]) 3119 else: 3120 p2 = np.array(p2, dtype=float) 3121 3122 self.corner1 = p1 3123 self.corner2 = p2 3124 3125 color = c 3126 smoothr = False 3127 risseq = False 3128 if utils.is_sequence(radius): 3129 risseq = True 3130 smoothr = True 3131 if max(radius) == 0: 3132 smoothr = False 3133 elif radius: 3134 smoothr = True 3135 3136 if not smoothr: 3137 radius = None 3138 self.radius = radius 3139 3140 if smoothr: 3141 r = radius 3142 if not risseq: 3143 r = [r, r, r, r] 3144 rd, ra, rb, rc = r 3145 3146 if p1[0] > p2[0]: # flip p1 - p2 3147 p1, p2 = p2, p1 3148 if p1[1] > p2[1]: # flip p1y - p2y 3149 p1[1], p2[1] = p2[1], p1[1] 3150 3151 px, py, _ = p2 - p1 3152 k = min(px / 2, py / 2) 3153 ra = min(abs(ra), k) 3154 rb = min(abs(rb), k) 3155 rc = min(abs(rc), k) 3156 rd = min(abs(rd), k) 3157 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3158 betas = np.split(beta, 4) 3159 rrx = np.cos(betas) 3160 rry = np.sin(betas) 3161 3162 q1 = (rd, 0) 3163 # q2 = (px-ra, 0) 3164 q3 = (px, ra) 3165 # q4 = (px, py-rb) 3166 q5 = (px - rb, py) 3167 # q6 = (rc, py) 3168 q7 = (0, py - rc) 3169 # q8 = (0, rd) 3170 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3171 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3172 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3173 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3174 3175 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3176 faces = [list(range(len(pts)))] 3177 else: 3178 p1r = np.array([p2[0], p1[1], 0.0]) 3179 p2l = np.array([p1[0], p2[1], 0.0]) 3180 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3181 faces = [(0, 1, 2, 3)] 3182 3183 super().__init__([pts, faces], color, alpha) 3184 self.pos(p1) 3185 self.properties.LightingOff() 3186 self.name = "Rectangle"
Build a rectangle in the xy plane identified by any two corner points.
Arguments:
- p1 : (list) bottom-left position of the corner
- p2 : (list) top-right position of the corner
- radius : (float, list) smoothing radius of the corner in world units. A list can be passed with 4 individual values.
2402class Disc(Mesh): 2403 """ 2404 Build a 2D disc. 2405 """ 2406 2407 def __init__( 2408 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2409 ) -> None: 2410 """ 2411 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2412 2413 Set `res` as the resolution in R and Phi (can be a list). 2414 2415 Use `angle_range` to create a disc sector between the 2 specified angles. 2416 2417 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2418 """ 2419 if utils.is_sequence(res): 2420 res_r, res_phi = res 2421 else: 2422 res_r, res_phi = res, 12 * res 2423 2424 if len(angle_range) == 0: 2425 ps = vtki.new("DiskSource") 2426 else: 2427 ps = vtki.new("SectorSource") 2428 ps.SetStartAngle(angle_range[0]) 2429 ps.SetEndAngle(angle_range[1]) 2430 2431 ps.SetInnerRadius(r1) 2432 ps.SetOuterRadius(r2) 2433 ps.SetRadialResolution(res_r) 2434 ps.SetCircumferentialResolution(res_phi) 2435 ps.Update() 2436 super().__init__(ps.GetOutput(), c, alpha) 2437 self.flat() 2438 self.pos(utils.make3d(pos)) 2439 self.name = "Disc"
Build a 2D disc.
2407 def __init__( 2408 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2409 ) -> None: 2410 """ 2411 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2412 2413 Set `res` as the resolution in R and Phi (can be a list). 2414 2415 Use `angle_range` to create a disc sector between the 2 specified angles. 2416 2417 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2418 """ 2419 if utils.is_sequence(res): 2420 res_r, res_phi = res 2421 else: 2422 res_r, res_phi = res, 12 * res 2423 2424 if len(angle_range) == 0: 2425 ps = vtki.new("DiskSource") 2426 else: 2427 ps = vtki.new("SectorSource") 2428 ps.SetStartAngle(angle_range[0]) 2429 ps.SetEndAngle(angle_range[1]) 2430 2431 ps.SetInnerRadius(r1) 2432 ps.SetOuterRadius(r2) 2433 ps.SetRadialResolution(res_r) 2434 ps.SetCircumferentialResolution(res_phi) 2435 ps.Update() 2436 super().__init__(ps.GetOutput(), c, alpha) 2437 self.flat() 2438 self.pos(utils.make3d(pos)) 2439 self.name = "Disc"
Build a 2D disc of inner radius r1
and outer radius r2
.
Set res
as the resolution in R and Phi (can be a list).
Use angle_range
to create a disc sector between the 2 specified angles.
2294class Circle(Polygon): 2295 """ 2296 Build a Circle of radius `r`. 2297 """ 2298 2299 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2300 """ 2301 Build a Circle of radius `r`. 2302 """ 2303 super().__init__(pos, nsides=res, r=r) 2304 2305 self.nr_of_points = 0 2306 self.va = 0 2307 self.vb = 0 2308 self.axis1: List[float] = [] 2309 self.axis2: List[float] = [] 2310 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2311 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2312 self.alpha(alpha).c(c) 2313 self.name = "Circle" 2314 2315 def acircularity(self) -> float: 2316 """ 2317 Return a measure of how different an ellipse is from a circle. 2318 Values close to zero correspond to a circular object. 2319 """ 2320 a, b = self.va, self.vb 2321 value = 0.0 2322 if a+b: 2323 value = ((a-b)/(a+b))**2 2324 return value
Build a Circle of radius r
.
2299 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2300 """ 2301 Build a Circle of radius `r`. 2302 """ 2303 super().__init__(pos, nsides=res, r=r) 2304 2305 self.nr_of_points = 0 2306 self.va = 0 2307 self.vb = 0 2308 self.axis1: List[float] = [] 2309 self.axis2: List[float] = [] 2310 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2311 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2312 self.alpha(alpha).c(c) 2313 self.name = "Circle"
Build a Circle of radius r
.
2315 def acircularity(self) -> float: 2316 """ 2317 Return a measure of how different an ellipse is from a circle. 2318 Values close to zero correspond to a circular object. 2319 """ 2320 a, b = self.va, self.vb 2321 value = 0.0 2322 if a+b: 2323 value = ((a-b)/(a+b))**2 2324 return value
Return a measure of how different an ellipse is from a circle. Values close to zero correspond to a circular object.
2326class GeoCircle(Polygon): 2327 """ 2328 Build a Circle of radius `r`. 2329 """ 2330 2331 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2332 """ 2333 Build a Circle of radius `r` as projected on a geographic map. 2334 Circles near the poles will look very squashed. 2335 2336 See example: 2337 ```bash 2338 vedo -r earthquake 2339 ``` 2340 """ 2341 coords = [] 2342 sinr, cosr = np.sin(r), np.cos(r) 2343 sinlat, coslat = np.sin(lat), np.cos(lat) 2344 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2345 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2346 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2347 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2348 2349 super().__init__(nsides=res, c=c, alpha=alpha) 2350 self.vertices = coords # warp polygon points to match geo projection 2351 self.name = "Circle"
Build a Circle of radius r
.
2331 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2332 """ 2333 Build a Circle of radius `r` as projected on a geographic map. 2334 Circles near the poles will look very squashed. 2335 2336 See example: 2337 ```bash 2338 vedo -r earthquake 2339 ``` 2340 """ 2341 coords = [] 2342 sinr, cosr = np.sin(r), np.cos(r) 2343 sinlat, coslat = np.sin(lat), np.cos(lat) 2344 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2345 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2346 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2347 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2348 2349 super().__init__(nsides=res, c=c, alpha=alpha) 2350 self.vertices = coords # warp polygon points to match geo projection 2351 self.name = "Circle"
Build a Circle of radius r
as projected on a geographic map.
Circles near the poles will look very squashed.
See example:
vedo -r earthquake
812 @property 813 def vertices(self): 814 """Return the vertices (points) coordinates.""" 815 try: 816 # for polydata and unstructured grid 817 varr = self.dataset.GetPoints().GetData() 818 except (AttributeError, TypeError): 819 try: 820 # for RectilinearGrid, StructuredGrid 821 vpts = vtki.vtkPoints() 822 self.dataset.GetPoints(vpts) 823 varr = vpts.GetData() 824 except (AttributeError, TypeError): 825 try: 826 # for ImageData 827 v2p = vtki.new("ImageToPoints") 828 v2p.SetInputData(self.dataset) 829 v2p.Update() 830 varr = v2p.GetOutput().GetPoints().GetData() 831 except AttributeError: 832 return np.array([]) 833 834 return utils.vtk2numpy(varr)
Return the vertices (points) coordinates.
2442class Arc(Mesh): 2443 """ 2444 Build a 2D circular arc between 2 points. 2445 """ 2446 2447 def __init__( 2448 self, 2449 center, 2450 point1, 2451 point2=None, 2452 normal=None, 2453 angle=None, 2454 invert=False, 2455 res=50, 2456 c="gray4", 2457 alpha=1.0, 2458 ) -> None: 2459 """ 2460 Build a 2D circular arc between 2 points `point1` and `point2`. 2461 2462 If `normal` is specified then `center` is ignored, and 2463 normal vector, a starting `point1` (polar vector) 2464 and an angle defining the arc length need to be assigned. 2465 2466 Arc spans the shortest angular sector point1 and point2, 2467 if `invert=True`, then the opposite happens. 2468 """ 2469 if len(point1) == 2: 2470 point1 = (point1[0], point1[1], 0) 2471 if point2 is not None and len(point2) == 2: 2472 point2 = (point2[0], point2[1], 0) 2473 2474 ar = vtki.new("ArcSource") 2475 if point2 is not None: 2476 self.top = point2 2477 point2 = point2 - np.asarray(point1) 2478 ar.UseNormalAndAngleOff() 2479 ar.SetPoint1([0, 0, 0]) 2480 ar.SetPoint2(point2) 2481 # ar.SetCenter(center) 2482 elif normal is not None and angle is not None: 2483 ar.UseNormalAndAngleOn() 2484 ar.SetAngle(angle) 2485 ar.SetPolarVector(point1) 2486 ar.SetNormal(normal) 2487 else: 2488 vedo.logger.error("incorrect input combination") 2489 return 2490 ar.SetNegative(invert) 2491 ar.SetResolution(res) 2492 ar.Update() 2493 2494 super().__init__(ar.GetOutput(), c, alpha) 2495 self.pos(center) 2496 self.lw(2).lighting("off") 2497 self.name = "Arc"
Build a 2D circular arc between 2 points.
2447 def __init__( 2448 self, 2449 center, 2450 point1, 2451 point2=None, 2452 normal=None, 2453 angle=None, 2454 invert=False, 2455 res=50, 2456 c="gray4", 2457 alpha=1.0, 2458 ) -> None: 2459 """ 2460 Build a 2D circular arc between 2 points `point1` and `point2`. 2461 2462 If `normal` is specified then `center` is ignored, and 2463 normal vector, a starting `point1` (polar vector) 2464 and an angle defining the arc length need to be assigned. 2465 2466 Arc spans the shortest angular sector point1 and point2, 2467 if `invert=True`, then the opposite happens. 2468 """ 2469 if len(point1) == 2: 2470 point1 = (point1[0], point1[1], 0) 2471 if point2 is not None and len(point2) == 2: 2472 point2 = (point2[0], point2[1], 0) 2473 2474 ar = vtki.new("ArcSource") 2475 if point2 is not None: 2476 self.top = point2 2477 point2 = point2 - np.asarray(point1) 2478 ar.UseNormalAndAngleOff() 2479 ar.SetPoint1([0, 0, 0]) 2480 ar.SetPoint2(point2) 2481 # ar.SetCenter(center) 2482 elif normal is not None and angle is not None: 2483 ar.UseNormalAndAngleOn() 2484 ar.SetAngle(angle) 2485 ar.SetPolarVector(point1) 2486 ar.SetNormal(normal) 2487 else: 2488 vedo.logger.error("incorrect input combination") 2489 return 2490 ar.SetNegative(invert) 2491 ar.SetResolution(res) 2492 ar.Update() 2493 2494 super().__init__(ar.GetOutput(), c, alpha) 2495 self.pos(center) 2496 self.lw(2).lighting("off") 2497 self.name = "Arc"
Build a 2D circular arc between 2 points point1
and point2
.
If normal
is specified then center
is ignored, and
normal vector, a starting point1
(polar vector)
and an angle defining the arc length need to be assigned.
Arc spans the shortest angular sector point1 and point2,
if invert=True
, then the opposite happens.
2354class Star(Mesh): 2355 """ 2356 Build a 2D star shape. 2357 """ 2358 2359 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2360 """ 2361 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2362 2363 If line is True then only build the outer line (no internal surface meshing). 2364 2365 Example: 2366 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2367 2368 ![](https://vedo.embl.es/images/basic/extrude.png) 2369 """ 2370 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2371 x, y = pol2cart(np.ones_like(t) * r2, t) 2372 pts = np.c_[x, y, np.zeros_like(x)] 2373 2374 apts = [] 2375 for i, p in enumerate(pts): 2376 apts.append(p) 2377 if i + 1 < n: 2378 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2379 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2380 2381 if line: 2382 apts.append(pts[0]) 2383 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2384 super().__init__(poly, c, alpha) 2385 self.lw(2) 2386 else: 2387 apts.append((0, 0, 0)) 2388 cells = [] 2389 for i in range(2 * n - 1): 2390 cell = [2 * n, i, i + 1] 2391 cells.append(cell) 2392 cells.append([2 * n, i + 1, 0]) 2393 super().__init__([apts, cells], c, alpha) 2394 2395 if len(pos) == 2: 2396 pos = (pos[0], pos[1], 0) 2397 2398 self.properties.LightingOff() 2399 self.name = "Star"
Build a 2D star shape.
2359 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2360 """ 2361 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2362 2363 If line is True then only build the outer line (no internal surface meshing). 2364 2365 Example: 2366 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2367 2368 ![](https://vedo.embl.es/images/basic/extrude.png) 2369 """ 2370 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2371 x, y = pol2cart(np.ones_like(t) * r2, t) 2372 pts = np.c_[x, y, np.zeros_like(x)] 2373 2374 apts = [] 2375 for i, p in enumerate(pts): 2376 apts.append(p) 2377 if i + 1 < n: 2378 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2379 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2380 2381 if line: 2382 apts.append(pts[0]) 2383 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2384 super().__init__(poly, c, alpha) 2385 self.lw(2) 2386 else: 2387 apts.append((0, 0, 0)) 2388 cells = [] 2389 for i in range(2 * n - 1): 2390 cell = [2 * n, i, i + 1] 2391 cells.append(cell) 2392 cells.append([2 * n, i + 1, 0]) 2393 super().__init__([apts, cells], c, alpha) 2394 2395 if len(pos) == 2: 2396 pos = (pos[0], pos[1], 0) 2397 2398 self.properties.LightingOff() 2399 self.name = "Star"
Build a 2D star shape of n
cusps of inner radius r1
and outer radius r2
.
If line is True then only build the outer line (no internal surface meshing).
Example:
3819class Star3D(Mesh): 3820 """ 3821 Build a 3D starred shape. 3822 """ 3823 3824 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3825 """ 3826 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3827 """ 3828 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3829 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3830 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3831 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3832 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3833 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3834 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3835 [10,1, 0],[10,11, 9]] 3836 3837 super().__init__([pts, fcs], c, alpha) 3838 self.rotate_x(90) 3839 self.scale(r).lighting("shiny") 3840 3841 if len(pos) == 2: 3842 pos = (pos[0], pos[1], 0) 3843 self.pos(pos) 3844 self.name = "Star3D"
Build a 3D starred shape.
3824 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3825 """ 3826 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3827 """ 3828 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3829 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3830 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3831 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3832 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3833 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3834 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3835 [10,1, 0],[10,11, 9]] 3836 3837 super().__init__([pts, fcs], c, alpha) 3838 self.rotate_x(90) 3839 self.scale(r).lighting("shiny") 3840 3841 if len(pos) == 2: 3842 pos = (pos[0], pos[1], 0) 3843 self.pos(pos) 3844 self.name = "Star3D"
Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3847class Cross3D(Mesh): 3848 """ 3849 Build a 3D cross shape. 3850 """ 3851 3852 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3853 """ 3854 Build a 3D cross shape, mainly useful as a 3D marker. 3855 """ 3856 if len(pos) == 2: 3857 pos = (pos[0], pos[1], 0) 3858 3859 c1 = Cylinder(r=thickness * s, height=2 * s) 3860 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3861 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3862 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3863 super().__init__(poly, c, alpha) 3864 self.name = "Cross3D"
Build a 3D cross shape.
3852 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3853 """ 3854 Build a 3D cross shape, mainly useful as a 3D marker. 3855 """ 3856 if len(pos) == 2: 3857 pos = (pos[0], pos[1], 0) 3858 3859 c1 = Cylinder(r=thickness * s, height=2 * s) 3860 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3861 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3862 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3863 super().__init__(poly, c, alpha) 3864 self.name = "Cross3D"
Build a 3D cross shape, mainly useful as a 3D marker.
2500class IcoSphere(Mesh): 2501 """ 2502 Create a sphere made of a uniform triangle mesh. 2503 """ 2504 2505 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2506 """ 2507 Create a sphere made of a uniform triangle mesh 2508 (from recursive subdivision of an icosahedron). 2509 2510 Example: 2511 ```python 2512 from vedo import * 2513 icos = IcoSphere(subdivisions=3) 2514 icos.compute_quality().cmap('coolwarm') 2515 icos.show(axes=1).close() 2516 ``` 2517 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2518 """ 2519 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2520 2521 t = (1.0 + np.sqrt(5.0)) / 2.0 2522 points = np.array( 2523 [ 2524 [-1, t, 0], 2525 [1, t, 0], 2526 [-1, -t, 0], 2527 [1, -t, 0], 2528 [0, -1, t], 2529 [0, 1, t], 2530 [0, -1, -t], 2531 [0, 1, -t], 2532 [t, 0, -1], 2533 [t, 0, 1], 2534 [-t, 0, -1], 2535 [-t, 0, 1], 2536 ] 2537 ) 2538 faces = [ 2539 [0, 11, 5], 2540 [0, 5, 1], 2541 [0, 1, 7], 2542 [0, 7, 10], 2543 [0, 10, 11], 2544 [1, 5, 9], 2545 [5, 11, 4], 2546 [11, 10, 2], 2547 [10, 7, 6], 2548 [7, 1, 8], 2549 [3, 9, 4], 2550 [3, 4, 2], 2551 [3, 2, 6], 2552 [3, 6, 8], 2553 [3, 8, 9], 2554 [4, 9, 5], 2555 [2, 4, 11], 2556 [6, 2, 10], 2557 [8, 6, 7], 2558 [9, 8, 1], 2559 ] 2560 super().__init__([points * r, faces], c=c, alpha=alpha) 2561 2562 for _ in range(subdivisions): 2563 self.subdivide(method=1) 2564 pts = utils.versor(self.vertices) * r 2565 self.vertices = pts 2566 2567 self.pos(pos) 2568 self.name = "IcoSphere"
Create a sphere made of a uniform triangle mesh.
2505 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2506 """ 2507 Create a sphere made of a uniform triangle mesh 2508 (from recursive subdivision of an icosahedron). 2509 2510 Example: 2511 ```python 2512 from vedo import * 2513 icos = IcoSphere(subdivisions=3) 2514 icos.compute_quality().cmap('coolwarm') 2515 icos.show(axes=1).close() 2516 ``` 2517 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2518 """ 2519 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2520 2521 t = (1.0 + np.sqrt(5.0)) / 2.0 2522 points = np.array( 2523 [ 2524 [-1, t, 0], 2525 [1, t, 0], 2526 [-1, -t, 0], 2527 [1, -t, 0], 2528 [0, -1, t], 2529 [0, 1, t], 2530 [0, -1, -t], 2531 [0, 1, -t], 2532 [t, 0, -1], 2533 [t, 0, 1], 2534 [-t, 0, -1], 2535 [-t, 0, 1], 2536 ] 2537 ) 2538 faces = [ 2539 [0, 11, 5], 2540 [0, 5, 1], 2541 [0, 1, 7], 2542 [0, 7, 10], 2543 [0, 10, 11], 2544 [1, 5, 9], 2545 [5, 11, 4], 2546 [11, 10, 2], 2547 [10, 7, 6], 2548 [7, 1, 8], 2549 [3, 9, 4], 2550 [3, 4, 2], 2551 [3, 2, 6], 2552 [3, 6, 8], 2553 [3, 8, 9], 2554 [4, 9, 5], 2555 [2, 4, 11], 2556 [6, 2, 10], 2557 [8, 6, 7], 2558 [9, 8, 1], 2559 ] 2560 super().__init__([points * r, faces], c=c, alpha=alpha) 2561 2562 for _ in range(subdivisions): 2563 self.subdivide(method=1) 2564 pts = utils.versor(self.vertices) * r 2565 self.vertices = pts 2566 2567 self.pos(pos) 2568 self.name = "IcoSphere"
Create a sphere made of a uniform triangle mesh (from recursive subdivision of an icosahedron).
Example:
from vedo import *
icos = IcoSphere(subdivisions=3)
icos.compute_quality().cmap('coolwarm')
icos.show(axes=1).close()
2571class Sphere(Mesh): 2572 """ 2573 Build a sphere. 2574 """ 2575 2576 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2577 """ 2578 Build a sphere at position `pos` of radius `r`. 2579 2580 Arguments: 2581 r : (float) 2582 sphere radius 2583 res : (int, list) 2584 resolution in phi, resolution in theta is by default `2*res` 2585 quads : (bool) 2586 sphere mesh will be made of quads instead of triangles 2587 2588 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2589 """ 2590 if len(pos) == 2: 2591 pos = np.asarray([pos[0], pos[1], 0]) 2592 2593 self.radius = r # used by fitSphere 2594 self.center = pos 2595 self.residue = 0 2596 2597 if quads: 2598 res = max(res, 4) 2599 img = vtki.vtkImageData() 2600 img.SetDimensions(res - 1, res - 1, res - 1) 2601 rs = 1.0 / (res - 2) 2602 img.SetSpacing(rs, rs, rs) 2603 gf = vtki.new("GeometryFilter") 2604 gf.SetInputData(img) 2605 gf.Update() 2606 super().__init__(gf.GetOutput(), c, alpha) 2607 self.lw(0.1) 2608 2609 cgpts = self.vertices - (0.5, 0.5, 0.5) 2610 2611 x, y, z = cgpts.T 2612 x = x * (1 + x * x) / 2 2613 y = y * (1 + y * y) / 2 2614 z = z * (1 + z * z) / 2 2615 _, theta, phi = cart2spher(x, y, z) 2616 2617 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2618 self.vertices = pts 2619 2620 else: 2621 if utils.is_sequence(res): 2622 res_t, res_phi = res 2623 else: 2624 res_t, res_phi = 2 * res, res 2625 2626 ss = vtki.new("SphereSource") 2627 ss.SetRadius(r) 2628 ss.SetThetaResolution(res_t) 2629 ss.SetPhiResolution(res_phi) 2630 ss.Update() 2631 2632 super().__init__(ss.GetOutput(), c, alpha) 2633 2634 self.phong() 2635 self.pos(pos) 2636 self.name = "Sphere"
Build a sphere.
2576 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2577 """ 2578 Build a sphere at position `pos` of radius `r`. 2579 2580 Arguments: 2581 r : (float) 2582 sphere radius 2583 res : (int, list) 2584 resolution in phi, resolution in theta is by default `2*res` 2585 quads : (bool) 2586 sphere mesh will be made of quads instead of triangles 2587 2588 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2589 """ 2590 if len(pos) == 2: 2591 pos = np.asarray([pos[0], pos[1], 0]) 2592 2593 self.radius = r # used by fitSphere 2594 self.center = pos 2595 self.residue = 0 2596 2597 if quads: 2598 res = max(res, 4) 2599 img = vtki.vtkImageData() 2600 img.SetDimensions(res - 1, res - 1, res - 1) 2601 rs = 1.0 / (res - 2) 2602 img.SetSpacing(rs, rs, rs) 2603 gf = vtki.new("GeometryFilter") 2604 gf.SetInputData(img) 2605 gf.Update() 2606 super().__init__(gf.GetOutput(), c, alpha) 2607 self.lw(0.1) 2608 2609 cgpts = self.vertices - (0.5, 0.5, 0.5) 2610 2611 x, y, z = cgpts.T 2612 x = x * (1 + x * x) / 2 2613 y = y * (1 + y * y) / 2 2614 z = z * (1 + z * z) / 2 2615 _, theta, phi = cart2spher(x, y, z) 2616 2617 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2618 self.vertices = pts 2619 2620 else: 2621 if utils.is_sequence(res): 2622 res_t, res_phi = res 2623 else: 2624 res_t, res_phi = 2 * res, res 2625 2626 ss = vtki.new("SphereSource") 2627 ss.SetRadius(r) 2628 ss.SetThetaResolution(res_t) 2629 ss.SetPhiResolution(res_phi) 2630 ss.Update() 2631 2632 super().__init__(ss.GetOutput(), c, alpha) 2633 2634 self.phong() 2635 self.pos(pos) 2636 self.name = "Sphere"
Build a sphere at position pos
of radius r
.
Arguments:
- r : (float) sphere radius
- res : (int, list)
resolution in phi, resolution in theta is by default
2*res
- quads : (bool) sphere mesh will be made of quads instead of triangles
2639class Spheres(Mesh): 2640 """ 2641 Build a large set of spheres. 2642 """ 2643 2644 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2645 """ 2646 Build a (possibly large) set of spheres at `centers` of radius `r`. 2647 2648 Either `c` or `r` can be a list of RGB colors or radii. 2649 2650 Examples: 2651 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2652 2653 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2654 """ 2655 2656 if isinstance(centers, Points): 2657 centers = centers.vertices 2658 centers = np.asarray(centers, dtype=float) 2659 base = centers[0] 2660 2661 cisseq = False 2662 if utils.is_sequence(c): 2663 cisseq = True 2664 2665 if cisseq: 2666 if len(centers) != len(c): 2667 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2668 raise RuntimeError() 2669 2670 risseq = False 2671 if utils.is_sequence(r): 2672 risseq = True 2673 2674 if risseq: 2675 if len(centers) != len(r): 2676 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2677 raise RuntimeError() 2678 if cisseq and risseq: 2679 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2680 raise RuntimeError() 2681 2682 src = vtki.new("SphereSource") 2683 if not risseq: 2684 src.SetRadius(r) 2685 if utils.is_sequence(res): 2686 res_t, res_phi = res 2687 else: 2688 res_t, res_phi = 2 * res, res 2689 2690 src.SetThetaResolution(res_t) 2691 src.SetPhiResolution(res_phi) 2692 src.Update() 2693 2694 psrc = vtki.new("PointSource") 2695 psrc.SetNumberOfPoints(len(centers)) 2696 psrc.Update() 2697 pd = psrc.GetOutput() 2698 vpts = pd.GetPoints() 2699 2700 glyph = vtki.vtkGlyph3D() 2701 glyph.SetSourceConnection(src.GetOutputPort()) 2702 2703 if cisseq: 2704 glyph.SetColorModeToColorByScalar() 2705 ucols = vtki.vtkUnsignedCharArray() 2706 ucols.SetNumberOfComponents(3) 2707 ucols.SetName("Colors") 2708 for acol in c: 2709 cx, cy, cz = get_color(acol) 2710 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2711 pd.GetPointData().AddArray(ucols) 2712 pd.GetPointData().SetActiveScalars("Colors") 2713 glyph.ScalingOff() 2714 elif risseq: 2715 glyph.SetScaleModeToScaleByScalar() 2716 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2717 urads.SetName("Radii") 2718 pd.GetPointData().AddArray(urads) 2719 pd.GetPointData().SetActiveScalars("Radii") 2720 2721 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2722 2723 glyph.SetInputData(pd) 2724 glyph.Update() 2725 2726 super().__init__(glyph.GetOutput(), alpha=alpha) 2727 self.pos(base) 2728 self.phong() 2729 if cisseq: 2730 self.mapper.ScalarVisibilityOn() 2731 else: 2732 self.mapper.ScalarVisibilityOff() 2733 self.c(c) 2734 self.name = "Spheres"
Build a large set of spheres.
2644 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2645 """ 2646 Build a (possibly large) set of spheres at `centers` of radius `r`. 2647 2648 Either `c` or `r` can be a list of RGB colors or radii. 2649 2650 Examples: 2651 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2652 2653 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2654 """ 2655 2656 if isinstance(centers, Points): 2657 centers = centers.vertices 2658 centers = np.asarray(centers, dtype=float) 2659 base = centers[0] 2660 2661 cisseq = False 2662 if utils.is_sequence(c): 2663 cisseq = True 2664 2665 if cisseq: 2666 if len(centers) != len(c): 2667 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2668 raise RuntimeError() 2669 2670 risseq = False 2671 if utils.is_sequence(r): 2672 risseq = True 2673 2674 if risseq: 2675 if len(centers) != len(r): 2676 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2677 raise RuntimeError() 2678 if cisseq and risseq: 2679 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2680 raise RuntimeError() 2681 2682 src = vtki.new("SphereSource") 2683 if not risseq: 2684 src.SetRadius(r) 2685 if utils.is_sequence(res): 2686 res_t, res_phi = res 2687 else: 2688 res_t, res_phi = 2 * res, res 2689 2690 src.SetThetaResolution(res_t) 2691 src.SetPhiResolution(res_phi) 2692 src.Update() 2693 2694 psrc = vtki.new("PointSource") 2695 psrc.SetNumberOfPoints(len(centers)) 2696 psrc.Update() 2697 pd = psrc.GetOutput() 2698 vpts = pd.GetPoints() 2699 2700 glyph = vtki.vtkGlyph3D() 2701 glyph.SetSourceConnection(src.GetOutputPort()) 2702 2703 if cisseq: 2704 glyph.SetColorModeToColorByScalar() 2705 ucols = vtki.vtkUnsignedCharArray() 2706 ucols.SetNumberOfComponents(3) 2707 ucols.SetName("Colors") 2708 for acol in c: 2709 cx, cy, cz = get_color(acol) 2710 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2711 pd.GetPointData().AddArray(ucols) 2712 pd.GetPointData().SetActiveScalars("Colors") 2713 glyph.ScalingOff() 2714 elif risseq: 2715 glyph.SetScaleModeToScaleByScalar() 2716 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2717 urads.SetName("Radii") 2718 pd.GetPointData().AddArray(urads) 2719 pd.GetPointData().SetActiveScalars("Radii") 2720 2721 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2722 2723 glyph.SetInputData(pd) 2724 glyph.Update() 2725 2726 super().__init__(glyph.GetOutput(), alpha=alpha) 2727 self.pos(base) 2728 self.phong() 2729 if cisseq: 2730 self.mapper.ScalarVisibilityOn() 2731 else: 2732 self.mapper.ScalarVisibilityOff() 2733 self.c(c) 2734 self.name = "Spheres"
Build a (possibly large) set of spheres at centers
of radius r
.
Either c
or r
can be a list of RGB colors or radii.
Examples:
2737class Earth(Mesh): 2738 """ 2739 Build a textured mesh representing the Earth. 2740 """ 2741 2742 def __init__(self, style=1, r=1.0) -> None: 2743 """ 2744 Build a textured mesh representing the Earth. 2745 2746 Example: 2747 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2748 2749 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2750 """ 2751 tss = vtki.new("TexturedSphereSource") 2752 tss.SetRadius(r) 2753 tss.SetThetaResolution(72) 2754 tss.SetPhiResolution(36) 2755 tss.Update() 2756 super().__init__(tss.GetOutput(), c="w") 2757 atext = vtki.vtkTexture() 2758 pnm_reader = vtki.new("JPEGReader") 2759 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2760 pnm_reader.SetFileName(fn) 2761 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2762 atext.InterpolateOn() 2763 self.texture(atext) 2764 self.name = "Earth"
Build a textured mesh representing the Earth.
2742 def __init__(self, style=1, r=1.0) -> None: 2743 """ 2744 Build a textured mesh representing the Earth. 2745 2746 Example: 2747 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2748 2749 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2750 """ 2751 tss = vtki.new("TexturedSphereSource") 2752 tss.SetRadius(r) 2753 tss.SetThetaResolution(72) 2754 tss.SetPhiResolution(36) 2755 tss.Update() 2756 super().__init__(tss.GetOutput(), c="w") 2757 atext = vtki.vtkTexture() 2758 pnm_reader = vtki.new("JPEGReader") 2759 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2760 pnm_reader.SetFileName(fn) 2761 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2762 atext.InterpolateOn() 2763 self.texture(atext) 2764 self.name = "Earth"
2767class Ellipsoid(Mesh): 2768 """Build a 3D ellipsoid.""" 2769 def __init__( 2770 self, 2771 pos=(0, 0, 0), 2772 axis1=(0.5, 0, 0), 2773 axis2=(0, 1, 0), 2774 axis3=(0, 0, 1.5), 2775 res=24, 2776 c="cyan4", 2777 alpha=1.0, 2778 ) -> None: 2779 """ 2780 Build a 3D ellipsoid centered at position `pos`. 2781 2782 Arguments: 2783 axis1 : (list) 2784 First axis. Length corresponds to semi-axis. 2785 axis2 : (list) 2786 Second axis. Length corresponds to semi-axis. 2787 axis3 : (list) 2788 Third axis. Length corresponds to semi-axis. 2789 """ 2790 self.center = utils.make3d(pos) 2791 2792 self.axis1 = utils.make3d(axis1) 2793 self.axis2 = utils.make3d(axis2) 2794 self.axis3 = utils.make3d(axis3) 2795 2796 self.va = np.linalg.norm(self.axis1) 2797 self.vb = np.linalg.norm(self.axis2) 2798 self.vc = np.linalg.norm(self.axis3) 2799 2800 self.va_error = 0 2801 self.vb_error = 0 2802 self.vc_error = 0 2803 2804 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2805 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2806 2807 if utils.is_sequence(res): 2808 res_t, res_phi = res 2809 else: 2810 res_t, res_phi = 2 * res, res 2811 2812 elli_source = vtki.new("SphereSource") 2813 elli_source.SetRadius(1) 2814 elli_source.SetThetaResolution(res_t) 2815 elli_source.SetPhiResolution(res_phi) 2816 elli_source.Update() 2817 2818 super().__init__(elli_source.GetOutput(), c, alpha) 2819 2820 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2821 lt = LinearTransform(matrix).translate(pos) 2822 self.apply_transform(lt) 2823 self.name = "Ellipsoid" 2824 2825 def asphericity(self) -> float: 2826 """ 2827 Return a measure of how different an ellipsoid is from a sphere. 2828 Values close to zero correspond to a spheric object. 2829 """ 2830 a, b, c = self.va, self.vb, self.vc 2831 asp = ( ((a-b)/(a+b))**2 2832 + ((a-c)/(a+c))**2 2833 + ((b-c)/(b+c))**2 ) / 3. * 4. 2834 return float(asp) 2835 2836 def asphericity_error(self) -> float: 2837 """ 2838 Calculate statistical error on the asphericity value. 2839 2840 Errors on the main axes are stored in 2841 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2842 """ 2843 a, b, c = self.va, self.vb, self.vc 2844 sqrtn = np.sqrt(self.nr_of_points) 2845 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2846 2847 # from sympy import * 2848 # init_printing(use_unicode=True) 2849 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2850 # L = ( 2851 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2852 # / 3 * 4) 2853 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2854 # print(dl2) 2855 # exit() 2856 2857 dL2 = ( 2858 ea ** 2 2859 * ( 2860 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2861 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2862 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2863 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2864 ) ** 2 2865 + eb ** 2 2866 * ( 2867 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2868 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2869 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2870 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2871 ) ** 2 2872 + ec ** 2 2873 * ( 2874 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2875 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2876 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2877 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2878 ) ** 2 2879 ) 2880 err = np.sqrt(dL2) 2881 self.va_error = ea 2882 self.vb_error = eb 2883 self.vc_error = ec 2884 return err
Build a 3D ellipsoid.
2769 def __init__( 2770 self, 2771 pos=(0, 0, 0), 2772 axis1=(0.5, 0, 0), 2773 axis2=(0, 1, 0), 2774 axis3=(0, 0, 1.5), 2775 res=24, 2776 c="cyan4", 2777 alpha=1.0, 2778 ) -> None: 2779 """ 2780 Build a 3D ellipsoid centered at position `pos`. 2781 2782 Arguments: 2783 axis1 : (list) 2784 First axis. Length corresponds to semi-axis. 2785 axis2 : (list) 2786 Second axis. Length corresponds to semi-axis. 2787 axis3 : (list) 2788 Third axis. Length corresponds to semi-axis. 2789 """ 2790 self.center = utils.make3d(pos) 2791 2792 self.axis1 = utils.make3d(axis1) 2793 self.axis2 = utils.make3d(axis2) 2794 self.axis3 = utils.make3d(axis3) 2795 2796 self.va = np.linalg.norm(self.axis1) 2797 self.vb = np.linalg.norm(self.axis2) 2798 self.vc = np.linalg.norm(self.axis3) 2799 2800 self.va_error = 0 2801 self.vb_error = 0 2802 self.vc_error = 0 2803 2804 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2805 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2806 2807 if utils.is_sequence(res): 2808 res_t, res_phi = res 2809 else: 2810 res_t, res_phi = 2 * res, res 2811 2812 elli_source = vtki.new("SphereSource") 2813 elli_source.SetRadius(1) 2814 elli_source.SetThetaResolution(res_t) 2815 elli_source.SetPhiResolution(res_phi) 2816 elli_source.Update() 2817 2818 super().__init__(elli_source.GetOutput(), c, alpha) 2819 2820 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2821 lt = LinearTransform(matrix).translate(pos) 2822 self.apply_transform(lt) 2823 self.name = "Ellipsoid"
Build a 3D ellipsoid centered at position pos
.
Arguments:
- axis1 : (list) First axis. Length corresponds to semi-axis.
- axis2 : (list) Second axis. Length corresponds to semi-axis.
- axis3 : (list) Third axis. Length corresponds to semi-axis.
2825 def asphericity(self) -> float: 2826 """ 2827 Return a measure of how different an ellipsoid is from a sphere. 2828 Values close to zero correspond to a spheric object. 2829 """ 2830 a, b, c = self.va, self.vb, self.vc 2831 asp = ( ((a-b)/(a+b))**2 2832 + ((a-c)/(a+c))**2 2833 + ((b-c)/(b+c))**2 ) / 3. * 4. 2834 return float(asp)
Return a measure of how different an ellipsoid is from a sphere. Values close to zero correspond to a spheric object.
2836 def asphericity_error(self) -> float: 2837 """ 2838 Calculate statistical error on the asphericity value. 2839 2840 Errors on the main axes are stored in 2841 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2842 """ 2843 a, b, c = self.va, self.vb, self.vc 2844 sqrtn = np.sqrt(self.nr_of_points) 2845 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2846 2847 # from sympy import * 2848 # init_printing(use_unicode=True) 2849 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2850 # L = ( 2851 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2852 # / 3 * 4) 2853 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2854 # print(dl2) 2855 # exit() 2856 2857 dL2 = ( 2858 ea ** 2 2859 * ( 2860 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2861 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2862 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2863 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2864 ) ** 2 2865 + eb ** 2 2866 * ( 2867 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2868 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2869 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2870 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2871 ) ** 2 2872 + ec ** 2 2873 * ( 2874 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2875 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2876 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2877 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2878 ) ** 2 2879 ) 2880 err = np.sqrt(dL2) 2881 self.va_error = ea 2882 self.vb_error = eb 2883 self.vc_error = ec 2884 return err
Calculate statistical error on the asphericity value.
Errors on the main axes are stored in
Ellipsoid.va_error
, Ellipsoid.vb_errorand
Ellipsoid.vc_error`.
2887class Grid(Mesh): 2888 """ 2889 An even or uneven 2D grid. 2890 """ 2891 2892 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2893 """ 2894 Create an even or uneven 2D grid. 2895 Can also be created from a `np.mgrid` object (see example). 2896 2897 Arguments: 2898 pos : (list, Points, Mesh) 2899 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2900 s : (float, list) 2901 if a float is provided it is interpreted as the total size along x and y, 2902 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2903 In this case keyword `res` is ignored (see example below). 2904 res : (list) 2905 resolutions along x and y, e.i. the number of subdivisions 2906 lw : (int) 2907 line width 2908 2909 Example: 2910 ```python 2911 from vedo import * 2912 xcoords = np.arange(0, 2, 0.2) 2913 ycoords = np.arange(0, 1, 0.2) 2914 sqrtx = sqrt(xcoords) 2915 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2916 grid.show(axes=8).close() 2917 2918 # Can also create a grid from a np.mgrid: 2919 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2920 vgrid = Grid(s=(X[:,0], Y[0])) 2921 vgrid.show(axes=8).close() 2922 ``` 2923 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2924 """ 2925 resx, resy = res 2926 sx, sy = s 2927 2928 try: 2929 bb = pos.bounds() 2930 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2931 sx = bb[1] - bb[0] 2932 sy = bb[3] - bb[2] 2933 except AttributeError: 2934 pass 2935 2936 if len(pos) == 2: 2937 pos = (pos[0], pos[1], 0) 2938 elif len(pos) in [4,6]: # passing a bounding box 2939 bb = pos 2940 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2941 sx = bb[1] - bb[0] 2942 sy = bb[3] - bb[2] 2943 if len(pos)==6: 2944 pos[2] = bb[4] - bb[5] 2945 2946 if utils.is_sequence(sx) and utils.is_sequence(sy): 2947 verts = [] 2948 for y in sy: 2949 for x in sx: 2950 verts.append([x, y, 0]) 2951 faces = [] 2952 n = len(sx) 2953 m = len(sy) 2954 for j in range(m - 1): 2955 j1n = (j + 1) * n 2956 for i in range(n - 1): 2957 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2958 2959 super().__init__([verts, faces], c, alpha) 2960 2961 else: 2962 ps = vtki.new("PlaneSource") 2963 ps.SetResolution(resx, resy) 2964 ps.Update() 2965 2966 t = vtki.vtkTransform() 2967 t.Translate(pos) 2968 t.Scale(sx, sy, 1) 2969 2970 tf = vtki.new("TransformPolyDataFilter") 2971 tf.SetInputData(ps.GetOutput()) 2972 tf.SetTransform(t) 2973 tf.Update() 2974 2975 super().__init__(tf.GetOutput(), c, alpha) 2976 2977 self.wireframe().lw(lw) 2978 self.properties.LightingOff() 2979 self.name = "Grid"
An even or uneven 2D grid.
2892 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2893 """ 2894 Create an even or uneven 2D grid. 2895 Can also be created from a `np.mgrid` object (see example). 2896 2897 Arguments: 2898 pos : (list, Points, Mesh) 2899 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2900 s : (float, list) 2901 if a float is provided it is interpreted as the total size along x and y, 2902 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2903 In this case keyword `res` is ignored (see example below). 2904 res : (list) 2905 resolutions along x and y, e.i. the number of subdivisions 2906 lw : (int) 2907 line width 2908 2909 Example: 2910 ```python 2911 from vedo import * 2912 xcoords = np.arange(0, 2, 0.2) 2913 ycoords = np.arange(0, 1, 0.2) 2914 sqrtx = sqrt(xcoords) 2915 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2916 grid.show(axes=8).close() 2917 2918 # Can also create a grid from a np.mgrid: 2919 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2920 vgrid = Grid(s=(X[:,0], Y[0])) 2921 vgrid.show(axes=8).close() 2922 ``` 2923 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2924 """ 2925 resx, resy = res 2926 sx, sy = s 2927 2928 try: 2929 bb = pos.bounds() 2930 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2931 sx = bb[1] - bb[0] 2932 sy = bb[3] - bb[2] 2933 except AttributeError: 2934 pass 2935 2936 if len(pos) == 2: 2937 pos = (pos[0], pos[1], 0) 2938 elif len(pos) in [4,6]: # passing a bounding box 2939 bb = pos 2940 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2941 sx = bb[1] - bb[0] 2942 sy = bb[3] - bb[2] 2943 if len(pos)==6: 2944 pos[2] = bb[4] - bb[5] 2945 2946 if utils.is_sequence(sx) and utils.is_sequence(sy): 2947 verts = [] 2948 for y in sy: 2949 for x in sx: 2950 verts.append([x, y, 0]) 2951 faces = [] 2952 n = len(sx) 2953 m = len(sy) 2954 for j in range(m - 1): 2955 j1n = (j + 1) * n 2956 for i in range(n - 1): 2957 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2958 2959 super().__init__([verts, faces], c, alpha) 2960 2961 else: 2962 ps = vtki.new("PlaneSource") 2963 ps.SetResolution(resx, resy) 2964 ps.Update() 2965 2966 t = vtki.vtkTransform() 2967 t.Translate(pos) 2968 t.Scale(sx, sy, 1) 2969 2970 tf = vtki.new("TransformPolyDataFilter") 2971 tf.SetInputData(ps.GetOutput()) 2972 tf.SetTransform(t) 2973 tf.Update() 2974 2975 super().__init__(tf.GetOutput(), c, alpha) 2976 2977 self.wireframe().lw(lw) 2978 self.properties.LightingOff() 2979 self.name = "Grid"
Create an even or uneven 2D grid.
Can also be created from a np.mgrid
object (see example).
Arguments:
- pos : (list, Points, Mesh) position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
- s : (float, list)
if a float is provided it is interpreted as the total size along x and y,
if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
In this case keyword
res
is ignored (see example below). - res : (list) resolutions along x and y, e.i. the number of subdivisions
- lw : (int) line width
Example:
from vedo import * xcoords = np.arange(0, 2, 0.2) ycoords = np.arange(0, 1, 0.2) sqrtx = sqrt(xcoords) grid = Grid(s=(sqrtx, ycoords)).lw(2) grid.show(axes=8).close() # Can also create a grid from a np.mgrid: X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] vgrid = Grid(s=(X[:,0], Y[0])) vgrid.show(axes=8).close()
3284class TessellatedBox(Mesh): 3285 """ 3286 Build a cubic `Mesh` made of quads. 3287 """ 3288 3289 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3290 """ 3291 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3292 3293 Arguments: 3294 pos : (list) 3295 position of the left bottom corner 3296 n : (int, list) 3297 number of subdivisions along each side 3298 spacing : (float) 3299 size of the side of the single quad in the 3 directions 3300 """ 3301 if utils.is_sequence(n): # slow 3302 img = vtki.vtkImageData() 3303 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3304 img.SetSpacing(spacing) 3305 gf = vtki.new("GeometryFilter") 3306 gf.SetInputData(img) 3307 gf.Update() 3308 poly = gf.GetOutput() 3309 else: # fast 3310 n -= 1 3311 tbs = vtki.new("TessellatedBoxSource") 3312 tbs.SetLevel(n) 3313 if len(bounds): 3314 tbs.SetBounds(bounds) 3315 else: 3316 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3317 tbs.QuadsOn() 3318 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3319 tbs.Update() 3320 poly = tbs.GetOutput() 3321 super().__init__(poly, c=c, alpha=alpha) 3322 self.pos(pos) 3323 self.lw(1).lighting("off") 3324 self.name = "TessellatedBox"
Build a cubic Mesh
made of quads.
3289 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3290 """ 3291 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3292 3293 Arguments: 3294 pos : (list) 3295 position of the left bottom corner 3296 n : (int, list) 3297 number of subdivisions along each side 3298 spacing : (float) 3299 size of the side of the single quad in the 3 directions 3300 """ 3301 if utils.is_sequence(n): # slow 3302 img = vtki.vtkImageData() 3303 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3304 img.SetSpacing(spacing) 3305 gf = vtki.new("GeometryFilter") 3306 gf.SetInputData(img) 3307 gf.Update() 3308 poly = gf.GetOutput() 3309 else: # fast 3310 n -= 1 3311 tbs = vtki.new("TessellatedBoxSource") 3312 tbs.SetLevel(n) 3313 if len(bounds): 3314 tbs.SetBounds(bounds) 3315 else: 3316 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3317 tbs.QuadsOn() 3318 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3319 tbs.Update() 3320 poly = tbs.GetOutput() 3321 super().__init__(poly, c=c, alpha=alpha) 3322 self.pos(pos) 3323 self.lw(1).lighting("off") 3324 self.name = "TessellatedBox"
Build a cubic Mesh
made of n
small quads in the 3 axis directions.
Arguments:
- pos : (list) position of the left bottom corner
- n : (int, list) number of subdivisions along each side
- spacing : (float) size of the side of the single quad in the 3 directions
2982class Plane(Mesh): 2983 """Create a plane in space.""" 2984 2985 def __init__( 2986 self, 2987 pos=(0, 0, 0), 2988 normal=(0, 0, 1), 2989 s=(1, 1), 2990 res=(1, 1), 2991 c="gray5", alpha=1.0, 2992 ) -> None: 2993 """ 2994 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 2995 to vector `normal` so that it passes through point `pos`. 2996 2997 Arguments: 2998 pos : (list) 2999 position of the plane center 3000 normal : (list) 3001 normal vector to the plane 3002 s : (list) 3003 size of the plane along x and y 3004 res : (list) 3005 resolution of the plane along x and y 3006 """ 3007 if isinstance(pos, vtki.vtkPolyData): 3008 super().__init__(pos, c, alpha) 3009 # self.transform = LinearTransform().translate(pos) 3010 3011 else: 3012 ps = vtki.new("PlaneSource") 3013 ps.SetResolution(res[0], res[1]) 3014 tri = vtki.new("TriangleFilter") 3015 tri.SetInputConnection(ps.GetOutputPort()) 3016 tri.Update() 3017 3018 super().__init__(tri.GetOutput(), c, alpha) 3019 3020 pos = utils.make3d(pos) 3021 normal = np.asarray(normal, dtype=float) 3022 axis = normal / np.linalg.norm(normal) 3023 theta = np.arccos(axis[2]) 3024 phi = np.arctan2(axis[1], axis[0]) 3025 3026 t = LinearTransform() 3027 t.scale([s[0], s[1], 1]) 3028 t.rotate_y(np.rad2deg(theta)) 3029 t.rotate_z(np.rad2deg(phi)) 3030 t.translate(pos) 3031 self.apply_transform(t) 3032 3033 self.lighting("off") 3034 self.name = "Plane" 3035 self.variance = 0 3036 3037 def clone(self, deep=True) -> "Plane": 3038 newplane = Plane() 3039 if deep: 3040 newplane.dataset.DeepCopy(self.dataset) 3041 else: 3042 newplane.dataset.ShallowCopy(self.dataset) 3043 newplane.copy_properties_from(self) 3044 newplane.transform = self.transform.clone() 3045 newplane.variance = 0 3046 return newplane 3047 3048 @property 3049 def normal(self) -> np.ndarray: 3050 pts = self.vertices 3051 # this is necessary because plane can have high resolution 3052 # p0, p1 = pts[0], pts[1] 3053 # AB = p1 - p0 3054 # AB /= np.linalg.norm(AB) 3055 # for pt in pts[2:]: 3056 # AC = pt - p0 3057 # AC /= np.linalg.norm(AC) 3058 # cosine_angle = np.dot(AB, AC) 3059 # if abs(cosine_angle) < 0.99: 3060 # normal = np.cross(AB, AC) 3061 # return normal / np.linalg.norm(normal) 3062 p0, p1, p2 = pts[0], pts[1], pts[int(len(pts)/2 +0.5)] 3063 AB = p1 - p0 3064 AB /= np.linalg.norm(AB) 3065 AC = p2 - p0 3066 AC /= np.linalg.norm(AC) 3067 normal = np.cross(AB, AC) 3068 return normal / np.linalg.norm(normal) 3069 3070 @property 3071 def center(self) -> np.ndarray: 3072 pts = self.vertices 3073 return np.mean(pts, axis=0) 3074 3075 def contains(self, points, tol=0) -> np.ndarray: 3076 """ 3077 Check if each of the provided point lies on this plane. 3078 `points` is an array of shape (n, 3). 3079 """ 3080 points = np.array(points, dtype=float) 3081 bounds = self.vertices 3082 3083 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3084 3085 for i in [1, 3]: 3086 AB = bounds[i] - bounds[0] 3087 AP = points - bounds[0] 3088 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3089 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3090 mask = np.logical_and(mask, mask_l) 3091 mask = np.logical_and(mask, mask_g) 3092 return mask
Create a plane in space.
2985 def __init__( 2986 self, 2987 pos=(0, 0, 0), 2988 normal=(0, 0, 1), 2989 s=(1, 1), 2990 res=(1, 1), 2991 c="gray5", alpha=1.0, 2992 ) -> None: 2993 """ 2994 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 2995 to vector `normal` so that it passes through point `pos`. 2996 2997 Arguments: 2998 pos : (list) 2999 position of the plane center 3000 normal : (list) 3001 normal vector to the plane 3002 s : (list) 3003 size of the plane along x and y 3004 res : (list) 3005 resolution of the plane along x and y 3006 """ 3007 if isinstance(pos, vtki.vtkPolyData): 3008 super().__init__(pos, c, alpha) 3009 # self.transform = LinearTransform().translate(pos) 3010 3011 else: 3012 ps = vtki.new("PlaneSource") 3013 ps.SetResolution(res[0], res[1]) 3014 tri = vtki.new("TriangleFilter") 3015 tri.SetInputConnection(ps.GetOutputPort()) 3016 tri.Update() 3017 3018 super().__init__(tri.GetOutput(), c, alpha) 3019 3020 pos = utils.make3d(pos) 3021 normal = np.asarray(normal, dtype=float) 3022 axis = normal / np.linalg.norm(normal) 3023 theta = np.arccos(axis[2]) 3024 phi = np.arctan2(axis[1], axis[0]) 3025 3026 t = LinearTransform() 3027 t.scale([s[0], s[1], 1]) 3028 t.rotate_y(np.rad2deg(theta)) 3029 t.rotate_z(np.rad2deg(phi)) 3030 t.translate(pos) 3031 self.apply_transform(t) 3032 3033 self.lighting("off") 3034 self.name = "Plane" 3035 self.variance = 0
Create a plane of size s=(xsize, ysize)
oriented perpendicular
to vector normal
so that it passes through point pos
.
Arguments:
- pos : (list) position of the plane center
- normal : (list) normal vector to the plane
- s : (list) size of the plane along x and y
- res : (list) resolution of the plane along x and y
3037 def clone(self, deep=True) -> "Plane": 3038 newplane = Plane() 3039 if deep: 3040 newplane.dataset.DeepCopy(self.dataset) 3041 else: 3042 newplane.dataset.ShallowCopy(self.dataset) 3043 newplane.copy_properties_from(self) 3044 newplane.transform = self.transform.clone() 3045 newplane.variance = 0 3046 return newplane
3075 def contains(self, points, tol=0) -> np.ndarray: 3076 """ 3077 Check if each of the provided point lies on this plane. 3078 `points` is an array of shape (n, 3). 3079 """ 3080 points = np.array(points, dtype=float) 3081 bounds = self.vertices 3082 3083 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3084 3085 for i in [1, 3]: 3086 AB = bounds[i] - bounds[0] 3087 AP = points - bounds[0] 3088 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3089 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3090 mask = np.logical_and(mask, mask_l) 3091 mask = np.logical_and(mask, mask_g) 3092 return mask
Check if each of the provided point lies on this plane.
points
is an array of shape (n, 3).
3189class Box(Mesh): 3190 """ 3191 Build a box of specified dimensions. 3192 """ 3193 3194 def __init__( 3195 self, pos=(0, 0, 0), 3196 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3197 """ 3198 Build a box of dimensions `x=length, y=width and z=height`. 3199 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3200 3201 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3202 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3203 3204 Note that the shape polygonal data contains duplicated vertices. This is to allow 3205 each face to have its own normal, which is essential for some operations. 3206 Use the `clean()` method to remove duplicate points. 3207 3208 Examples: 3209 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3210 3211 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3212 """ 3213 src = vtki.new("CubeSource") 3214 3215 if len(pos) == 2: 3216 pos = (pos[0], pos[1], 0) 3217 3218 if len(pos) == 6: 3219 src.SetBounds(pos) 3220 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3221 elif len(size) == 3: 3222 length, width, height = size 3223 src.SetXLength(length) 3224 src.SetYLength(width) 3225 src.SetZLength(height) 3226 src.SetCenter(pos) 3227 else: 3228 src.SetXLength(length) 3229 src.SetYLength(width) 3230 src.SetZLength(height) 3231 src.SetCenter(pos) 3232 3233 src.Update() 3234 pd = src.GetOutput() 3235 3236 tc = [ 3237 [0.0, 0.0], 3238 [1.0, 0.0], 3239 [0.0, 1.0], 3240 [1.0, 1.0], 3241 [1.0, 0.0], 3242 [0.0, 0.0], 3243 [1.0, 1.0], 3244 [0.0, 1.0], 3245 [1.0, 1.0], 3246 [1.0, 0.0], 3247 [0.0, 1.0], 3248 [0.0, 0.0], 3249 [0.0, 1.0], 3250 [0.0, 0.0], 3251 [1.0, 1.0], 3252 [1.0, 0.0], 3253 [1.0, 0.0], 3254 [0.0, 0.0], 3255 [1.0, 1.0], 3256 [0.0, 1.0], 3257 [0.0, 0.0], 3258 [1.0, 0.0], 3259 [0.0, 1.0], 3260 [1.0, 1.0], 3261 ] 3262 vtc = utils.numpy2vtk(tc) 3263 pd.GetPointData().SetTCoords(vtc) 3264 super().__init__(pd, c, alpha) 3265 self.transform = LinearTransform().translate(pos) 3266 self.name = "Box"
Build a box of specified dimensions.
3194 def __init__( 3195 self, pos=(0, 0, 0), 3196 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3197 """ 3198 Build a box of dimensions `x=length, y=width and z=height`. 3199 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3200 3201 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3202 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3203 3204 Note that the shape polygonal data contains duplicated vertices. This is to allow 3205 each face to have its own normal, which is essential for some operations. 3206 Use the `clean()` method to remove duplicate points. 3207 3208 Examples: 3209 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3210 3211 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3212 """ 3213 src = vtki.new("CubeSource") 3214 3215 if len(pos) == 2: 3216 pos = (pos[0], pos[1], 0) 3217 3218 if len(pos) == 6: 3219 src.SetBounds(pos) 3220 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3221 elif len(size) == 3: 3222 length, width, height = size 3223 src.SetXLength(length) 3224 src.SetYLength(width) 3225 src.SetZLength(height) 3226 src.SetCenter(pos) 3227 else: 3228 src.SetXLength(length) 3229 src.SetYLength(width) 3230 src.SetZLength(height) 3231 src.SetCenter(pos) 3232 3233 src.Update() 3234 pd = src.GetOutput() 3235 3236 tc = [ 3237 [0.0, 0.0], 3238 [1.0, 0.0], 3239 [0.0, 1.0], 3240 [1.0, 1.0], 3241 [1.0, 0.0], 3242 [0.0, 0.0], 3243 [1.0, 1.0], 3244 [0.0, 1.0], 3245 [1.0, 1.0], 3246 [1.0, 0.0], 3247 [0.0, 1.0], 3248 [0.0, 0.0], 3249 [0.0, 1.0], 3250 [0.0, 0.0], 3251 [1.0, 1.0], 3252 [1.0, 0.0], 3253 [1.0, 0.0], 3254 [0.0, 0.0], 3255 [1.0, 1.0], 3256 [0.0, 1.0], 3257 [0.0, 0.0], 3258 [1.0, 0.0], 3259 [0.0, 1.0], 3260 [1.0, 1.0], 3261 ] 3262 vtc = utils.numpy2vtk(tc) 3263 pd.GetPointData().SetTCoords(vtc) 3264 super().__init__(pd, c, alpha) 3265 self.transform = LinearTransform().translate(pos) 3266 self.name = "Box"
Build a box of dimensions x=length, y=width and z=height
.
Alternatively dimensions can be defined by setting size
keyword with a tuple.
If pos
is a list of 6 numbers, this will be interpreted as the bounding box:
[xmin,xmax, ymin,ymax, zmin,zmax]
Note that the shape polygonal data contains duplicated vertices. This is to allow
each face to have its own normal, which is essential for some operations.
Use the clean()
method to remove duplicate points.
Examples:
3269class Cube(Box): 3270 """ 3271 Build a cube shape. 3272 3273 Note that the shape polygonal data contains duplicated vertices. This is to allow 3274 each face to have its own normal, which is essential for some operations. 3275 Use the `clean()` method to remove duplicate points. 3276 """ 3277 3278 def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None: 3279 """Build a cube of size `side`.""" 3280 super().__init__(pos, side, side, side, (), c, alpha) 3281 self.name = "Cube"
Build a cube shape.
Note that the shape polygonal data contains duplicated vertices. This is to allow
each face to have its own normal, which is essential for some operations.
Use the clean()
method to remove duplicate points.
3327class Spring(Mesh): 3328 """ 3329 Build a spring model. 3330 """ 3331 3332 def __init__( 3333 self, 3334 start_pt=(0, 0, 0), 3335 end_pt=(1, 0, 0), 3336 coils=20, 3337 r1=0.1, 3338 r2=None, 3339 thickness=None, 3340 c="gray5", 3341 alpha=1.0, 3342 ) -> None: 3343 """ 3344 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3345 3346 Arguments: 3347 coils : (int) 3348 number of coils 3349 r1 : (float) 3350 radius at start point 3351 r2 : (float) 3352 radius at end point 3353 thickness : (float) 3354 thickness of the coil section 3355 """ 3356 start_pt = utils.make3d(start_pt) 3357 end_pt = utils.make3d(end_pt) 3358 3359 diff = end_pt - start_pt 3360 length = np.linalg.norm(diff) 3361 if not length: 3362 return 3363 if not r1: 3364 r1 = length / 20 3365 trange = np.linspace(0, length, num=50 * coils) 3366 om = 6.283 * (coils - 0.5) / length 3367 if not r2: 3368 r2 = r1 3369 pts = [] 3370 for t in trange: 3371 f = (length - t) / length 3372 rd = r1 * f + r2 * (1 - f) 3373 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3374 3375 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3376 diff = diff / length 3377 theta = np.arccos(diff[2]) 3378 phi = np.arctan2(diff[1], diff[0]) 3379 sp = Line(pts) 3380 3381 t = vtki.vtkTransform() 3382 t.Translate(start_pt) 3383 t.RotateZ(np.rad2deg(phi)) 3384 t.RotateY(np.rad2deg(theta)) 3385 3386 tf = vtki.new("TransformPolyDataFilter") 3387 tf.SetInputData(sp.dataset) 3388 tf.SetTransform(t) 3389 tf.Update() 3390 3391 tuf = vtki.new("TubeFilter") 3392 tuf.SetNumberOfSides(12) 3393 tuf.CappingOn() 3394 tuf.SetInputData(tf.GetOutput()) 3395 if not thickness: 3396 thickness = r1 / 10 3397 tuf.SetRadius(thickness) 3398 tuf.Update() 3399 3400 super().__init__(tuf.GetOutput(), c, alpha) 3401 3402 self.phong() 3403 self.base = np.array(start_pt, dtype=float) 3404 self.top = np.array(end_pt, dtype=float) 3405 self.name = "Spring"
Build a spring model.
3332 def __init__( 3333 self, 3334 start_pt=(0, 0, 0), 3335 end_pt=(1, 0, 0), 3336 coils=20, 3337 r1=0.1, 3338 r2=None, 3339 thickness=None, 3340 c="gray5", 3341 alpha=1.0, 3342 ) -> None: 3343 """ 3344 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3345 3346 Arguments: 3347 coils : (int) 3348 number of coils 3349 r1 : (float) 3350 radius at start point 3351 r2 : (float) 3352 radius at end point 3353 thickness : (float) 3354 thickness of the coil section 3355 """ 3356 start_pt = utils.make3d(start_pt) 3357 end_pt = utils.make3d(end_pt) 3358 3359 diff = end_pt - start_pt 3360 length = np.linalg.norm(diff) 3361 if not length: 3362 return 3363 if not r1: 3364 r1 = length / 20 3365 trange = np.linspace(0, length, num=50 * coils) 3366 om = 6.283 * (coils - 0.5) / length 3367 if not r2: 3368 r2 = r1 3369 pts = [] 3370 for t in trange: 3371 f = (length - t) / length 3372 rd = r1 * f + r2 * (1 - f) 3373 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3374 3375 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3376 diff = diff / length 3377 theta = np.arccos(diff[2]) 3378 phi = np.arctan2(diff[1], diff[0]) 3379 sp = Line(pts) 3380 3381 t = vtki.vtkTransform() 3382 t.Translate(start_pt) 3383 t.RotateZ(np.rad2deg(phi)) 3384 t.RotateY(np.rad2deg(theta)) 3385 3386 tf = vtki.new("TransformPolyDataFilter") 3387 tf.SetInputData(sp.dataset) 3388 tf.SetTransform(t) 3389 tf.Update() 3390 3391 tuf = vtki.new("TubeFilter") 3392 tuf.SetNumberOfSides(12) 3393 tuf.CappingOn() 3394 tuf.SetInputData(tf.GetOutput()) 3395 if not thickness: 3396 thickness = r1 / 10 3397 tuf.SetRadius(thickness) 3398 tuf.Update() 3399 3400 super().__init__(tuf.GetOutput(), c, alpha) 3401 3402 self.phong() 3403 self.base = np.array(start_pt, dtype=float) 3404 self.top = np.array(end_pt, dtype=float) 3405 self.name = "Spring"
Build a spring of specified nr of coils
between start_pt
and end_pt
.
Arguments:
- coils : (int) number of coils
- r1 : (float) radius at start point
- r2 : (float) radius at end point
- thickness : (float) thickness of the coil section
3408class Cylinder(Mesh): 3409 """ 3410 Build a cylinder of specified height and radius. 3411 """ 3412 3413 def __init__( 3414 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3415 cap=True, res=24, c="teal3", alpha=1.0 3416 ) -> None: 3417 """ 3418 Build a cylinder of specified height and radius `r`, centered at `pos`. 3419 3420 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3421 centered at `v1` and top at `v2`. 3422 3423 Arguments: 3424 cap : (bool) 3425 enable/disable the caps of the cylinder 3426 res : (int) 3427 resolution of the cylinder sides 3428 3429 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3430 """ 3431 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3432 base = np.array(pos[0], dtype=float) 3433 top = np.array(pos[1], dtype=float) 3434 pos = (base + top) / 2 3435 height = np.linalg.norm(top - base) 3436 axis = top - base 3437 axis = utils.versor(axis) 3438 else: 3439 axis = utils.versor(axis) 3440 base = pos - axis * height / 2 3441 top = pos + axis * height / 2 3442 3443 cyl = vtki.new("CylinderSource") 3444 cyl.SetResolution(res) 3445 cyl.SetRadius(r) 3446 cyl.SetHeight(height) 3447 cyl.SetCapping(cap) 3448 cyl.Update() 3449 3450 theta = np.arccos(axis[2]) 3451 phi = np.arctan2(axis[1], axis[0]) 3452 t = vtki.vtkTransform() 3453 t.PostMultiply() 3454 t.RotateX(90) # put it along Z 3455 t.RotateY(np.rad2deg(theta)) 3456 t.RotateZ(np.rad2deg(phi)) 3457 t.Translate(pos) 3458 3459 tf = vtki.new("TransformPolyDataFilter") 3460 tf.SetInputData(cyl.GetOutput()) 3461 tf.SetTransform(t) 3462 tf.Update() 3463 3464 super().__init__(tf.GetOutput(), c, alpha) 3465 3466 self.phong() 3467 self.base = base 3468 self.top = top 3469 self.transform = LinearTransform().translate(pos) 3470 self.name = "Cylinder"
Build a cylinder of specified height and radius.
3413 def __init__( 3414 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3415 cap=True, res=24, c="teal3", alpha=1.0 3416 ) -> None: 3417 """ 3418 Build a cylinder of specified height and radius `r`, centered at `pos`. 3419 3420 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3421 centered at `v1` and top at `v2`. 3422 3423 Arguments: 3424 cap : (bool) 3425 enable/disable the caps of the cylinder 3426 res : (int) 3427 resolution of the cylinder sides 3428 3429 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3430 """ 3431 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3432 base = np.array(pos[0], dtype=float) 3433 top = np.array(pos[1], dtype=float) 3434 pos = (base + top) / 2 3435 height = np.linalg.norm(top - base) 3436 axis = top - base 3437 axis = utils.versor(axis) 3438 else: 3439 axis = utils.versor(axis) 3440 base = pos - axis * height / 2 3441 top = pos + axis * height / 2 3442 3443 cyl = vtki.new("CylinderSource") 3444 cyl.SetResolution(res) 3445 cyl.SetRadius(r) 3446 cyl.SetHeight(height) 3447 cyl.SetCapping(cap) 3448 cyl.Update() 3449 3450 theta = np.arccos(axis[2]) 3451 phi = np.arctan2(axis[1], axis[0]) 3452 t = vtki.vtkTransform() 3453 t.PostMultiply() 3454 t.RotateX(90) # put it along Z 3455 t.RotateY(np.rad2deg(theta)) 3456 t.RotateZ(np.rad2deg(phi)) 3457 t.Translate(pos) 3458 3459 tf = vtki.new("TransformPolyDataFilter") 3460 tf.SetInputData(cyl.GetOutput()) 3461 tf.SetTransform(t) 3462 tf.Update() 3463 3464 super().__init__(tf.GetOutput(), c, alpha) 3465 3466 self.phong() 3467 self.base = base 3468 self.top = top 3469 self.transform = LinearTransform().translate(pos) 3470 self.name = "Cylinder"
3473class Cone(Mesh): 3474 """Build a cone of specified radius and height.""" 3475 3476 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3477 res=48, c="green3", alpha=1.0) -> None: 3478 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3479 con = vtki.new("ConeSource") 3480 con.SetResolution(res) 3481 con.SetRadius(r) 3482 con.SetHeight(height) 3483 con.SetDirection(axis) 3484 con.Update() 3485 super().__init__(con.GetOutput(), c, alpha) 3486 self.phong() 3487 if len(pos) == 2: 3488 pos = (pos[0], pos[1], 0) 3489 self.pos(pos) 3490 v = utils.versor(axis) * height / 2 3491 self.base = pos - v 3492 self.top = pos + v 3493 self.name = "Cone"
Build a cone of specified radius and height.
3476 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3477 res=48, c="green3", alpha=1.0) -> None: 3478 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3479 con = vtki.new("ConeSource") 3480 con.SetResolution(res) 3481 con.SetRadius(r) 3482 con.SetHeight(height) 3483 con.SetDirection(axis) 3484 con.Update() 3485 super().__init__(con.GetOutput(), c, alpha) 3486 self.phong() 3487 if len(pos) == 2: 3488 pos = (pos[0], pos[1], 0) 3489 self.pos(pos) 3490 v = utils.versor(axis) * height / 2 3491 self.base = pos - v 3492 self.top = pos + v 3493 self.name = "Cone"
Build a cone of specified radius r
and height
, centered at pos
.
3496class Pyramid(Cone): 3497 """Build a pyramidal shape.""" 3498 3499 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3500 c="green3", alpha=1) -> None: 3501 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3502 super().__init__(pos, s, height, axis, 4, c, alpha) 3503 self.name = "Pyramid"
Build a pyramidal shape.
3499 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3500 c="green3", alpha=1) -> None: 3501 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3502 super().__init__(pos, s, height, axis, 4, c, alpha) 3503 self.name = "Pyramid"
Build a pyramid of specified base size s
and height
, centered at pos
.
3506class Torus(Mesh): 3507 """ 3508 Build a toroidal shape. 3509 """ 3510 3511 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3512 """ 3513 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3514 If `quad=True` a quad-mesh is generated. 3515 """ 3516 if utils.is_sequence(res): 3517 res_u, res_v = res 3518 else: 3519 res_u, res_v = 3 * res, res 3520 3521 if quads: 3522 # https://github.com/marcomusy/vedo/issues/710 3523 3524 n = res_v 3525 m = res_u 3526 3527 theta = np.linspace(0, 2.0 * np.pi, n) 3528 phi = np.linspace(0, 2.0 * np.pi, m) 3529 theta, phi = np.meshgrid(theta, phi) 3530 t = r1 + r2 * np.cos(theta) 3531 x = t * np.cos(phi) 3532 y = t * np.sin(phi) 3533 z = r2 * np.sin(theta) 3534 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3535 3536 faces = [] 3537 for j in range(m - 1): 3538 j1n = (j + 1) * n 3539 for i in range(n - 1): 3540 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3541 3542 super().__init__([pts, faces], c, alpha) 3543 3544 else: 3545 rs = vtki.new("ParametricTorus") 3546 rs.SetRingRadius(r1) 3547 rs.SetCrossSectionRadius(r2) 3548 pfs = vtki.new("ParametricFunctionSource") 3549 pfs.SetParametricFunction(rs) 3550 pfs.SetUResolution(res_u) 3551 pfs.SetVResolution(res_v) 3552 pfs.Update() 3553 3554 super().__init__(pfs.GetOutput(), c, alpha) 3555 3556 self.phong() 3557 if len(pos) == 2: 3558 pos = (pos[0], pos[1], 0) 3559 self.pos(pos) 3560 self.name = "Torus"
Build a toroidal shape.
3511 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3512 """ 3513 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3514 If `quad=True` a quad-mesh is generated. 3515 """ 3516 if utils.is_sequence(res): 3517 res_u, res_v = res 3518 else: 3519 res_u, res_v = 3 * res, res 3520 3521 if quads: 3522 # https://github.com/marcomusy/vedo/issues/710 3523 3524 n = res_v 3525 m = res_u 3526 3527 theta = np.linspace(0, 2.0 * np.pi, n) 3528 phi = np.linspace(0, 2.0 * np.pi, m) 3529 theta, phi = np.meshgrid(theta, phi) 3530 t = r1 + r2 * np.cos(theta) 3531 x = t * np.cos(phi) 3532 y = t * np.sin(phi) 3533 z = r2 * np.sin(theta) 3534 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3535 3536 faces = [] 3537 for j in range(m - 1): 3538 j1n = (j + 1) * n 3539 for i in range(n - 1): 3540 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3541 3542 super().__init__([pts, faces], c, alpha) 3543 3544 else: 3545 rs = vtki.new("ParametricTorus") 3546 rs.SetRingRadius(r1) 3547 rs.SetCrossSectionRadius(r2) 3548 pfs = vtki.new("ParametricFunctionSource") 3549 pfs.SetParametricFunction(rs) 3550 pfs.SetUResolution(res_u) 3551 pfs.SetVResolution(res_v) 3552 pfs.Update() 3553 3554 super().__init__(pfs.GetOutput(), c, alpha) 3555 3556 self.phong() 3557 if len(pos) == 2: 3558 pos = (pos[0], pos[1], 0) 3559 self.pos(pos) 3560 self.name = "Torus"
Build a torus of specified outer radius r1
internal radius r2
, centered at pos
.
If quad=True
a quad-mesh is generated.
3563class Paraboloid(Mesh): 3564 """ 3565 Build a paraboloid. 3566 """ 3567 3568 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3569 """ 3570 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3571 3572 Full volumetric expression is: 3573 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3574 3575 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3576 """ 3577 quadric = vtki.new("Quadric") 3578 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3579 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3580 # + a3*x*y + a4*y*z + a5*x*z 3581 # + a6*x + a7*y + a8*z +a9 3582 sample = vtki.new("SampleFunction") 3583 sample.SetSampleDimensions(res, res, res) 3584 sample.SetImplicitFunction(quadric) 3585 3586 contours = vtki.new("ContourFilter") 3587 contours.SetInputConnection(sample.GetOutputPort()) 3588 contours.GenerateValues(1, 0.01, 0.01) 3589 contours.Update() 3590 3591 super().__init__(contours.GetOutput(), c, alpha) 3592 self.compute_normals().phong() 3593 self.mapper.ScalarVisibilityOff() 3594 self.pos(pos) 3595 self.name = "Paraboloid"
Build a paraboloid.
3568 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3569 """ 3570 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3571 3572 Full volumetric expression is: 3573 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3574 3575 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3576 """ 3577 quadric = vtki.new("Quadric") 3578 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3579 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3580 # + a3*x*y + a4*y*z + a5*x*z 3581 # + a6*x + a7*y + a8*z +a9 3582 sample = vtki.new("SampleFunction") 3583 sample.SetSampleDimensions(res, res, res) 3584 sample.SetImplicitFunction(quadric) 3585 3586 contours = vtki.new("ContourFilter") 3587 contours.SetInputConnection(sample.GetOutputPort()) 3588 contours.GenerateValues(1, 0.01, 0.01) 3589 contours.Update() 3590 3591 super().__init__(contours.GetOutput(), c, alpha) 3592 self.compute_normals().phong() 3593 self.mapper.ScalarVisibilityOff() 3594 self.pos(pos) 3595 self.name = "Paraboloid"
Build a paraboloid of specified height and radius r
, centered at pos
.
Full volumetric expression is:
F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9
3598class Hyperboloid(Mesh): 3599 """ 3600 Build a hyperboloid. 3601 """ 3602 3603 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3604 """ 3605 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3606 3607 Full volumetric expression is: 3608 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3609 """ 3610 q = vtki.new("Quadric") 3611 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3612 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3613 # + a3*x*y + a4*y*z + a5*x*z 3614 # + a6*x + a7*y + a8*z +a9 3615 sample = vtki.new("SampleFunction") 3616 sample.SetSampleDimensions(res, res, res) 3617 sample.SetImplicitFunction(q) 3618 3619 contours = vtki.new("ContourFilter") 3620 contours.SetInputConnection(sample.GetOutputPort()) 3621 contours.GenerateValues(1, value, value) 3622 contours.Update() 3623 3624 super().__init__(contours.GetOutput(), c, alpha) 3625 self.compute_normals().phong() 3626 self.mapper.ScalarVisibilityOff() 3627 self.pos(pos) 3628 self.name = "Hyperboloid"
Build a hyperboloid.
3603 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3604 """ 3605 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3606 3607 Full volumetric expression is: 3608 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3609 """ 3610 q = vtki.new("Quadric") 3611 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3612 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3613 # + a3*x*y + a4*y*z + a5*x*z 3614 # + a6*x + a7*y + a8*z +a9 3615 sample = vtki.new("SampleFunction") 3616 sample.SetSampleDimensions(res, res, res) 3617 sample.SetImplicitFunction(q) 3618 3619 contours = vtki.new("ContourFilter") 3620 contours.SetInputConnection(sample.GetOutputPort()) 3621 contours.GenerateValues(1, value, value) 3622 contours.Update() 3623 3624 super().__init__(contours.GetOutput(), c, alpha) 3625 self.compute_normals().phong() 3626 self.mapper.ScalarVisibilityOff() 3627 self.pos(pos) 3628 self.name = "Hyperboloid"
Build a hyperboloid of specified aperture a2
and height
, centered at pos
.
Full volumetric expression is:
F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9
4393class TextBase: 4394 "Base class." 4395 4396 def __init__(self): 4397 "Do not instantiate this base class." 4398 4399 self.rendered_at = set() 4400 self.properties = None 4401 4402 self.name = "Text" 4403 self.filename = "" 4404 self.time = 0 4405 self.info = {} 4406 4407 if isinstance(settings.default_font, int): 4408 lfonts = list(settings.font_parameters.keys()) 4409 font = settings.default_font % len(lfonts) 4410 self.fontname = lfonts[font] 4411 else: 4412 self.fontname = settings.default_font 4413 4414 def angle(self, value: float): 4415 """Orientation angle in degrees""" 4416 self.properties.SetOrientation(value) 4417 return self 4418 4419 def line_spacing(self, value: float): 4420 """Set the extra spacing between lines 4421 expressed as a text height multiplicative factor.""" 4422 self.properties.SetLineSpacing(value) 4423 return self 4424 4425 def line_offset(self, value: float): 4426 """Set/Get the vertical offset (measured in pixels).""" 4427 self.properties.SetLineOffset(value) 4428 return self 4429 4430 def bold(self, value=True): 4431 """Set bold face""" 4432 self.properties.SetBold(value) 4433 return self 4434 4435 def italic(self, value=True): 4436 """Set italic face""" 4437 self.properties.SetItalic(value) 4438 return self 4439 4440 def shadow(self, offset=(1, -1)): 4441 """Text shadowing. Set to `None` to disable it.""" 4442 if offset is None: 4443 self.properties.ShadowOff() 4444 else: 4445 self.properties.ShadowOn() 4446 self.properties.SetShadowOffset(offset) 4447 return self 4448 4449 def color(self, c=None): 4450 """Set the text color""" 4451 if c is None: 4452 return get_color(self.properties.GetColor()) 4453 self.properties.SetColor(get_color(c)) 4454 return self 4455 4456 def c(self, color=None): 4457 """Set the text color""" 4458 if color is None: 4459 return get_color(self.properties.GetColor()) 4460 return self.color(color) 4461 4462 def alpha(self, value: float): 4463 """Set the text opacity""" 4464 self.properties.SetBackgroundOpacity(value) 4465 return self 4466 4467 def background(self, color="k9", alpha=1.0): 4468 """Text background. Set to `None` to disable it.""" 4469 bg = get_color(color) 4470 if color is None: 4471 self.properties.SetBackgroundOpacity(0) 4472 else: 4473 self.properties.SetBackgroundColor(bg) 4474 if alpha: 4475 self.properties.SetBackgroundOpacity(alpha) 4476 return self 4477 4478 def frame(self, color="k1", lw=2): 4479 """Border color and width""" 4480 if color is None: 4481 self.properties.FrameOff() 4482 else: 4483 c = get_color(color) 4484 self.properties.FrameOn() 4485 self.properties.SetFrameColor(c) 4486 self.properties.SetFrameWidth(lw) 4487 return self 4488 4489 def font(self, font: str): 4490 """Text font face""" 4491 if isinstance(font, int): 4492 lfonts = list(settings.font_parameters.keys()) 4493 n = font % len(lfonts) 4494 font = lfonts[n] 4495 self.fontname = font 4496 4497 if not font: # use default font 4498 font = self.fontname 4499 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4500 elif font.startswith("https"): # user passed URL link, make it a path 4501 fpath = vedo.file_io.download(font, verbose=False, force=False) 4502 elif font.endswith(".ttf"): # user passing a local path to font file 4503 fpath = font 4504 else: # user passing name of preset font 4505 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4506 4507 if font == "Courier": self.properties.SetFontFamilyToCourier() 4508 elif font == "Times": self.properties.SetFontFamilyToTimes() 4509 elif font == "Arial": self.properties.SetFontFamilyToArial() 4510 else: 4511 fpath = utils.get_font_path(font) 4512 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4513 self.properties.SetFontFile(fpath) 4514 self.fontname = font # io.tonumpy() uses it 4515 4516 return self 4517 4518 def on(self): 4519 """Make text visible""" 4520 self.actor.SetVisibility(True) 4521 return self 4522 4523 def off(self): 4524 """Make text invisible""" 4525 self.actor.SetVisibility(False) 4526 return self
Base class.
4396 def __init__(self): 4397 "Do not instantiate this base class." 4398 4399 self.rendered_at = set() 4400 self.properties = None 4401 4402 self.name = "Text" 4403 self.filename = "" 4404 self.time = 0 4405 self.info = {} 4406 4407 if isinstance(settings.default_font, int): 4408 lfonts = list(settings.font_parameters.keys()) 4409 font = settings.default_font % len(lfonts) 4410 self.fontname = lfonts[font] 4411 else: 4412 self.fontname = settings.default_font
Do not instantiate this base class.
4414 def angle(self, value: float): 4415 """Orientation angle in degrees""" 4416 self.properties.SetOrientation(value) 4417 return self
Orientation angle in degrees
4419 def line_spacing(self, value: float): 4420 """Set the extra spacing between lines 4421 expressed as a text height multiplicative factor.""" 4422 self.properties.SetLineSpacing(value) 4423 return self
Set the extra spacing between lines expressed as a text height multiplicative factor.
4425 def line_offset(self, value: float): 4426 """Set/Get the vertical offset (measured in pixels).""" 4427 self.properties.SetLineOffset(value) 4428 return self
Set/Get the vertical offset (measured in pixels).
4430 def bold(self, value=True): 4431 """Set bold face""" 4432 self.properties.SetBold(value) 4433 return self
Set bold face
4435 def italic(self, value=True): 4436 """Set italic face""" 4437 self.properties.SetItalic(value) 4438 return self
Set italic face
4440 def shadow(self, offset=(1, -1)): 4441 """Text shadowing. Set to `None` to disable it.""" 4442 if offset is None: 4443 self.properties.ShadowOff() 4444 else: 4445 self.properties.ShadowOn() 4446 self.properties.SetShadowOffset(offset) 4447 return self
Text shadowing. Set to None
to disable it.
4449 def color(self, c=None): 4450 """Set the text color""" 4451 if c is None: 4452 return get_color(self.properties.GetColor()) 4453 self.properties.SetColor(get_color(c)) 4454 return self
Set the text color
4456 def c(self, color=None): 4457 """Set the text color""" 4458 if color is None: 4459 return get_color(self.properties.GetColor()) 4460 return self.color(color)
Set the text color
4462 def alpha(self, value: float): 4463 """Set the text opacity""" 4464 self.properties.SetBackgroundOpacity(value) 4465 return self
Set the text opacity
4467 def background(self, color="k9", alpha=1.0): 4468 """Text background. Set to `None` to disable it.""" 4469 bg = get_color(color) 4470 if color is None: 4471 self.properties.SetBackgroundOpacity(0) 4472 else: 4473 self.properties.SetBackgroundColor(bg) 4474 if alpha: 4475 self.properties.SetBackgroundOpacity(alpha) 4476 return self
Text background. Set to None
to disable it.
4478 def frame(self, color="k1", lw=2): 4479 """Border color and width""" 4480 if color is None: 4481 self.properties.FrameOff() 4482 else: 4483 c = get_color(color) 4484 self.properties.FrameOn() 4485 self.properties.SetFrameColor(c) 4486 self.properties.SetFrameWidth(lw) 4487 return self
Border color and width
4489 def font(self, font: str): 4490 """Text font face""" 4491 if isinstance(font, int): 4492 lfonts = list(settings.font_parameters.keys()) 4493 n = font % len(lfonts) 4494 font = lfonts[n] 4495 self.fontname = font 4496 4497 if not font: # use default font 4498 font = self.fontname 4499 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4500 elif font.startswith("https"): # user passed URL link, make it a path 4501 fpath = vedo.file_io.download(font, verbose=False, force=False) 4502 elif font.endswith(".ttf"): # user passing a local path to font file 4503 fpath = font 4504 else: # user passing name of preset font 4505 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4506 4507 if font == "Courier": self.properties.SetFontFamilyToCourier() 4508 elif font == "Times": self.properties.SetFontFamilyToTimes() 4509 elif font == "Arial": self.properties.SetFontFamilyToArial() 4510 else: 4511 fpath = utils.get_font_path(font) 4512 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4513 self.properties.SetFontFile(fpath) 4514 self.fontname = font # io.tonumpy() uses it 4515 4516 return self
Text font face
4054class Text3D(Mesh): 4055 """ 4056 Generate a 3D polygonal Mesh to represent a text string. 4057 """ 4058 4059 def __init__( 4060 self, 4061 txt, 4062 pos=(0, 0, 0), 4063 s=1.0, 4064 font="", 4065 hspacing=1.15, 4066 vspacing=2.15, 4067 depth=0.0, 4068 italic=False, 4069 justify="bottom-left", 4070 literal=False, 4071 c=None, 4072 alpha=1.0, 4073 ) -> None: 4074 """ 4075 Generate a 3D polygonal `Mesh` representing a text string. 4076 4077 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4078 Most Latex symbols are also supported. 4079 4080 Symbols `~ ^ _` are reserved modifiers: 4081 - use ~ to add a short space, 1/4 of the default empty space, 4082 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4083 4084 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4085 4086 More fonts at: https://vedo.embl.es/fonts/ 4087 4088 Arguments: 4089 pos : (list) 4090 position coordinates in 3D space 4091 s : (float) 4092 vertical size of the text (as scaling factor) 4093 depth : (float) 4094 text thickness (along z) 4095 italic : (bool), float 4096 italic font type (can be a signed float too) 4097 justify : (str) 4098 text justification as centering of the bounding box 4099 (bottom-left, bottom-right, top-left, top-right, centered) 4100 font : (str, int) 4101 some of the available 3D-polygonized fonts are: 4102 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4103 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4104 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4105 4106 Check for more at https://vedo.embl.es/fonts/ 4107 4108 Or type in your terminal `vedo --run fonts`. 4109 4110 Default is Normografo, which can be changed using `settings.default_font`. 4111 4112 hspacing : (float) 4113 horizontal spacing of the font 4114 vspacing : (float) 4115 vertical spacing of the font for multiple lines text 4116 literal : (bool) 4117 if set to True will ignore modifiers like _ or ^ 4118 4119 Examples: 4120 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4121 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4122 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4123 4124 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4125 4126 .. note:: Type `vedo -r fonts` for a demo. 4127 """ 4128 if len(pos) == 2: 4129 pos = (pos[0], pos[1], 0) 4130 4131 if c is None: # automatic black or white 4132 pli = vedo.plotter_instance 4133 if pli and pli.renderer: 4134 c = (0.9, 0.9, 0.9) 4135 if pli.renderer.GetGradientBackground(): 4136 bgcol = pli.renderer.GetBackground2() 4137 else: 4138 bgcol = pli.renderer.GetBackground() 4139 if np.sum(bgcol) > 1.5: 4140 c = (0.1, 0.1, 0.1) 4141 else: 4142 c = (0.6, 0.6, 0.6) 4143 4144 tpoly = self._get_text3d_poly( 4145 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4146 ) 4147 4148 super().__init__(tpoly, c, alpha) 4149 4150 self.pos(pos) 4151 self.lighting("off") 4152 4153 self.actor.PickableOff() 4154 self.actor.DragableOff() 4155 self.init_scale = s 4156 self.name = "Text3D" 4157 self.txt = txt 4158 self.justify = justify 4159 4160 def text( 4161 self, 4162 txt=None, 4163 s=1, 4164 font="", 4165 hspacing=1.15, 4166 vspacing=2.15, 4167 depth=0, 4168 italic=False, 4169 justify="", 4170 literal=False, 4171 ) -> "Text3D": 4172 """ 4173 Update the text and some of its properties. 4174 4175 Check [available fonts here](https://vedo.embl.es/fonts). 4176 """ 4177 if txt is None: 4178 return self.txt 4179 if not justify: 4180 justify = self.justify 4181 4182 poly = self._get_text3d_poly( 4183 txt, self.init_scale * s, font, hspacing, vspacing, 4184 depth, italic, justify, literal 4185 ) 4186 4187 # apply the current transformation to the new polydata 4188 tf = vtki.new("TransformPolyDataFilter") 4189 tf.SetInputData(poly) 4190 tf.SetTransform(self.transform.T) 4191 tf.Update() 4192 tpoly = tf.GetOutput() 4193 4194 self._update(tpoly) 4195 self.txt = txt 4196 return self 4197 4198 def _get_text3d_poly( 4199 self, 4200 txt, 4201 s=1, 4202 font="", 4203 hspacing=1.15, 4204 vspacing=2.15, 4205 depth=0, 4206 italic=False, 4207 justify="bottom-left", 4208 literal=False, 4209 ) -> vtki.vtkPolyData: 4210 if not font: 4211 font = settings.default_font 4212 4213 txt = str(txt) 4214 4215 if font == "VTK": ####################################### 4216 vtt = vtki.new("VectorText") 4217 vtt.SetText(txt) 4218 vtt.Update() 4219 tpoly = vtt.GetOutput() 4220 4221 else: ################################################### 4222 4223 stxt = set(txt) # check here if null or only spaces 4224 if not txt or (len(stxt) == 1 and " " in stxt): 4225 return vtki.vtkPolyData() 4226 4227 if italic is True: 4228 italic = 1 4229 4230 if isinstance(font, int): 4231 lfonts = list(settings.font_parameters.keys()) 4232 font = font % len(lfonts) 4233 font = lfonts[font] 4234 4235 if font not in settings.font_parameters.keys(): 4236 fpars = settings.font_parameters["Normografo"] 4237 else: 4238 fpars = settings.font_parameters[font] 4239 4240 # ad hoc adjustments 4241 mono = fpars["mono"] 4242 lspacing = fpars["lspacing"] 4243 hspacing *= fpars["hspacing"] 4244 fscale = fpars["fscale"] 4245 dotsep = fpars["dotsep"] 4246 4247 # replacements 4248 if ":" in txt: 4249 for r in _reps: 4250 txt = txt.replace(r[0], r[1]) 4251 4252 if not literal: 4253 reps2 = [ 4254 (r"\_", "┭"), # trick to protect ~ _ and ^ chars 4255 (r"\^", "┮"), # 4256 (r"\~", "┯"), # 4257 ("**", "^"), # order matters 4258 ("e+0", dotsep + "10^"), 4259 ("e-0", dotsep + "10^-"), 4260 ("E+0", dotsep + "10^"), 4261 ("E-0", dotsep + "10^-"), 4262 ("e+", dotsep + "10^"), 4263 ("e-", dotsep + "10^-"), 4264 ("E+", dotsep + "10^"), 4265 ("E-", dotsep + "10^-"), 4266 ] 4267 for r in reps2: 4268 txt = txt.replace(r[0], r[1]) 4269 4270 xmax, ymax, yshift, scale = 0.0, 0.0, 0.0, 1.0 4271 save_xmax = 0.0 4272 4273 notfounds = set() 4274 polyletters = [] 4275 ntxt = len(txt) 4276 for i, t in enumerate(txt): 4277 ########## 4278 if t == "┭": 4279 t = "_" 4280 elif t == "┮": 4281 t = "^" 4282 elif t == "┯": 4283 t = "~" 4284 elif t == "^" and not literal: 4285 if yshift < 0: 4286 xmax = save_xmax 4287 yshift = 0.9 * fscale 4288 scale = 0.5 4289 continue 4290 elif t == "_" and not literal: 4291 if yshift > 0: 4292 xmax = save_xmax 4293 yshift = -0.3 * fscale 4294 scale = 0.5 4295 continue 4296 elif (t in (" ", "\\n")) and yshift: 4297 yshift = 0.0 4298 scale = 1.0 4299 save_xmax = xmax 4300 if t == " ": 4301 continue 4302 elif t == "~": 4303 if i < ntxt - 1 and txt[i + 1] == "_": 4304 continue 4305 xmax += hspacing * scale * fscale / 4 4306 continue 4307 4308 ############ 4309 if t == " ": 4310 xmax += hspacing * scale * fscale 4311 4312 elif t == "\n": 4313 xmax = 0.0 4314 save_xmax = 0.0 4315 ymax -= vspacing 4316 4317 else: 4318 poly = _get_font_letter(font, t) 4319 if not poly: 4320 notfounds.add(t) 4321 xmax += hspacing * scale * fscale 4322 continue 4323 4324 if poly.GetNumberOfPoints() == 0: 4325 continue 4326 4327 tr = vtki.vtkTransform() 4328 tr.Translate(xmax, ymax + yshift, 0) 4329 pscale = scale * fscale / 1000 4330 tr.Scale(pscale, pscale, pscale) 4331 if italic: 4332 tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 4333 tf = vtki.new("TransformPolyDataFilter") 4334 tf.SetInputData(poly) 4335 tf.SetTransform(tr) 4336 tf.Update() 4337 poly = tf.GetOutput() 4338 polyletters.append(poly) 4339 4340 bx = poly.GetBounds() 4341 if mono: 4342 xmax += hspacing * scale * fscale 4343 else: 4344 xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing 4345 if yshift == 0: 4346 save_xmax = xmax 4347 4348 if len(polyletters) == 1: 4349 tpoly = polyletters[0] 4350 else: 4351 polyapp = vtki.new("AppendPolyData") 4352 for polyd in polyletters: 4353 polyapp.AddInputData(polyd) 4354 polyapp.Update() 4355 tpoly = polyapp.GetOutput() 4356 4357 if notfounds: 4358 wmsg = f"unavailable characters in font name '{font}': {notfounds}." 4359 wmsg += '\nType "vedo -r fonts" for a demo.' 4360 vedo.logger.warning(wmsg) 4361 4362 bb = tpoly.GetBounds() 4363 dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s 4364 shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2 4365 if "bottom" in justify: shift += np.array([ 0, dy, 0.]) 4366 if "top" in justify: shift += np.array([ 0,-dy, 0.]) 4367 if "left" in justify: shift += np.array([ dx, 0, 0.]) 4368 if "right" in justify: shift += np.array([-dx, 0, 0.]) 4369 4370 if tpoly.GetNumberOfPoints(): 4371 t = vtki.vtkTransform() 4372 t.PostMultiply() 4373 t.Scale(s, s, s) 4374 t.Translate(shift) 4375 tf = vtki.new("TransformPolyDataFilter") 4376 tf.SetInputData(tpoly) 4377 tf.SetTransform(t) 4378 tf.Update() 4379 tpoly = tf.GetOutput() 4380 4381 if depth: 4382 extrude = vtki.new("LinearExtrusionFilter") 4383 extrude.SetInputData(tpoly) 4384 extrude.SetExtrusionTypeToVectorExtrusion() 4385 extrude.SetVector(0, 0, 1) 4386 extrude.SetScaleFactor(depth * dy) 4387 extrude.Update() 4388 tpoly = extrude.GetOutput() 4389 4390 return tpoly
Generate a 3D polygonal Mesh to represent a text string.
4059 def __init__( 4060 self, 4061 txt, 4062 pos=(0, 0, 0), 4063 s=1.0, 4064 font="", 4065 hspacing=1.15, 4066 vspacing=2.15, 4067 depth=0.0, 4068 italic=False, 4069 justify="bottom-left", 4070 literal=False, 4071 c=None, 4072 alpha=1.0, 4073 ) -> None: 4074 """ 4075 Generate a 3D polygonal `Mesh` representing a text string. 4076 4077 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4078 Most Latex symbols are also supported. 4079 4080 Symbols `~ ^ _` are reserved modifiers: 4081 - use ~ to add a short space, 1/4 of the default empty space, 4082 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4083 4084 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4085 4086 More fonts at: https://vedo.embl.es/fonts/ 4087 4088 Arguments: 4089 pos : (list) 4090 position coordinates in 3D space 4091 s : (float) 4092 vertical size of the text (as scaling factor) 4093 depth : (float) 4094 text thickness (along z) 4095 italic : (bool), float 4096 italic font type (can be a signed float too) 4097 justify : (str) 4098 text justification as centering of the bounding box 4099 (bottom-left, bottom-right, top-left, top-right, centered) 4100 font : (str, int) 4101 some of the available 3D-polygonized fonts are: 4102 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4103 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4104 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4105 4106 Check for more at https://vedo.embl.es/fonts/ 4107 4108 Or type in your terminal `vedo --run fonts`. 4109 4110 Default is Normografo, which can be changed using `settings.default_font`. 4111 4112 hspacing : (float) 4113 horizontal spacing of the font 4114 vspacing : (float) 4115 vertical spacing of the font for multiple lines text 4116 literal : (bool) 4117 if set to True will ignore modifiers like _ or ^ 4118 4119 Examples: 4120 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4121 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4122 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4123 4124 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4125 4126 .. note:: Type `vedo -r fonts` for a demo. 4127 """ 4128 if len(pos) == 2: 4129 pos = (pos[0], pos[1], 0) 4130 4131 if c is None: # automatic black or white 4132 pli = vedo.plotter_instance 4133 if pli and pli.renderer: 4134 c = (0.9, 0.9, 0.9) 4135 if pli.renderer.GetGradientBackground(): 4136 bgcol = pli.renderer.GetBackground2() 4137 else: 4138 bgcol = pli.renderer.GetBackground() 4139 if np.sum(bgcol) > 1.5: 4140 c = (0.1, 0.1, 0.1) 4141 else: 4142 c = (0.6, 0.6, 0.6) 4143 4144 tpoly = self._get_text3d_poly( 4145 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4146 ) 4147 4148 super().__init__(tpoly, c, alpha) 4149 4150 self.pos(pos) 4151 self.lighting("off") 4152 4153 self.actor.PickableOff() 4154 self.actor.DragableOff() 4155 self.init_scale = s 4156 self.name = "Text3D" 4157 self.txt = txt 4158 self.justify = justify
Generate a 3D polygonal Mesh
representing a text string.
Can render strings like 3.7 10^9
or H_2 O
with subscripts and superscripts.
Most Latex symbols are also supported.
Symbols ~ ^ _
are reserved modifiers:
- use ~ to add a short space, 1/4 of the default empty space,
- use ^ and _ to start up/sub scripting, a space terminates their effect.
Monospaced fonts are: Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino
.
More fonts at: https://vedo.embl.es/fonts/
Arguments:
- pos : (list) position coordinates in 3D space
- s : (float) vertical size of the text (as scaling factor)
- depth : (float) text thickness (along z)
- italic : (bool), float italic font type (can be a signed float too)
- justify : (str) text justification as centering of the bounding box (bottom-left, bottom-right, top-left, top-right, centered)
font : (str, int) some of the available 3D-polygonized fonts are: Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, Capsmall, Cartoons123, Vega, Justino, Spears, Meson.
Check for more at https://vedo.embl.es/fonts/
Or type in your terminal
vedo --run fonts
.Default is Normografo, which can be changed using
settings.default_font
.- hspacing : (float) horizontal spacing of the font
- vspacing : (float) vertical spacing of the font for multiple lines text
- literal : (bool) if set to True will ignore modifiers like _ or ^
Examples:
Type vedo -r fonts
for a demo.
4160 def text( 4161 self, 4162 txt=None, 4163 s=1, 4164 font="", 4165 hspacing=1.15, 4166 vspacing=2.15, 4167 depth=0, 4168 italic=False, 4169 justify="", 4170 literal=False, 4171 ) -> "Text3D": 4172 """ 4173 Update the text and some of its properties. 4174 4175 Check [available fonts here](https://vedo.embl.es/fonts). 4176 """ 4177 if txt is None: 4178 return self.txt 4179 if not justify: 4180 justify = self.justify 4181 4182 poly = self._get_text3d_poly( 4183 txt, self.init_scale * s, font, hspacing, vspacing, 4184 depth, italic, justify, literal 4185 ) 4186 4187 # apply the current transformation to the new polydata 4188 tf = vtki.new("TransformPolyDataFilter") 4189 tf.SetInputData(poly) 4190 tf.SetTransform(self.transform.T) 4191 tf.Update() 4192 tpoly = tf.GetOutput() 4193 4194 self._update(tpoly) 4195 self.txt = txt 4196 return self
Update the text and some of its properties.
Check available fonts here.
4528class Text2D(TextBase, vedo.visual.Actor2D): 4529 """ 4530 Create a 2D text object. 4531 """ 4532 def __init__( 4533 self, 4534 txt="", 4535 pos="top-left", 4536 s=1.0, 4537 bg=None, 4538 font="", 4539 justify="", 4540 bold=False, 4541 italic=False, 4542 c=None, 4543 alpha=0.5, 4544 ) -> None: 4545 """ 4546 Create a 2D text object. 4547 4548 All properties of the text, and the text itself, can be changed after creation 4549 (which is especially useful in loops). 4550 4551 Arguments: 4552 pos : (str) 4553 text is placed in one of the 8 positions: 4554 - bottom-left 4555 - bottom-right 4556 - top-left 4557 - top-right 4558 - bottom-middle 4559 - middle-right 4560 - middle-left 4561 - top-middle 4562 4563 If a pair (x,y) is passed as input the 2D text is place at that 4564 position in the coordinate system of the 2D screen (with the 4565 origin sitting at the bottom left). 4566 4567 s : (float) 4568 size of text 4569 bg : (color) 4570 background color 4571 alpha : (float) 4572 background opacity 4573 justify : (str) 4574 text justification 4575 4576 font : (str) 4577 built-in available fonts are: 4578 - Antares 4579 - Arial 4580 - Bongas 4581 - Calco 4582 - Comae 4583 - ComicMono 4584 - Courier 4585 - Glasgo 4586 - Kanopus 4587 - LogoType 4588 - Normografo 4589 - Quikhand 4590 - SmartCouric 4591 - Theemim 4592 - Times 4593 - VictorMono 4594 - More fonts at: https://vedo.embl.es/fonts/ 4595 4596 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4597 4598 Examples: 4599 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4600 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4601 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4602 4603 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4604 """ 4605 super().__init__() 4606 self.name = "Text2D" 4607 4608 self.mapper = vtki.new("TextMapper") 4609 self.SetMapper(self.mapper) 4610 4611 self.properties = self.mapper.GetTextProperty() 4612 self.actor = self 4613 self.actor.retrieve_object = weak_ref_to(self) 4614 4615 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4616 4617 # automatic black or white 4618 if c is None: 4619 c = (0.1, 0.1, 0.1) 4620 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4621 if vedo.plotter_instance.renderer.GetGradientBackground(): 4622 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4623 else: 4624 bgcol = vedo.plotter_instance.renderer.GetBackground() 4625 c = (0.9, 0.9, 0.9) 4626 if np.sum(bgcol) > 1.5: 4627 c = (0.1, 0.1, 0.1) 4628 4629 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4630 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4631 self.PickableOff() 4632 4633 def pos(self, pos="top-left", justify=""): 4634 """ 4635 Set position of the text to draw. Keyword `pos` can be a string 4636 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4637 """ 4638 ajustify = "top-left" # autojustify 4639 if isinstance(pos, str): # corners 4640 ajustify = pos 4641 if "top" in pos: 4642 if "left" in pos: 4643 pos = (0.008, 0.994) 4644 elif "right" in pos: 4645 pos = (0.994, 0.994) 4646 elif "mid" in pos or "cent" in pos: 4647 pos = (0.5, 0.994) 4648 elif "bottom" in pos: 4649 if "left" in pos: 4650 pos = (0.008, 0.008) 4651 elif "right" in pos: 4652 pos = (0.994, 0.008) 4653 elif "mid" in pos or "cent" in pos: 4654 pos = (0.5, 0.008) 4655 elif "mid" in pos or "cent" in pos: 4656 if "left" in pos: 4657 pos = (0.008, 0.5) 4658 elif "right" in pos: 4659 pos = (0.994, 0.5) 4660 else: 4661 pos = (0.5, 0.5) 4662 4663 else: 4664 vedo.logger.warning(f"cannot understand text position {pos}") 4665 pos = (0.008, 0.994) 4666 ajustify = "top-left" 4667 4668 elif len(pos) != 2: 4669 vedo.logger.error("pos must be of length 2 or integer value or string") 4670 raise RuntimeError() 4671 4672 if not justify: 4673 justify = ajustify 4674 4675 self.properties.SetJustificationToLeft() 4676 if "top" in justify: 4677 self.properties.SetVerticalJustificationToTop() 4678 if "bottom" in justify: 4679 self.properties.SetVerticalJustificationToBottom() 4680 if "cent" in justify or "mid" in justify: 4681 self.properties.SetJustificationToCentered() 4682 if "left" in justify: 4683 self.properties.SetJustificationToLeft() 4684 if "right" in justify: 4685 self.properties.SetJustificationToRight() 4686 4687 self.SetPosition(pos) 4688 return self 4689 4690 def text(self, txt=None): 4691 """Set/get the input text string.""" 4692 if txt is None: 4693 return self.mapper.GetInput() 4694 4695 if ":" in txt: 4696 for r in _reps: 4697 txt = txt.replace(r[0], r[1]) 4698 else: 4699 txt = str(txt) 4700 4701 self.mapper.SetInput(txt) 4702 return self 4703 4704 def size(self, s): 4705 """Set the font size.""" 4706 self.properties.SetFontSize(int(s * 22.5)) 4707 return self
Create a 2D text object.
4532 def __init__( 4533 self, 4534 txt="", 4535 pos="top-left", 4536 s=1.0, 4537 bg=None, 4538 font="", 4539 justify="", 4540 bold=False, 4541 italic=False, 4542 c=None, 4543 alpha=0.5, 4544 ) -> None: 4545 """ 4546 Create a 2D text object. 4547 4548 All properties of the text, and the text itself, can be changed after creation 4549 (which is especially useful in loops). 4550 4551 Arguments: 4552 pos : (str) 4553 text is placed in one of the 8 positions: 4554 - bottom-left 4555 - bottom-right 4556 - top-left 4557 - top-right 4558 - bottom-middle 4559 - middle-right 4560 - middle-left 4561 - top-middle 4562 4563 If a pair (x,y) is passed as input the 2D text is place at that 4564 position in the coordinate system of the 2D screen (with the 4565 origin sitting at the bottom left). 4566 4567 s : (float) 4568 size of text 4569 bg : (color) 4570 background color 4571 alpha : (float) 4572 background opacity 4573 justify : (str) 4574 text justification 4575 4576 font : (str) 4577 built-in available fonts are: 4578 - Antares 4579 - Arial 4580 - Bongas 4581 - Calco 4582 - Comae 4583 - ComicMono 4584 - Courier 4585 - Glasgo 4586 - Kanopus 4587 - LogoType 4588 - Normografo 4589 - Quikhand 4590 - SmartCouric 4591 - Theemim 4592 - Times 4593 - VictorMono 4594 - More fonts at: https://vedo.embl.es/fonts/ 4595 4596 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4597 4598 Examples: 4599 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4600 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4601 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4602 4603 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4604 """ 4605 super().__init__() 4606 self.name = "Text2D" 4607 4608 self.mapper = vtki.new("TextMapper") 4609 self.SetMapper(self.mapper) 4610 4611 self.properties = self.mapper.GetTextProperty() 4612 self.actor = self 4613 self.actor.retrieve_object = weak_ref_to(self) 4614 4615 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4616 4617 # automatic black or white 4618 if c is None: 4619 c = (0.1, 0.1, 0.1) 4620 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4621 if vedo.plotter_instance.renderer.GetGradientBackground(): 4622 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4623 else: 4624 bgcol = vedo.plotter_instance.renderer.GetBackground() 4625 c = (0.9, 0.9, 0.9) 4626 if np.sum(bgcol) > 1.5: 4627 c = (0.1, 0.1, 0.1) 4628 4629 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4630 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4631 self.PickableOff()
Create a 2D text object.
All properties of the text, and the text itself, can be changed after creation (which is especially useful in loops).
Arguments:
pos : (str) text is placed in one of the 8 positions:
- bottom-left
- bottom-right
- top-left
- top-right
- bottom-middle
- middle-right
- middle-left
- top-middle
If a pair (x,y) is passed as input the 2D text is place at that position in the coordinate system of the 2D screen (with the origin sitting at the bottom left).
- s : (float) size of text
- bg : (color) background color
- alpha : (float) background opacity
- justify : (str) text justification
font : (str) built-in available fonts are:
- Antares
- Arial
- Bongas
- Calco
- Comae
- ComicMono
- Courier
- Glasgo
- Kanopus
- LogoType
- Normografo
- Quikhand
- SmartCouric
- Theemim
- Times
- VictorMono
- More fonts at: https://vedo.embl.es/fonts/
A path to a
.otf
or.ttf
font-file can also be supplied as input.
Examples:
557 @property 558 def mapper(self): 559 """Get the internal vtkMapper.""" 560 return self.GetMapper()
Get the internal vtkMapper.
4633 def pos(self, pos="top-left", justify=""): 4634 """ 4635 Set position of the text to draw. Keyword `pos` can be a string 4636 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4637 """ 4638 ajustify = "top-left" # autojustify 4639 if isinstance(pos, str): # corners 4640 ajustify = pos 4641 if "top" in pos: 4642 if "left" in pos: 4643 pos = (0.008, 0.994) 4644 elif "right" in pos: 4645 pos = (0.994, 0.994) 4646 elif "mid" in pos or "cent" in pos: 4647 pos = (0.5, 0.994) 4648 elif "bottom" in pos: 4649 if "left" in pos: 4650 pos = (0.008, 0.008) 4651 elif "right" in pos: 4652 pos = (0.994, 0.008) 4653 elif "mid" in pos or "cent" in pos: 4654 pos = (0.5, 0.008) 4655 elif "mid" in pos or "cent" in pos: 4656 if "left" in pos: 4657 pos = (0.008, 0.5) 4658 elif "right" in pos: 4659 pos = (0.994, 0.5) 4660 else: 4661 pos = (0.5, 0.5) 4662 4663 else: 4664 vedo.logger.warning(f"cannot understand text position {pos}") 4665 pos = (0.008, 0.994) 4666 ajustify = "top-left" 4667 4668 elif len(pos) != 2: 4669 vedo.logger.error("pos must be of length 2 or integer value or string") 4670 raise RuntimeError() 4671 4672 if not justify: 4673 justify = ajustify 4674 4675 self.properties.SetJustificationToLeft() 4676 if "top" in justify: 4677 self.properties.SetVerticalJustificationToTop() 4678 if "bottom" in justify: 4679 self.properties.SetVerticalJustificationToBottom() 4680 if "cent" in justify or "mid" in justify: 4681 self.properties.SetJustificationToCentered() 4682 if "left" in justify: 4683 self.properties.SetJustificationToLeft() 4684 if "right" in justify: 4685 self.properties.SetJustificationToRight() 4686 4687 self.SetPosition(pos) 4688 return self
Set position of the text to draw. Keyword pos
can be a string
or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4690 def text(self, txt=None): 4691 """Set/get the input text string.""" 4692 if txt is None: 4693 return self.mapper.GetInput() 4694 4695 if ":" in txt: 4696 for r in _reps: 4697 txt = txt.replace(r[0], r[1]) 4698 else: 4699 txt = str(txt) 4700 4701 self.mapper.SetInput(txt) 4702 return self
Set/get the input text string.
4710class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation): 4711 # PROBABLY USELESS given that Text2D does pretty much the same ... 4712 """ 4713 Annotate the window corner with 2D text. 4714 4715 See `Text2D` description as the basic functionality is very similar. 4716 4717 The added value of this class is the possibility to manage with one single 4718 object the all corner annotations (instead of creating 4 `Text2D` instances). 4719 4720 Examples: 4721 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 4722 """ 4723 4724 def __init__(self, c=None) -> None: 4725 4726 super().__init__() 4727 4728 self.properties = self.GetTextProperty() 4729 4730 # automatic black or white 4731 if c is None: 4732 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4733 c = (0.9, 0.9, 0.9) 4734 if vedo.plotter_instance.renderer.GetGradientBackground(): 4735 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4736 else: 4737 bgcol = vedo.plotter_instance.renderer.GetBackground() 4738 if np.sum(bgcol) > 1.5: 4739 c = (0.1, 0.1, 0.1) 4740 else: 4741 c = (0.5, 0.5, 0.5) 4742 4743 self.SetNonlinearFontScaleFactor(1 / 2.75) 4744 self.PickableOff() 4745 self.properties.SetColor(get_color(c)) 4746 self.properties.SetBold(False) 4747 self.properties.SetItalic(False) 4748 4749 def size(self, s:float, linear=False) -> "CornerAnnotation": 4750 """ 4751 The font size is calculated as the largest possible value such that the annotations 4752 for the given viewport do not overlap. 4753 4754 This font size can be scaled non-linearly with the viewport size, to maintain an 4755 acceptable readable size at larger viewport sizes, without being too big. 4756 `f' = linearScale * pow(f,nonlinearScale)` 4757 """ 4758 if linear: 4759 self.SetLinearFontScaleFactor(s * 5.5) 4760 else: 4761 self.SetNonlinearFontScaleFactor(s / 2.75) 4762 return self 4763 4764 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4765 """Set text at the assigned position""" 4766 4767 if isinstance(pos, str): # corners 4768 if "top" in pos: 4769 if "left" in pos: pos = 2 4770 elif "right" in pos: pos = 3 4771 elif "mid" in pos or "cent" in pos: pos = 7 4772 elif "bottom" in pos: 4773 if "left" in pos: pos = 0 4774 elif "right" in pos: pos = 1 4775 elif "mid" in pos or "cent" in pos: pos = 4 4776 else: 4777 if "left" in pos: pos = 6 4778 elif "right" in pos: pos = 5 4779 else: pos = 2 4780 4781 if "\\" in repr(txt): 4782 for r in _reps: 4783 txt = txt.replace(r[0], r[1]) 4784 else: 4785 txt = str(txt) 4786 4787 self.SetText(pos, txt) 4788 return self 4789 4790 def clear(self) -> "CornerAnnotation": 4791 """Remove all text from all corners""" 4792 self.ClearAllTexts() 4793 return self
Annotate the window corner with 2D text.
See Text2D
description as the basic functionality is very similar.
The added value of this class is the possibility to manage with one single
object the all corner annotations (instead of creating 4 Text2D
instances).
Examples:
4724 def __init__(self, c=None) -> None: 4725 4726 super().__init__() 4727 4728 self.properties = self.GetTextProperty() 4729 4730 # automatic black or white 4731 if c is None: 4732 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4733 c = (0.9, 0.9, 0.9) 4734 if vedo.plotter_instance.renderer.GetGradientBackground(): 4735 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4736 else: 4737 bgcol = vedo.plotter_instance.renderer.GetBackground() 4738 if np.sum(bgcol) > 1.5: 4739 c = (0.1, 0.1, 0.1) 4740 else: 4741 c = (0.5, 0.5, 0.5) 4742 4743 self.SetNonlinearFontScaleFactor(1 / 2.75) 4744 self.PickableOff() 4745 self.properties.SetColor(get_color(c)) 4746 self.properties.SetBold(False) 4747 self.properties.SetItalic(False)
Do not instantiate this base class.
4749 def size(self, s:float, linear=False) -> "CornerAnnotation": 4750 """ 4751 The font size is calculated as the largest possible value such that the annotations 4752 for the given viewport do not overlap. 4753 4754 This font size can be scaled non-linearly with the viewport size, to maintain an 4755 acceptable readable size at larger viewport sizes, without being too big. 4756 `f' = linearScale * pow(f,nonlinearScale)` 4757 """ 4758 if linear: 4759 self.SetLinearFontScaleFactor(s * 5.5) 4760 else: 4761 self.SetNonlinearFontScaleFactor(s / 2.75) 4762 return self
The font size is calculated as the largest possible value such that the annotations for the given viewport do not overlap.
This font size can be scaled non-linearly with the viewport size, to maintain an
acceptable readable size at larger viewport sizes, without being too big.
f' = linearScale * pow(f,nonlinearScale)
4764 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4765 """Set text at the assigned position""" 4766 4767 if isinstance(pos, str): # corners 4768 if "top" in pos: 4769 if "left" in pos: pos = 2 4770 elif "right" in pos: pos = 3 4771 elif "mid" in pos or "cent" in pos: pos = 7 4772 elif "bottom" in pos: 4773 if "left" in pos: pos = 0 4774 elif "right" in pos: pos = 1 4775 elif "mid" in pos or "cent" in pos: pos = 4 4776 else: 4777 if "left" in pos: pos = 6 4778 elif "right" in pos: pos = 5 4779 else: pos = 2 4780 4781 if "\\" in repr(txt): 4782 for r in _reps: 4783 txt = txt.replace(r[0], r[1]) 4784 else: 4785 txt = str(txt) 4786 4787 self.SetText(pos, txt) 4788 return self
Set text at the assigned position
4796class Latex(Image): 4797 """ 4798 Render Latex text and formulas. 4799 """ 4800 4801 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4802 """ 4803 Render Latex text and formulas. 4804 4805 Arguments: 4806 formula : (str) 4807 latex text string 4808 pos : (list) 4809 position coordinates in space 4810 bg : (color) 4811 background color box 4812 res : (int) 4813 dpi resolution 4814 usetex : (bool) 4815 use latex compiler of matplotlib if available 4816 4817 You can access the latex formula in `Latex.formula`. 4818 4819 Examples: 4820 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4821 4822 ![](https://vedo.embl.es/images/pyplot/latex.png) 4823 """ 4824 from tempfile import NamedTemporaryFile 4825 import matplotlib.pyplot as mpltib 4826 4827 def build_img_plt(formula, tfile): 4828 4829 mpltib.rc("text", usetex=usetex) 4830 4831 formula1 = "$" + formula + "$" 4832 mpltib.axis("off") 4833 col = get_color(c) 4834 if bg: 4835 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4836 else: 4837 bx = None 4838 mpltib.text( 4839 0.5, 4840 0.5, 4841 formula1, 4842 size=res, 4843 color=col, 4844 alpha=alpha, 4845 ha="center", 4846 va="center", 4847 bbox=bx, 4848 ) 4849 mpltib.savefig( 4850 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4851 ) 4852 mpltib.close() 4853 4854 if len(pos) == 2: 4855 pos = (pos[0], pos[1], 0) 4856 4857 tmp_file = NamedTemporaryFile(delete=True) 4858 tmp_file.name = tmp_file.name + ".png" 4859 4860 build_img_plt(formula, tmp_file.name) 4861 4862 super().__init__(tmp_file.name, channels=4) 4863 self.alpha(alpha) 4864 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4865 self.pos(pos) 4866 self.name = "Latex" 4867 self.formula = formula 4868 4869 # except: 4870 # printc("Error in Latex()\n", formula, c="r") 4871 # printc(" latex or dvipng not installed?", c="r") 4872 # printc(" Try: usetex=False", c="r") 4873 # printc(" Try: sudo apt install dvipng", c="r")
Render Latex text and formulas.
4801 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4802 """ 4803 Render Latex text and formulas. 4804 4805 Arguments: 4806 formula : (str) 4807 latex text string 4808 pos : (list) 4809 position coordinates in space 4810 bg : (color) 4811 background color box 4812 res : (int) 4813 dpi resolution 4814 usetex : (bool) 4815 use latex compiler of matplotlib if available 4816 4817 You can access the latex formula in `Latex.formula`. 4818 4819 Examples: 4820 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4821 4822 ![](https://vedo.embl.es/images/pyplot/latex.png) 4823 """ 4824 from tempfile import NamedTemporaryFile 4825 import matplotlib.pyplot as mpltib 4826 4827 def build_img_plt(formula, tfile): 4828 4829 mpltib.rc("text", usetex=usetex) 4830 4831 formula1 = "$" + formula + "$" 4832 mpltib.axis("off") 4833 col = get_color(c) 4834 if bg: 4835 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4836 else: 4837 bx = None 4838 mpltib.text( 4839 0.5, 4840 0.5, 4841 formula1, 4842 size=res, 4843 color=col, 4844 alpha=alpha, 4845 ha="center", 4846 va="center", 4847 bbox=bx, 4848 ) 4849 mpltib.savefig( 4850 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4851 ) 4852 mpltib.close() 4853 4854 if len(pos) == 2: 4855 pos = (pos[0], pos[1], 0) 4856 4857 tmp_file = NamedTemporaryFile(delete=True) 4858 tmp_file.name = tmp_file.name + ".png" 4859 4860 build_img_plt(formula, tmp_file.name) 4861 4862 super().__init__(tmp_file.name, channels=4) 4863 self.alpha(alpha) 4864 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4865 self.pos(pos) 4866 self.name = "Latex" 4867 self.formula = formula 4868 4869 # except: 4870 # printc("Error in Latex()\n", formula, c="r") 4871 # printc(" latex or dvipng not installed?", c="r") 4872 # printc(" Try: usetex=False", c="r") 4873 # printc(" Try: sudo apt install dvipng", c="r")
Render Latex text and formulas.
Arguments:
- formula : (str) latex text string
- pos : (list) position coordinates in space
- bg : (color) background color box
- res : (int) dpi resolution
- usetex : (bool) use latex compiler of matplotlib if available
You can access the latex formula in Latex.formula
.
Examples:
153class Glyph(Mesh): 154 """ 155 At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with 156 various orientation options and coloring. 157 158 The input can also be a simple list of 2D or 3D coordinates. 159 Color can be specified as a colormap which maps the size of the orientation 160 vectors in `orientation_array`. 161 """ 162 163 def __init__( 164 self, 165 mesh, 166 glyph, 167 orientation_array=None, 168 scale_by_scalar=False, 169 scale_by_vector_size=False, 170 scale_by_vector_components=False, 171 color_by_scalar=False, 172 color_by_vector_size=False, 173 c="k8", 174 alpha=1.0, 175 ) -> None: 176 """ 177 Arguments: 178 orientation_array: (list, str, vtkArray) 179 list of vectors, `vtkArray` or name of an already existing pointdata array 180 scale_by_scalar : (bool) 181 glyph mesh is scaled by the active scalars 182 scale_by_vector_size : (bool) 183 glyph mesh is scaled by the size of the vectors 184 scale_by_vector_components : (bool) 185 glyph mesh is scaled by the 3 vectors components 186 color_by_scalar : (bool) 187 glyph mesh is colored based on the scalar value 188 color_by_vector_size : (bool) 189 glyph mesh is colored based on the vector size 190 191 Examples: 192 - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 193 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 194 195 ![](https://vedo.embl.es/images/basic/glyphs.png) 196 """ 197 if utils.is_sequence(mesh): 198 # create a cloud of points 199 poly = utils.buildPolyData(mesh) 200 else: 201 poly = mesh.dataset 202 203 cmap = "" 204 if isinstance(c, str) and c in cmaps_names: 205 cmap = c 206 c = None 207 elif utils.is_sequence(c): # user passing an array of point colors 208 ucols = vtki.vtkUnsignedCharArray() 209 ucols.SetNumberOfComponents(3) 210 ucols.SetName("GlyphRGB") 211 for col in c: 212 cl = get_color(col) 213 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 214 poly.GetPointData().AddArray(ucols) 215 poly.GetPointData().SetActiveScalars("GlyphRGB") 216 c = None 217 218 gly = vtki.vtkGlyph3D() 219 gly.GeneratePointIdsOn() 220 gly.SetInputData(poly) 221 try: 222 gly.SetSourceData(glyph) 223 except TypeError: 224 gly.SetSourceData(glyph.dataset) 225 226 if scale_by_scalar: 227 gly.SetScaleModeToScaleByScalar() 228 elif scale_by_vector_size: 229 gly.SetScaleModeToScaleByVector() 230 elif scale_by_vector_components: 231 gly.SetScaleModeToScaleByVectorComponents() 232 else: 233 gly.SetScaleModeToDataScalingOff() 234 235 if color_by_vector_size: 236 gly.SetVectorModeToUseVector() 237 gly.SetColorModeToColorByVector() 238 elif color_by_scalar: 239 gly.SetColorModeToColorByScalar() 240 else: 241 gly.SetColorModeToColorByScale() 242 243 if orientation_array is not None: 244 gly.OrientOn() 245 if isinstance(orientation_array, str): 246 if orientation_array.lower() == "normals": 247 gly.SetVectorModeToUseNormal() 248 else: # passing a name 249 poly.GetPointData().SetActiveVectors(orientation_array) 250 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 251 gly.SetVectorModeToUseVector() 252 elif utils.is_sequence(orientation_array): # passing a list 253 varr = vtki.vtkFloatArray() 254 varr.SetNumberOfComponents(3) 255 varr.SetName("glyph_vectors") 256 for v in orientation_array: 257 varr.InsertNextTuple(v) 258 poly.GetPointData().AddArray(varr) 259 poly.GetPointData().SetActiveVectors("glyph_vectors") 260 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 261 gly.SetVectorModeToUseVector() 262 263 gly.Update() 264 265 super().__init__(gly.GetOutput(), c, alpha) 266 self.flat() 267 268 if cmap: 269 self.cmap(cmap, "VectorMagnitude") 270 elif c is None: 271 self.pointdata.select("GlyphRGB") 272 273 self.name = "Glyph"
At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with various orientation options and coloring.
The input can also be a simple list of 2D or 3D coordinates.
Color can be specified as a colormap which maps the size of the orientation
vectors in orientation_array
.
163 def __init__( 164 self, 165 mesh, 166 glyph, 167 orientation_array=None, 168 scale_by_scalar=False, 169 scale_by_vector_size=False, 170 scale_by_vector_components=False, 171 color_by_scalar=False, 172 color_by_vector_size=False, 173 c="k8", 174 alpha=1.0, 175 ) -> None: 176 """ 177 Arguments: 178 orientation_array: (list, str, vtkArray) 179 list of vectors, `vtkArray` or name of an already existing pointdata array 180 scale_by_scalar : (bool) 181 glyph mesh is scaled by the active scalars 182 scale_by_vector_size : (bool) 183 glyph mesh is scaled by the size of the vectors 184 scale_by_vector_components : (bool) 185 glyph mesh is scaled by the 3 vectors components 186 color_by_scalar : (bool) 187 glyph mesh is colored based on the scalar value 188 color_by_vector_size : (bool) 189 glyph mesh is colored based on the vector size 190 191 Examples: 192 - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 193 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 194 195 ![](https://vedo.embl.es/images/basic/glyphs.png) 196 """ 197 if utils.is_sequence(mesh): 198 # create a cloud of points 199 poly = utils.buildPolyData(mesh) 200 else: 201 poly = mesh.dataset 202 203 cmap = "" 204 if isinstance(c, str) and c in cmaps_names: 205 cmap = c 206 c = None 207 elif utils.is_sequence(c): # user passing an array of point colors 208 ucols = vtki.vtkUnsignedCharArray() 209 ucols.SetNumberOfComponents(3) 210 ucols.SetName("GlyphRGB") 211 for col in c: 212 cl = get_color(col) 213 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 214 poly.GetPointData().AddArray(ucols) 215 poly.GetPointData().SetActiveScalars("GlyphRGB") 216 c = None 217 218 gly = vtki.vtkGlyph3D() 219 gly.GeneratePointIdsOn() 220 gly.SetInputData(poly) 221 try: 222 gly.SetSourceData(glyph) 223 except TypeError: 224 gly.SetSourceData(glyph.dataset) 225 226 if scale_by_scalar: 227 gly.SetScaleModeToScaleByScalar() 228 elif scale_by_vector_size: 229 gly.SetScaleModeToScaleByVector() 230 elif scale_by_vector_components: 231 gly.SetScaleModeToScaleByVectorComponents() 232 else: 233 gly.SetScaleModeToDataScalingOff() 234 235 if color_by_vector_size: 236 gly.SetVectorModeToUseVector() 237 gly.SetColorModeToColorByVector() 238 elif color_by_scalar: 239 gly.SetColorModeToColorByScalar() 240 else: 241 gly.SetColorModeToColorByScale() 242 243 if orientation_array is not None: 244 gly.OrientOn() 245 if isinstance(orientation_array, str): 246 if orientation_array.lower() == "normals": 247 gly.SetVectorModeToUseNormal() 248 else: # passing a name 249 poly.GetPointData().SetActiveVectors(orientation_array) 250 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 251 gly.SetVectorModeToUseVector() 252 elif utils.is_sequence(orientation_array): # passing a list 253 varr = vtki.vtkFloatArray() 254 varr.SetNumberOfComponents(3) 255 varr.SetName("glyph_vectors") 256 for v in orientation_array: 257 varr.InsertNextTuple(v) 258 poly.GetPointData().AddArray(varr) 259 poly.GetPointData().SetActiveVectors("glyph_vectors") 260 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 261 gly.SetVectorModeToUseVector() 262 263 gly.Update() 264 265 super().__init__(gly.GetOutput(), c, alpha) 266 self.flat() 267 268 if cmap: 269 self.cmap(cmap, "VectorMagnitude") 270 elif c is None: 271 self.pointdata.select("GlyphRGB") 272 273 self.name = "Glyph"
Arguments:
- orientation_array: (list, str, vtkArray)
list of vectors,
vtkArray
or name of an already existing pointdata array - scale_by_scalar : (bool) glyph mesh is scaled by the active scalars
- scale_by_vector_size : (bool) glyph mesh is scaled by the size of the vectors
- scale_by_vector_components : (bool) glyph mesh is scaled by the 3 vectors components
- color_by_scalar : (bool) glyph mesh is colored based on the scalar value
- color_by_vector_size : (bool) glyph mesh is colored based on the vector size
Examples:
276class Tensors(Mesh): 277 """ 278 Geometric representation of tensors defined on a domain or set of points. 279 Tensors can be scaled and/or rotated according to the source at each input point. 280 Scaling and rotation is controlled by the eigenvalues/eigenvectors of the 281 symmetrical part of the tensor as follows: 282 283 For each tensor, the eigenvalues (and associated eigenvectors) are sorted 284 to determine the major, medium, and minor eigenvalues/eigenvectors. 285 The eigenvalue decomposition only makes sense for symmetric tensors, 286 hence the need to only consider the symmetric part of the tensor, 287 which is `1/2*(T+T.transposed())`. 288 """ 289 290 def __init__( 291 self, 292 domain, 293 source="ellipsoid", 294 use_eigenvalues=True, 295 is_symmetric=True, 296 three_axes=False, 297 scale=1.0, 298 max_scale=None, 299 length=None, 300 res=24, 301 c=None, 302 alpha=1.0, 303 ) -> None: 304 """ 305 Arguments: 306 source : (str, Mesh) 307 preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object. 308 use_eigenvalues : (bool) 309 color source glyph using the eigenvalues or by scalars 310 three_axes : (bool) 311 if `False` scale the source in the x-direction, 312 the medium in the y-direction, and the minor in the z-direction. 313 Then, the source is rotated so that the glyph's local x-axis lies 314 along the major eigenvector, y-axis along the medium eigenvector, 315 and z-axis along the minor. 316 317 If `True` three sources are produced, each of them oriented along an eigenvector 318 and scaled according to the corresponding eigenvector. 319 is_symmetric : (bool) 320 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 321 The x-axis of the source glyph will correspond to the eigenvector on output. 322 length : (float) 323 distance from the origin to the tip of the source glyph along the x-axis 324 scale : (float) 325 scaling factor of the source glyph. 326 max_scale : (float) 327 clamp scaling at this factor. 328 329 Examples: 330 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 331 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 332 - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py) 333 334 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 335 """ 336 if isinstance(source, Points): 337 src = source.dataset 338 else: # is string 339 if "ellip" in source: 340 src = vtki.new("SphereSource") 341 src.SetPhiResolution(res) 342 src.SetThetaResolution(res*2) 343 elif "cyl" in source: 344 src = vtki.new("CylinderSource") 345 src.SetResolution(res) 346 src.CappingOn() 347 elif source == "cube": 348 src = vtki.new("CubeSource") 349 else: 350 vedo.logger.error(f"Unknown source type {source}") 351 raise ValueError() 352 src.Update() 353 src = src.GetOutput() 354 355 tg = vtki.new("TensorGlyph") 356 if isinstance(domain, vtki.vtkPolyData): 357 tg.SetInputData(domain) 358 else: 359 tg.SetInputData(domain.dataset) 360 tg.SetSourceData(src) 361 362 if c is None: 363 tg.ColorGlyphsOn() 364 else: 365 tg.ColorGlyphsOff() 366 367 tg.SetSymmetric(int(is_symmetric)) 368 369 if length is not None: 370 tg.SetLength(length) 371 if use_eigenvalues: 372 tg.ExtractEigenvaluesOn() 373 tg.SetColorModeToEigenvalues() 374 else: 375 tg.SetColorModeToScalars() 376 377 tg.SetThreeGlyphs(three_axes) 378 tg.ScalingOn() 379 tg.SetScaleFactor(scale) 380 if max_scale is None: 381 tg.ClampScalingOn() 382 max_scale = scale * 10 383 tg.SetMaxScaleFactor(max_scale) 384 385 tg.Update() 386 tgn = vtki.new("PolyDataNormals") 387 tgn.ComputeCellNormalsOff() 388 tgn.SetInputData(tg.GetOutput()) 389 tgn.Update() 390 391 super().__init__(tgn.GetOutput(), c, alpha) 392 self.name = "Tensors"
Geometric representation of tensors defined on a domain or set of points. Tensors can be scaled and/or rotated according to the source at each input point. Scaling and rotation is controlled by the eigenvalues/eigenvectors of the symmetrical part of the tensor as follows:
For each tensor, the eigenvalues (and associated eigenvectors) are sorted
to determine the major, medium, and minor eigenvalues/eigenvectors.
The eigenvalue decomposition only makes sense for symmetric tensors,
hence the need to only consider the symmetric part of the tensor,
which is 1/2*(T+T.transposed())
.
290 def __init__( 291 self, 292 domain, 293 source="ellipsoid", 294 use_eigenvalues=True, 295 is_symmetric=True, 296 three_axes=False, 297 scale=1.0, 298 max_scale=None, 299 length=None, 300 res=24, 301 c=None, 302 alpha=1.0, 303 ) -> None: 304 """ 305 Arguments: 306 source : (str, Mesh) 307 preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object. 308 use_eigenvalues : (bool) 309 color source glyph using the eigenvalues or by scalars 310 three_axes : (bool) 311 if `False` scale the source in the x-direction, 312 the medium in the y-direction, and the minor in the z-direction. 313 Then, the source is rotated so that the glyph's local x-axis lies 314 along the major eigenvector, y-axis along the medium eigenvector, 315 and z-axis along the minor. 316 317 If `True` three sources are produced, each of them oriented along an eigenvector 318 and scaled according to the corresponding eigenvector. 319 is_symmetric : (bool) 320 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 321 The x-axis of the source glyph will correspond to the eigenvector on output. 322 length : (float) 323 distance from the origin to the tip of the source glyph along the x-axis 324 scale : (float) 325 scaling factor of the source glyph. 326 max_scale : (float) 327 clamp scaling at this factor. 328 329 Examples: 330 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 331 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 332 - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py) 333 334 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 335 """ 336 if isinstance(source, Points): 337 src = source.dataset 338 else: # is string 339 if "ellip" in source: 340 src = vtki.new("SphereSource") 341 src.SetPhiResolution(res) 342 src.SetThetaResolution(res*2) 343 elif "cyl" in source: 344 src = vtki.new("CylinderSource") 345 src.SetResolution(res) 346 src.CappingOn() 347 elif source == "cube": 348 src = vtki.new("CubeSource") 349 else: 350 vedo.logger.error(f"Unknown source type {source}") 351 raise ValueError() 352 src.Update() 353 src = src.GetOutput() 354 355 tg = vtki.new("TensorGlyph") 356 if isinstance(domain, vtki.vtkPolyData): 357 tg.SetInputData(domain) 358 else: 359 tg.SetInputData(domain.dataset) 360 tg.SetSourceData(src) 361 362 if c is None: 363 tg.ColorGlyphsOn() 364 else: 365 tg.ColorGlyphsOff() 366 367 tg.SetSymmetric(int(is_symmetric)) 368 369 if length is not None: 370 tg.SetLength(length) 371 if use_eigenvalues: 372 tg.ExtractEigenvaluesOn() 373 tg.SetColorModeToEigenvalues() 374 else: 375 tg.SetColorModeToScalars() 376 377 tg.SetThreeGlyphs(three_axes) 378 tg.ScalingOn() 379 tg.SetScaleFactor(scale) 380 if max_scale is None: 381 tg.ClampScalingOn() 382 max_scale = scale * 10 383 tg.SetMaxScaleFactor(max_scale) 384 385 tg.Update() 386 tgn = vtki.new("PolyDataNormals") 387 tgn.ComputeCellNormalsOff() 388 tgn.SetInputData(tg.GetOutput()) 389 tgn.Update() 390 391 super().__init__(tgn.GetOutput(), c, alpha) 392 self.name = "Tensors"
Arguments:
- source : (str, Mesh)
preset types of source shapes is "ellipsoid", "cylinder", "cube" or a
Mesh
object. - use_eigenvalues : (bool) color source glyph using the eigenvalues or by scalars
three_axes : (bool) if
False
scale the source in the x-direction, the medium in the y-direction, and the minor in the z-direction. Then, the source is rotated so that the glyph's local x-axis lies along the major eigenvector, y-axis along the medium eigenvector, and z-axis along the minor.If
True
three sources are produced, each of them oriented along an eigenvector and scaled according to the corresponding eigenvector.- is_symmetric : (bool)
If
True
each source glyph is mirrored (2 or 6 glyphs will be produced). The x-axis of the source glyph will correspond to the eigenvector on output. - length : (float) distance from the origin to the tip of the source glyph along the x-axis
- scale : (float) scaling factor of the source glyph.
- max_scale : (float) clamp scaling at this factor.
Examples:
3867class ParametricShape(Mesh): 3868 """ 3869 A set of built-in shapes mainly for illustration purposes. 3870 """ 3871 3872 def __init__(self, name, res=51, n=25, seed=1): 3873 """ 3874 A set of built-in shapes mainly for illustration purposes. 3875 3876 Name can be an integer or a string in this list: 3877 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3878 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3879 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3880 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3881 3882 Example: 3883 ```python 3884 from vedo import * 3885 settings.immediate_rendering = False 3886 plt = Plotter(N=18) 3887 for i in range(18): 3888 ps = ParametricShape(i).color(i) 3889 plt.at(i).show(ps, ps.name) 3890 plt.interactive().close() 3891 ``` 3892 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3893 """ 3894 3895 shapes = [ 3896 "Boy", 3897 "ConicSpiral", 3898 "CrossCap", 3899 "Enneper", 3900 "Figure8Klein", 3901 "Klein", 3902 "Dini", 3903 "Mobius", 3904 "RandomHills", 3905 "Roman", 3906 "SuperEllipsoid", 3907 "BohemianDome", 3908 "Bour", 3909 "CatalanMinimal", 3910 "Henneberg", 3911 "Kuen", 3912 "PluckerConoid", 3913 "Pseudosphere", 3914 ] 3915 3916 if isinstance(name, int): 3917 name = name % len(shapes) 3918 name = shapes[name] 3919 3920 if name == "Boy": 3921 ps = vtki.new("ParametricBoy") 3922 elif name == "ConicSpiral": 3923 ps = vtki.new("ParametricConicSpiral") 3924 elif name == "CrossCap": 3925 ps = vtki.new("ParametricCrossCap") 3926 elif name == "Dini": 3927 ps = vtki.new("ParametricDini") 3928 elif name == "Enneper": 3929 ps = vtki.new("ParametricEnneper") 3930 elif name == "Figure8Klein": 3931 ps = vtki.new("ParametricFigure8Klein") 3932 elif name == "Klein": 3933 ps = vtki.new("ParametricKlein") 3934 elif name == "Mobius": 3935 ps = vtki.new("ParametricMobius") 3936 ps.SetRadius(2.0) 3937 ps.SetMinimumV(-0.5) 3938 ps.SetMaximumV(0.5) 3939 elif name == "RandomHills": 3940 ps = vtki.new("ParametricRandomHills") 3941 ps.AllowRandomGenerationOn() 3942 ps.SetRandomSeed(seed) 3943 ps.SetNumberOfHills(n) 3944 elif name == "Roman": 3945 ps = vtki.new("ParametricRoman") 3946 elif name == "SuperEllipsoid": 3947 ps = vtki.new("ParametricSuperEllipsoid") 3948 ps.SetN1(0.5) 3949 ps.SetN2(0.4) 3950 elif name == "BohemianDome": 3951 ps = vtki.new("ParametricBohemianDome") 3952 ps.SetA(5.0) 3953 ps.SetB(1.0) 3954 ps.SetC(2.0) 3955 elif name == "Bour": 3956 ps = vtki.new("ParametricBour") 3957 elif name == "CatalanMinimal": 3958 ps = vtki.new("ParametricCatalanMinimal") 3959 elif name == "Henneberg": 3960 ps = vtki.new("ParametricHenneberg") 3961 elif name == "Kuen": 3962 ps = vtki.new("ParametricKuen") 3963 ps.SetDeltaV0(0.001) 3964 elif name == "PluckerConoid": 3965 ps = vtki.new("ParametricPluckerConoid") 3966 elif name == "Pseudosphere": 3967 ps = vtki.new("ParametricPseudosphere") 3968 else: 3969 vedo.logger.error(f"unknown ParametricShape {name}") 3970 return 3971 3972 pfs = vtki.new("ParametricFunctionSource") 3973 pfs.SetParametricFunction(ps) 3974 pfs.SetUResolution(res) 3975 pfs.SetVResolution(res) 3976 pfs.SetWResolution(res) 3977 pfs.SetScalarModeToZ() 3978 pfs.Update() 3979 3980 super().__init__(pfs.GetOutput()) 3981 3982 if name == "RandomHills": self.shift([0,-10,-2.25]) 3983 if name != 'Kuen': self.normalize() 3984 if name == 'Dini': self.scale(0.4) 3985 if name == 'Enneper': self.scale(0.4) 3986 if name == 'ConicSpiral': self.bc('tomato') 3987 self.name = name
A set of built-in shapes mainly for illustration purposes.
3872 def __init__(self, name, res=51, n=25, seed=1): 3873 """ 3874 A set of built-in shapes mainly for illustration purposes. 3875 3876 Name can be an integer or a string in this list: 3877 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3878 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3879 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3880 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3881 3882 Example: 3883 ```python 3884 from vedo import * 3885 settings.immediate_rendering = False 3886 plt = Plotter(N=18) 3887 for i in range(18): 3888 ps = ParametricShape(i).color(i) 3889 plt.at(i).show(ps, ps.name) 3890 plt.interactive().close() 3891 ``` 3892 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3893 """ 3894 3895 shapes = [ 3896 "Boy", 3897 "ConicSpiral", 3898 "CrossCap", 3899 "Enneper", 3900 "Figure8Klein", 3901 "Klein", 3902 "Dini", 3903 "Mobius", 3904 "RandomHills", 3905 "Roman", 3906 "SuperEllipsoid", 3907 "BohemianDome", 3908 "Bour", 3909 "CatalanMinimal", 3910 "Henneberg", 3911 "Kuen", 3912 "PluckerConoid", 3913 "Pseudosphere", 3914 ] 3915 3916 if isinstance(name, int): 3917 name = name % len(shapes) 3918 name = shapes[name] 3919 3920 if name == "Boy": 3921 ps = vtki.new("ParametricBoy") 3922 elif name == "ConicSpiral": 3923 ps = vtki.new("ParametricConicSpiral") 3924 elif name == "CrossCap": 3925 ps = vtki.new("ParametricCrossCap") 3926 elif name == "Dini": 3927 ps = vtki.new("ParametricDini") 3928 elif name == "Enneper": 3929 ps = vtki.new("ParametricEnneper") 3930 elif name == "Figure8Klein": 3931 ps = vtki.new("ParametricFigure8Klein") 3932 elif name == "Klein": 3933 ps = vtki.new("ParametricKlein") 3934 elif name == "Mobius": 3935 ps = vtki.new("ParametricMobius") 3936 ps.SetRadius(2.0) 3937 ps.SetMinimumV(-0.5) 3938 ps.SetMaximumV(0.5) 3939 elif name == "RandomHills": 3940 ps = vtki.new("ParametricRandomHills") 3941 ps.AllowRandomGenerationOn() 3942 ps.SetRandomSeed(seed) 3943 ps.SetNumberOfHills(n) 3944 elif name == "Roman": 3945 ps = vtki.new("ParametricRoman") 3946 elif name == "SuperEllipsoid": 3947 ps = vtki.new("ParametricSuperEllipsoid") 3948 ps.SetN1(0.5) 3949 ps.SetN2(0.4) 3950 elif name == "BohemianDome": 3951 ps = vtki.new("ParametricBohemianDome") 3952 ps.SetA(5.0) 3953 ps.SetB(1.0) 3954 ps.SetC(2.0) 3955 elif name == "Bour": 3956 ps = vtki.new("ParametricBour") 3957 elif name == "CatalanMinimal": 3958 ps = vtki.new("ParametricCatalanMinimal") 3959 elif name == "Henneberg": 3960 ps = vtki.new("ParametricHenneberg") 3961 elif name == "Kuen": 3962 ps = vtki.new("ParametricKuen") 3963 ps.SetDeltaV0(0.001) 3964 elif name == "PluckerConoid": 3965 ps = vtki.new("ParametricPluckerConoid") 3966 elif name == "Pseudosphere": 3967 ps = vtki.new("ParametricPseudosphere") 3968 else: 3969 vedo.logger.error(f"unknown ParametricShape {name}") 3970 return 3971 3972 pfs = vtki.new("ParametricFunctionSource") 3973 pfs.SetParametricFunction(ps) 3974 pfs.SetUResolution(res) 3975 pfs.SetVResolution(res) 3976 pfs.SetWResolution(res) 3977 pfs.SetScalarModeToZ() 3978 pfs.Update() 3979 3980 super().__init__(pfs.GetOutput()) 3981 3982 if name == "RandomHills": self.shift([0,-10,-2.25]) 3983 if name != 'Kuen': self.normalize() 3984 if name == 'Dini': self.scale(0.4) 3985 if name == 'Enneper': self.scale(0.4) 3986 if name == 'ConicSpiral': self.bc('tomato') 3987 self.name = name
A set of built-in shapes mainly for illustration purposes.
Name can be an integer or a string in this list:
['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']
.
Example:
from vedo import * settings.immediate_rendering = False plt = Plotter(N=18) for i in range(18): ps = ParametricShape(i).color(i) plt.at(i).show(ps, ps.name) plt.interactive().close()
4876class ConvexHull(Mesh): 4877 """ 4878 Create the 2D/3D convex hull from a set of points. 4879 """ 4880 4881 def __init__(self, pts) -> None: 4882 """ 4883 Create the 2D/3D convex hull from a set of input points or input Mesh. 4884 4885 Examples: 4886 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4887 4888 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4889 """ 4890 if utils.is_sequence(pts): 4891 pts = utils.make3d(pts).astype(float) 4892 mesh = Points(pts) 4893 else: 4894 mesh = pts 4895 apoly = mesh.clean().dataset 4896 4897 # Create the convex hull of the pointcloud 4898 z0, z1 = mesh.zbounds() 4899 d = mesh.diagonal_size() 4900 if (z1 - z0) / d > 0.0001: 4901 delaunay = vtki.new("Delaunay3D") 4902 delaunay.SetInputData(apoly) 4903 delaunay.Update() 4904 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4905 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4906 surfaceFilter.Update() 4907 out = surfaceFilter.GetOutput() 4908 else: 4909 delaunay = vtki.new("Delaunay2D") 4910 delaunay.SetInputData(apoly) 4911 delaunay.Update() 4912 fe = vtki.new("FeatureEdges") 4913 fe.SetInputConnection(delaunay.GetOutputPort()) 4914 fe.BoundaryEdgesOn() 4915 fe.Update() 4916 out = fe.GetOutput() 4917 4918 super().__init__(out, c=mesh.color(), alpha=0.75) 4919 self.flat() 4920 self.name = "ConvexHull"
Create the 2D/3D convex hull from a set of points.
4881 def __init__(self, pts) -> None: 4882 """ 4883 Create the 2D/3D convex hull from a set of input points or input Mesh. 4884 4885 Examples: 4886 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4887 4888 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4889 """ 4890 if utils.is_sequence(pts): 4891 pts = utils.make3d(pts).astype(float) 4892 mesh = Points(pts) 4893 else: 4894 mesh = pts 4895 apoly = mesh.clean().dataset 4896 4897 # Create the convex hull of the pointcloud 4898 z0, z1 = mesh.zbounds() 4899 d = mesh.diagonal_size() 4900 if (z1 - z0) / d > 0.0001: 4901 delaunay = vtki.new("Delaunay3D") 4902 delaunay.SetInputData(apoly) 4903 delaunay.Update() 4904 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4905 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4906 surfaceFilter.Update() 4907 out = surfaceFilter.GetOutput() 4908 else: 4909 delaunay = vtki.new("Delaunay2D") 4910 delaunay.SetInputData(apoly) 4911 delaunay.Update() 4912 fe = vtki.new("FeatureEdges") 4913 fe.SetInputConnection(delaunay.GetOutputPort()) 4914 fe.BoundaryEdgesOn() 4915 fe.Update() 4916 out = fe.GetOutput() 4917 4918 super().__init__(out, c=mesh.color(), alpha=0.75) 4919 self.flat() 4920 self.name = "ConvexHull"
4923def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True) -> "vedo.Assembly": 4924 """ 4925 Create the 3D vedo logo. 4926 4927 Arguments: 4928 distance : (float) 4929 send back logo by this distance from camera 4930 version : (bool) 4931 add version text to the right end of the logo 4932 bc : (color) 4933 text back face color 4934 """ 4935 if c is None: 4936 c = (0, 0, 0) 4937 if vedo.plotter_instance: 4938 if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5: 4939 c = [0, 0, 0] 4940 else: 4941 c = "linen" 4942 4943 font = "Comae" 4944 vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8) 4945 vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc) 4946 vlogo.properties.LightingOn() 4947 4948 vr, rul = None, None 4949 if version: 4950 vr = Text3D( 4951 vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1 4952 ).scale([1, 0.7, 1]) 4953 vr.rotate_z(90).pos(2450, 50, 80) 4954 vr.bc(bc).pickable(False) 4955 elif frame: 4956 rul = vedo.RulerAxes( 4957 (-2600, 2110, 0, 1650, 0, 0), 4958 xlabel="European Molecular Biology Laboratory", 4959 ylabel=vedo.__version__, 4960 font=font, 4961 xpadding=0.09, 4962 ypadding=0.04, 4963 ) 4964 fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0) 4965 return vedo.Assembly([vlogo, vr, fakept, rul]).scale(1 / 1725)
Create the 3D vedo logo.
Arguments:
- distance : (float) send back logo by this distance from camera
- version : (bool) add version text to the right end of the logo
- bc : (color) text back face color