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 # self.pos(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 = None 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 1922class Arrows(Glyph): 1923 """ 1924 Build arrows between two lists of points. 1925 """ 1926 1927 def __init__( 1928 self, 1929 start_pts, 1930 end_pts=None, 1931 s=None, 1932 shaft_radius=None, 1933 head_radius=None, 1934 head_length=None, 1935 thickness=1.0, 1936 res=6, 1937 c='k3', 1938 alpha=1.0, 1939 ) -> None: 1940 """ 1941 Build arrows between two lists of points `start_pts` and `end_pts`. 1942 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1943 1944 Color can be specified as a colormap which maps the size of the arrows. 1945 1946 Arguments: 1947 s : (float) 1948 fix aspect-ratio of the arrow and scale its cross section 1949 c : (color) 1950 color or color map name 1951 alpha : (float) 1952 set object opacity 1953 res : (int) 1954 set arrow resolution 1955 1956 Examples: 1957 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 1958 1959 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 1960 """ 1961 if isinstance(start_pts, Points): 1962 start_pts = start_pts.vertices 1963 if isinstance(end_pts, Points): 1964 end_pts = end_pts.vertices 1965 1966 start_pts = np.asarray(start_pts) 1967 if end_pts is None: 1968 strt = start_pts[:, 0] 1969 end_pts = start_pts[:, 1] 1970 start_pts = strt 1971 else: 1972 end_pts = np.asarray(end_pts) 1973 1974 start_pts = utils.make3d(start_pts) 1975 end_pts = utils.make3d(end_pts) 1976 1977 arr = vtki.new("ArrowSource") 1978 arr.SetShaftResolution(res) 1979 arr.SetTipResolution(res) 1980 1981 if s: 1982 sz = 0.02 * s 1983 arr.SetTipRadius(sz * 2) 1984 arr.SetShaftRadius(sz * thickness) 1985 arr.SetTipLength(sz * 10) 1986 1987 if head_radius: 1988 arr.SetTipRadius(head_radius) 1989 if shaft_radius: 1990 arr.SetShaftRadius(shaft_radius) 1991 if head_length: 1992 arr.SetTipLength(head_length) 1993 1994 arr.Update() 1995 out = arr.GetOutput() 1996 1997 orients = end_pts - start_pts 1998 1999 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2000 2001 super().__init__( 2002 start_pts, 2003 out, 2004 orientation_array=orients, 2005 scale_by_vector_size=True, 2006 color_by_vector_size=color_by_vector_size, 2007 c=c, 2008 alpha=alpha, 2009 ) 2010 self.lighting("off") 2011 if color_by_vector_size: 2012 vals = np.linalg.norm(orients, axis=1) 2013 self.mapper.SetScalarRange(vals.min(), vals.max()) 2014 else: 2015 self.c(c) 2016 self.name = "Arrows" 2017 2018 2019class Arrow2D(Mesh): 2020 """ 2021 Build a 2D arrow. 2022 """ 2023 2024 def __init__( 2025 self, 2026 start_pt=(0, 0, 0), 2027 end_pt=(1, 0, 0), 2028 s=1, 2029 rotation=0.0, 2030 shaft_length=0.85, 2031 shaft_width=0.055, 2032 head_length=0.175, 2033 head_width=0.175, 2034 fill=True, 2035 c="red4", 2036 alpha=1.0, 2037 ) -> None: 2038 """ 2039 Build a 2D arrow from `start_pt` to `end_pt`. 2040 2041 Arguments: 2042 s : (float) 2043 a global multiplicative convenience factor controlling the arrow size 2044 shaft_length : (float) 2045 fractional shaft length 2046 shaft_width : (float) 2047 fractional shaft width 2048 head_length : (float) 2049 fractional head length 2050 head_width : (float) 2051 fractional head width 2052 fill : (bool) 2053 if False only generate the outline 2054 """ 2055 self.fill = fill ## needed by pyplot.__iadd() 2056 self.s = s ## needed by pyplot.__iadd() 2057 2058 if s != 1: 2059 shaft_width *= s 2060 head_width *= np.sqrt(s) 2061 2062 # in case user is passing meshs 2063 if isinstance(start_pt, vtki.vtkActor): 2064 start_pt = start_pt.GetPosition() 2065 if isinstance(end_pt, vtki.vtkActor): 2066 end_pt = end_pt.GetPosition() 2067 if len(start_pt) == 2: 2068 start_pt = [start_pt[0], start_pt[1], 0] 2069 if len(end_pt) == 2: 2070 end_pt = [end_pt[0], end_pt[1], 0] 2071 2072 headBase = 1 - head_length 2073 head_width = max(head_width, shaft_width) 2074 if head_length is None or headBase > shaft_length: 2075 headBase = shaft_length 2076 2077 verts = [] 2078 verts.append([0, -shaft_width / 2, 0]) 2079 verts.append([shaft_length, -shaft_width / 2, 0]) 2080 verts.append([headBase, -head_width / 2, 0]) 2081 verts.append([1, 0, 0]) 2082 verts.append([headBase, head_width / 2, 0]) 2083 verts.append([shaft_length, shaft_width / 2, 0]) 2084 verts.append([0, shaft_width / 2, 0]) 2085 if fill: 2086 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2087 poly = utils.buildPolyData(verts, faces) 2088 else: 2089 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2090 poly = utils.buildPolyData(verts, [], lines=lines) 2091 2092 axis = np.array(end_pt) - np.array(start_pt) 2093 length = float(np.linalg.norm(axis)) 2094 if length: 2095 axis = axis / length 2096 theta = 0 2097 if len(axis) > 2: 2098 theta = np.arccos(axis[2]) 2099 phi = np.arctan2(axis[1], axis[0]) 2100 2101 t = vtki.vtkTransform() 2102 t.Translate(start_pt) 2103 if phi: 2104 t.RotateZ(np.rad2deg(phi)) 2105 if theta: 2106 t.RotateY(np.rad2deg(theta)) 2107 t.RotateY(-90) # put it along Z 2108 if rotation: 2109 t.RotateX(rotation) 2110 t.Scale(length, length, length) 2111 2112 tf = vtki.new("TransformPolyDataFilter") 2113 tf.SetInputData(poly) 2114 tf.SetTransform(t) 2115 tf.Update() 2116 2117 super().__init__(tf.GetOutput(), c, alpha) 2118 2119 self.transform = LinearTransform().translate(start_pt) 2120 2121 self.lighting("off") 2122 self.actor.DragableOff() 2123 self.actor.PickableOff() 2124 self.base = np.array(start_pt, dtype=float) # used by pyplot 2125 self.top = np.array(end_pt, dtype=float) # used by pyplot 2126 self.name = "Arrow2D" 2127 2128 2129class Arrows2D(Glyph): 2130 """ 2131 Build 2D arrows between two lists of points. 2132 """ 2133 2134 def __init__( 2135 self, 2136 start_pts, 2137 end_pts=None, 2138 s=1.0, 2139 rotation=0.0, 2140 shaft_length=0.8, 2141 shaft_width=0.05, 2142 head_length=0.225, 2143 head_width=0.175, 2144 fill=True, 2145 c=None, 2146 alpha=1.0, 2147 ) -> None: 2148 """ 2149 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2150 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2151 2152 Color can be specified as a colormap which maps the size of the arrows. 2153 2154 Arguments: 2155 shaft_length : (float) 2156 fractional shaft length 2157 shaft_width : (float) 2158 fractional shaft width 2159 head_length : (float) 2160 fractional head length 2161 head_width : (float) 2162 fractional head width 2163 fill : (bool) 2164 if False only generate the outline 2165 """ 2166 if isinstance(start_pts, Points): 2167 start_pts = start_pts.vertices 2168 if isinstance(end_pts, Points): 2169 end_pts = end_pts.vertices 2170 2171 start_pts = np.asarray(start_pts, dtype=float) 2172 if end_pts is None: 2173 strt = start_pts[:, 0] 2174 end_pts = start_pts[:, 1] 2175 start_pts = strt 2176 else: 2177 end_pts = np.asarray(end_pts, dtype=float) 2178 2179 if head_length is None: 2180 head_length = 1 - shaft_length 2181 2182 arr = Arrow2D( 2183 (0, 0, 0), 2184 (1, 0, 0), 2185 s=s, 2186 rotation=rotation, 2187 shaft_length=shaft_length, 2188 shaft_width=shaft_width, 2189 head_length=head_length, 2190 head_width=head_width, 2191 fill=fill, 2192 ) 2193 2194 orients = end_pts - start_pts 2195 orients = utils.make3d(orients) 2196 2197 pts = Points(start_pts) 2198 super().__init__( 2199 pts, 2200 arr, 2201 orientation_array=orients, 2202 scale_by_vector_size=True, 2203 c=c, 2204 alpha=alpha, 2205 ) 2206 self.flat().lighting("off").pickable(False) 2207 if c is not None: 2208 self.color(c) 2209 self.name = "Arrows2D" 2210 2211 2212class FlatArrow(Ribbon): 2213 """ 2214 Build a 2D arrow in 3D space by joining two close lines. 2215 """ 2216 2217 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2218 """ 2219 Build a 2D arrow in 3D space by joining two close lines. 2220 2221 Examples: 2222 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2223 2224 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2225 """ 2226 if isinstance(line1, Points): 2227 line1 = line1.vertices 2228 if isinstance(line2, Points): 2229 line2 = line2.vertices 2230 2231 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2232 2233 v = (sm1 - sm2) / 3 * tip_width 2234 p1 = sm1 + v 2235 p2 = sm2 - v 2236 pm1 = (sm1 + sm2) / 2 2237 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2238 pm12 = pm1 - pm2 2239 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2240 2241 line1.append(p1) 2242 line1.append(tip) 2243 line2.append(p2) 2244 line2.append(tip) 2245 resm = max(100, len(line1)) 2246 2247 super().__init__(line1, line2, res=(resm, 1)) 2248 self.phong().lighting("off") 2249 self.actor.PickableOff() 2250 self.actor.DragableOff() 2251 self.name = "FlatArrow" 2252 2253 2254class Triangle(Mesh): 2255 """Create a triangle from 3 points in space.""" 2256 2257 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2258 """Create a triangle from 3 points in space.""" 2259 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2260 self.properties.LightingOff() 2261 self.name = "Triangle" 2262 2263 2264class Polygon(Mesh): 2265 """ 2266 Build a polygon in the `xy` plane. 2267 """ 2268 2269 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2270 """ 2271 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2272 2273 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2274 """ 2275 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2276 pts = pol2cart(np.ones_like(t) * r, t).T 2277 faces = [list(range(nsides))] 2278 # do not use: vtkRegularPolygonSource 2279 super().__init__([pts, faces], c, alpha) 2280 if len(pos) == 2: 2281 pos = (pos[0], pos[1], 0) 2282 self.pos(pos) 2283 self.properties.LightingOff() 2284 self.name = "Polygon " + str(nsides) 2285 2286 2287class Circle(Polygon): 2288 """ 2289 Build a Circle of radius `r`. 2290 """ 2291 2292 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2293 """ 2294 Build a Circle of radius `r`. 2295 """ 2296 super().__init__(pos, nsides=res, r=r) 2297 2298 self.nr_of_points = 0 2299 self.va = 0 2300 self.vb = 0 2301 self.axis1: List[float] = [] 2302 self.axis2: List[float] = [] 2303 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2304 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2305 self.alpha(alpha).c(c) 2306 self.name = "Circle" 2307 2308 def acircularity(self) -> float: 2309 """ 2310 Return a measure of how different an ellipse is from a circle. 2311 Values close to zero correspond to a circular object. 2312 """ 2313 a, b = self.va, self.vb 2314 value = 0.0 2315 if a+b: 2316 value = ((a-b)/(a+b))**2 2317 return value 2318 2319class GeoCircle(Polygon): 2320 """ 2321 Build a Circle of radius `r`. 2322 """ 2323 2324 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2325 """ 2326 Build a Circle of radius `r` as projected on a geographic map. 2327 Circles near the poles will look very squashed. 2328 2329 See example: 2330 ```bash 2331 vedo -r earthquake 2332 ``` 2333 """ 2334 coords = [] 2335 sinr, cosr = np.sin(r), np.cos(r) 2336 sinlat, coslat = np.sin(lat), np.cos(lat) 2337 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2338 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2339 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2340 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2341 2342 super().__init__(nsides=res, c=c, alpha=alpha) 2343 self.vertices = coords # warp polygon points to match geo projection 2344 self.name = "Circle" 2345 2346 2347class Star(Mesh): 2348 """ 2349 Build a 2D star shape. 2350 """ 2351 2352 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2353 """ 2354 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2355 2356 If line is True then only build the outer line (no internal surface meshing). 2357 2358 Example: 2359 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2360 2361 ![](https://vedo.embl.es/images/basic/extrude.png) 2362 """ 2363 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2364 x, y = pol2cart(np.ones_like(t) * r2, t) 2365 pts = np.c_[x, y, np.zeros_like(x)] 2366 2367 apts = [] 2368 for i, p in enumerate(pts): 2369 apts.append(p) 2370 if i + 1 < n: 2371 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2372 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2373 2374 if line: 2375 apts.append(pts[0]) 2376 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2377 super().__init__(poly, c, alpha) 2378 self.lw(2) 2379 else: 2380 apts.append((0, 0, 0)) 2381 cells = [] 2382 for i in range(2 * n - 1): 2383 cell = [2 * n, i, i + 1] 2384 cells.append(cell) 2385 cells.append([2 * n, i + 1, 0]) 2386 super().__init__([apts, cells], c, alpha) 2387 2388 if len(pos) == 2: 2389 pos = (pos[0], pos[1], 0) 2390 2391 self.properties.LightingOff() 2392 self.name = "Star" 2393 2394 2395class Disc(Mesh): 2396 """ 2397 Build a 2D disc. 2398 """ 2399 2400 def __init__( 2401 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2402 ) -> None: 2403 """ 2404 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2405 2406 Set `res` as the resolution in R and Phi (can be a list). 2407 2408 Use `angle_range` to create a disc sector between the 2 specified angles. 2409 2410 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2411 """ 2412 if utils.is_sequence(res): 2413 res_r, res_phi = res 2414 else: 2415 res_r, res_phi = res, 12 * res 2416 2417 if len(angle_range) == 0: 2418 ps = vtki.new("DiskSource") 2419 else: 2420 ps = vtki.new("SectorSource") 2421 ps.SetStartAngle(angle_range[0]) 2422 ps.SetEndAngle(angle_range[1]) 2423 2424 ps.SetInnerRadius(r1) 2425 ps.SetOuterRadius(r2) 2426 ps.SetRadialResolution(res_r) 2427 ps.SetCircumferentialResolution(res_phi) 2428 ps.Update() 2429 super().__init__(ps.GetOutput(), c, alpha) 2430 self.flat() 2431 self.pos(utils.make3d(pos)) 2432 self.name = "Disc" 2433 2434 2435class Arc(Mesh): 2436 """ 2437 Build a 2D circular arc between 2 points. 2438 """ 2439 2440 def __init__( 2441 self, 2442 center, 2443 point1, 2444 point2=None, 2445 normal=None, 2446 angle=None, 2447 invert=False, 2448 res=50, 2449 c="gray4", 2450 alpha=1.0, 2451 ) -> None: 2452 """ 2453 Build a 2D circular arc between 2 points `point1` and `point2`. 2454 2455 If `normal` is specified then `center` is ignored, and 2456 normal vector, a starting `point1` (polar vector) 2457 and an angle defining the arc length need to be assigned. 2458 2459 Arc spans the shortest angular sector point1 and point2, 2460 if `invert=True`, then the opposite happens. 2461 """ 2462 if len(point1) == 2: 2463 point1 = (point1[0], point1[1], 0) 2464 if point2 is not None and len(point2) == 2: 2465 point2 = (point2[0], point2[1], 0) 2466 2467 ar = vtki.new("ArcSource") 2468 if point2 is not None: 2469 self.top = point2 2470 point2 = point2 - np.asarray(point1) 2471 ar.UseNormalAndAngleOff() 2472 ar.SetPoint1([0, 0, 0]) 2473 ar.SetPoint2(point2) 2474 # ar.SetCenter(center) 2475 elif normal is not None and angle is not None: 2476 ar.UseNormalAndAngleOn() 2477 ar.SetAngle(angle) 2478 ar.SetPolarVector(point1) 2479 ar.SetNormal(normal) 2480 else: 2481 vedo.logger.error("incorrect input combination") 2482 return 2483 ar.SetNegative(invert) 2484 ar.SetResolution(res) 2485 ar.Update() 2486 2487 super().__init__(ar.GetOutput(), c, alpha) 2488 self.pos(center) 2489 self.lw(2).lighting("off") 2490 self.name = "Arc" 2491 2492 2493class IcoSphere(Mesh): 2494 """ 2495 Create a sphere made of a uniform triangle mesh. 2496 """ 2497 2498 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2499 """ 2500 Create a sphere made of a uniform triangle mesh 2501 (from recursive subdivision of an icosahedron). 2502 2503 Example: 2504 ```python 2505 from vedo import * 2506 icos = IcoSphere(subdivisions=3) 2507 icos.compute_quality().cmap('coolwarm') 2508 icos.show(axes=1).close() 2509 ``` 2510 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2511 """ 2512 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2513 2514 t = (1.0 + np.sqrt(5.0)) / 2.0 2515 points = np.array( 2516 [ 2517 [-1, t, 0], 2518 [1, t, 0], 2519 [-1, -t, 0], 2520 [1, -t, 0], 2521 [0, -1, t], 2522 [0, 1, t], 2523 [0, -1, -t], 2524 [0, 1, -t], 2525 [t, 0, -1], 2526 [t, 0, 1], 2527 [-t, 0, -1], 2528 [-t, 0, 1], 2529 ] 2530 ) 2531 faces = [ 2532 [0, 11, 5], 2533 [0, 5, 1], 2534 [0, 1, 7], 2535 [0, 7, 10], 2536 [0, 10, 11], 2537 [1, 5, 9], 2538 [5, 11, 4], 2539 [11, 10, 2], 2540 [10, 7, 6], 2541 [7, 1, 8], 2542 [3, 9, 4], 2543 [3, 4, 2], 2544 [3, 2, 6], 2545 [3, 6, 8], 2546 [3, 8, 9], 2547 [4, 9, 5], 2548 [2, 4, 11], 2549 [6, 2, 10], 2550 [8, 6, 7], 2551 [9, 8, 1], 2552 ] 2553 super().__init__([points * r, faces], c=c, alpha=alpha) 2554 2555 for _ in range(subdivisions): 2556 self.subdivide(method=1) 2557 pts = utils.versor(self.vertices) * r 2558 self.vertices = pts 2559 2560 self.pos(pos) 2561 self.name = "IcoSphere" 2562 2563 2564class Sphere(Mesh): 2565 """ 2566 Build a sphere. 2567 """ 2568 2569 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2570 """ 2571 Build a sphere at position `pos` of radius `r`. 2572 2573 Arguments: 2574 r : (float) 2575 sphere radius 2576 res : (int, list) 2577 resolution in phi, resolution in theta is by default `2*res` 2578 quads : (bool) 2579 sphere mesh will be made of quads instead of triangles 2580 2581 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2582 """ 2583 if len(pos) == 2: 2584 pos = np.asarray([pos[0], pos[1], 0]) 2585 2586 self.radius = r # used by fitSphere 2587 self.center = pos 2588 self.residue = 0 2589 2590 if quads: 2591 res = max(res, 4) 2592 img = vtki.vtkImageData() 2593 img.SetDimensions(res - 1, res - 1, res - 1) 2594 rs = 1.0 / (res - 2) 2595 img.SetSpacing(rs, rs, rs) 2596 gf = vtki.new("GeometryFilter") 2597 gf.SetInputData(img) 2598 gf.Update() 2599 super().__init__(gf.GetOutput(), c, alpha) 2600 self.lw(0.1) 2601 2602 cgpts = self.vertices - (0.5, 0.5, 0.5) 2603 2604 x, y, z = cgpts.T 2605 x = x * (1 + x * x) / 2 2606 y = y * (1 + y * y) / 2 2607 z = z * (1 + z * z) / 2 2608 _, theta, phi = cart2spher(x, y, z) 2609 2610 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2611 self.vertices = pts 2612 2613 else: 2614 if utils.is_sequence(res): 2615 res_t, res_phi = res 2616 else: 2617 res_t, res_phi = 2 * res, res 2618 2619 ss = vtki.new("SphereSource") 2620 ss.SetRadius(r) 2621 ss.SetThetaResolution(res_t) 2622 ss.SetPhiResolution(res_phi) 2623 ss.Update() 2624 2625 super().__init__(ss.GetOutput(), c, alpha) 2626 2627 self.phong() 2628 self.pos(pos) 2629 self.name = "Sphere" 2630 2631 2632class Spheres(Mesh): 2633 """ 2634 Build a large set of spheres. 2635 """ 2636 2637 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2638 """ 2639 Build a (possibly large) set of spheres at `centers` of radius `r`. 2640 2641 Either `c` or `r` can be a list of RGB colors or radii. 2642 2643 Examples: 2644 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2645 2646 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2647 """ 2648 2649 if isinstance(centers, Points): 2650 centers = centers.vertices 2651 centers = np.asarray(centers, dtype=float) 2652 base = centers[0] 2653 2654 cisseq = False 2655 if utils.is_sequence(c): 2656 cisseq = True 2657 2658 if cisseq: 2659 if len(centers) != len(c): 2660 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2661 raise RuntimeError() 2662 2663 risseq = False 2664 if utils.is_sequence(r): 2665 risseq = True 2666 2667 if risseq: 2668 if len(centers) != len(r): 2669 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2670 raise RuntimeError() 2671 if cisseq and risseq: 2672 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2673 raise RuntimeError() 2674 2675 src = vtki.new("SphereSource") 2676 if not risseq: 2677 src.SetRadius(r) 2678 if utils.is_sequence(res): 2679 res_t, res_phi = res 2680 else: 2681 res_t, res_phi = 2 * res, res 2682 2683 src.SetThetaResolution(res_t) 2684 src.SetPhiResolution(res_phi) 2685 src.Update() 2686 2687 psrc = vtki.new("PointSource") 2688 psrc.SetNumberOfPoints(len(centers)) 2689 psrc.Update() 2690 pd = psrc.GetOutput() 2691 vpts = pd.GetPoints() 2692 2693 glyph = vtki.vtkGlyph3D() 2694 glyph.SetSourceConnection(src.GetOutputPort()) 2695 2696 if cisseq: 2697 glyph.SetColorModeToColorByScalar() 2698 ucols = vtki.vtkUnsignedCharArray() 2699 ucols.SetNumberOfComponents(3) 2700 ucols.SetName("Colors") 2701 for acol in c: 2702 cx, cy, cz = get_color(acol) 2703 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2704 pd.GetPointData().AddArray(ucols) 2705 pd.GetPointData().SetActiveScalars("Colors") 2706 glyph.ScalingOff() 2707 elif risseq: 2708 glyph.SetScaleModeToScaleByScalar() 2709 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2710 urads.SetName("Radii") 2711 pd.GetPointData().AddArray(urads) 2712 pd.GetPointData().SetActiveScalars("Radii") 2713 2714 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2715 2716 glyph.SetInputData(pd) 2717 glyph.Update() 2718 2719 super().__init__(glyph.GetOutput(), alpha=alpha) 2720 self.pos(base) 2721 self.phong() 2722 if cisseq: 2723 self.mapper.ScalarVisibilityOn() 2724 else: 2725 self.mapper.ScalarVisibilityOff() 2726 self.c(c) 2727 self.name = "Spheres" 2728 2729 2730class Earth(Mesh): 2731 """ 2732 Build a textured mesh representing the Earth. 2733 """ 2734 2735 def __init__(self, style=1, r=1.0) -> None: 2736 """ 2737 Build a textured mesh representing the Earth. 2738 2739 Example: 2740 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2741 2742 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2743 """ 2744 tss = vtki.new("TexturedSphereSource") 2745 tss.SetRadius(r) 2746 tss.SetThetaResolution(72) 2747 tss.SetPhiResolution(36) 2748 tss.Update() 2749 super().__init__(tss.GetOutput(), c="w") 2750 atext = vtki.vtkTexture() 2751 pnm_reader = vtki.new("JPEGReader") 2752 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2753 pnm_reader.SetFileName(fn) 2754 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2755 atext.InterpolateOn() 2756 self.texture(atext) 2757 self.name = "Earth" 2758 2759 2760class Ellipsoid(Mesh): 2761 """Build a 3D ellipsoid.""" 2762 def __init__( 2763 self, 2764 pos=(0, 0, 0), 2765 axis1=(0.5, 0, 0), 2766 axis2=(0, 1, 0), 2767 axis3=(0, 0, 1.5), 2768 res=24, 2769 c="cyan4", 2770 alpha=1.0, 2771 ) -> None: 2772 """ 2773 Build a 3D ellipsoid centered at position `pos`. 2774 2775 Arguments: 2776 axis1 : (list) 2777 First axis. Length corresponds to semi-axis. 2778 axis2 : (list) 2779 Second axis. Length corresponds to semi-axis. 2780 axis3 : (list) 2781 Third axis. Length corresponds to semi-axis. 2782 """ 2783 self.center = utils.make3d(pos) 2784 2785 self.axis1 = utils.make3d(axis1) 2786 self.axis2 = utils.make3d(axis2) 2787 self.axis3 = utils.make3d(axis3) 2788 2789 self.va = np.linalg.norm(self.axis1) 2790 self.vb = np.linalg.norm(self.axis2) 2791 self.vc = np.linalg.norm(self.axis3) 2792 2793 self.va_error = 0 2794 self.vb_error = 0 2795 self.vc_error = 0 2796 2797 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2798 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2799 2800 if utils.is_sequence(res): 2801 res_t, res_phi = res 2802 else: 2803 res_t, res_phi = 2 * res, res 2804 2805 elli_source = vtki.new("SphereSource") 2806 elli_source.SetRadius(1) 2807 elli_source.SetThetaResolution(res_t) 2808 elli_source.SetPhiResolution(res_phi) 2809 elli_source.Update() 2810 2811 super().__init__(elli_source.GetOutput(), c, alpha) 2812 2813 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2814 lt = LinearTransform(matrix).translate(pos) 2815 self.apply_transform(lt) 2816 self.name = "Ellipsoid" 2817 2818 def asphericity(self) -> float: 2819 """ 2820 Return a measure of how different an ellipsoid is from a sphere. 2821 Values close to zero correspond to a spheric object. 2822 """ 2823 a, b, c = self.va, self.vb, self.vc 2824 asp = ( ((a-b)/(a+b))**2 2825 + ((a-c)/(a+c))**2 2826 + ((b-c)/(b+c))**2 ) / 3. * 4. 2827 return float(asp) 2828 2829 def asphericity_error(self) -> float: 2830 """ 2831 Calculate statistical error on the asphericity value. 2832 2833 Errors on the main axes are stored in 2834 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2835 """ 2836 a, b, c = self.va, self.vb, self.vc 2837 sqrtn = np.sqrt(self.nr_of_points) 2838 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2839 2840 # from sympy import * 2841 # init_printing(use_unicode=True) 2842 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2843 # L = ( 2844 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2845 # / 3 * 4) 2846 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2847 # print(dl2) 2848 # exit() 2849 2850 dL2 = ( 2851 ea ** 2 2852 * ( 2853 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2854 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2855 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2856 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2857 ) ** 2 2858 + eb ** 2 2859 * ( 2860 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2861 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2862 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2863 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2864 ) ** 2 2865 + ec ** 2 2866 * ( 2867 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2868 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2869 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2870 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2871 ) ** 2 2872 ) 2873 err = np.sqrt(dL2) 2874 self.va_error = ea 2875 self.vb_error = eb 2876 self.vc_error = ec 2877 return err 2878 2879 2880class Grid(Mesh): 2881 """ 2882 An even or uneven 2D grid. 2883 """ 2884 2885 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2886 """ 2887 Create an even or uneven 2D grid. 2888 Can also be created from a `np.mgrid` object (see example). 2889 2890 Arguments: 2891 pos : (list, Points, Mesh) 2892 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2893 s : (float, list) 2894 if a float is provided it is interpreted as the total size along x and y, 2895 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2896 In this case keyword `res` is ignored (see example below). 2897 res : (list) 2898 resolutions along x and y, e.i. the number of subdivisions 2899 lw : (int) 2900 line width 2901 2902 Example: 2903 ```python 2904 from vedo import * 2905 xcoords = np.arange(0, 2, 0.2) 2906 ycoords = np.arange(0, 1, 0.2) 2907 sqrtx = sqrt(xcoords) 2908 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2909 grid.show(axes=8).close() 2910 2911 # Can also create a grid from a np.mgrid: 2912 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2913 vgrid = Grid(s=(X[:,0], Y[0])) 2914 vgrid.show(axes=8).close() 2915 ``` 2916 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2917 """ 2918 resx, resy = res 2919 sx, sy = s 2920 2921 try: 2922 bb = pos.bounds() 2923 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2924 sx = bb[1] - bb[0] 2925 sy = bb[3] - bb[2] 2926 except AttributeError: 2927 pass 2928 2929 if len(pos) == 2: 2930 pos = (pos[0], pos[1], 0) 2931 elif len(pos) in [4,6]: # passing a bounding box 2932 bb = pos 2933 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2934 sx = bb[1] - bb[0] 2935 sy = bb[3] - bb[2] 2936 if len(pos)==6: 2937 pos[2] = bb[4] - bb[5] 2938 2939 if utils.is_sequence(sx) and utils.is_sequence(sy): 2940 verts = [] 2941 for y in sy: 2942 for x in sx: 2943 verts.append([x, y, 0]) 2944 faces = [] 2945 n = len(sx) 2946 m = len(sy) 2947 for j in range(m - 1): 2948 j1n = (j + 1) * n 2949 for i in range(n - 1): 2950 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2951 2952 super().__init__([verts, faces], c, alpha) 2953 2954 else: 2955 ps = vtki.new("PlaneSource") 2956 ps.SetResolution(resx, resy) 2957 ps.Update() 2958 2959 t = vtki.vtkTransform() 2960 t.Translate(pos) 2961 t.Scale(sx, sy, 1) 2962 2963 tf = vtki.new("TransformPolyDataFilter") 2964 tf.SetInputData(ps.GetOutput()) 2965 tf.SetTransform(t) 2966 tf.Update() 2967 2968 super().__init__(tf.GetOutput(), c, alpha) 2969 2970 self.wireframe().lw(lw) 2971 self.properties.LightingOff() 2972 self.name = "Grid" 2973 2974 2975class Plane(Mesh): 2976 """Create a plane in space.""" 2977 2978 def __init__( 2979 self, 2980 pos=(0, 0, 0), 2981 normal=(0, 0, 1), 2982 s=(1, 1), 2983 res=(1, 1), 2984 c="gray5", alpha=1.0, 2985 ) -> None: 2986 """ 2987 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 2988 to vector `normal` so that it passes through point `pos`. 2989 2990 Arguments: 2991 pos : (list) 2992 position of the plane center 2993 normal : (list) 2994 normal vector to the plane 2995 s : (list) 2996 size of the plane along x and y 2997 res : (list) 2998 resolution of the plane along x and y 2999 """ 3000 if isinstance(pos, vtki.vtkPolyData): 3001 super().__init__(pos, c, alpha) 3002 # self.transform = LinearTransform().translate(pos) 3003 3004 else: 3005 ps = vtki.new("PlaneSource") 3006 ps.SetResolution(res[0], res[1]) 3007 tri = vtki.new("TriangleFilter") 3008 tri.SetInputConnection(ps.GetOutputPort()) 3009 tri.Update() 3010 3011 super().__init__(tri.GetOutput(), c, alpha) 3012 3013 pos = utils.make3d(pos) 3014 normal = np.asarray(normal, dtype=float) 3015 axis = normal / np.linalg.norm(normal) 3016 theta = np.arccos(axis[2]) 3017 phi = np.arctan2(axis[1], axis[0]) 3018 3019 t = LinearTransform() 3020 t.scale([s[0], s[1], 1]) 3021 t.rotate_y(np.rad2deg(theta)) 3022 t.rotate_z(np.rad2deg(phi)) 3023 t.translate(pos) 3024 self.apply_transform(t) 3025 3026 self.lighting("off") 3027 self.name = "Plane" 3028 self.variance = 0 3029 3030 def clone(self, deep=True) -> "Plane": 3031 newplane = Plane() 3032 if deep: 3033 newplane.dataset.DeepCopy(self.dataset) 3034 else: 3035 newplane.dataset.ShallowCopy(self.dataset) 3036 newplane.copy_properties_from(self) 3037 newplane.transform = self.transform.clone() 3038 newplane.variance = 0 3039 return newplane 3040 3041 @property 3042 def normal(self) -> np.ndarray: 3043 pts = self.vertices 3044 AB = pts[1] - pts[0] 3045 AC = pts[2] - pts[0] 3046 normal = np.cross(AB, AC) 3047 normal = normal / np.linalg.norm(normal) 3048 return normal 3049 3050 @property 3051 def center(self) -> np.ndarray: 3052 pts = self.vertices 3053 return np.mean(pts, axis=0) 3054 3055 def contains(self, points, tol=0) -> np.ndarray: 3056 """ 3057 Check if each of the provided point lies on this plane. 3058 `points` is an array of shape (n, 3). 3059 """ 3060 points = np.array(points, dtype=float) 3061 bounds = self.vertices 3062 3063 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3064 3065 for i in [1, 3]: 3066 AB = bounds[i] - bounds[0] 3067 AP = points - bounds[0] 3068 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3069 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3070 mask = np.logical_and(mask, mask_l) 3071 mask = np.logical_and(mask, mask_g) 3072 return mask 3073 3074 3075class Rectangle(Mesh): 3076 """ 3077 Build a rectangle in the xy plane. 3078 """ 3079 3080 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3081 """ 3082 Build a rectangle in the xy plane identified by any two corner points. 3083 3084 Arguments: 3085 p1 : (list) 3086 bottom-left position of the corner 3087 p2 : (list) 3088 top-right position of the corner 3089 radius : (float, list) 3090 smoothing radius of the corner in world units. 3091 A list can be passed with 4 individual values. 3092 """ 3093 if len(p1) == 2: 3094 p1 = np.array([p1[0], p1[1], 0.0]) 3095 else: 3096 p1 = np.array(p1, dtype=float) 3097 if len(p2) == 2: 3098 p2 = np.array([p2[0], p2[1], 0.0]) 3099 else: 3100 p2 = np.array(p2, dtype=float) 3101 3102 self.corner1 = p1 3103 self.corner2 = p2 3104 3105 color = c 3106 smoothr = False 3107 risseq = False 3108 if utils.is_sequence(radius): 3109 risseq = True 3110 smoothr = True 3111 if max(radius) == 0: 3112 smoothr = False 3113 elif radius: 3114 smoothr = True 3115 3116 if not smoothr: 3117 radius = None 3118 self.radius = radius 3119 3120 if smoothr: 3121 r = radius 3122 if not risseq: 3123 r = [r, r, r, r] 3124 rd, ra, rb, rc = r 3125 3126 if p1[0] > p2[0]: # flip p1 - p2 3127 p1, p2 = p2, p1 3128 if p1[1] > p2[1]: # flip p1y - p2y 3129 p1[1], p2[1] = p2[1], p1[1] 3130 3131 px, py, _ = p2 - p1 3132 k = min(px / 2, py / 2) 3133 ra = min(abs(ra), k) 3134 rb = min(abs(rb), k) 3135 rc = min(abs(rc), k) 3136 rd = min(abs(rd), k) 3137 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3138 betas = np.split(beta, 4) 3139 rrx = np.cos(betas) 3140 rry = np.sin(betas) 3141 3142 q1 = (rd, 0) 3143 # q2 = (px-ra, 0) 3144 q3 = (px, ra) 3145 # q4 = (px, py-rb) 3146 q5 = (px - rb, py) 3147 # q6 = (rc, py) 3148 q7 = (0, py - rc) 3149 # q8 = (0, rd) 3150 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3151 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3152 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3153 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3154 3155 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3156 faces = [list(range(len(pts)))] 3157 else: 3158 p1r = np.array([p2[0], p1[1], 0.0]) 3159 p2l = np.array([p1[0], p2[1], 0.0]) 3160 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3161 faces = [(0, 1, 2, 3)] 3162 3163 super().__init__([pts, faces], color, alpha) 3164 self.pos(p1) 3165 self.properties.LightingOff() 3166 self.name = "Rectangle" 3167 3168 3169class Box(Mesh): 3170 """ 3171 Build a box of specified dimensions. 3172 """ 3173 3174 def __init__( 3175 self, pos=(0, 0, 0), 3176 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3177 """ 3178 Build a box of dimensions `x=length, y=width and z=height`. 3179 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3180 3181 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3182 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3183 3184 Examples: 3185 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3186 3187 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3188 """ 3189 src = vtki.new("CubeSource") 3190 3191 if len(pos) == 2: 3192 pos = (pos[0], pos[1], 0) 3193 3194 if len(pos) == 6: 3195 src.SetBounds(pos) 3196 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3197 elif len(size) == 3: 3198 length, width, height = size 3199 src.SetXLength(length) 3200 src.SetYLength(width) 3201 src.SetZLength(height) 3202 src.SetCenter(pos) 3203 else: 3204 src.SetXLength(length) 3205 src.SetYLength(width) 3206 src.SetZLength(height) 3207 src.SetCenter(pos) 3208 3209 src.Update() 3210 pd = src.GetOutput() 3211 3212 tc = [ 3213 [0.0, 0.0], 3214 [1.0, 0.0], 3215 [0.0, 1.0], 3216 [1.0, 1.0], 3217 [1.0, 0.0], 3218 [0.0, 0.0], 3219 [1.0, 1.0], 3220 [0.0, 1.0], 3221 [1.0, 1.0], 3222 [1.0, 0.0], 3223 [0.0, 1.0], 3224 [0.0, 0.0], 3225 [0.0, 1.0], 3226 [0.0, 0.0], 3227 [1.0, 1.0], 3228 [1.0, 0.0], 3229 [1.0, 0.0], 3230 [0.0, 0.0], 3231 [1.0, 1.0], 3232 [0.0, 1.0], 3233 [0.0, 0.0], 3234 [1.0, 0.0], 3235 [0.0, 1.0], 3236 [1.0, 1.0], 3237 ] 3238 vtc = utils.numpy2vtk(tc) 3239 pd.GetPointData().SetTCoords(vtc) 3240 super().__init__(pd, c, alpha) 3241 self.transform = LinearTransform().translate(pos) 3242 self.name = "Box" 3243 3244 3245class Cube(Box): 3246 """Build a cube.""" 3247 3248 def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None: 3249 """Build a cube of size `side`.""" 3250 super().__init__(pos, side, side, side, (), c, alpha) 3251 self.name = "Cube" 3252 3253 3254class TessellatedBox(Mesh): 3255 """ 3256 Build a cubic `Mesh` made of quads. 3257 """ 3258 3259 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3260 """ 3261 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3262 3263 Arguments: 3264 pos : (list) 3265 position of the left bottom corner 3266 n : (int, list) 3267 number of subdivisions along each side 3268 spacing : (float) 3269 size of the side of the single quad in the 3 directions 3270 """ 3271 if utils.is_sequence(n): # slow 3272 img = vtki.vtkImageData() 3273 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3274 img.SetSpacing(spacing) 3275 gf = vtki.new("GeometryFilter") 3276 gf.SetInputData(img) 3277 gf.Update() 3278 poly = gf.GetOutput() 3279 else: # fast 3280 n -= 1 3281 tbs = vtki.new("TessellatedBoxSource") 3282 tbs.SetLevel(n) 3283 if len(bounds): 3284 tbs.SetBounds(bounds) 3285 else: 3286 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3287 tbs.QuadsOn() 3288 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3289 tbs.Update() 3290 poly = tbs.GetOutput() 3291 super().__init__(poly, c=c, alpha=alpha) 3292 self.pos(pos) 3293 self.lw(1).lighting("off") 3294 self.name = "TessellatedBox" 3295 3296 3297class Spring(Mesh): 3298 """ 3299 Build a spring model. 3300 """ 3301 3302 def __init__( 3303 self, 3304 start_pt=(0, 0, 0), 3305 end_pt=(1, 0, 0), 3306 coils=20, 3307 r1=0.1, 3308 r2=None, 3309 thickness=None, 3310 c="gray5", 3311 alpha=1.0, 3312 ) -> None: 3313 """ 3314 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3315 3316 Arguments: 3317 coils : (int) 3318 number of coils 3319 r1 : (float) 3320 radius at start point 3321 r2 : (float) 3322 radius at end point 3323 thickness : (float) 3324 thickness of the coil section 3325 """ 3326 start_pt = utils.make3d(start_pt) 3327 end_pt = utils.make3d(end_pt) 3328 3329 diff = end_pt - start_pt 3330 length = np.linalg.norm(diff) 3331 if not length: 3332 return 3333 if not r1: 3334 r1 = length / 20 3335 trange = np.linspace(0, length, num=50 * coils) 3336 om = 6.283 * (coils - 0.5) / length 3337 if not r2: 3338 r2 = r1 3339 pts = [] 3340 for t in trange: 3341 f = (length - t) / length 3342 rd = r1 * f + r2 * (1 - f) 3343 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3344 3345 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3346 diff = diff / length 3347 theta = np.arccos(diff[2]) 3348 phi = np.arctan2(diff[1], diff[0]) 3349 sp = Line(pts) 3350 3351 t = vtki.vtkTransform() 3352 t.Translate(start_pt) 3353 t.RotateZ(np.rad2deg(phi)) 3354 t.RotateY(np.rad2deg(theta)) 3355 3356 tf = vtki.new("TransformPolyDataFilter") 3357 tf.SetInputData(sp.dataset) 3358 tf.SetTransform(t) 3359 tf.Update() 3360 3361 tuf = vtki.new("TubeFilter") 3362 tuf.SetNumberOfSides(12) 3363 tuf.CappingOn() 3364 tuf.SetInputData(tf.GetOutput()) 3365 if not thickness: 3366 thickness = r1 / 10 3367 tuf.SetRadius(thickness) 3368 tuf.Update() 3369 3370 super().__init__(tuf.GetOutput(), c, alpha) 3371 3372 self.phong() 3373 self.base = np.array(start_pt, dtype=float) 3374 self.top = np.array(end_pt, dtype=float) 3375 self.name = "Spring" 3376 3377 3378class Cylinder(Mesh): 3379 """ 3380 Build a cylinder of specified height and radius. 3381 """ 3382 3383 def __init__( 3384 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3385 cap=True, res=24, c="teal3", alpha=1.0 3386 ) -> None: 3387 """ 3388 Build a cylinder of specified height and radius `r`, centered at `pos`. 3389 3390 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3391 centered at `v1` and top at `v2`. 3392 3393 Arguments: 3394 cap : (bool) 3395 enable/disable the caps of the cylinder 3396 res : (int) 3397 resolution of the cylinder sides 3398 3399 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3400 """ 3401 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3402 base = np.array(pos[0], dtype=float) 3403 top = np.array(pos[1], dtype=float) 3404 pos = (base + top) / 2 3405 height = np.linalg.norm(top - base) 3406 axis = top - base 3407 axis = utils.versor(axis) 3408 else: 3409 axis = utils.versor(axis) 3410 base = pos - axis * height / 2 3411 top = pos + axis * height / 2 3412 3413 cyl = vtki.new("CylinderSource") 3414 cyl.SetResolution(res) 3415 cyl.SetRadius(r) 3416 cyl.SetHeight(height) 3417 cyl.SetCapping(cap) 3418 cyl.Update() 3419 3420 theta = np.arccos(axis[2]) 3421 phi = np.arctan2(axis[1], axis[0]) 3422 t = vtki.vtkTransform() 3423 t.PostMultiply() 3424 t.RotateX(90) # put it along Z 3425 t.RotateY(np.rad2deg(theta)) 3426 t.RotateZ(np.rad2deg(phi)) 3427 t.Translate(pos) 3428 3429 tf = vtki.new("TransformPolyDataFilter") 3430 tf.SetInputData(cyl.GetOutput()) 3431 tf.SetTransform(t) 3432 tf.Update() 3433 3434 super().__init__(tf.GetOutput(), c, alpha) 3435 3436 self.phong() 3437 self.base = base 3438 self.top = top 3439 self.transform = LinearTransform().translate(pos) 3440 self.name = "Cylinder" 3441 3442 3443class Cone(Mesh): 3444 """Build a cone of specified radius and height.""" 3445 3446 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3447 res=48, c="green3", alpha=1.0) -> None: 3448 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3449 con = vtki.new("ConeSource") 3450 con.SetResolution(res) 3451 con.SetRadius(r) 3452 con.SetHeight(height) 3453 con.SetDirection(axis) 3454 con.Update() 3455 super().__init__(con.GetOutput(), c, alpha) 3456 self.phong() 3457 if len(pos) == 2: 3458 pos = (pos[0], pos[1], 0) 3459 self.pos(pos) 3460 v = utils.versor(axis) * height / 2 3461 self.base = pos - v 3462 self.top = pos + v 3463 self.name = "Cone" 3464 3465 3466class Pyramid(Cone): 3467 """Build a pyramidal shape.""" 3468 3469 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3470 c="green3", alpha=1) -> None: 3471 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3472 super().__init__(pos, s, height, axis, 4, c, alpha) 3473 self.name = "Pyramid" 3474 3475 3476class Torus(Mesh): 3477 """ 3478 Build a toroidal shape. 3479 """ 3480 3481 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3482 """ 3483 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3484 If `quad=True` a quad-mesh is generated. 3485 """ 3486 if utils.is_sequence(res): 3487 res_u, res_v = res 3488 else: 3489 res_u, res_v = 3 * res, res 3490 3491 if quads: 3492 # https://github.com/marcomusy/vedo/issues/710 3493 3494 n = res_v 3495 m = res_u 3496 3497 theta = np.linspace(0, 2.0 * np.pi, n) 3498 phi = np.linspace(0, 2.0 * np.pi, m) 3499 theta, phi = np.meshgrid(theta, phi) 3500 t = r1 + r2 * np.cos(theta) 3501 x = t * np.cos(phi) 3502 y = t * np.sin(phi) 3503 z = r2 * np.sin(theta) 3504 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3505 3506 faces = [] 3507 for j in range(m - 1): 3508 j1n = (j + 1) * n 3509 for i in range(n - 1): 3510 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3511 3512 super().__init__([pts, faces], c, alpha) 3513 3514 else: 3515 rs = vtki.new("ParametricTorus") 3516 rs.SetRingRadius(r1) 3517 rs.SetCrossSectionRadius(r2) 3518 pfs = vtki.new("ParametricFunctionSource") 3519 pfs.SetParametricFunction(rs) 3520 pfs.SetUResolution(res_u) 3521 pfs.SetVResolution(res_v) 3522 pfs.Update() 3523 3524 super().__init__(pfs.GetOutput(), c, alpha) 3525 3526 self.phong() 3527 if len(pos) == 2: 3528 pos = (pos[0], pos[1], 0) 3529 self.pos(pos) 3530 self.name = "Torus" 3531 3532 3533class Paraboloid(Mesh): 3534 """ 3535 Build a paraboloid. 3536 """ 3537 3538 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3539 """ 3540 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3541 3542 Full volumetric expression is: 3543 `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` 3544 3545 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3546 """ 3547 quadric = vtki.new("Quadric") 3548 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3549 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3550 # + a3*x*y + a4*y*z + a5*x*z 3551 # + a6*x + a7*y + a8*z +a9 3552 sample = vtki.new("SampleFunction") 3553 sample.SetSampleDimensions(res, res, res) 3554 sample.SetImplicitFunction(quadric) 3555 3556 contours = vtki.new("ContourFilter") 3557 contours.SetInputConnection(sample.GetOutputPort()) 3558 contours.GenerateValues(1, 0.01, 0.01) 3559 contours.Update() 3560 3561 super().__init__(contours.GetOutput(), c, alpha) 3562 self.compute_normals().phong() 3563 self.mapper.ScalarVisibilityOff() 3564 self.pos(pos) 3565 self.name = "Paraboloid" 3566 3567 3568class Hyperboloid(Mesh): 3569 """ 3570 Build a hyperboloid. 3571 """ 3572 3573 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3574 """ 3575 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3576 3577 Full volumetric expression is: 3578 `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` 3579 """ 3580 q = vtki.new("Quadric") 3581 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3582 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3583 # + a3*x*y + a4*y*z + a5*x*z 3584 # + a6*x + a7*y + a8*z +a9 3585 sample = vtki.new("SampleFunction") 3586 sample.SetSampleDimensions(res, res, res) 3587 sample.SetImplicitFunction(q) 3588 3589 contours = vtki.new("ContourFilter") 3590 contours.SetInputConnection(sample.GetOutputPort()) 3591 contours.GenerateValues(1, value, value) 3592 contours.Update() 3593 3594 super().__init__(contours.GetOutput(), c, alpha) 3595 self.compute_normals().phong() 3596 self.mapper.ScalarVisibilityOff() 3597 self.pos(pos) 3598 self.name = "Hyperboloid" 3599 3600 3601def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True) -> Any: 3602 """ 3603 Generate a marker shape. Typically used in association with `Glyph`. 3604 """ 3605 if isinstance(symbol, Mesh): 3606 return symbol.c(c).alpha(alpha).lighting("off") 3607 3608 if isinstance(symbol, int): 3609 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 3610 symbol = symbol % len(symbs) 3611 symbol = symbs[symbol] 3612 3613 if symbol == ".": 3614 mesh = Polygon(nsides=24, r=s * 0.6) 3615 elif symbol == "o": 3616 mesh = Polygon(nsides=24, r=s * 0.75) 3617 elif symbol == "O": 3618 mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3619 elif symbol == "0": 3620 m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3621 m2 = Circle(r=s * 0.36).reverse() 3622 mesh = merge(m1, m2) 3623 elif symbol == "p": 3624 mesh = Polygon(nsides=5, r=s) 3625 elif symbol == "*": 3626 mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled) 3627 elif symbol == "h": 3628 mesh = Polygon(nsides=6, r=s) 3629 elif symbol == "D": 3630 mesh = Polygon(nsides=4, r=s) 3631 elif symbol == "d": 3632 mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1]) 3633 elif symbol == "v": 3634 mesh = Polygon(nsides=3, r=s).rotate_z(180) 3635 elif symbol == "^": 3636 mesh = Polygon(nsides=3, r=s) 3637 elif symbol == ">": 3638 mesh = Polygon(nsides=3, r=s).rotate_z(-90) 3639 elif symbol == "<": 3640 mesh = Polygon(nsides=3, r=s).rotate_z(90) 3641 elif symbol == "s": 3642 mesh = Mesh( 3643 [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]] 3644 ).scale(s / 1.4) 3645 elif symbol == "x": 3646 mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3647 # mesh.rotate_z(45) 3648 elif symbol == "a": 3649 mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3650 else: 3651 mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0) 3652 mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha) 3653 if len(pos) == 2: 3654 pos = (pos[0], pos[1], 0) 3655 mesh.pos(pos) 3656 mesh.name = "Marker" 3657 return mesh 3658 3659 3660class Brace(Mesh): 3661 """ 3662 Create a brace (bracket) shape. 3663 """ 3664 3665 def __init__( 3666 self, 3667 q1, 3668 q2, 3669 style="}", 3670 padding1=0.0, 3671 font="Theemim", 3672 comment="", 3673 justify=None, 3674 angle=0.0, 3675 padding2=0.2, 3676 s=1.0, 3677 italic=0, 3678 c="k1", 3679 alpha=1.0, 3680 ) -> None: 3681 """ 3682 Create a brace (bracket) shape which spans from point q1 to point q2. 3683 3684 Arguments: 3685 q1 : (list) 3686 point 1. 3687 q2 : (list) 3688 point 2. 3689 style : (str) 3690 style of the bracket, eg. `{}, [], (), <>`. 3691 padding1 : (float) 3692 padding space in percent form the input points. 3693 font : (str) 3694 font type 3695 comment : (str) 3696 additional text to appear next to the brace symbol. 3697 justify : (str) 3698 specify the anchor point to justify text comment, e.g. "top-left". 3699 italic : float 3700 italicness of the text comment (can be a positive or negative number) 3701 angle : (float) 3702 rotation angle of text. Use `None` to keep it horizontal. 3703 padding2 : (float) 3704 padding space in percent form brace to text comment. 3705 s : (float) 3706 scale factor for the comment 3707 3708 Examples: 3709 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3710 3711 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3712 """ 3713 if isinstance(q1, vtki.vtkActor): 3714 q1 = q1.GetPosition() 3715 if isinstance(q2, vtki.vtkActor): 3716 q2 = q2.GetPosition() 3717 if len(q1) == 2: 3718 q1 = [q1[0], q1[1], 0.0] 3719 if len(q2) == 2: 3720 q2 = [q2[0], q2[1], 0.0] 3721 q1 = np.array(q1, dtype=float) 3722 q2 = np.array(q2, dtype=float) 3723 mq = (q1 + q2) / 2 3724 q1 = q1 - mq 3725 q2 = q2 - mq 3726 d = np.linalg.norm(q2 - q1) 3727 q2[2] = q1[2] 3728 3729 if style not in "{}[]()<>|I": 3730 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3731 style = "}" 3732 3733 flip = False 3734 if style in ["{", "[", "(", "<"]: 3735 flip = True 3736 i = ["{", "[", "(", "<"].index(style) 3737 style = ["}", "]", ")", ">"][i] 3738 3739 br = Text3D(style, font="Theemim", justify="center-left") 3740 br.scale([0.4, 1, 1]) 3741 3742 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3743 if flip: 3744 angler += 180 3745 3746 _, x1, y0, y1, _, _ = br.bounds() 3747 if comment: 3748 just = "center-top" 3749 if angle is None: 3750 angle = -angler + 90 3751 if not flip: 3752 angle += 180 3753 3754 if flip: 3755 angle += 180 3756 just = "center-bottom" 3757 if justify is not None: 3758 just = justify 3759 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3760 cx0, cx1 = cmt.xbounds() 3761 cmt.rotate_z(90 + angle) 3762 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3763 cmt.shift(x1 * (1 + padding2), 0, 0) 3764 poly = merge(br, cmt).dataset 3765 3766 else: 3767 poly = br.dataset 3768 3769 tr = vtki.vtkTransform() 3770 tr.Translate(mq) 3771 tr.RotateZ(angler) 3772 tr.Translate(padding1 * d, 0, 0) 3773 pscale = 1 3774 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3775 3776 tf = vtki.new("TransformPolyDataFilter") 3777 tf.SetInputData(poly) 3778 tf.SetTransform(tr) 3779 tf.Update() 3780 poly = tf.GetOutput() 3781 3782 super().__init__(poly, c, alpha) 3783 3784 self.base = q1 3785 self.top = q2 3786 self.name = "Brace" 3787 3788 3789class Star3D(Mesh): 3790 """ 3791 Build a 3D starred shape. 3792 """ 3793 3794 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3795 """ 3796 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3797 """ 3798 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3799 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3800 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3801 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3802 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3803 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3804 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3805 [10,1, 0],[10,11, 9]] 3806 3807 super().__init__([pts, fcs], c, alpha) 3808 self.rotate_x(90) 3809 self.scale(r).lighting("shiny") 3810 3811 if len(pos) == 2: 3812 pos = (pos[0], pos[1], 0) 3813 self.pos(pos) 3814 self.name = "Star3D" 3815 3816 3817class Cross3D(Mesh): 3818 """ 3819 Build a 3D cross shape. 3820 """ 3821 3822 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3823 """ 3824 Build a 3D cross shape, mainly useful as a 3D marker. 3825 """ 3826 if len(pos) == 2: 3827 pos = (pos[0], pos[1], 0) 3828 3829 c1 = Cylinder(r=thickness * s, height=2 * s) 3830 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3831 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3832 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3833 super().__init__(poly, c, alpha) 3834 self.name = "Cross3D" 3835 3836 3837class ParametricShape(Mesh): 3838 """ 3839 A set of built-in shapes mainly for illustration purposes. 3840 """ 3841 3842 def __init__(self, name, res=51, n=25, seed=1): 3843 """ 3844 A set of built-in shapes mainly for illustration purposes. 3845 3846 Name can be an integer or a string in this list: 3847 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3848 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3849 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3850 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3851 3852 Example: 3853 ```python 3854 from vedo import * 3855 settings.immediate_rendering = False 3856 plt = Plotter(N=18) 3857 for i in range(18): 3858 ps = ParametricShape(i).color(i) 3859 plt.at(i).show(ps, ps.name) 3860 plt.interactive().close() 3861 ``` 3862 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3863 """ 3864 3865 shapes = [ 3866 "Boy", 3867 "ConicSpiral", 3868 "CrossCap", 3869 "Enneper", 3870 "Figure8Klein", 3871 "Klein", 3872 "Dini", 3873 "Mobius", 3874 "RandomHills", 3875 "Roman", 3876 "SuperEllipsoid", 3877 "BohemianDome", 3878 "Bour", 3879 "CatalanMinimal", 3880 "Henneberg", 3881 "Kuen", 3882 "PluckerConoid", 3883 "Pseudosphere", 3884 ] 3885 3886 if isinstance(name, int): 3887 name = name % len(shapes) 3888 name = shapes[name] 3889 3890 if name == "Boy": 3891 ps = vtki.new("ParametricBoy") 3892 elif name == "ConicSpiral": 3893 ps = vtki.new("ParametricConicSpiral") 3894 elif name == "CrossCap": 3895 ps = vtki.new("ParametricCrossCap") 3896 elif name == "Dini": 3897 ps = vtki.new("ParametricDini") 3898 elif name == "Enneper": 3899 ps = vtki.new("ParametricEnneper") 3900 elif name == "Figure8Klein": 3901 ps = vtki.new("ParametricFigure8Klein") 3902 elif name == "Klein": 3903 ps = vtki.new("ParametricKlein") 3904 elif name == "Mobius": 3905 ps = vtki.new("ParametricMobius") 3906 ps.SetRadius(2.0) 3907 ps.SetMinimumV(-0.5) 3908 ps.SetMaximumV(0.5) 3909 elif name == "RandomHills": 3910 ps = vtki.new("ParametricRandomHills") 3911 ps.AllowRandomGenerationOn() 3912 ps.SetRandomSeed(seed) 3913 ps.SetNumberOfHills(n) 3914 elif name == "Roman": 3915 ps = vtki.new("ParametricRoman") 3916 elif name == "SuperEllipsoid": 3917 ps = vtki.new("ParametricSuperEllipsoid") 3918 ps.SetN1(0.5) 3919 ps.SetN2(0.4) 3920 elif name == "BohemianDome": 3921 ps = vtki.new("ParametricBohemianDome") 3922 ps.SetA(5.0) 3923 ps.SetB(1.0) 3924 ps.SetC(2.0) 3925 elif name == "Bour": 3926 ps = vtki.new("ParametricBour") 3927 elif name == "CatalanMinimal": 3928 ps = vtki.new("ParametricCatalanMinimal") 3929 elif name == "Henneberg": 3930 ps = vtki.new("ParametricHenneberg") 3931 elif name == "Kuen": 3932 ps = vtki.new("ParametricKuen") 3933 ps.SetDeltaV0(0.001) 3934 elif name == "PluckerConoid": 3935 ps = vtki.new("ParametricPluckerConoid") 3936 elif name == "Pseudosphere": 3937 ps = vtki.new("ParametricPseudosphere") 3938 else: 3939 vedo.logger.error(f"unknown ParametricShape {name}") 3940 return 3941 3942 pfs = vtki.new("ParametricFunctionSource") 3943 pfs.SetParametricFunction(ps) 3944 pfs.SetUResolution(res) 3945 pfs.SetVResolution(res) 3946 pfs.SetWResolution(res) 3947 pfs.SetScalarModeToZ() 3948 pfs.Update() 3949 3950 super().__init__(pfs.GetOutput()) 3951 3952 if name == "RandomHills": self.shift([0,-10,-2.25]) 3953 if name != 'Kuen': self.normalize() 3954 if name == 'Dini': self.scale(0.4) 3955 if name == 'Enneper': self.scale(0.4) 3956 if name == 'ConicSpiral': self.bc('tomato') 3957 self.name = name 3958 3959 3960@lru_cache(None) 3961def _load_font(font) -> np.ndarray: 3962 # print('_load_font()', font) 3963 3964 if utils.is_number(font): 3965 font = list(settings.font_parameters.keys())[int(font)] 3966 3967 if font.endswith(".npz"): # user passed font as a local path 3968 fontfile = font 3969 font = os.path.basename(font).split(".")[0] 3970 3971 elif font.startswith("https"): # user passed URL link, make it a path 3972 try: 3973 fontfile = vedo.file_io.download(font, verbose=False, force=False) 3974 font = os.path.basename(font).split(".")[0] 3975 except: 3976 vedo.logger.warning(f"font {font} not found") 3977 font = settings.default_font 3978 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 3979 3980 else: # user passed font by its standard name 3981 font = font[:1].upper() + font[1:] # capitalize first letter only 3982 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 3983 3984 if font not in settings.font_parameters.keys(): 3985 font = "Normografo" 3986 vedo.logger.warning( 3987 f"Unknown font: {font}\n" 3988 f"Available 3D fonts are: " 3989 f"{list(settings.font_parameters.keys())}\n" 3990 f"Using font {font} instead." 3991 ) 3992 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 3993 3994 if not settings.font_parameters[font]["islocal"]: 3995 font = "https://vedo.embl.es/fonts/" + font + ".npz" 3996 try: 3997 fontfile = vedo.file_io.download(font, verbose=False, force=False) 3998 font = os.path.basename(font).split(".")[0] 3999 except: 4000 vedo.logger.warning(f"font {font} not found") 4001 font = settings.default_font 4002 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4003 4004 ##### 4005 try: 4006 font_meshes = np.load(fontfile, allow_pickle=True)["font"][0] 4007 except: 4008 vedo.logger.warning(f"font name {font} not found.") 4009 raise RuntimeError 4010 return font_meshes 4011 4012 4013@lru_cache(None) 4014def _get_font_letter(font, letter): 4015 # print("_get_font_letter", font, letter) 4016 font_meshes = _load_font(font) 4017 try: 4018 pts, faces = font_meshes[letter] 4019 return utils.buildPolyData(pts, faces) 4020 except KeyError: 4021 return None 4022 4023 4024class Text3D(Mesh): 4025 """ 4026 Generate a 3D polygonal Mesh to represent a text string. 4027 """ 4028 4029 def __init__( 4030 self, 4031 txt, 4032 pos=(0, 0, 0), 4033 s=1.0, 4034 font="", 4035 hspacing=1.15, 4036 vspacing=2.15, 4037 depth=0.0, 4038 italic=False, 4039 justify="bottom-left", 4040 literal=False, 4041 c=None, 4042 alpha=1.0, 4043 ) -> None: 4044 """ 4045 Generate a 3D polygonal `Mesh` representing a text string. 4046 4047 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4048 Most Latex symbols are also supported. 4049 4050 Symbols `~ ^ _` are reserved modifiers: 4051 - use ~ to add a short space, 1/4 of the default empty space, 4052 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4053 4054 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4055 4056 More fonts at: https://vedo.embl.es/fonts/ 4057 4058 Arguments: 4059 pos : (list) 4060 position coordinates in 3D space 4061 s : (float) 4062 vertical size of the text (as scaling factor) 4063 depth : (float) 4064 text thickness (along z) 4065 italic : (bool), float 4066 italic font type (can be a signed float too) 4067 justify : (str) 4068 text justification as centering of the bounding box 4069 (bottom-left, bottom-right, top-left, top-right, centered) 4070 font : (str, int) 4071 some of the available 3D-polygonized fonts are: 4072 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4073 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4074 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4075 4076 Check for more at https://vedo.embl.es/fonts/ 4077 4078 Or type in your terminal `vedo --run fonts`. 4079 4080 Default is Normografo, which can be changed using `settings.default_font`. 4081 4082 hspacing : (float) 4083 horizontal spacing of the font 4084 vspacing : (float) 4085 vertical spacing of the font for multiple lines text 4086 literal : (bool) 4087 if set to True will ignore modifiers like _ or ^ 4088 4089 Examples: 4090 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4091 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4092 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4093 4094 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4095 4096 .. note:: Type `vedo -r fonts` for a demo. 4097 """ 4098 if len(pos) == 2: 4099 pos = (pos[0], pos[1], 0) 4100 4101 if c is None: # automatic black or white 4102 pli = vedo.plotter_instance 4103 if pli and pli.renderer: 4104 c = (0.9, 0.9, 0.9) 4105 if pli.renderer.GetGradientBackground(): 4106 bgcol = pli.renderer.GetBackground2() 4107 else: 4108 bgcol = pli.renderer.GetBackground() 4109 if np.sum(bgcol) > 1.5: 4110 c = (0.1, 0.1, 0.1) 4111 else: 4112 c = (0.6, 0.6, 0.6) 4113 4114 tpoly = self._get_text3d_poly( 4115 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4116 ) 4117 4118 super().__init__(tpoly, c, alpha) 4119 4120 self.pos(pos) 4121 self.lighting("off") 4122 4123 self.actor.PickableOff() 4124 self.actor.DragableOff() 4125 self.init_scale = s 4126 self.name = "Text3D" 4127 self.txt = txt 4128 self.justify = justify 4129 4130 def text( 4131 self, 4132 txt=None, 4133 s=1, 4134 font="", 4135 hspacing=1.15, 4136 vspacing=2.15, 4137 depth=0, 4138 italic=False, 4139 justify="", 4140 literal=False, 4141 ) -> "Text3D": 4142 """ 4143 Update the text and some of its properties. 4144 4145 Check [available fonts here](https://vedo.embl.es/fonts). 4146 """ 4147 if txt is None: 4148 return self.txt 4149 if not justify: 4150 justify = self.justify 4151 4152 poly = self._get_text3d_poly( 4153 txt, self.init_scale * s, font, hspacing, vspacing, 4154 depth, italic, justify, literal 4155 ) 4156 4157 # apply the current transformation to the new polydata 4158 tf = vtki.new("TransformPolyDataFilter") 4159 tf.SetInputData(poly) 4160 tf.SetTransform(self.transform.T) 4161 tf.Update() 4162 tpoly = tf.GetOutput() 4163 4164 self._update(tpoly) 4165 self.txt = txt 4166 return self 4167 4168 def _get_text3d_poly( 4169 self, 4170 txt, 4171 s=1, 4172 font="", 4173 hspacing=1.15, 4174 vspacing=2.15, 4175 depth=0, 4176 italic=False, 4177 justify="bottom-left", 4178 literal=False, 4179 ) -> vtki.vtkPolyData: 4180 if not font: 4181 font = settings.default_font 4182 4183 txt = str(txt) 4184 4185 if font == "VTK": ####################################### 4186 vtt = vtki.new("VectorText") 4187 vtt.SetText(txt) 4188 vtt.Update() 4189 tpoly = vtt.GetOutput() 4190 4191 else: ################################################### 4192 4193 stxt = set(txt) # check here if null or only spaces 4194 if not txt or (len(stxt) == 1 and " " in stxt): 4195 return vtki.vtkPolyData() 4196 4197 if italic is True: 4198 italic = 1 4199 4200 if isinstance(font, int): 4201 lfonts = list(settings.font_parameters.keys()) 4202 font = font % len(lfonts) 4203 font = lfonts[font] 4204 4205 if font not in settings.font_parameters.keys(): 4206 fpars = settings.font_parameters["Normografo"] 4207 else: 4208 fpars = settings.font_parameters[font] 4209 4210 # ad hoc adjustments 4211 mono = fpars["mono"] 4212 lspacing = fpars["lspacing"] 4213 hspacing *= fpars["hspacing"] 4214 fscale = fpars["fscale"] 4215 dotsep = fpars["dotsep"] 4216 4217 # replacements 4218 if ":" in txt: 4219 for r in _reps: 4220 txt = txt.replace(r[0], r[1]) 4221 4222 if not literal: 4223 reps2 = [ 4224 (r"\_", "┭"), # trick to protect ~ _ and ^ chars 4225 (r"\^", "┮"), # 4226 (r"\~", "┯"), # 4227 ("**", "^"), # order matters 4228 ("e+0", dotsep + "10^"), 4229 ("e-0", dotsep + "10^-"), 4230 ("E+0", dotsep + "10^"), 4231 ("E-0", dotsep + "10^-"), 4232 ("e+", dotsep + "10^"), 4233 ("e-", dotsep + "10^-"), 4234 ("E+", dotsep + "10^"), 4235 ("E-", dotsep + "10^-"), 4236 ] 4237 for r in reps2: 4238 txt = txt.replace(r[0], r[1]) 4239 4240 xmax, ymax, yshift, scale = 0.0, 0.0, 0.0, 1.0 4241 save_xmax = 0.0 4242 4243 notfounds = set() 4244 polyletters = [] 4245 ntxt = len(txt) 4246 for i, t in enumerate(txt): 4247 ########## 4248 if t == "┭": 4249 t = "_" 4250 elif t == "┮": 4251 t = "^" 4252 elif t == "┯": 4253 t = "~" 4254 elif t == "^" and not literal: 4255 if yshift < 0: 4256 xmax = save_xmax 4257 yshift = 0.9 * fscale 4258 scale = 0.5 4259 continue 4260 elif t == "_" and not literal: 4261 if yshift > 0: 4262 xmax = save_xmax 4263 yshift = -0.3 * fscale 4264 scale = 0.5 4265 continue 4266 elif (t in (" ", "\\n")) and yshift: 4267 yshift = 0.0 4268 scale = 1.0 4269 save_xmax = xmax 4270 if t == " ": 4271 continue 4272 elif t == "~": 4273 if i < ntxt - 1 and txt[i + 1] == "_": 4274 continue 4275 xmax += hspacing * scale * fscale / 4 4276 continue 4277 4278 ############ 4279 if t == " ": 4280 xmax += hspacing * scale * fscale 4281 4282 elif t == "\n": 4283 xmax = 0.0 4284 save_xmax = 0.0 4285 ymax -= vspacing 4286 4287 else: 4288 poly = _get_font_letter(font, t) 4289 if not poly: 4290 notfounds.add(t) 4291 xmax += hspacing * scale * fscale 4292 continue 4293 4294 if poly.GetNumberOfPoints() == 0: 4295 continue 4296 4297 tr = vtki.vtkTransform() 4298 tr.Translate(xmax, ymax + yshift, 0) 4299 pscale = scale * fscale / 1000 4300 tr.Scale(pscale, pscale, pscale) 4301 if italic: 4302 tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 4303 tf = vtki.new("TransformPolyDataFilter") 4304 tf.SetInputData(poly) 4305 tf.SetTransform(tr) 4306 tf.Update() 4307 poly = tf.GetOutput() 4308 polyletters.append(poly) 4309 4310 bx = poly.GetBounds() 4311 if mono: 4312 xmax += hspacing * scale * fscale 4313 else: 4314 xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing 4315 if yshift == 0: 4316 save_xmax = xmax 4317 4318 if len(polyletters) == 1: 4319 tpoly = polyletters[0] 4320 else: 4321 polyapp = vtki.new("AppendPolyData") 4322 for polyd in polyletters: 4323 polyapp.AddInputData(polyd) 4324 polyapp.Update() 4325 tpoly = polyapp.GetOutput() 4326 4327 if notfounds: 4328 wmsg = f"unavailable characters in font name '{font}': {notfounds}." 4329 wmsg += '\nType "vedo -r fonts" for a demo.' 4330 vedo.logger.warning(wmsg) 4331 4332 bb = tpoly.GetBounds() 4333 dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s 4334 shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2 4335 if "bottom" in justify: shift += np.array([ 0, dy, 0.]) 4336 if "top" in justify: shift += np.array([ 0,-dy, 0.]) 4337 if "left" in justify: shift += np.array([ dx, 0, 0.]) 4338 if "right" in justify: shift += np.array([-dx, 0, 0.]) 4339 4340 if tpoly.GetNumberOfPoints(): 4341 t = vtki.vtkTransform() 4342 t.PostMultiply() 4343 t.Scale(s, s, s) 4344 t.Translate(shift) 4345 tf = vtki.new("TransformPolyDataFilter") 4346 tf.SetInputData(tpoly) 4347 tf.SetTransform(t) 4348 tf.Update() 4349 tpoly = tf.GetOutput() 4350 4351 if depth: 4352 extrude = vtki.new("LinearExtrusionFilter") 4353 extrude.SetInputData(tpoly) 4354 extrude.SetExtrusionTypeToVectorExtrusion() 4355 extrude.SetVector(0, 0, 1) 4356 extrude.SetScaleFactor(depth * dy) 4357 extrude.Update() 4358 tpoly = extrude.GetOutput() 4359 4360 return tpoly 4361 4362 4363class TextBase: 4364 "Base class." 4365 4366 def __init__(self): 4367 "Do not instantiate this base class." 4368 4369 self.rendered_at = set() 4370 self.properties = None 4371 4372 self.name = "Text" 4373 self.filename = "" 4374 self.time = 0 4375 self.info = {} 4376 4377 if isinstance(settings.default_font, int): 4378 lfonts = list(settings.font_parameters.keys()) 4379 font = settings.default_font % len(lfonts) 4380 self.fontname = lfonts[font] 4381 else: 4382 self.fontname = settings.default_font 4383 4384 def angle(self, value: float): 4385 """Orientation angle in degrees""" 4386 self.properties.SetOrientation(value) 4387 return self 4388 4389 def line_spacing(self, value: float): 4390 """Set the extra spacing between lines 4391 expressed as a text height multiplicative factor.""" 4392 self.properties.SetLineSpacing(value) 4393 return self 4394 4395 def line_offset(self, value: float): 4396 """Set/Get the vertical offset (measured in pixels).""" 4397 self.properties.SetLineOffset(value) 4398 return self 4399 4400 def bold(self, value=True): 4401 """Set bold face""" 4402 self.properties.SetBold(value) 4403 return self 4404 4405 def italic(self, value=True): 4406 """Set italic face""" 4407 self.properties.SetItalic(value) 4408 return self 4409 4410 def shadow(self, offset=(1, -1)): 4411 """Text shadowing. Set to `None` to disable it.""" 4412 if offset is None: 4413 self.properties.ShadowOff() 4414 else: 4415 self.properties.ShadowOn() 4416 self.properties.SetShadowOffset(offset) 4417 return self 4418 4419 def color(self, c=None): 4420 """Set the text color""" 4421 if c is None: 4422 return get_color(self.properties.GetColor()) 4423 self.properties.SetColor(get_color(c)) 4424 return self 4425 4426 def c(self, color=None): 4427 """Set the text color""" 4428 if color is None: 4429 return get_color(self.properties.GetColor()) 4430 return self.color(color) 4431 4432 def alpha(self, value: float): 4433 """Set the text opacity""" 4434 self.properties.SetBackgroundOpacity(value) 4435 return self 4436 4437 def background(self, color="k9", alpha=1.0): 4438 """Text background. Set to `None` to disable it.""" 4439 bg = get_color(color) 4440 if color is None: 4441 self.properties.SetBackgroundOpacity(0) 4442 else: 4443 self.properties.SetBackgroundColor(bg) 4444 if alpha: 4445 self.properties.SetBackgroundOpacity(alpha) 4446 return self 4447 4448 def frame(self, color="k1", lw=2): 4449 """Border color and width""" 4450 if color is None: 4451 self.properties.FrameOff() 4452 else: 4453 c = get_color(color) 4454 self.properties.FrameOn() 4455 self.properties.SetFrameColor(c) 4456 self.properties.SetFrameWidth(lw) 4457 return self 4458 4459 def font(self, font: str): 4460 """Text font face""" 4461 if isinstance(font, int): 4462 lfonts = list(settings.font_parameters.keys()) 4463 n = font % len(lfonts) 4464 font = lfonts[n] 4465 self.fontname = font 4466 4467 if not font: # use default font 4468 font = self.fontname 4469 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4470 elif font.startswith("https"): # user passed URL link, make it a path 4471 fpath = vedo.file_io.download(font, verbose=False, force=False) 4472 elif font.endswith(".ttf"): # user passing a local path to font file 4473 fpath = font 4474 else: # user passing name of preset font 4475 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4476 4477 if font == "Courier": self.properties.SetFontFamilyToCourier() 4478 elif font == "Times": self.properties.SetFontFamilyToTimes() 4479 elif font == "Arial": self.properties.SetFontFamilyToArial() 4480 else: 4481 fpath = utils.get_font_path(font) 4482 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4483 self.properties.SetFontFile(fpath) 4484 self.fontname = font # io.tonumpy() uses it 4485 4486 return self 4487 4488 def on(self): 4489 """Make text visible""" 4490 self.actor.SetVisibility(True) 4491 return self 4492 4493 def off(self): 4494 """Make text invisible""" 4495 self.actor.SetVisibility(False) 4496 return self 4497 4498class Text2D(TextBase, vedo.visual.Actor2D): 4499 """ 4500 Create a 2D text object. 4501 """ 4502 def __init__( 4503 self, 4504 txt="", 4505 pos="top-left", 4506 s=1.0, 4507 bg=None, 4508 font="", 4509 justify="", 4510 bold=False, 4511 italic=False, 4512 c=None, 4513 alpha=0.5, 4514 ) -> None: 4515 """ 4516 Create a 2D text object. 4517 4518 All properties of the text, and the text itself, can be changed after creation 4519 (which is especially useful in loops). 4520 4521 Arguments: 4522 pos : (str) 4523 text is placed in one of the 8 positions: 4524 - bottom-left 4525 - bottom-right 4526 - top-left 4527 - top-right 4528 - bottom-middle 4529 - middle-right 4530 - middle-left 4531 - top-middle 4532 4533 If a pair (x,y) is passed as input the 2D text is place at that 4534 position in the coordinate system of the 2D screen (with the 4535 origin sitting at the bottom left). 4536 4537 s : (float) 4538 size of text 4539 bg : (color) 4540 background color 4541 alpha : (float) 4542 background opacity 4543 justify : (str) 4544 text justification 4545 4546 font : (str) 4547 built-in available fonts are: 4548 - Antares 4549 - Arial 4550 - Bongas 4551 - Calco 4552 - Comae 4553 - ComicMono 4554 - Courier 4555 - Glasgo 4556 - Kanopus 4557 - LogoType 4558 - Normografo 4559 - Quikhand 4560 - SmartCouric 4561 - Theemim 4562 - Times 4563 - VictorMono 4564 - More fonts at: https://vedo.embl.es/fonts/ 4565 4566 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4567 4568 Examples: 4569 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4570 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4571 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4572 4573 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4574 """ 4575 super().__init__() 4576 self.name = "Text2D" 4577 4578 self.mapper = vtki.new("TextMapper") 4579 self.SetMapper(self.mapper) 4580 4581 self.properties = self.mapper.GetTextProperty() 4582 self.actor = self 4583 self.actor.retrieve_object = weak_ref_to(self) 4584 4585 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4586 4587 # automatic black or white 4588 if c is None: 4589 c = (0.1, 0.1, 0.1) 4590 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4591 if vedo.plotter_instance.renderer.GetGradientBackground(): 4592 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4593 else: 4594 bgcol = vedo.plotter_instance.renderer.GetBackground() 4595 c = (0.9, 0.9, 0.9) 4596 if np.sum(bgcol) > 1.5: 4597 c = (0.1, 0.1, 0.1) 4598 4599 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4600 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4601 self.PickableOff() 4602 4603 def pos(self, pos="top-left", justify=""): 4604 """ 4605 Set position of the text to draw. Keyword `pos` can be a string 4606 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4607 """ 4608 ajustify = "top-left" # autojustify 4609 if isinstance(pos, str): # corners 4610 ajustify = pos 4611 if "top" in pos: 4612 if "left" in pos: 4613 pos = (0.008, 0.994) 4614 elif "right" in pos: 4615 pos = (0.994, 0.994) 4616 elif "mid" in pos or "cent" in pos: 4617 pos = (0.5, 0.994) 4618 elif "bottom" in pos: 4619 if "left" in pos: 4620 pos = (0.008, 0.008) 4621 elif "right" in pos: 4622 pos = (0.994, 0.008) 4623 elif "mid" in pos or "cent" in pos: 4624 pos = (0.5, 0.008) 4625 elif "mid" in pos or "cent" in pos: 4626 if "left" in pos: 4627 pos = (0.008, 0.5) 4628 elif "right" in pos: 4629 pos = (0.994, 0.5) 4630 else: 4631 pos = (0.5, 0.5) 4632 4633 else: 4634 vedo.logger.warning(f"cannot understand text position {pos}") 4635 pos = (0.008, 0.994) 4636 ajustify = "top-left" 4637 4638 elif len(pos) != 2: 4639 vedo.logger.error("pos must be of length 2 or integer value or string") 4640 raise RuntimeError() 4641 4642 if not justify: 4643 justify = ajustify 4644 4645 self.properties.SetJustificationToLeft() 4646 if "top" in justify: 4647 self.properties.SetVerticalJustificationToTop() 4648 if "bottom" in justify: 4649 self.properties.SetVerticalJustificationToBottom() 4650 if "cent" in justify or "mid" in justify: 4651 self.properties.SetJustificationToCentered() 4652 if "left" in justify: 4653 self.properties.SetJustificationToLeft() 4654 if "right" in justify: 4655 self.properties.SetJustificationToRight() 4656 4657 self.SetPosition(pos) 4658 return self 4659 4660 def text(self, txt=None): 4661 """Set/get the input text string.""" 4662 if txt is None: 4663 return self.mapper.GetInput() 4664 4665 if ":" in txt: 4666 for r in _reps: 4667 txt = txt.replace(r[0], r[1]) 4668 else: 4669 txt = str(txt) 4670 4671 self.mapper.SetInput(txt) 4672 return self 4673 4674 def size(self, s): 4675 """Set the font size.""" 4676 self.properties.SetFontSize(int(s * 22.5)) 4677 return self 4678 4679 4680class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation): 4681 # PROBABLY USELESS given that Text2D does pretty much the same ... 4682 """ 4683 Annotate the window corner with 2D text. 4684 4685 See `Text2D` description as the basic functionality is very similar. 4686 4687 The added value of this class is the possibility to manage with one single 4688 object the all corner annotations (instead of creating 4 `Text2D` instances). 4689 4690 Examples: 4691 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 4692 """ 4693 4694 def __init__(self, c=None) -> None: 4695 4696 super().__init__() 4697 4698 self.properties = self.GetTextProperty() 4699 4700 # automatic black or white 4701 if c is None: 4702 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4703 c = (0.9, 0.9, 0.9) 4704 if vedo.plotter_instance.renderer.GetGradientBackground(): 4705 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4706 else: 4707 bgcol = vedo.plotter_instance.renderer.GetBackground() 4708 if np.sum(bgcol) > 1.5: 4709 c = (0.1, 0.1, 0.1) 4710 else: 4711 c = (0.5, 0.5, 0.5) 4712 4713 self.SetNonlinearFontScaleFactor(1 / 2.75) 4714 self.PickableOff() 4715 self.properties.SetColor(get_color(c)) 4716 self.properties.SetBold(False) 4717 self.properties.SetItalic(False) 4718 4719 def size(self, s:float, linear=False) -> "CornerAnnotation": 4720 """ 4721 The font size is calculated as the largest possible value such that the annotations 4722 for the given viewport do not overlap. 4723 4724 This font size can be scaled non-linearly with the viewport size, to maintain an 4725 acceptable readable size at larger viewport sizes, without being too big. 4726 `f' = linearScale * pow(f,nonlinearScale)` 4727 """ 4728 if linear: 4729 self.SetLinearFontScaleFactor(s * 5.5) 4730 else: 4731 self.SetNonlinearFontScaleFactor(s / 2.75) 4732 return self 4733 4734 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4735 """Set text at the assigned position""" 4736 4737 if isinstance(pos, str): # corners 4738 if "top" in pos: 4739 if "left" in pos: pos = 2 4740 elif "right" in pos: pos = 3 4741 elif "mid" in pos or "cent" in pos: pos = 7 4742 elif "bottom" in pos: 4743 if "left" in pos: pos = 0 4744 elif "right" in pos: pos = 1 4745 elif "mid" in pos or "cent" in pos: pos = 4 4746 else: 4747 if "left" in pos: pos = 6 4748 elif "right" in pos: pos = 5 4749 else: pos = 2 4750 4751 if "\\" in repr(txt): 4752 for r in _reps: 4753 txt = txt.replace(r[0], r[1]) 4754 else: 4755 txt = str(txt) 4756 4757 self.SetText(pos, txt) 4758 return self 4759 4760 def clear(self) -> "CornerAnnotation": 4761 """Remove all text from all corners""" 4762 self.ClearAllTexts() 4763 return self 4764 4765 4766class Latex(Image): 4767 """ 4768 Render Latex text and formulas. 4769 """ 4770 4771 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4772 """ 4773 Render Latex text and formulas. 4774 4775 Arguments: 4776 formula : (str) 4777 latex text string 4778 pos : (list) 4779 position coordinates in space 4780 bg : (color) 4781 background color box 4782 res : (int) 4783 dpi resolution 4784 usetex : (bool) 4785 use latex compiler of matplotlib if available 4786 4787 You can access the latex formula in `Latex.formula`. 4788 4789 Examples: 4790 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4791 4792 ![](https://vedo.embl.es/images/pyplot/latex.png) 4793 """ 4794 from tempfile import NamedTemporaryFile 4795 import matplotlib.pyplot as mpltib 4796 4797 def build_img_plt(formula, tfile): 4798 4799 mpltib.rc("text", usetex=usetex) 4800 4801 formula1 = "$" + formula + "$" 4802 mpltib.axis("off") 4803 col = get_color(c) 4804 if bg: 4805 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4806 else: 4807 bx = None 4808 mpltib.text( 4809 0.5, 4810 0.5, 4811 formula1, 4812 size=res, 4813 color=col, 4814 alpha=alpha, 4815 ha="center", 4816 va="center", 4817 bbox=bx, 4818 ) 4819 mpltib.savefig( 4820 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4821 ) 4822 mpltib.close() 4823 4824 if len(pos) == 2: 4825 pos = (pos[0], pos[1], 0) 4826 4827 tmp_file = NamedTemporaryFile(delete=True) 4828 tmp_file.name = tmp_file.name + ".png" 4829 4830 build_img_plt(formula, tmp_file.name) 4831 4832 super().__init__(tmp_file.name, channels=4) 4833 self.alpha(alpha) 4834 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4835 self.pos(pos) 4836 self.name = "Latex" 4837 self.formula = formula 4838 4839 # except: 4840 # printc("Error in Latex()\n", formula, c="r") 4841 # printc(" latex or dvipng not installed?", c="r") 4842 # printc(" Try: usetex=False", c="r") 4843 # printc(" Try: sudo apt install dvipng", c="r") 4844 4845 4846class ConvexHull(Mesh): 4847 """ 4848 Create the 2D/3D convex hull from a set of points. 4849 """ 4850 4851 def __init__(self, pts) -> None: 4852 """ 4853 Create the 2D/3D convex hull from a set of input points or input Mesh. 4854 4855 Examples: 4856 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4857 4858 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4859 """ 4860 if utils.is_sequence(pts): 4861 pts = utils.make3d(pts).astype(float) 4862 mesh = Points(pts) 4863 else: 4864 mesh = pts 4865 apoly = mesh.clean().dataset 4866 4867 # Create the convex hull of the pointcloud 4868 z0, z1 = mesh.zbounds() 4869 d = mesh.diagonal_size() 4870 if (z1 - z0) / d > 0.0001: 4871 delaunay = vtki.new("Delaunay3D") 4872 delaunay.SetInputData(apoly) 4873 delaunay.Update() 4874 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4875 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4876 surfaceFilter.Update() 4877 out = surfaceFilter.GetOutput() 4878 else: 4879 delaunay = vtki.new("Delaunay2D") 4880 delaunay.SetInputData(apoly) 4881 delaunay.Update() 4882 fe = vtki.new("FeatureEdges") 4883 fe.SetInputConnection(delaunay.GetOutputPort()) 4884 fe.BoundaryEdgesOn() 4885 fe.Update() 4886 out = fe.GetOutput() 4887 4888 super().__init__(out, c=mesh.color(), alpha=0.75) 4889 self.flat() 4890 self.name = "ConvexHull" 4891 4892 4893def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True) -> "vedo.Assembly": 4894 """ 4895 Create the 3D vedo logo. 4896 4897 Arguments: 4898 distance : (float) 4899 send back logo by this distance from camera 4900 version : (bool) 4901 add version text to the right end of the logo 4902 bc : (color) 4903 text back face color 4904 """ 4905 if c is None: 4906 c = (0, 0, 0) 4907 if vedo.plotter_instance: 4908 if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5: 4909 c = [0, 0, 0] 4910 else: 4911 c = "linen" 4912 4913 font = "Comae" 4914 vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8) 4915 vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc) 4916 vlogo.properties.LightingOn() 4917 4918 vr, rul = None, None 4919 if version: 4920 vr = Text3D( 4921 vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1 4922 ).scale([1, 0.7, 1]) 4923 vr.rotate_z(90).pos(2450, 50, 80) 4924 vr.bc(bc).pickable(False) 4925 elif frame: 4926 rul = vedo.RulerAxes( 4927 (-2600, 2110, 0, 1650, 0, 0), 4928 xlabel="European Molecular Biology Laboratory", 4929 ylabel=vedo.__version__, 4930 font=font, 4931 xpadding=0.09, 4932 ypadding=0.04, 4933 ) 4934 fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0) 4935 return vedo.Assembly([vlogo, vr, fakept, rul]).scale(1 / 1725)
3602def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True) -> Any: 3603 """ 3604 Generate a marker shape. Typically used in association with `Glyph`. 3605 """ 3606 if isinstance(symbol, Mesh): 3607 return symbol.c(c).alpha(alpha).lighting("off") 3608 3609 if isinstance(symbol, int): 3610 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 3611 symbol = symbol % len(symbs) 3612 symbol = symbs[symbol] 3613 3614 if symbol == ".": 3615 mesh = Polygon(nsides=24, r=s * 0.6) 3616 elif symbol == "o": 3617 mesh = Polygon(nsides=24, r=s * 0.75) 3618 elif symbol == "O": 3619 mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3620 elif symbol == "0": 3621 m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3622 m2 = Circle(r=s * 0.36).reverse() 3623 mesh = merge(m1, m2) 3624 elif symbol == "p": 3625 mesh = Polygon(nsides=5, r=s) 3626 elif symbol == "*": 3627 mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled) 3628 elif symbol == "h": 3629 mesh = Polygon(nsides=6, r=s) 3630 elif symbol == "D": 3631 mesh = Polygon(nsides=4, r=s) 3632 elif symbol == "d": 3633 mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1]) 3634 elif symbol == "v": 3635 mesh = Polygon(nsides=3, r=s).rotate_z(180) 3636 elif symbol == "^": 3637 mesh = Polygon(nsides=3, r=s) 3638 elif symbol == ">": 3639 mesh = Polygon(nsides=3, r=s).rotate_z(-90) 3640 elif symbol == "<": 3641 mesh = Polygon(nsides=3, r=s).rotate_z(90) 3642 elif symbol == "s": 3643 mesh = Mesh( 3644 [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]] 3645 ).scale(s / 1.4) 3646 elif symbol == "x": 3647 mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3648 # mesh.rotate_z(45) 3649 elif symbol == "a": 3650 mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3651 else: 3652 mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0) 3653 mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha) 3654 if len(pos) == 2: 3655 pos = (pos[0], pos[1], 0) 3656 mesh.pos(pos) 3657 mesh.name = "Marker" 3658 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()
3661class Brace(Mesh): 3662 """ 3663 Create a brace (bracket) shape. 3664 """ 3665 3666 def __init__( 3667 self, 3668 q1, 3669 q2, 3670 style="}", 3671 padding1=0.0, 3672 font="Theemim", 3673 comment="", 3674 justify=None, 3675 angle=0.0, 3676 padding2=0.2, 3677 s=1.0, 3678 italic=0, 3679 c="k1", 3680 alpha=1.0, 3681 ) -> None: 3682 """ 3683 Create a brace (bracket) shape which spans from point q1 to point q2. 3684 3685 Arguments: 3686 q1 : (list) 3687 point 1. 3688 q2 : (list) 3689 point 2. 3690 style : (str) 3691 style of the bracket, eg. `{}, [], (), <>`. 3692 padding1 : (float) 3693 padding space in percent form the input points. 3694 font : (str) 3695 font type 3696 comment : (str) 3697 additional text to appear next to the brace symbol. 3698 justify : (str) 3699 specify the anchor point to justify text comment, e.g. "top-left". 3700 italic : float 3701 italicness of the text comment (can be a positive or negative number) 3702 angle : (float) 3703 rotation angle of text. Use `None` to keep it horizontal. 3704 padding2 : (float) 3705 padding space in percent form brace to text comment. 3706 s : (float) 3707 scale factor for the comment 3708 3709 Examples: 3710 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3711 3712 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3713 """ 3714 if isinstance(q1, vtki.vtkActor): 3715 q1 = q1.GetPosition() 3716 if isinstance(q2, vtki.vtkActor): 3717 q2 = q2.GetPosition() 3718 if len(q1) == 2: 3719 q1 = [q1[0], q1[1], 0.0] 3720 if len(q2) == 2: 3721 q2 = [q2[0], q2[1], 0.0] 3722 q1 = np.array(q1, dtype=float) 3723 q2 = np.array(q2, dtype=float) 3724 mq = (q1 + q2) / 2 3725 q1 = q1 - mq 3726 q2 = q2 - mq 3727 d = np.linalg.norm(q2 - q1) 3728 q2[2] = q1[2] 3729 3730 if style not in "{}[]()<>|I": 3731 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3732 style = "}" 3733 3734 flip = False 3735 if style in ["{", "[", "(", "<"]: 3736 flip = True 3737 i = ["{", "[", "(", "<"].index(style) 3738 style = ["}", "]", ")", ">"][i] 3739 3740 br = Text3D(style, font="Theemim", justify="center-left") 3741 br.scale([0.4, 1, 1]) 3742 3743 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3744 if flip: 3745 angler += 180 3746 3747 _, x1, y0, y1, _, _ = br.bounds() 3748 if comment: 3749 just = "center-top" 3750 if angle is None: 3751 angle = -angler + 90 3752 if not flip: 3753 angle += 180 3754 3755 if flip: 3756 angle += 180 3757 just = "center-bottom" 3758 if justify is not None: 3759 just = justify 3760 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3761 cx0, cx1 = cmt.xbounds() 3762 cmt.rotate_z(90 + angle) 3763 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3764 cmt.shift(x1 * (1 + padding2), 0, 0) 3765 poly = merge(br, cmt).dataset 3766 3767 else: 3768 poly = br.dataset 3769 3770 tr = vtki.vtkTransform() 3771 tr.Translate(mq) 3772 tr.RotateZ(angler) 3773 tr.Translate(padding1 * d, 0, 0) 3774 pscale = 1 3775 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3776 3777 tf = vtki.new("TransformPolyDataFilter") 3778 tf.SetInputData(poly) 3779 tf.SetTransform(tr) 3780 tf.Update() 3781 poly = tf.GetOutput() 3782 3783 super().__init__(poly, c, alpha) 3784 3785 self.base = q1 3786 self.top = q2 3787 self.name = "Brace"
Create a brace (bracket) shape.
3666 def __init__( 3667 self, 3668 q1, 3669 q2, 3670 style="}", 3671 padding1=0.0, 3672 font="Theemim", 3673 comment="", 3674 justify=None, 3675 angle=0.0, 3676 padding2=0.2, 3677 s=1.0, 3678 italic=0, 3679 c="k1", 3680 alpha=1.0, 3681 ) -> None: 3682 """ 3683 Create a brace (bracket) shape which spans from point q1 to point q2. 3684 3685 Arguments: 3686 q1 : (list) 3687 point 1. 3688 q2 : (list) 3689 point 2. 3690 style : (str) 3691 style of the bracket, eg. `{}, [], (), <>`. 3692 padding1 : (float) 3693 padding space in percent form the input points. 3694 font : (str) 3695 font type 3696 comment : (str) 3697 additional text to appear next to the brace symbol. 3698 justify : (str) 3699 specify the anchor point to justify text comment, e.g. "top-left". 3700 italic : float 3701 italicness of the text comment (can be a positive or negative number) 3702 angle : (float) 3703 rotation angle of text. Use `None` to keep it horizontal. 3704 padding2 : (float) 3705 padding space in percent form brace to text comment. 3706 s : (float) 3707 scale factor for the comment 3708 3709 Examples: 3710 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3711 3712 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3713 """ 3714 if isinstance(q1, vtki.vtkActor): 3715 q1 = q1.GetPosition() 3716 if isinstance(q2, vtki.vtkActor): 3717 q2 = q2.GetPosition() 3718 if len(q1) == 2: 3719 q1 = [q1[0], q1[1], 0.0] 3720 if len(q2) == 2: 3721 q2 = [q2[0], q2[1], 0.0] 3722 q1 = np.array(q1, dtype=float) 3723 q2 = np.array(q2, dtype=float) 3724 mq = (q1 + q2) / 2 3725 q1 = q1 - mq 3726 q2 = q2 - mq 3727 d = np.linalg.norm(q2 - q1) 3728 q2[2] = q1[2] 3729 3730 if style not in "{}[]()<>|I": 3731 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3732 style = "}" 3733 3734 flip = False 3735 if style in ["{", "[", "(", "<"]: 3736 flip = True 3737 i = ["{", "[", "(", "<"].index(style) 3738 style = ["}", "]", ")", ">"][i] 3739 3740 br = Text3D(style, font="Theemim", justify="center-left") 3741 br.scale([0.4, 1, 1]) 3742 3743 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3744 if flip: 3745 angler += 180 3746 3747 _, x1, y0, y1, _, _ = br.bounds() 3748 if comment: 3749 just = "center-top" 3750 if angle is None: 3751 angle = -angler + 90 3752 if not flip: 3753 angle += 180 3754 3755 if flip: 3756 angle += 180 3757 just = "center-bottom" 3758 if justify is not None: 3759 just = justify 3760 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3761 cx0, cx1 = cmt.xbounds() 3762 cmt.rotate_z(90 + angle) 3763 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3764 cmt.shift(x1 * (1 + padding2), 0, 0) 3765 poly = merge(br, cmt).dataset 3766 3767 else: 3768 poly = br.dataset 3769 3770 tr = vtki.vtkTransform() 3771 tr.Translate(mq) 3772 tr.RotateZ(angler) 3773 tr.Translate(padding1 * d, 0, 0) 3774 pscale = 1 3775 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3776 3777 tf = vtki.new("TransformPolyDataFilter") 3778 tf.SetInputData(poly) 3779 tf.SetTransform(tr) 3780 tf.Update() 3781 poly = tf.GetOutput() 3782 3783 super().__init__(poly, c, alpha) 3784 3785 self.base = q1 3786 self.top = q2 3787 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 # self.pos(start_pt) 1911 1912 self.phong().lighting("plastic") 1913 self.actor.PickableOff() 1914 self.actor.DragableOff() 1915 self.base = np.array(start_pt, dtype=float) # used by pyplot 1916 self.top = np.array(end_pt, dtype=float) # used by pyplot 1917 self.top_index = None 1918 self.fill = True # used by pyplot.__iadd__() 1919 self.s = s if s is not None else 1 # used by pyplot.__iadd__() 1920 self.name = "Arrow"
Build a 3D arrow from start_pt
to end_pt
of section size s
,
expressed as the fraction of the window size.
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 # self.pos(start_pt) 1911 1912 self.phong().lighting("plastic") 1913 self.actor.PickableOff() 1914 self.actor.DragableOff() 1915 self.base = np.array(start_pt, dtype=float) # used by pyplot 1916 self.top = np.array(end_pt, dtype=float) # used by pyplot 1917 self.top_index = None 1918 self.fill = True # used by pyplot.__iadd__() 1919 self.s = s if s is not None else 1 # used by pyplot.__iadd__() 1920 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
1923class Arrows(Glyph): 1924 """ 1925 Build arrows between two lists of points. 1926 """ 1927 1928 def __init__( 1929 self, 1930 start_pts, 1931 end_pts=None, 1932 s=None, 1933 shaft_radius=None, 1934 head_radius=None, 1935 head_length=None, 1936 thickness=1.0, 1937 res=6, 1938 c='k3', 1939 alpha=1.0, 1940 ) -> None: 1941 """ 1942 Build arrows between two lists of points `start_pts` and `end_pts`. 1943 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1944 1945 Color can be specified as a colormap which maps the size of the arrows. 1946 1947 Arguments: 1948 s : (float) 1949 fix aspect-ratio of the arrow and scale its cross section 1950 c : (color) 1951 color or color map name 1952 alpha : (float) 1953 set object opacity 1954 res : (int) 1955 set arrow resolution 1956 1957 Examples: 1958 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 1959 1960 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 1961 """ 1962 if isinstance(start_pts, Points): 1963 start_pts = start_pts.vertices 1964 if isinstance(end_pts, Points): 1965 end_pts = end_pts.vertices 1966 1967 start_pts = np.asarray(start_pts) 1968 if end_pts is None: 1969 strt = start_pts[:, 0] 1970 end_pts = start_pts[:, 1] 1971 start_pts = strt 1972 else: 1973 end_pts = np.asarray(end_pts) 1974 1975 start_pts = utils.make3d(start_pts) 1976 end_pts = utils.make3d(end_pts) 1977 1978 arr = vtki.new("ArrowSource") 1979 arr.SetShaftResolution(res) 1980 arr.SetTipResolution(res) 1981 1982 if s: 1983 sz = 0.02 * s 1984 arr.SetTipRadius(sz * 2) 1985 arr.SetShaftRadius(sz * thickness) 1986 arr.SetTipLength(sz * 10) 1987 1988 if head_radius: 1989 arr.SetTipRadius(head_radius) 1990 if shaft_radius: 1991 arr.SetShaftRadius(shaft_radius) 1992 if head_length: 1993 arr.SetTipLength(head_length) 1994 1995 arr.Update() 1996 out = arr.GetOutput() 1997 1998 orients = end_pts - start_pts 1999 2000 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2001 2002 super().__init__( 2003 start_pts, 2004 out, 2005 orientation_array=orients, 2006 scale_by_vector_size=True, 2007 color_by_vector_size=color_by_vector_size, 2008 c=c, 2009 alpha=alpha, 2010 ) 2011 self.lighting("off") 2012 if color_by_vector_size: 2013 vals = np.linalg.norm(orients, axis=1) 2014 self.mapper.SetScalarRange(vals.min(), vals.max()) 2015 else: 2016 self.c(c) 2017 self.name = "Arrows"
Build arrows between two lists of points.
1928 def __init__( 1929 self, 1930 start_pts, 1931 end_pts=None, 1932 s=None, 1933 shaft_radius=None, 1934 head_radius=None, 1935 head_length=None, 1936 thickness=1.0, 1937 res=6, 1938 c='k3', 1939 alpha=1.0, 1940 ) -> None: 1941 """ 1942 Build arrows between two lists of points `start_pts` and `end_pts`. 1943 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1944 1945 Color can be specified as a colormap which maps the size of the arrows. 1946 1947 Arguments: 1948 s : (float) 1949 fix aspect-ratio of the arrow and scale its cross section 1950 c : (color) 1951 color or color map name 1952 alpha : (float) 1953 set object opacity 1954 res : (int) 1955 set arrow resolution 1956 1957 Examples: 1958 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 1959 1960 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 1961 """ 1962 if isinstance(start_pts, Points): 1963 start_pts = start_pts.vertices 1964 if isinstance(end_pts, Points): 1965 end_pts = end_pts.vertices 1966 1967 start_pts = np.asarray(start_pts) 1968 if end_pts is None: 1969 strt = start_pts[:, 0] 1970 end_pts = start_pts[:, 1] 1971 start_pts = strt 1972 else: 1973 end_pts = np.asarray(end_pts) 1974 1975 start_pts = utils.make3d(start_pts) 1976 end_pts = utils.make3d(end_pts) 1977 1978 arr = vtki.new("ArrowSource") 1979 arr.SetShaftResolution(res) 1980 arr.SetTipResolution(res) 1981 1982 if s: 1983 sz = 0.02 * s 1984 arr.SetTipRadius(sz * 2) 1985 arr.SetShaftRadius(sz * thickness) 1986 arr.SetTipLength(sz * 10) 1987 1988 if head_radius: 1989 arr.SetTipRadius(head_radius) 1990 if shaft_radius: 1991 arr.SetShaftRadius(shaft_radius) 1992 if head_length: 1993 arr.SetTipLength(head_length) 1994 1995 arr.Update() 1996 out = arr.GetOutput() 1997 1998 orients = end_pts - start_pts 1999 2000 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2001 2002 super().__init__( 2003 start_pts, 2004 out, 2005 orientation_array=orients, 2006 scale_by_vector_size=True, 2007 color_by_vector_size=color_by_vector_size, 2008 c=c, 2009 alpha=alpha, 2010 ) 2011 self.lighting("off") 2012 if color_by_vector_size: 2013 vals = np.linalg.norm(orients, axis=1) 2014 self.mapper.SetScalarRange(vals.min(), vals.max()) 2015 else: 2016 self.c(c) 2017 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:
2020class Arrow2D(Mesh): 2021 """ 2022 Build a 2D arrow. 2023 """ 2024 2025 def __init__( 2026 self, 2027 start_pt=(0, 0, 0), 2028 end_pt=(1, 0, 0), 2029 s=1, 2030 rotation=0.0, 2031 shaft_length=0.85, 2032 shaft_width=0.055, 2033 head_length=0.175, 2034 head_width=0.175, 2035 fill=True, 2036 c="red4", 2037 alpha=1.0, 2038 ) -> None: 2039 """ 2040 Build a 2D arrow from `start_pt` to `end_pt`. 2041 2042 Arguments: 2043 s : (float) 2044 a global multiplicative convenience factor controlling the arrow size 2045 shaft_length : (float) 2046 fractional shaft length 2047 shaft_width : (float) 2048 fractional shaft width 2049 head_length : (float) 2050 fractional head length 2051 head_width : (float) 2052 fractional head width 2053 fill : (bool) 2054 if False only generate the outline 2055 """ 2056 self.fill = fill ## needed by pyplot.__iadd() 2057 self.s = s ## needed by pyplot.__iadd() 2058 2059 if s != 1: 2060 shaft_width *= s 2061 head_width *= np.sqrt(s) 2062 2063 # in case user is passing meshs 2064 if isinstance(start_pt, vtki.vtkActor): 2065 start_pt = start_pt.GetPosition() 2066 if isinstance(end_pt, vtki.vtkActor): 2067 end_pt = end_pt.GetPosition() 2068 if len(start_pt) == 2: 2069 start_pt = [start_pt[0], start_pt[1], 0] 2070 if len(end_pt) == 2: 2071 end_pt = [end_pt[0], end_pt[1], 0] 2072 2073 headBase = 1 - head_length 2074 head_width = max(head_width, shaft_width) 2075 if head_length is None or headBase > shaft_length: 2076 headBase = shaft_length 2077 2078 verts = [] 2079 verts.append([0, -shaft_width / 2, 0]) 2080 verts.append([shaft_length, -shaft_width / 2, 0]) 2081 verts.append([headBase, -head_width / 2, 0]) 2082 verts.append([1, 0, 0]) 2083 verts.append([headBase, head_width / 2, 0]) 2084 verts.append([shaft_length, shaft_width / 2, 0]) 2085 verts.append([0, shaft_width / 2, 0]) 2086 if fill: 2087 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2088 poly = utils.buildPolyData(verts, faces) 2089 else: 2090 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2091 poly = utils.buildPolyData(verts, [], lines=lines) 2092 2093 axis = np.array(end_pt) - np.array(start_pt) 2094 length = float(np.linalg.norm(axis)) 2095 if length: 2096 axis = axis / length 2097 theta = 0 2098 if len(axis) > 2: 2099 theta = np.arccos(axis[2]) 2100 phi = np.arctan2(axis[1], axis[0]) 2101 2102 t = vtki.vtkTransform() 2103 t.Translate(start_pt) 2104 if phi: 2105 t.RotateZ(np.rad2deg(phi)) 2106 if theta: 2107 t.RotateY(np.rad2deg(theta)) 2108 t.RotateY(-90) # put it along Z 2109 if rotation: 2110 t.RotateX(rotation) 2111 t.Scale(length, length, length) 2112 2113 tf = vtki.new("TransformPolyDataFilter") 2114 tf.SetInputData(poly) 2115 tf.SetTransform(t) 2116 tf.Update() 2117 2118 super().__init__(tf.GetOutput(), c, alpha) 2119 2120 self.transform = LinearTransform().translate(start_pt) 2121 2122 self.lighting("off") 2123 self.actor.DragableOff() 2124 self.actor.PickableOff() 2125 self.base = np.array(start_pt, dtype=float) # used by pyplot 2126 self.top = np.array(end_pt, dtype=float) # used by pyplot 2127 self.name = "Arrow2D"
Build a 2D arrow.
2025 def __init__( 2026 self, 2027 start_pt=(0, 0, 0), 2028 end_pt=(1, 0, 0), 2029 s=1, 2030 rotation=0.0, 2031 shaft_length=0.85, 2032 shaft_width=0.055, 2033 head_length=0.175, 2034 head_width=0.175, 2035 fill=True, 2036 c="red4", 2037 alpha=1.0, 2038 ) -> None: 2039 """ 2040 Build a 2D arrow from `start_pt` to `end_pt`. 2041 2042 Arguments: 2043 s : (float) 2044 a global multiplicative convenience factor controlling the arrow size 2045 shaft_length : (float) 2046 fractional shaft length 2047 shaft_width : (float) 2048 fractional shaft width 2049 head_length : (float) 2050 fractional head length 2051 head_width : (float) 2052 fractional head width 2053 fill : (bool) 2054 if False only generate the outline 2055 """ 2056 self.fill = fill ## needed by pyplot.__iadd() 2057 self.s = s ## needed by pyplot.__iadd() 2058 2059 if s != 1: 2060 shaft_width *= s 2061 head_width *= np.sqrt(s) 2062 2063 # in case user is passing meshs 2064 if isinstance(start_pt, vtki.vtkActor): 2065 start_pt = start_pt.GetPosition() 2066 if isinstance(end_pt, vtki.vtkActor): 2067 end_pt = end_pt.GetPosition() 2068 if len(start_pt) == 2: 2069 start_pt = [start_pt[0], start_pt[1], 0] 2070 if len(end_pt) == 2: 2071 end_pt = [end_pt[0], end_pt[1], 0] 2072 2073 headBase = 1 - head_length 2074 head_width = max(head_width, shaft_width) 2075 if head_length is None or headBase > shaft_length: 2076 headBase = shaft_length 2077 2078 verts = [] 2079 verts.append([0, -shaft_width / 2, 0]) 2080 verts.append([shaft_length, -shaft_width / 2, 0]) 2081 verts.append([headBase, -head_width / 2, 0]) 2082 verts.append([1, 0, 0]) 2083 verts.append([headBase, head_width / 2, 0]) 2084 verts.append([shaft_length, shaft_width / 2, 0]) 2085 verts.append([0, shaft_width / 2, 0]) 2086 if fill: 2087 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2088 poly = utils.buildPolyData(verts, faces) 2089 else: 2090 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2091 poly = utils.buildPolyData(verts, [], lines=lines) 2092 2093 axis = np.array(end_pt) - np.array(start_pt) 2094 length = float(np.linalg.norm(axis)) 2095 if length: 2096 axis = axis / length 2097 theta = 0 2098 if len(axis) > 2: 2099 theta = np.arccos(axis[2]) 2100 phi = np.arctan2(axis[1], axis[0]) 2101 2102 t = vtki.vtkTransform() 2103 t.Translate(start_pt) 2104 if phi: 2105 t.RotateZ(np.rad2deg(phi)) 2106 if theta: 2107 t.RotateY(np.rad2deg(theta)) 2108 t.RotateY(-90) # put it along Z 2109 if rotation: 2110 t.RotateX(rotation) 2111 t.Scale(length, length, length) 2112 2113 tf = vtki.new("TransformPolyDataFilter") 2114 tf.SetInputData(poly) 2115 tf.SetTransform(t) 2116 tf.Update() 2117 2118 super().__init__(tf.GetOutput(), c, alpha) 2119 2120 self.transform = LinearTransform().translate(start_pt) 2121 2122 self.lighting("off") 2123 self.actor.DragableOff() 2124 self.actor.PickableOff() 2125 self.base = np.array(start_pt, dtype=float) # used by pyplot 2126 self.top = np.array(end_pt, dtype=float) # used by pyplot 2127 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
2130class Arrows2D(Glyph): 2131 """ 2132 Build 2D arrows between two lists of points. 2133 """ 2134 2135 def __init__( 2136 self, 2137 start_pts, 2138 end_pts=None, 2139 s=1.0, 2140 rotation=0.0, 2141 shaft_length=0.8, 2142 shaft_width=0.05, 2143 head_length=0.225, 2144 head_width=0.175, 2145 fill=True, 2146 c=None, 2147 alpha=1.0, 2148 ) -> None: 2149 """ 2150 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2151 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2152 2153 Color can be specified as a colormap which maps the size of the arrows. 2154 2155 Arguments: 2156 shaft_length : (float) 2157 fractional shaft length 2158 shaft_width : (float) 2159 fractional shaft width 2160 head_length : (float) 2161 fractional head length 2162 head_width : (float) 2163 fractional head width 2164 fill : (bool) 2165 if False only generate the outline 2166 """ 2167 if isinstance(start_pts, Points): 2168 start_pts = start_pts.vertices 2169 if isinstance(end_pts, Points): 2170 end_pts = end_pts.vertices 2171 2172 start_pts = np.asarray(start_pts, dtype=float) 2173 if end_pts is None: 2174 strt = start_pts[:, 0] 2175 end_pts = start_pts[:, 1] 2176 start_pts = strt 2177 else: 2178 end_pts = np.asarray(end_pts, dtype=float) 2179 2180 if head_length is None: 2181 head_length = 1 - shaft_length 2182 2183 arr = Arrow2D( 2184 (0, 0, 0), 2185 (1, 0, 0), 2186 s=s, 2187 rotation=rotation, 2188 shaft_length=shaft_length, 2189 shaft_width=shaft_width, 2190 head_length=head_length, 2191 head_width=head_width, 2192 fill=fill, 2193 ) 2194 2195 orients = end_pts - start_pts 2196 orients = utils.make3d(orients) 2197 2198 pts = Points(start_pts) 2199 super().__init__( 2200 pts, 2201 arr, 2202 orientation_array=orients, 2203 scale_by_vector_size=True, 2204 c=c, 2205 alpha=alpha, 2206 ) 2207 self.flat().lighting("off").pickable(False) 2208 if c is not None: 2209 self.color(c) 2210 self.name = "Arrows2D"
Build 2D arrows between two lists of points.
2135 def __init__( 2136 self, 2137 start_pts, 2138 end_pts=None, 2139 s=1.0, 2140 rotation=0.0, 2141 shaft_length=0.8, 2142 shaft_width=0.05, 2143 head_length=0.225, 2144 head_width=0.175, 2145 fill=True, 2146 c=None, 2147 alpha=1.0, 2148 ) -> None: 2149 """ 2150 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2151 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2152 2153 Color can be specified as a colormap which maps the size of the arrows. 2154 2155 Arguments: 2156 shaft_length : (float) 2157 fractional shaft length 2158 shaft_width : (float) 2159 fractional shaft width 2160 head_length : (float) 2161 fractional head length 2162 head_width : (float) 2163 fractional head width 2164 fill : (bool) 2165 if False only generate the outline 2166 """ 2167 if isinstance(start_pts, Points): 2168 start_pts = start_pts.vertices 2169 if isinstance(end_pts, Points): 2170 end_pts = end_pts.vertices 2171 2172 start_pts = np.asarray(start_pts, dtype=float) 2173 if end_pts is None: 2174 strt = start_pts[:, 0] 2175 end_pts = start_pts[:, 1] 2176 start_pts = strt 2177 else: 2178 end_pts = np.asarray(end_pts, dtype=float) 2179 2180 if head_length is None: 2181 head_length = 1 - shaft_length 2182 2183 arr = Arrow2D( 2184 (0, 0, 0), 2185 (1, 0, 0), 2186 s=s, 2187 rotation=rotation, 2188 shaft_length=shaft_length, 2189 shaft_width=shaft_width, 2190 head_length=head_length, 2191 head_width=head_width, 2192 fill=fill, 2193 ) 2194 2195 orients = end_pts - start_pts 2196 orients = utils.make3d(orients) 2197 2198 pts = Points(start_pts) 2199 super().__init__( 2200 pts, 2201 arr, 2202 orientation_array=orients, 2203 scale_by_vector_size=True, 2204 c=c, 2205 alpha=alpha, 2206 ) 2207 self.flat().lighting("off").pickable(False) 2208 if c is not None: 2209 self.color(c) 2210 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
2213class FlatArrow(Ribbon): 2214 """ 2215 Build a 2D arrow in 3D space by joining two close lines. 2216 """ 2217 2218 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2219 """ 2220 Build a 2D arrow in 3D space by joining two close lines. 2221 2222 Examples: 2223 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2224 2225 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2226 """ 2227 if isinstance(line1, Points): 2228 line1 = line1.vertices 2229 if isinstance(line2, Points): 2230 line2 = line2.vertices 2231 2232 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2233 2234 v = (sm1 - sm2) / 3 * tip_width 2235 p1 = sm1 + v 2236 p2 = sm2 - v 2237 pm1 = (sm1 + sm2) / 2 2238 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2239 pm12 = pm1 - pm2 2240 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2241 2242 line1.append(p1) 2243 line1.append(tip) 2244 line2.append(p2) 2245 line2.append(tip) 2246 resm = max(100, len(line1)) 2247 2248 super().__init__(line1, line2, res=(resm, 1)) 2249 self.phong().lighting("off") 2250 self.actor.PickableOff() 2251 self.actor.DragableOff() 2252 self.name = "FlatArrow"
Build a 2D arrow in 3D space by joining two close lines.
2218 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2219 """ 2220 Build a 2D arrow in 3D space by joining two close lines. 2221 2222 Examples: 2223 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2224 2225 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2226 """ 2227 if isinstance(line1, Points): 2228 line1 = line1.vertices 2229 if isinstance(line2, Points): 2230 line2 = line2.vertices 2231 2232 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2233 2234 v = (sm1 - sm2) / 3 * tip_width 2235 p1 = sm1 + v 2236 p2 = sm2 - v 2237 pm1 = (sm1 + sm2) / 2 2238 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2239 pm12 = pm1 - pm2 2240 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2241 2242 line1.append(p1) 2243 line1.append(tip) 2244 line2.append(p2) 2245 line2.append(tip) 2246 resm = max(100, len(line1)) 2247 2248 super().__init__(line1, line2, res=(resm, 1)) 2249 self.phong().lighting("off") 2250 self.actor.PickableOff() 2251 self.actor.DragableOff() 2252 self.name = "FlatArrow"
2265class Polygon(Mesh): 2266 """ 2267 Build a polygon in the `xy` plane. 2268 """ 2269 2270 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2271 """ 2272 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2273 2274 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2275 """ 2276 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2277 pts = pol2cart(np.ones_like(t) * r, t).T 2278 faces = [list(range(nsides))] 2279 # do not use: vtkRegularPolygonSource 2280 super().__init__([pts, faces], c, alpha) 2281 if len(pos) == 2: 2282 pos = (pos[0], pos[1], 0) 2283 self.pos(pos) 2284 self.properties.LightingOff() 2285 self.name = "Polygon " + str(nsides)
Build a polygon in the xy
plane.
2270 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2271 """ 2272 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2273 2274 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2275 """ 2276 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2277 pts = pol2cart(np.ones_like(t) * r, t).T 2278 faces = [list(range(nsides))] 2279 # do not use: vtkRegularPolygonSource 2280 super().__init__([pts, faces], c, alpha) 2281 if len(pos) == 2: 2282 pos = (pos[0], pos[1], 0) 2283 self.pos(pos) 2284 self.properties.LightingOff() 2285 self.name = "Polygon " + str(nsides)
Build a polygon in the xy
plane of nsides
of radius r
.
2255class Triangle(Mesh): 2256 """Create a triangle from 3 points in space.""" 2257 2258 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2259 """Create a triangle from 3 points in space.""" 2260 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2261 self.properties.LightingOff() 2262 self.name = "Triangle"
Create a triangle from 3 points in space.
2258 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2259 """Create a triangle from 3 points in space.""" 2260 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2261 self.properties.LightingOff() 2262 self.name = "Triangle"
Create a triangle from 3 points in space.
3076class Rectangle(Mesh): 3077 """ 3078 Build a rectangle in the xy plane. 3079 """ 3080 3081 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3082 """ 3083 Build a rectangle in the xy plane identified by any two corner points. 3084 3085 Arguments: 3086 p1 : (list) 3087 bottom-left position of the corner 3088 p2 : (list) 3089 top-right position of the corner 3090 radius : (float, list) 3091 smoothing radius of the corner in world units. 3092 A list can be passed with 4 individual values. 3093 """ 3094 if len(p1) == 2: 3095 p1 = np.array([p1[0], p1[1], 0.0]) 3096 else: 3097 p1 = np.array(p1, dtype=float) 3098 if len(p2) == 2: 3099 p2 = np.array([p2[0], p2[1], 0.0]) 3100 else: 3101 p2 = np.array(p2, dtype=float) 3102 3103 self.corner1 = p1 3104 self.corner2 = p2 3105 3106 color = c 3107 smoothr = False 3108 risseq = False 3109 if utils.is_sequence(radius): 3110 risseq = True 3111 smoothr = True 3112 if max(radius) == 0: 3113 smoothr = False 3114 elif radius: 3115 smoothr = True 3116 3117 if not smoothr: 3118 radius = None 3119 self.radius = radius 3120 3121 if smoothr: 3122 r = radius 3123 if not risseq: 3124 r = [r, r, r, r] 3125 rd, ra, rb, rc = r 3126 3127 if p1[0] > p2[0]: # flip p1 - p2 3128 p1, p2 = p2, p1 3129 if p1[1] > p2[1]: # flip p1y - p2y 3130 p1[1], p2[1] = p2[1], p1[1] 3131 3132 px, py, _ = p2 - p1 3133 k = min(px / 2, py / 2) 3134 ra = min(abs(ra), k) 3135 rb = min(abs(rb), k) 3136 rc = min(abs(rc), k) 3137 rd = min(abs(rd), k) 3138 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3139 betas = np.split(beta, 4) 3140 rrx = np.cos(betas) 3141 rry = np.sin(betas) 3142 3143 q1 = (rd, 0) 3144 # q2 = (px-ra, 0) 3145 q3 = (px, ra) 3146 # q4 = (px, py-rb) 3147 q5 = (px - rb, py) 3148 # q6 = (rc, py) 3149 q7 = (0, py - rc) 3150 # q8 = (0, rd) 3151 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3152 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3153 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3154 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3155 3156 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3157 faces = [list(range(len(pts)))] 3158 else: 3159 p1r = np.array([p2[0], p1[1], 0.0]) 3160 p2l = np.array([p1[0], p2[1], 0.0]) 3161 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3162 faces = [(0, 1, 2, 3)] 3163 3164 super().__init__([pts, faces], color, alpha) 3165 self.pos(p1) 3166 self.properties.LightingOff() 3167 self.name = "Rectangle"
Build a rectangle in the xy plane.
3081 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3082 """ 3083 Build a rectangle in the xy plane identified by any two corner points. 3084 3085 Arguments: 3086 p1 : (list) 3087 bottom-left position of the corner 3088 p2 : (list) 3089 top-right position of the corner 3090 radius : (float, list) 3091 smoothing radius of the corner in world units. 3092 A list can be passed with 4 individual values. 3093 """ 3094 if len(p1) == 2: 3095 p1 = np.array([p1[0], p1[1], 0.0]) 3096 else: 3097 p1 = np.array(p1, dtype=float) 3098 if len(p2) == 2: 3099 p2 = np.array([p2[0], p2[1], 0.0]) 3100 else: 3101 p2 = np.array(p2, dtype=float) 3102 3103 self.corner1 = p1 3104 self.corner2 = p2 3105 3106 color = c 3107 smoothr = False 3108 risseq = False 3109 if utils.is_sequence(radius): 3110 risseq = True 3111 smoothr = True 3112 if max(radius) == 0: 3113 smoothr = False 3114 elif radius: 3115 smoothr = True 3116 3117 if not smoothr: 3118 radius = None 3119 self.radius = radius 3120 3121 if smoothr: 3122 r = radius 3123 if not risseq: 3124 r = [r, r, r, r] 3125 rd, ra, rb, rc = r 3126 3127 if p1[0] > p2[0]: # flip p1 - p2 3128 p1, p2 = p2, p1 3129 if p1[1] > p2[1]: # flip p1y - p2y 3130 p1[1], p2[1] = p2[1], p1[1] 3131 3132 px, py, _ = p2 - p1 3133 k = min(px / 2, py / 2) 3134 ra = min(abs(ra), k) 3135 rb = min(abs(rb), k) 3136 rc = min(abs(rc), k) 3137 rd = min(abs(rd), k) 3138 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3139 betas = np.split(beta, 4) 3140 rrx = np.cos(betas) 3141 rry = np.sin(betas) 3142 3143 q1 = (rd, 0) 3144 # q2 = (px-ra, 0) 3145 q3 = (px, ra) 3146 # q4 = (px, py-rb) 3147 q5 = (px - rb, py) 3148 # q6 = (rc, py) 3149 q7 = (0, py - rc) 3150 # q8 = (0, rd) 3151 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3152 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3153 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3154 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3155 3156 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3157 faces = [list(range(len(pts)))] 3158 else: 3159 p1r = np.array([p2[0], p1[1], 0.0]) 3160 p2l = np.array([p1[0], p2[1], 0.0]) 3161 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3162 faces = [(0, 1, 2, 3)] 3163 3164 super().__init__([pts, faces], color, alpha) 3165 self.pos(p1) 3166 self.properties.LightingOff() 3167 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.
2396class Disc(Mesh): 2397 """ 2398 Build a 2D disc. 2399 """ 2400 2401 def __init__( 2402 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2403 ) -> None: 2404 """ 2405 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2406 2407 Set `res` as the resolution in R and Phi (can be a list). 2408 2409 Use `angle_range` to create a disc sector between the 2 specified angles. 2410 2411 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2412 """ 2413 if utils.is_sequence(res): 2414 res_r, res_phi = res 2415 else: 2416 res_r, res_phi = res, 12 * res 2417 2418 if len(angle_range) == 0: 2419 ps = vtki.new("DiskSource") 2420 else: 2421 ps = vtki.new("SectorSource") 2422 ps.SetStartAngle(angle_range[0]) 2423 ps.SetEndAngle(angle_range[1]) 2424 2425 ps.SetInnerRadius(r1) 2426 ps.SetOuterRadius(r2) 2427 ps.SetRadialResolution(res_r) 2428 ps.SetCircumferentialResolution(res_phi) 2429 ps.Update() 2430 super().__init__(ps.GetOutput(), c, alpha) 2431 self.flat() 2432 self.pos(utils.make3d(pos)) 2433 self.name = "Disc"
Build a 2D disc.
2401 def __init__( 2402 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2403 ) -> None: 2404 """ 2405 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2406 2407 Set `res` as the resolution in R and Phi (can be a list). 2408 2409 Use `angle_range` to create a disc sector between the 2 specified angles. 2410 2411 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2412 """ 2413 if utils.is_sequence(res): 2414 res_r, res_phi = res 2415 else: 2416 res_r, res_phi = res, 12 * res 2417 2418 if len(angle_range) == 0: 2419 ps = vtki.new("DiskSource") 2420 else: 2421 ps = vtki.new("SectorSource") 2422 ps.SetStartAngle(angle_range[0]) 2423 ps.SetEndAngle(angle_range[1]) 2424 2425 ps.SetInnerRadius(r1) 2426 ps.SetOuterRadius(r2) 2427 ps.SetRadialResolution(res_r) 2428 ps.SetCircumferentialResolution(res_phi) 2429 ps.Update() 2430 super().__init__(ps.GetOutput(), c, alpha) 2431 self.flat() 2432 self.pos(utils.make3d(pos)) 2433 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.
2288class Circle(Polygon): 2289 """ 2290 Build a Circle of radius `r`. 2291 """ 2292 2293 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2294 """ 2295 Build a Circle of radius `r`. 2296 """ 2297 super().__init__(pos, nsides=res, r=r) 2298 2299 self.nr_of_points = 0 2300 self.va = 0 2301 self.vb = 0 2302 self.axis1: List[float] = [] 2303 self.axis2: List[float] = [] 2304 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2305 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2306 self.alpha(alpha).c(c) 2307 self.name = "Circle" 2308 2309 def acircularity(self) -> float: 2310 """ 2311 Return a measure of how different an ellipse is from a circle. 2312 Values close to zero correspond to a circular object. 2313 """ 2314 a, b = self.va, self.vb 2315 value = 0.0 2316 if a+b: 2317 value = ((a-b)/(a+b))**2 2318 return value
Build a Circle of radius r
.
2293 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2294 """ 2295 Build a Circle of radius `r`. 2296 """ 2297 super().__init__(pos, nsides=res, r=r) 2298 2299 self.nr_of_points = 0 2300 self.va = 0 2301 self.vb = 0 2302 self.axis1: List[float] = [] 2303 self.axis2: List[float] = [] 2304 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2305 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2306 self.alpha(alpha).c(c) 2307 self.name = "Circle"
Build a Circle of radius r
.
2309 def acircularity(self) -> float: 2310 """ 2311 Return a measure of how different an ellipse is from a circle. 2312 Values close to zero correspond to a circular object. 2313 """ 2314 a, b = self.va, self.vb 2315 value = 0.0 2316 if a+b: 2317 value = ((a-b)/(a+b))**2 2318 return value
Return a measure of how different an ellipse is from a circle. Values close to zero correspond to a circular object.
2320class GeoCircle(Polygon): 2321 """ 2322 Build a Circle of radius `r`. 2323 """ 2324 2325 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2326 """ 2327 Build a Circle of radius `r` as projected on a geographic map. 2328 Circles near the poles will look very squashed. 2329 2330 See example: 2331 ```bash 2332 vedo -r earthquake 2333 ``` 2334 """ 2335 coords = [] 2336 sinr, cosr = np.sin(r), np.cos(r) 2337 sinlat, coslat = np.sin(lat), np.cos(lat) 2338 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2339 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2340 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2341 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2342 2343 super().__init__(nsides=res, c=c, alpha=alpha) 2344 self.vertices = coords # warp polygon points to match geo projection 2345 self.name = "Circle"
Build a Circle of radius r
.
2325 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2326 """ 2327 Build a Circle of radius `r` as projected on a geographic map. 2328 Circles near the poles will look very squashed. 2329 2330 See example: 2331 ```bash 2332 vedo -r earthquake 2333 ``` 2334 """ 2335 coords = [] 2336 sinr, cosr = np.sin(r), np.cos(r) 2337 sinlat, coslat = np.sin(lat), np.cos(lat) 2338 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2339 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2340 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2341 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2342 2343 super().__init__(nsides=res, c=c, alpha=alpha) 2344 self.vertices = coords # warp polygon points to match geo projection 2345 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.
2436class Arc(Mesh): 2437 """ 2438 Build a 2D circular arc between 2 points. 2439 """ 2440 2441 def __init__( 2442 self, 2443 center, 2444 point1, 2445 point2=None, 2446 normal=None, 2447 angle=None, 2448 invert=False, 2449 res=50, 2450 c="gray4", 2451 alpha=1.0, 2452 ) -> None: 2453 """ 2454 Build a 2D circular arc between 2 points `point1` and `point2`. 2455 2456 If `normal` is specified then `center` is ignored, and 2457 normal vector, a starting `point1` (polar vector) 2458 and an angle defining the arc length need to be assigned. 2459 2460 Arc spans the shortest angular sector point1 and point2, 2461 if `invert=True`, then the opposite happens. 2462 """ 2463 if len(point1) == 2: 2464 point1 = (point1[0], point1[1], 0) 2465 if point2 is not None and len(point2) == 2: 2466 point2 = (point2[0], point2[1], 0) 2467 2468 ar = vtki.new("ArcSource") 2469 if point2 is not None: 2470 self.top = point2 2471 point2 = point2 - np.asarray(point1) 2472 ar.UseNormalAndAngleOff() 2473 ar.SetPoint1([0, 0, 0]) 2474 ar.SetPoint2(point2) 2475 # ar.SetCenter(center) 2476 elif normal is not None and angle is not None: 2477 ar.UseNormalAndAngleOn() 2478 ar.SetAngle(angle) 2479 ar.SetPolarVector(point1) 2480 ar.SetNormal(normal) 2481 else: 2482 vedo.logger.error("incorrect input combination") 2483 return 2484 ar.SetNegative(invert) 2485 ar.SetResolution(res) 2486 ar.Update() 2487 2488 super().__init__(ar.GetOutput(), c, alpha) 2489 self.pos(center) 2490 self.lw(2).lighting("off") 2491 self.name = "Arc"
Build a 2D circular arc between 2 points.
2441 def __init__( 2442 self, 2443 center, 2444 point1, 2445 point2=None, 2446 normal=None, 2447 angle=None, 2448 invert=False, 2449 res=50, 2450 c="gray4", 2451 alpha=1.0, 2452 ) -> None: 2453 """ 2454 Build a 2D circular arc between 2 points `point1` and `point2`. 2455 2456 If `normal` is specified then `center` is ignored, and 2457 normal vector, a starting `point1` (polar vector) 2458 and an angle defining the arc length need to be assigned. 2459 2460 Arc spans the shortest angular sector point1 and point2, 2461 if `invert=True`, then the opposite happens. 2462 """ 2463 if len(point1) == 2: 2464 point1 = (point1[0], point1[1], 0) 2465 if point2 is not None and len(point2) == 2: 2466 point2 = (point2[0], point2[1], 0) 2467 2468 ar = vtki.new("ArcSource") 2469 if point2 is not None: 2470 self.top = point2 2471 point2 = point2 - np.asarray(point1) 2472 ar.UseNormalAndAngleOff() 2473 ar.SetPoint1([0, 0, 0]) 2474 ar.SetPoint2(point2) 2475 # ar.SetCenter(center) 2476 elif normal is not None and angle is not None: 2477 ar.UseNormalAndAngleOn() 2478 ar.SetAngle(angle) 2479 ar.SetPolarVector(point1) 2480 ar.SetNormal(normal) 2481 else: 2482 vedo.logger.error("incorrect input combination") 2483 return 2484 ar.SetNegative(invert) 2485 ar.SetResolution(res) 2486 ar.Update() 2487 2488 super().__init__(ar.GetOutput(), c, alpha) 2489 self.pos(center) 2490 self.lw(2).lighting("off") 2491 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.
2348class Star(Mesh): 2349 """ 2350 Build a 2D star shape. 2351 """ 2352 2353 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2354 """ 2355 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2356 2357 If line is True then only build the outer line (no internal surface meshing). 2358 2359 Example: 2360 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2361 2362 ![](https://vedo.embl.es/images/basic/extrude.png) 2363 """ 2364 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2365 x, y = pol2cart(np.ones_like(t) * r2, t) 2366 pts = np.c_[x, y, np.zeros_like(x)] 2367 2368 apts = [] 2369 for i, p in enumerate(pts): 2370 apts.append(p) 2371 if i + 1 < n: 2372 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2373 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2374 2375 if line: 2376 apts.append(pts[0]) 2377 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2378 super().__init__(poly, c, alpha) 2379 self.lw(2) 2380 else: 2381 apts.append((0, 0, 0)) 2382 cells = [] 2383 for i in range(2 * n - 1): 2384 cell = [2 * n, i, i + 1] 2385 cells.append(cell) 2386 cells.append([2 * n, i + 1, 0]) 2387 super().__init__([apts, cells], c, alpha) 2388 2389 if len(pos) == 2: 2390 pos = (pos[0], pos[1], 0) 2391 2392 self.properties.LightingOff() 2393 self.name = "Star"
Build a 2D star shape.
2353 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2354 """ 2355 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2356 2357 If line is True then only build the outer line (no internal surface meshing). 2358 2359 Example: 2360 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2361 2362 ![](https://vedo.embl.es/images/basic/extrude.png) 2363 """ 2364 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2365 x, y = pol2cart(np.ones_like(t) * r2, t) 2366 pts = np.c_[x, y, np.zeros_like(x)] 2367 2368 apts = [] 2369 for i, p in enumerate(pts): 2370 apts.append(p) 2371 if i + 1 < n: 2372 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2373 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2374 2375 if line: 2376 apts.append(pts[0]) 2377 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2378 super().__init__(poly, c, alpha) 2379 self.lw(2) 2380 else: 2381 apts.append((0, 0, 0)) 2382 cells = [] 2383 for i in range(2 * n - 1): 2384 cell = [2 * n, i, i + 1] 2385 cells.append(cell) 2386 cells.append([2 * n, i + 1, 0]) 2387 super().__init__([apts, cells], c, alpha) 2388 2389 if len(pos) == 2: 2390 pos = (pos[0], pos[1], 0) 2391 2392 self.properties.LightingOff() 2393 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:
3790class Star3D(Mesh): 3791 """ 3792 Build a 3D starred shape. 3793 """ 3794 3795 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3796 """ 3797 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3798 """ 3799 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3800 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3801 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3802 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3803 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3804 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3805 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3806 [10,1, 0],[10,11, 9]] 3807 3808 super().__init__([pts, fcs], c, alpha) 3809 self.rotate_x(90) 3810 self.scale(r).lighting("shiny") 3811 3812 if len(pos) == 2: 3813 pos = (pos[0], pos[1], 0) 3814 self.pos(pos) 3815 self.name = "Star3D"
Build a 3D starred shape.
3795 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3796 """ 3797 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3798 """ 3799 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3800 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3801 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3802 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3803 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3804 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3805 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3806 [10,1, 0],[10,11, 9]] 3807 3808 super().__init__([pts, fcs], c, alpha) 3809 self.rotate_x(90) 3810 self.scale(r).lighting("shiny") 3811 3812 if len(pos) == 2: 3813 pos = (pos[0], pos[1], 0) 3814 self.pos(pos) 3815 self.name = "Star3D"
Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3818class Cross3D(Mesh): 3819 """ 3820 Build a 3D cross shape. 3821 """ 3822 3823 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3824 """ 3825 Build a 3D cross shape, mainly useful as a 3D marker. 3826 """ 3827 if len(pos) == 2: 3828 pos = (pos[0], pos[1], 0) 3829 3830 c1 = Cylinder(r=thickness * s, height=2 * s) 3831 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3832 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3833 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3834 super().__init__(poly, c, alpha) 3835 self.name = "Cross3D"
Build a 3D cross shape.
3823 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3824 """ 3825 Build a 3D cross shape, mainly useful as a 3D marker. 3826 """ 3827 if len(pos) == 2: 3828 pos = (pos[0], pos[1], 0) 3829 3830 c1 = Cylinder(r=thickness * s, height=2 * s) 3831 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3832 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3833 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3834 super().__init__(poly, c, alpha) 3835 self.name = "Cross3D"
Build a 3D cross shape, mainly useful as a 3D marker.
2494class IcoSphere(Mesh): 2495 """ 2496 Create a sphere made of a uniform triangle mesh. 2497 """ 2498 2499 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2500 """ 2501 Create a sphere made of a uniform triangle mesh 2502 (from recursive subdivision of an icosahedron). 2503 2504 Example: 2505 ```python 2506 from vedo import * 2507 icos = IcoSphere(subdivisions=3) 2508 icos.compute_quality().cmap('coolwarm') 2509 icos.show(axes=1).close() 2510 ``` 2511 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2512 """ 2513 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2514 2515 t = (1.0 + np.sqrt(5.0)) / 2.0 2516 points = np.array( 2517 [ 2518 [-1, t, 0], 2519 [1, t, 0], 2520 [-1, -t, 0], 2521 [1, -t, 0], 2522 [0, -1, t], 2523 [0, 1, t], 2524 [0, -1, -t], 2525 [0, 1, -t], 2526 [t, 0, -1], 2527 [t, 0, 1], 2528 [-t, 0, -1], 2529 [-t, 0, 1], 2530 ] 2531 ) 2532 faces = [ 2533 [0, 11, 5], 2534 [0, 5, 1], 2535 [0, 1, 7], 2536 [0, 7, 10], 2537 [0, 10, 11], 2538 [1, 5, 9], 2539 [5, 11, 4], 2540 [11, 10, 2], 2541 [10, 7, 6], 2542 [7, 1, 8], 2543 [3, 9, 4], 2544 [3, 4, 2], 2545 [3, 2, 6], 2546 [3, 6, 8], 2547 [3, 8, 9], 2548 [4, 9, 5], 2549 [2, 4, 11], 2550 [6, 2, 10], 2551 [8, 6, 7], 2552 [9, 8, 1], 2553 ] 2554 super().__init__([points * r, faces], c=c, alpha=alpha) 2555 2556 for _ in range(subdivisions): 2557 self.subdivide(method=1) 2558 pts = utils.versor(self.vertices) * r 2559 self.vertices = pts 2560 2561 self.pos(pos) 2562 self.name = "IcoSphere"
Create a sphere made of a uniform triangle mesh.
2499 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2500 """ 2501 Create a sphere made of a uniform triangle mesh 2502 (from recursive subdivision of an icosahedron). 2503 2504 Example: 2505 ```python 2506 from vedo import * 2507 icos = IcoSphere(subdivisions=3) 2508 icos.compute_quality().cmap('coolwarm') 2509 icos.show(axes=1).close() 2510 ``` 2511 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2512 """ 2513 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2514 2515 t = (1.0 + np.sqrt(5.0)) / 2.0 2516 points = np.array( 2517 [ 2518 [-1, t, 0], 2519 [1, t, 0], 2520 [-1, -t, 0], 2521 [1, -t, 0], 2522 [0, -1, t], 2523 [0, 1, t], 2524 [0, -1, -t], 2525 [0, 1, -t], 2526 [t, 0, -1], 2527 [t, 0, 1], 2528 [-t, 0, -1], 2529 [-t, 0, 1], 2530 ] 2531 ) 2532 faces = [ 2533 [0, 11, 5], 2534 [0, 5, 1], 2535 [0, 1, 7], 2536 [0, 7, 10], 2537 [0, 10, 11], 2538 [1, 5, 9], 2539 [5, 11, 4], 2540 [11, 10, 2], 2541 [10, 7, 6], 2542 [7, 1, 8], 2543 [3, 9, 4], 2544 [3, 4, 2], 2545 [3, 2, 6], 2546 [3, 6, 8], 2547 [3, 8, 9], 2548 [4, 9, 5], 2549 [2, 4, 11], 2550 [6, 2, 10], 2551 [8, 6, 7], 2552 [9, 8, 1], 2553 ] 2554 super().__init__([points * r, faces], c=c, alpha=alpha) 2555 2556 for _ in range(subdivisions): 2557 self.subdivide(method=1) 2558 pts = utils.versor(self.vertices) * r 2559 self.vertices = pts 2560 2561 self.pos(pos) 2562 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()
2565class Sphere(Mesh): 2566 """ 2567 Build a sphere. 2568 """ 2569 2570 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2571 """ 2572 Build a sphere at position `pos` of radius `r`. 2573 2574 Arguments: 2575 r : (float) 2576 sphere radius 2577 res : (int, list) 2578 resolution in phi, resolution in theta is by default `2*res` 2579 quads : (bool) 2580 sphere mesh will be made of quads instead of triangles 2581 2582 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2583 """ 2584 if len(pos) == 2: 2585 pos = np.asarray([pos[0], pos[1], 0]) 2586 2587 self.radius = r # used by fitSphere 2588 self.center = pos 2589 self.residue = 0 2590 2591 if quads: 2592 res = max(res, 4) 2593 img = vtki.vtkImageData() 2594 img.SetDimensions(res - 1, res - 1, res - 1) 2595 rs = 1.0 / (res - 2) 2596 img.SetSpacing(rs, rs, rs) 2597 gf = vtki.new("GeometryFilter") 2598 gf.SetInputData(img) 2599 gf.Update() 2600 super().__init__(gf.GetOutput(), c, alpha) 2601 self.lw(0.1) 2602 2603 cgpts = self.vertices - (0.5, 0.5, 0.5) 2604 2605 x, y, z = cgpts.T 2606 x = x * (1 + x * x) / 2 2607 y = y * (1 + y * y) / 2 2608 z = z * (1 + z * z) / 2 2609 _, theta, phi = cart2spher(x, y, z) 2610 2611 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2612 self.vertices = pts 2613 2614 else: 2615 if utils.is_sequence(res): 2616 res_t, res_phi = res 2617 else: 2618 res_t, res_phi = 2 * res, res 2619 2620 ss = vtki.new("SphereSource") 2621 ss.SetRadius(r) 2622 ss.SetThetaResolution(res_t) 2623 ss.SetPhiResolution(res_phi) 2624 ss.Update() 2625 2626 super().__init__(ss.GetOutput(), c, alpha) 2627 2628 self.phong() 2629 self.pos(pos) 2630 self.name = "Sphere"
Build a sphere.
2570 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2571 """ 2572 Build a sphere at position `pos` of radius `r`. 2573 2574 Arguments: 2575 r : (float) 2576 sphere radius 2577 res : (int, list) 2578 resolution in phi, resolution in theta is by default `2*res` 2579 quads : (bool) 2580 sphere mesh will be made of quads instead of triangles 2581 2582 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2583 """ 2584 if len(pos) == 2: 2585 pos = np.asarray([pos[0], pos[1], 0]) 2586 2587 self.radius = r # used by fitSphere 2588 self.center = pos 2589 self.residue = 0 2590 2591 if quads: 2592 res = max(res, 4) 2593 img = vtki.vtkImageData() 2594 img.SetDimensions(res - 1, res - 1, res - 1) 2595 rs = 1.0 / (res - 2) 2596 img.SetSpacing(rs, rs, rs) 2597 gf = vtki.new("GeometryFilter") 2598 gf.SetInputData(img) 2599 gf.Update() 2600 super().__init__(gf.GetOutput(), c, alpha) 2601 self.lw(0.1) 2602 2603 cgpts = self.vertices - (0.5, 0.5, 0.5) 2604 2605 x, y, z = cgpts.T 2606 x = x * (1 + x * x) / 2 2607 y = y * (1 + y * y) / 2 2608 z = z * (1 + z * z) / 2 2609 _, theta, phi = cart2spher(x, y, z) 2610 2611 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2612 self.vertices = pts 2613 2614 else: 2615 if utils.is_sequence(res): 2616 res_t, res_phi = res 2617 else: 2618 res_t, res_phi = 2 * res, res 2619 2620 ss = vtki.new("SphereSource") 2621 ss.SetRadius(r) 2622 ss.SetThetaResolution(res_t) 2623 ss.SetPhiResolution(res_phi) 2624 ss.Update() 2625 2626 super().__init__(ss.GetOutput(), c, alpha) 2627 2628 self.phong() 2629 self.pos(pos) 2630 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
2633class Spheres(Mesh): 2634 """ 2635 Build a large set of spheres. 2636 """ 2637 2638 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2639 """ 2640 Build a (possibly large) set of spheres at `centers` of radius `r`. 2641 2642 Either `c` or `r` can be a list of RGB colors or radii. 2643 2644 Examples: 2645 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2646 2647 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2648 """ 2649 2650 if isinstance(centers, Points): 2651 centers = centers.vertices 2652 centers = np.asarray(centers, dtype=float) 2653 base = centers[0] 2654 2655 cisseq = False 2656 if utils.is_sequence(c): 2657 cisseq = True 2658 2659 if cisseq: 2660 if len(centers) != len(c): 2661 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2662 raise RuntimeError() 2663 2664 risseq = False 2665 if utils.is_sequence(r): 2666 risseq = True 2667 2668 if risseq: 2669 if len(centers) != len(r): 2670 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2671 raise RuntimeError() 2672 if cisseq and risseq: 2673 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2674 raise RuntimeError() 2675 2676 src = vtki.new("SphereSource") 2677 if not risseq: 2678 src.SetRadius(r) 2679 if utils.is_sequence(res): 2680 res_t, res_phi = res 2681 else: 2682 res_t, res_phi = 2 * res, res 2683 2684 src.SetThetaResolution(res_t) 2685 src.SetPhiResolution(res_phi) 2686 src.Update() 2687 2688 psrc = vtki.new("PointSource") 2689 psrc.SetNumberOfPoints(len(centers)) 2690 psrc.Update() 2691 pd = psrc.GetOutput() 2692 vpts = pd.GetPoints() 2693 2694 glyph = vtki.vtkGlyph3D() 2695 glyph.SetSourceConnection(src.GetOutputPort()) 2696 2697 if cisseq: 2698 glyph.SetColorModeToColorByScalar() 2699 ucols = vtki.vtkUnsignedCharArray() 2700 ucols.SetNumberOfComponents(3) 2701 ucols.SetName("Colors") 2702 for acol in c: 2703 cx, cy, cz = get_color(acol) 2704 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2705 pd.GetPointData().AddArray(ucols) 2706 pd.GetPointData().SetActiveScalars("Colors") 2707 glyph.ScalingOff() 2708 elif risseq: 2709 glyph.SetScaleModeToScaleByScalar() 2710 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2711 urads.SetName("Radii") 2712 pd.GetPointData().AddArray(urads) 2713 pd.GetPointData().SetActiveScalars("Radii") 2714 2715 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2716 2717 glyph.SetInputData(pd) 2718 glyph.Update() 2719 2720 super().__init__(glyph.GetOutput(), alpha=alpha) 2721 self.pos(base) 2722 self.phong() 2723 if cisseq: 2724 self.mapper.ScalarVisibilityOn() 2725 else: 2726 self.mapper.ScalarVisibilityOff() 2727 self.c(c) 2728 self.name = "Spheres"
Build a large set of spheres.
2638 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2639 """ 2640 Build a (possibly large) set of spheres at `centers` of radius `r`. 2641 2642 Either `c` or `r` can be a list of RGB colors or radii. 2643 2644 Examples: 2645 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2646 2647 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2648 """ 2649 2650 if isinstance(centers, Points): 2651 centers = centers.vertices 2652 centers = np.asarray(centers, dtype=float) 2653 base = centers[0] 2654 2655 cisseq = False 2656 if utils.is_sequence(c): 2657 cisseq = True 2658 2659 if cisseq: 2660 if len(centers) != len(c): 2661 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2662 raise RuntimeError() 2663 2664 risseq = False 2665 if utils.is_sequence(r): 2666 risseq = True 2667 2668 if risseq: 2669 if len(centers) != len(r): 2670 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2671 raise RuntimeError() 2672 if cisseq and risseq: 2673 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2674 raise RuntimeError() 2675 2676 src = vtki.new("SphereSource") 2677 if not risseq: 2678 src.SetRadius(r) 2679 if utils.is_sequence(res): 2680 res_t, res_phi = res 2681 else: 2682 res_t, res_phi = 2 * res, res 2683 2684 src.SetThetaResolution(res_t) 2685 src.SetPhiResolution(res_phi) 2686 src.Update() 2687 2688 psrc = vtki.new("PointSource") 2689 psrc.SetNumberOfPoints(len(centers)) 2690 psrc.Update() 2691 pd = psrc.GetOutput() 2692 vpts = pd.GetPoints() 2693 2694 glyph = vtki.vtkGlyph3D() 2695 glyph.SetSourceConnection(src.GetOutputPort()) 2696 2697 if cisseq: 2698 glyph.SetColorModeToColorByScalar() 2699 ucols = vtki.vtkUnsignedCharArray() 2700 ucols.SetNumberOfComponents(3) 2701 ucols.SetName("Colors") 2702 for acol in c: 2703 cx, cy, cz = get_color(acol) 2704 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2705 pd.GetPointData().AddArray(ucols) 2706 pd.GetPointData().SetActiveScalars("Colors") 2707 glyph.ScalingOff() 2708 elif risseq: 2709 glyph.SetScaleModeToScaleByScalar() 2710 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2711 urads.SetName("Radii") 2712 pd.GetPointData().AddArray(urads) 2713 pd.GetPointData().SetActiveScalars("Radii") 2714 2715 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2716 2717 glyph.SetInputData(pd) 2718 glyph.Update() 2719 2720 super().__init__(glyph.GetOutput(), alpha=alpha) 2721 self.pos(base) 2722 self.phong() 2723 if cisseq: 2724 self.mapper.ScalarVisibilityOn() 2725 else: 2726 self.mapper.ScalarVisibilityOff() 2727 self.c(c) 2728 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:
2731class Earth(Mesh): 2732 """ 2733 Build a textured mesh representing the Earth. 2734 """ 2735 2736 def __init__(self, style=1, r=1.0) -> None: 2737 """ 2738 Build a textured mesh representing the Earth. 2739 2740 Example: 2741 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2742 2743 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2744 """ 2745 tss = vtki.new("TexturedSphereSource") 2746 tss.SetRadius(r) 2747 tss.SetThetaResolution(72) 2748 tss.SetPhiResolution(36) 2749 tss.Update() 2750 super().__init__(tss.GetOutput(), c="w") 2751 atext = vtki.vtkTexture() 2752 pnm_reader = vtki.new("JPEGReader") 2753 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2754 pnm_reader.SetFileName(fn) 2755 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2756 atext.InterpolateOn() 2757 self.texture(atext) 2758 self.name = "Earth"
Build a textured mesh representing the Earth.
2736 def __init__(self, style=1, r=1.0) -> None: 2737 """ 2738 Build a textured mesh representing the Earth. 2739 2740 Example: 2741 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2742 2743 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2744 """ 2745 tss = vtki.new("TexturedSphereSource") 2746 tss.SetRadius(r) 2747 tss.SetThetaResolution(72) 2748 tss.SetPhiResolution(36) 2749 tss.Update() 2750 super().__init__(tss.GetOutput(), c="w") 2751 atext = vtki.vtkTexture() 2752 pnm_reader = vtki.new("JPEGReader") 2753 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2754 pnm_reader.SetFileName(fn) 2755 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2756 atext.InterpolateOn() 2757 self.texture(atext) 2758 self.name = "Earth"
2761class Ellipsoid(Mesh): 2762 """Build a 3D ellipsoid.""" 2763 def __init__( 2764 self, 2765 pos=(0, 0, 0), 2766 axis1=(0.5, 0, 0), 2767 axis2=(0, 1, 0), 2768 axis3=(0, 0, 1.5), 2769 res=24, 2770 c="cyan4", 2771 alpha=1.0, 2772 ) -> None: 2773 """ 2774 Build a 3D ellipsoid centered at position `pos`. 2775 2776 Arguments: 2777 axis1 : (list) 2778 First axis. Length corresponds to semi-axis. 2779 axis2 : (list) 2780 Second axis. Length corresponds to semi-axis. 2781 axis3 : (list) 2782 Third axis. Length corresponds to semi-axis. 2783 """ 2784 self.center = utils.make3d(pos) 2785 2786 self.axis1 = utils.make3d(axis1) 2787 self.axis2 = utils.make3d(axis2) 2788 self.axis3 = utils.make3d(axis3) 2789 2790 self.va = np.linalg.norm(self.axis1) 2791 self.vb = np.linalg.norm(self.axis2) 2792 self.vc = np.linalg.norm(self.axis3) 2793 2794 self.va_error = 0 2795 self.vb_error = 0 2796 self.vc_error = 0 2797 2798 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2799 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2800 2801 if utils.is_sequence(res): 2802 res_t, res_phi = res 2803 else: 2804 res_t, res_phi = 2 * res, res 2805 2806 elli_source = vtki.new("SphereSource") 2807 elli_source.SetRadius(1) 2808 elli_source.SetThetaResolution(res_t) 2809 elli_source.SetPhiResolution(res_phi) 2810 elli_source.Update() 2811 2812 super().__init__(elli_source.GetOutput(), c, alpha) 2813 2814 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2815 lt = LinearTransform(matrix).translate(pos) 2816 self.apply_transform(lt) 2817 self.name = "Ellipsoid" 2818 2819 def asphericity(self) -> float: 2820 """ 2821 Return a measure of how different an ellipsoid is from a sphere. 2822 Values close to zero correspond to a spheric object. 2823 """ 2824 a, b, c = self.va, self.vb, self.vc 2825 asp = ( ((a-b)/(a+b))**2 2826 + ((a-c)/(a+c))**2 2827 + ((b-c)/(b+c))**2 ) / 3. * 4. 2828 return float(asp) 2829 2830 def asphericity_error(self) -> float: 2831 """ 2832 Calculate statistical error on the asphericity value. 2833 2834 Errors on the main axes are stored in 2835 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2836 """ 2837 a, b, c = self.va, self.vb, self.vc 2838 sqrtn = np.sqrt(self.nr_of_points) 2839 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2840 2841 # from sympy import * 2842 # init_printing(use_unicode=True) 2843 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2844 # L = ( 2845 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2846 # / 3 * 4) 2847 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2848 # print(dl2) 2849 # exit() 2850 2851 dL2 = ( 2852 ea ** 2 2853 * ( 2854 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2855 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2856 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2857 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2858 ) ** 2 2859 + eb ** 2 2860 * ( 2861 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2862 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2863 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2864 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2865 ) ** 2 2866 + ec ** 2 2867 * ( 2868 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2869 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2870 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2871 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2872 ) ** 2 2873 ) 2874 err = np.sqrt(dL2) 2875 self.va_error = ea 2876 self.vb_error = eb 2877 self.vc_error = ec 2878 return err
Build a 3D ellipsoid.
2763 def __init__( 2764 self, 2765 pos=(0, 0, 0), 2766 axis1=(0.5, 0, 0), 2767 axis2=(0, 1, 0), 2768 axis3=(0, 0, 1.5), 2769 res=24, 2770 c="cyan4", 2771 alpha=1.0, 2772 ) -> None: 2773 """ 2774 Build a 3D ellipsoid centered at position `pos`. 2775 2776 Arguments: 2777 axis1 : (list) 2778 First axis. Length corresponds to semi-axis. 2779 axis2 : (list) 2780 Second axis. Length corresponds to semi-axis. 2781 axis3 : (list) 2782 Third axis. Length corresponds to semi-axis. 2783 """ 2784 self.center = utils.make3d(pos) 2785 2786 self.axis1 = utils.make3d(axis1) 2787 self.axis2 = utils.make3d(axis2) 2788 self.axis3 = utils.make3d(axis3) 2789 2790 self.va = np.linalg.norm(self.axis1) 2791 self.vb = np.linalg.norm(self.axis2) 2792 self.vc = np.linalg.norm(self.axis3) 2793 2794 self.va_error = 0 2795 self.vb_error = 0 2796 self.vc_error = 0 2797 2798 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2799 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2800 2801 if utils.is_sequence(res): 2802 res_t, res_phi = res 2803 else: 2804 res_t, res_phi = 2 * res, res 2805 2806 elli_source = vtki.new("SphereSource") 2807 elli_source.SetRadius(1) 2808 elli_source.SetThetaResolution(res_t) 2809 elli_source.SetPhiResolution(res_phi) 2810 elli_source.Update() 2811 2812 super().__init__(elli_source.GetOutput(), c, alpha) 2813 2814 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2815 lt = LinearTransform(matrix).translate(pos) 2816 self.apply_transform(lt) 2817 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.
2819 def asphericity(self) -> float: 2820 """ 2821 Return a measure of how different an ellipsoid is from a sphere. 2822 Values close to zero correspond to a spheric object. 2823 """ 2824 a, b, c = self.va, self.vb, self.vc 2825 asp = ( ((a-b)/(a+b))**2 2826 + ((a-c)/(a+c))**2 2827 + ((b-c)/(b+c))**2 ) / 3. * 4. 2828 return float(asp)
Return a measure of how different an ellipsoid is from a sphere. Values close to zero correspond to a spheric object.
2830 def asphericity_error(self) -> float: 2831 """ 2832 Calculate statistical error on the asphericity value. 2833 2834 Errors on the main axes are stored in 2835 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2836 """ 2837 a, b, c = self.va, self.vb, self.vc 2838 sqrtn = np.sqrt(self.nr_of_points) 2839 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2840 2841 # from sympy import * 2842 # init_printing(use_unicode=True) 2843 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2844 # L = ( 2845 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2846 # / 3 * 4) 2847 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2848 # print(dl2) 2849 # exit() 2850 2851 dL2 = ( 2852 ea ** 2 2853 * ( 2854 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2855 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2856 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2857 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2858 ) ** 2 2859 + eb ** 2 2860 * ( 2861 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2862 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2863 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2864 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2865 ) ** 2 2866 + ec ** 2 2867 * ( 2868 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2869 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2870 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2871 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2872 ) ** 2 2873 ) 2874 err = np.sqrt(dL2) 2875 self.va_error = ea 2876 self.vb_error = eb 2877 self.vc_error = ec 2878 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`.
2881class Grid(Mesh): 2882 """ 2883 An even or uneven 2D grid. 2884 """ 2885 2886 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2887 """ 2888 Create an even or uneven 2D grid. 2889 Can also be created from a `np.mgrid` object (see example). 2890 2891 Arguments: 2892 pos : (list, Points, Mesh) 2893 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2894 s : (float, list) 2895 if a float is provided it is interpreted as the total size along x and y, 2896 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2897 In this case keyword `res` is ignored (see example below). 2898 res : (list) 2899 resolutions along x and y, e.i. the number of subdivisions 2900 lw : (int) 2901 line width 2902 2903 Example: 2904 ```python 2905 from vedo import * 2906 xcoords = np.arange(0, 2, 0.2) 2907 ycoords = np.arange(0, 1, 0.2) 2908 sqrtx = sqrt(xcoords) 2909 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2910 grid.show(axes=8).close() 2911 2912 # Can also create a grid from a np.mgrid: 2913 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2914 vgrid = Grid(s=(X[:,0], Y[0])) 2915 vgrid.show(axes=8).close() 2916 ``` 2917 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2918 """ 2919 resx, resy = res 2920 sx, sy = s 2921 2922 try: 2923 bb = pos.bounds() 2924 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2925 sx = bb[1] - bb[0] 2926 sy = bb[3] - bb[2] 2927 except AttributeError: 2928 pass 2929 2930 if len(pos) == 2: 2931 pos = (pos[0], pos[1], 0) 2932 elif len(pos) in [4,6]: # passing a bounding box 2933 bb = pos 2934 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2935 sx = bb[1] - bb[0] 2936 sy = bb[3] - bb[2] 2937 if len(pos)==6: 2938 pos[2] = bb[4] - bb[5] 2939 2940 if utils.is_sequence(sx) and utils.is_sequence(sy): 2941 verts = [] 2942 for y in sy: 2943 for x in sx: 2944 verts.append([x, y, 0]) 2945 faces = [] 2946 n = len(sx) 2947 m = len(sy) 2948 for j in range(m - 1): 2949 j1n = (j + 1) * n 2950 for i in range(n - 1): 2951 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2952 2953 super().__init__([verts, faces], c, alpha) 2954 2955 else: 2956 ps = vtki.new("PlaneSource") 2957 ps.SetResolution(resx, resy) 2958 ps.Update() 2959 2960 t = vtki.vtkTransform() 2961 t.Translate(pos) 2962 t.Scale(sx, sy, 1) 2963 2964 tf = vtki.new("TransformPolyDataFilter") 2965 tf.SetInputData(ps.GetOutput()) 2966 tf.SetTransform(t) 2967 tf.Update() 2968 2969 super().__init__(tf.GetOutput(), c, alpha) 2970 2971 self.wireframe().lw(lw) 2972 self.properties.LightingOff() 2973 self.name = "Grid"
An even or uneven 2D grid.
2886 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2887 """ 2888 Create an even or uneven 2D grid. 2889 Can also be created from a `np.mgrid` object (see example). 2890 2891 Arguments: 2892 pos : (list, Points, Mesh) 2893 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2894 s : (float, list) 2895 if a float is provided it is interpreted as the total size along x and y, 2896 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2897 In this case keyword `res` is ignored (see example below). 2898 res : (list) 2899 resolutions along x and y, e.i. the number of subdivisions 2900 lw : (int) 2901 line width 2902 2903 Example: 2904 ```python 2905 from vedo import * 2906 xcoords = np.arange(0, 2, 0.2) 2907 ycoords = np.arange(0, 1, 0.2) 2908 sqrtx = sqrt(xcoords) 2909 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2910 grid.show(axes=8).close() 2911 2912 # Can also create a grid from a np.mgrid: 2913 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2914 vgrid = Grid(s=(X[:,0], Y[0])) 2915 vgrid.show(axes=8).close() 2916 ``` 2917 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2918 """ 2919 resx, resy = res 2920 sx, sy = s 2921 2922 try: 2923 bb = pos.bounds() 2924 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2925 sx = bb[1] - bb[0] 2926 sy = bb[3] - bb[2] 2927 except AttributeError: 2928 pass 2929 2930 if len(pos) == 2: 2931 pos = (pos[0], pos[1], 0) 2932 elif len(pos) in [4,6]: # passing a bounding box 2933 bb = pos 2934 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2935 sx = bb[1] - bb[0] 2936 sy = bb[3] - bb[2] 2937 if len(pos)==6: 2938 pos[2] = bb[4] - bb[5] 2939 2940 if utils.is_sequence(sx) and utils.is_sequence(sy): 2941 verts = [] 2942 for y in sy: 2943 for x in sx: 2944 verts.append([x, y, 0]) 2945 faces = [] 2946 n = len(sx) 2947 m = len(sy) 2948 for j in range(m - 1): 2949 j1n = (j + 1) * n 2950 for i in range(n - 1): 2951 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2952 2953 super().__init__([verts, faces], c, alpha) 2954 2955 else: 2956 ps = vtki.new("PlaneSource") 2957 ps.SetResolution(resx, resy) 2958 ps.Update() 2959 2960 t = vtki.vtkTransform() 2961 t.Translate(pos) 2962 t.Scale(sx, sy, 1) 2963 2964 tf = vtki.new("TransformPolyDataFilter") 2965 tf.SetInputData(ps.GetOutput()) 2966 tf.SetTransform(t) 2967 tf.Update() 2968 2969 super().__init__(tf.GetOutput(), c, alpha) 2970 2971 self.wireframe().lw(lw) 2972 self.properties.LightingOff() 2973 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()
3255class TessellatedBox(Mesh): 3256 """ 3257 Build a cubic `Mesh` made of quads. 3258 """ 3259 3260 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3261 """ 3262 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3263 3264 Arguments: 3265 pos : (list) 3266 position of the left bottom corner 3267 n : (int, list) 3268 number of subdivisions along each side 3269 spacing : (float) 3270 size of the side of the single quad in the 3 directions 3271 """ 3272 if utils.is_sequence(n): # slow 3273 img = vtki.vtkImageData() 3274 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3275 img.SetSpacing(spacing) 3276 gf = vtki.new("GeometryFilter") 3277 gf.SetInputData(img) 3278 gf.Update() 3279 poly = gf.GetOutput() 3280 else: # fast 3281 n -= 1 3282 tbs = vtki.new("TessellatedBoxSource") 3283 tbs.SetLevel(n) 3284 if len(bounds): 3285 tbs.SetBounds(bounds) 3286 else: 3287 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3288 tbs.QuadsOn() 3289 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3290 tbs.Update() 3291 poly = tbs.GetOutput() 3292 super().__init__(poly, c=c, alpha=alpha) 3293 self.pos(pos) 3294 self.lw(1).lighting("off") 3295 self.name = "TessellatedBox"
Build a cubic Mesh
made of quads.
3260 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3261 """ 3262 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3263 3264 Arguments: 3265 pos : (list) 3266 position of the left bottom corner 3267 n : (int, list) 3268 number of subdivisions along each side 3269 spacing : (float) 3270 size of the side of the single quad in the 3 directions 3271 """ 3272 if utils.is_sequence(n): # slow 3273 img = vtki.vtkImageData() 3274 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3275 img.SetSpacing(spacing) 3276 gf = vtki.new("GeometryFilter") 3277 gf.SetInputData(img) 3278 gf.Update() 3279 poly = gf.GetOutput() 3280 else: # fast 3281 n -= 1 3282 tbs = vtki.new("TessellatedBoxSource") 3283 tbs.SetLevel(n) 3284 if len(bounds): 3285 tbs.SetBounds(bounds) 3286 else: 3287 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3288 tbs.QuadsOn() 3289 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3290 tbs.Update() 3291 poly = tbs.GetOutput() 3292 super().__init__(poly, c=c, alpha=alpha) 3293 self.pos(pos) 3294 self.lw(1).lighting("off") 3295 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
2976class Plane(Mesh): 2977 """Create a plane in space.""" 2978 2979 def __init__( 2980 self, 2981 pos=(0, 0, 0), 2982 normal=(0, 0, 1), 2983 s=(1, 1), 2984 res=(1, 1), 2985 c="gray5", alpha=1.0, 2986 ) -> None: 2987 """ 2988 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 2989 to vector `normal` so that it passes through point `pos`. 2990 2991 Arguments: 2992 pos : (list) 2993 position of the plane center 2994 normal : (list) 2995 normal vector to the plane 2996 s : (list) 2997 size of the plane along x and y 2998 res : (list) 2999 resolution of the plane along x and y 3000 """ 3001 if isinstance(pos, vtki.vtkPolyData): 3002 super().__init__(pos, c, alpha) 3003 # self.transform = LinearTransform().translate(pos) 3004 3005 else: 3006 ps = vtki.new("PlaneSource") 3007 ps.SetResolution(res[0], res[1]) 3008 tri = vtki.new("TriangleFilter") 3009 tri.SetInputConnection(ps.GetOutputPort()) 3010 tri.Update() 3011 3012 super().__init__(tri.GetOutput(), c, alpha) 3013 3014 pos = utils.make3d(pos) 3015 normal = np.asarray(normal, dtype=float) 3016 axis = normal / np.linalg.norm(normal) 3017 theta = np.arccos(axis[2]) 3018 phi = np.arctan2(axis[1], axis[0]) 3019 3020 t = LinearTransform() 3021 t.scale([s[0], s[1], 1]) 3022 t.rotate_y(np.rad2deg(theta)) 3023 t.rotate_z(np.rad2deg(phi)) 3024 t.translate(pos) 3025 self.apply_transform(t) 3026 3027 self.lighting("off") 3028 self.name = "Plane" 3029 self.variance = 0 3030 3031 def clone(self, deep=True) -> "Plane": 3032 newplane = Plane() 3033 if deep: 3034 newplane.dataset.DeepCopy(self.dataset) 3035 else: 3036 newplane.dataset.ShallowCopy(self.dataset) 3037 newplane.copy_properties_from(self) 3038 newplane.transform = self.transform.clone() 3039 newplane.variance = 0 3040 return newplane 3041 3042 @property 3043 def normal(self) -> np.ndarray: 3044 pts = self.vertices 3045 AB = pts[1] - pts[0] 3046 AC = pts[2] - pts[0] 3047 normal = np.cross(AB, AC) 3048 normal = normal / np.linalg.norm(normal) 3049 return normal 3050 3051 @property 3052 def center(self) -> np.ndarray: 3053 pts = self.vertices 3054 return np.mean(pts, axis=0) 3055 3056 def contains(self, points, tol=0) -> np.ndarray: 3057 """ 3058 Check if each of the provided point lies on this plane. 3059 `points` is an array of shape (n, 3). 3060 """ 3061 points = np.array(points, dtype=float) 3062 bounds = self.vertices 3063 3064 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3065 3066 for i in [1, 3]: 3067 AB = bounds[i] - bounds[0] 3068 AP = points - bounds[0] 3069 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3070 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3071 mask = np.logical_and(mask, mask_l) 3072 mask = np.logical_and(mask, mask_g) 3073 return mask
Create a plane in space.
2979 def __init__( 2980 self, 2981 pos=(0, 0, 0), 2982 normal=(0, 0, 1), 2983 s=(1, 1), 2984 res=(1, 1), 2985 c="gray5", alpha=1.0, 2986 ) -> None: 2987 """ 2988 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 2989 to vector `normal` so that it passes through point `pos`. 2990 2991 Arguments: 2992 pos : (list) 2993 position of the plane center 2994 normal : (list) 2995 normal vector to the plane 2996 s : (list) 2997 size of the plane along x and y 2998 res : (list) 2999 resolution of the plane along x and y 3000 """ 3001 if isinstance(pos, vtki.vtkPolyData): 3002 super().__init__(pos, c, alpha) 3003 # self.transform = LinearTransform().translate(pos) 3004 3005 else: 3006 ps = vtki.new("PlaneSource") 3007 ps.SetResolution(res[0], res[1]) 3008 tri = vtki.new("TriangleFilter") 3009 tri.SetInputConnection(ps.GetOutputPort()) 3010 tri.Update() 3011 3012 super().__init__(tri.GetOutput(), c, alpha) 3013 3014 pos = utils.make3d(pos) 3015 normal = np.asarray(normal, dtype=float) 3016 axis = normal / np.linalg.norm(normal) 3017 theta = np.arccos(axis[2]) 3018 phi = np.arctan2(axis[1], axis[0]) 3019 3020 t = LinearTransform() 3021 t.scale([s[0], s[1], 1]) 3022 t.rotate_y(np.rad2deg(theta)) 3023 t.rotate_z(np.rad2deg(phi)) 3024 t.translate(pos) 3025 self.apply_transform(t) 3026 3027 self.lighting("off") 3028 self.name = "Plane" 3029 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
3031 def clone(self, deep=True) -> "Plane": 3032 newplane = Plane() 3033 if deep: 3034 newplane.dataset.DeepCopy(self.dataset) 3035 else: 3036 newplane.dataset.ShallowCopy(self.dataset) 3037 newplane.copy_properties_from(self) 3038 newplane.transform = self.transform.clone() 3039 newplane.variance = 0 3040 return newplane
3056 def contains(self, points, tol=0) -> np.ndarray: 3057 """ 3058 Check if each of the provided point lies on this plane. 3059 `points` is an array of shape (n, 3). 3060 """ 3061 points = np.array(points, dtype=float) 3062 bounds = self.vertices 3063 3064 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3065 3066 for i in [1, 3]: 3067 AB = bounds[i] - bounds[0] 3068 AP = points - bounds[0] 3069 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3070 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3071 mask = np.logical_and(mask, mask_l) 3072 mask = np.logical_and(mask, mask_g) 3073 return mask
Check if each of the provided point lies on this plane.
points
is an array of shape (n, 3).
3170class Box(Mesh): 3171 """ 3172 Build a box of specified dimensions. 3173 """ 3174 3175 def __init__( 3176 self, pos=(0, 0, 0), 3177 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3178 """ 3179 Build a box of dimensions `x=length, y=width and z=height`. 3180 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3181 3182 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3183 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3184 3185 Examples: 3186 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3187 3188 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3189 """ 3190 src = vtki.new("CubeSource") 3191 3192 if len(pos) == 2: 3193 pos = (pos[0], pos[1], 0) 3194 3195 if len(pos) == 6: 3196 src.SetBounds(pos) 3197 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3198 elif len(size) == 3: 3199 length, width, height = size 3200 src.SetXLength(length) 3201 src.SetYLength(width) 3202 src.SetZLength(height) 3203 src.SetCenter(pos) 3204 else: 3205 src.SetXLength(length) 3206 src.SetYLength(width) 3207 src.SetZLength(height) 3208 src.SetCenter(pos) 3209 3210 src.Update() 3211 pd = src.GetOutput() 3212 3213 tc = [ 3214 [0.0, 0.0], 3215 [1.0, 0.0], 3216 [0.0, 1.0], 3217 [1.0, 1.0], 3218 [1.0, 0.0], 3219 [0.0, 0.0], 3220 [1.0, 1.0], 3221 [0.0, 1.0], 3222 [1.0, 1.0], 3223 [1.0, 0.0], 3224 [0.0, 1.0], 3225 [0.0, 0.0], 3226 [0.0, 1.0], 3227 [0.0, 0.0], 3228 [1.0, 1.0], 3229 [1.0, 0.0], 3230 [1.0, 0.0], 3231 [0.0, 0.0], 3232 [1.0, 1.0], 3233 [0.0, 1.0], 3234 [0.0, 0.0], 3235 [1.0, 0.0], 3236 [0.0, 1.0], 3237 [1.0, 1.0], 3238 ] 3239 vtc = utils.numpy2vtk(tc) 3240 pd.GetPointData().SetTCoords(vtc) 3241 super().__init__(pd, c, alpha) 3242 self.transform = LinearTransform().translate(pos) 3243 self.name = "Box"
Build a box of specified dimensions.
3175 def __init__( 3176 self, pos=(0, 0, 0), 3177 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3178 """ 3179 Build a box of dimensions `x=length, y=width and z=height`. 3180 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3181 3182 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3183 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3184 3185 Examples: 3186 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3187 3188 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3189 """ 3190 src = vtki.new("CubeSource") 3191 3192 if len(pos) == 2: 3193 pos = (pos[0], pos[1], 0) 3194 3195 if len(pos) == 6: 3196 src.SetBounds(pos) 3197 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3198 elif len(size) == 3: 3199 length, width, height = size 3200 src.SetXLength(length) 3201 src.SetYLength(width) 3202 src.SetZLength(height) 3203 src.SetCenter(pos) 3204 else: 3205 src.SetXLength(length) 3206 src.SetYLength(width) 3207 src.SetZLength(height) 3208 src.SetCenter(pos) 3209 3210 src.Update() 3211 pd = src.GetOutput() 3212 3213 tc = [ 3214 [0.0, 0.0], 3215 [1.0, 0.0], 3216 [0.0, 1.0], 3217 [1.0, 1.0], 3218 [1.0, 0.0], 3219 [0.0, 0.0], 3220 [1.0, 1.0], 3221 [0.0, 1.0], 3222 [1.0, 1.0], 3223 [1.0, 0.0], 3224 [0.0, 1.0], 3225 [0.0, 0.0], 3226 [0.0, 1.0], 3227 [0.0, 0.0], 3228 [1.0, 1.0], 3229 [1.0, 0.0], 3230 [1.0, 0.0], 3231 [0.0, 0.0], 3232 [1.0, 1.0], 3233 [0.0, 1.0], 3234 [0.0, 0.0], 3235 [1.0, 0.0], 3236 [0.0, 1.0], 3237 [1.0, 1.0], 3238 ] 3239 vtc = utils.numpy2vtk(tc) 3240 pd.GetPointData().SetTCoords(vtc) 3241 super().__init__(pd, c, alpha) 3242 self.transform = LinearTransform().translate(pos) 3243 self.name = "Box"
Build a box of dimensions x=length, y=width and z=height
.
Alternatively dimensions can be defined by setting size
keyword with a tuple.
If pos
is a list of 6 numbers, this will be interpreted as the bounding box:
[xmin,xmax, ymin,ymax, zmin,zmax]
Examples:
3246class Cube(Box): 3247 """Build a cube.""" 3248 3249 def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None: 3250 """Build a cube of size `side`.""" 3251 super().__init__(pos, side, side, side, (), c, alpha) 3252 self.name = "Cube"
Build a cube.
3298class Spring(Mesh): 3299 """ 3300 Build a spring model. 3301 """ 3302 3303 def __init__( 3304 self, 3305 start_pt=(0, 0, 0), 3306 end_pt=(1, 0, 0), 3307 coils=20, 3308 r1=0.1, 3309 r2=None, 3310 thickness=None, 3311 c="gray5", 3312 alpha=1.0, 3313 ) -> None: 3314 """ 3315 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3316 3317 Arguments: 3318 coils : (int) 3319 number of coils 3320 r1 : (float) 3321 radius at start point 3322 r2 : (float) 3323 radius at end point 3324 thickness : (float) 3325 thickness of the coil section 3326 """ 3327 start_pt = utils.make3d(start_pt) 3328 end_pt = utils.make3d(end_pt) 3329 3330 diff = end_pt - start_pt 3331 length = np.linalg.norm(diff) 3332 if not length: 3333 return 3334 if not r1: 3335 r1 = length / 20 3336 trange = np.linspace(0, length, num=50 * coils) 3337 om = 6.283 * (coils - 0.5) / length 3338 if not r2: 3339 r2 = r1 3340 pts = [] 3341 for t in trange: 3342 f = (length - t) / length 3343 rd = r1 * f + r2 * (1 - f) 3344 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3345 3346 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3347 diff = diff / length 3348 theta = np.arccos(diff[2]) 3349 phi = np.arctan2(diff[1], diff[0]) 3350 sp = Line(pts) 3351 3352 t = vtki.vtkTransform() 3353 t.Translate(start_pt) 3354 t.RotateZ(np.rad2deg(phi)) 3355 t.RotateY(np.rad2deg(theta)) 3356 3357 tf = vtki.new("TransformPolyDataFilter") 3358 tf.SetInputData(sp.dataset) 3359 tf.SetTransform(t) 3360 tf.Update() 3361 3362 tuf = vtki.new("TubeFilter") 3363 tuf.SetNumberOfSides(12) 3364 tuf.CappingOn() 3365 tuf.SetInputData(tf.GetOutput()) 3366 if not thickness: 3367 thickness = r1 / 10 3368 tuf.SetRadius(thickness) 3369 tuf.Update() 3370 3371 super().__init__(tuf.GetOutput(), c, alpha) 3372 3373 self.phong() 3374 self.base = np.array(start_pt, dtype=float) 3375 self.top = np.array(end_pt, dtype=float) 3376 self.name = "Spring"
Build a spring model.
3303 def __init__( 3304 self, 3305 start_pt=(0, 0, 0), 3306 end_pt=(1, 0, 0), 3307 coils=20, 3308 r1=0.1, 3309 r2=None, 3310 thickness=None, 3311 c="gray5", 3312 alpha=1.0, 3313 ) -> None: 3314 """ 3315 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3316 3317 Arguments: 3318 coils : (int) 3319 number of coils 3320 r1 : (float) 3321 radius at start point 3322 r2 : (float) 3323 radius at end point 3324 thickness : (float) 3325 thickness of the coil section 3326 """ 3327 start_pt = utils.make3d(start_pt) 3328 end_pt = utils.make3d(end_pt) 3329 3330 diff = end_pt - start_pt 3331 length = np.linalg.norm(diff) 3332 if not length: 3333 return 3334 if not r1: 3335 r1 = length / 20 3336 trange = np.linspace(0, length, num=50 * coils) 3337 om = 6.283 * (coils - 0.5) / length 3338 if not r2: 3339 r2 = r1 3340 pts = [] 3341 for t in trange: 3342 f = (length - t) / length 3343 rd = r1 * f + r2 * (1 - f) 3344 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3345 3346 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3347 diff = diff / length 3348 theta = np.arccos(diff[2]) 3349 phi = np.arctan2(diff[1], diff[0]) 3350 sp = Line(pts) 3351 3352 t = vtki.vtkTransform() 3353 t.Translate(start_pt) 3354 t.RotateZ(np.rad2deg(phi)) 3355 t.RotateY(np.rad2deg(theta)) 3356 3357 tf = vtki.new("TransformPolyDataFilter") 3358 tf.SetInputData(sp.dataset) 3359 tf.SetTransform(t) 3360 tf.Update() 3361 3362 tuf = vtki.new("TubeFilter") 3363 tuf.SetNumberOfSides(12) 3364 tuf.CappingOn() 3365 tuf.SetInputData(tf.GetOutput()) 3366 if not thickness: 3367 thickness = r1 / 10 3368 tuf.SetRadius(thickness) 3369 tuf.Update() 3370 3371 super().__init__(tuf.GetOutput(), c, alpha) 3372 3373 self.phong() 3374 self.base = np.array(start_pt, dtype=float) 3375 self.top = np.array(end_pt, dtype=float) 3376 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
3379class Cylinder(Mesh): 3380 """ 3381 Build a cylinder of specified height and radius. 3382 """ 3383 3384 def __init__( 3385 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3386 cap=True, res=24, c="teal3", alpha=1.0 3387 ) -> None: 3388 """ 3389 Build a cylinder of specified height and radius `r`, centered at `pos`. 3390 3391 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3392 centered at `v1` and top at `v2`. 3393 3394 Arguments: 3395 cap : (bool) 3396 enable/disable the caps of the cylinder 3397 res : (int) 3398 resolution of the cylinder sides 3399 3400 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3401 """ 3402 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3403 base = np.array(pos[0], dtype=float) 3404 top = np.array(pos[1], dtype=float) 3405 pos = (base + top) / 2 3406 height = np.linalg.norm(top - base) 3407 axis = top - base 3408 axis = utils.versor(axis) 3409 else: 3410 axis = utils.versor(axis) 3411 base = pos - axis * height / 2 3412 top = pos + axis * height / 2 3413 3414 cyl = vtki.new("CylinderSource") 3415 cyl.SetResolution(res) 3416 cyl.SetRadius(r) 3417 cyl.SetHeight(height) 3418 cyl.SetCapping(cap) 3419 cyl.Update() 3420 3421 theta = np.arccos(axis[2]) 3422 phi = np.arctan2(axis[1], axis[0]) 3423 t = vtki.vtkTransform() 3424 t.PostMultiply() 3425 t.RotateX(90) # put it along Z 3426 t.RotateY(np.rad2deg(theta)) 3427 t.RotateZ(np.rad2deg(phi)) 3428 t.Translate(pos) 3429 3430 tf = vtki.new("TransformPolyDataFilter") 3431 tf.SetInputData(cyl.GetOutput()) 3432 tf.SetTransform(t) 3433 tf.Update() 3434 3435 super().__init__(tf.GetOutput(), c, alpha) 3436 3437 self.phong() 3438 self.base = base 3439 self.top = top 3440 self.transform = LinearTransform().translate(pos) 3441 self.name = "Cylinder"
Build a cylinder of specified height and radius.
3384 def __init__( 3385 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3386 cap=True, res=24, c="teal3", alpha=1.0 3387 ) -> None: 3388 """ 3389 Build a cylinder of specified height and radius `r`, centered at `pos`. 3390 3391 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3392 centered at `v1` and top at `v2`. 3393 3394 Arguments: 3395 cap : (bool) 3396 enable/disable the caps of the cylinder 3397 res : (int) 3398 resolution of the cylinder sides 3399 3400 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3401 """ 3402 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3403 base = np.array(pos[0], dtype=float) 3404 top = np.array(pos[1], dtype=float) 3405 pos = (base + top) / 2 3406 height = np.linalg.norm(top - base) 3407 axis = top - base 3408 axis = utils.versor(axis) 3409 else: 3410 axis = utils.versor(axis) 3411 base = pos - axis * height / 2 3412 top = pos + axis * height / 2 3413 3414 cyl = vtki.new("CylinderSource") 3415 cyl.SetResolution(res) 3416 cyl.SetRadius(r) 3417 cyl.SetHeight(height) 3418 cyl.SetCapping(cap) 3419 cyl.Update() 3420 3421 theta = np.arccos(axis[2]) 3422 phi = np.arctan2(axis[1], axis[0]) 3423 t = vtki.vtkTransform() 3424 t.PostMultiply() 3425 t.RotateX(90) # put it along Z 3426 t.RotateY(np.rad2deg(theta)) 3427 t.RotateZ(np.rad2deg(phi)) 3428 t.Translate(pos) 3429 3430 tf = vtki.new("TransformPolyDataFilter") 3431 tf.SetInputData(cyl.GetOutput()) 3432 tf.SetTransform(t) 3433 tf.Update() 3434 3435 super().__init__(tf.GetOutput(), c, alpha) 3436 3437 self.phong() 3438 self.base = base 3439 self.top = top 3440 self.transform = LinearTransform().translate(pos) 3441 self.name = "Cylinder"
3444class Cone(Mesh): 3445 """Build a cone of specified radius and height.""" 3446 3447 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3448 res=48, c="green3", alpha=1.0) -> None: 3449 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3450 con = vtki.new("ConeSource") 3451 con.SetResolution(res) 3452 con.SetRadius(r) 3453 con.SetHeight(height) 3454 con.SetDirection(axis) 3455 con.Update() 3456 super().__init__(con.GetOutput(), c, alpha) 3457 self.phong() 3458 if len(pos) == 2: 3459 pos = (pos[0], pos[1], 0) 3460 self.pos(pos) 3461 v = utils.versor(axis) * height / 2 3462 self.base = pos - v 3463 self.top = pos + v 3464 self.name = "Cone"
Build a cone of specified radius and height.
3447 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3448 res=48, c="green3", alpha=1.0) -> None: 3449 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3450 con = vtki.new("ConeSource") 3451 con.SetResolution(res) 3452 con.SetRadius(r) 3453 con.SetHeight(height) 3454 con.SetDirection(axis) 3455 con.Update() 3456 super().__init__(con.GetOutput(), c, alpha) 3457 self.phong() 3458 if len(pos) == 2: 3459 pos = (pos[0], pos[1], 0) 3460 self.pos(pos) 3461 v = utils.versor(axis) * height / 2 3462 self.base = pos - v 3463 self.top = pos + v 3464 self.name = "Cone"
Build a cone of specified radius r
and height
, centered at pos
.
3467class Pyramid(Cone): 3468 """Build a pyramidal shape.""" 3469 3470 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3471 c="green3", alpha=1) -> None: 3472 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3473 super().__init__(pos, s, height, axis, 4, c, alpha) 3474 self.name = "Pyramid"
Build a pyramidal shape.
3470 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3471 c="green3", alpha=1) -> None: 3472 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3473 super().__init__(pos, s, height, axis, 4, c, alpha) 3474 self.name = "Pyramid"
Build a pyramid of specified base size s
and height
, centered at pos
.
3477class Torus(Mesh): 3478 """ 3479 Build a toroidal shape. 3480 """ 3481 3482 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3483 """ 3484 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3485 If `quad=True` a quad-mesh is generated. 3486 """ 3487 if utils.is_sequence(res): 3488 res_u, res_v = res 3489 else: 3490 res_u, res_v = 3 * res, res 3491 3492 if quads: 3493 # https://github.com/marcomusy/vedo/issues/710 3494 3495 n = res_v 3496 m = res_u 3497 3498 theta = np.linspace(0, 2.0 * np.pi, n) 3499 phi = np.linspace(0, 2.0 * np.pi, m) 3500 theta, phi = np.meshgrid(theta, phi) 3501 t = r1 + r2 * np.cos(theta) 3502 x = t * np.cos(phi) 3503 y = t * np.sin(phi) 3504 z = r2 * np.sin(theta) 3505 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3506 3507 faces = [] 3508 for j in range(m - 1): 3509 j1n = (j + 1) * n 3510 for i in range(n - 1): 3511 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3512 3513 super().__init__([pts, faces], c, alpha) 3514 3515 else: 3516 rs = vtki.new("ParametricTorus") 3517 rs.SetRingRadius(r1) 3518 rs.SetCrossSectionRadius(r2) 3519 pfs = vtki.new("ParametricFunctionSource") 3520 pfs.SetParametricFunction(rs) 3521 pfs.SetUResolution(res_u) 3522 pfs.SetVResolution(res_v) 3523 pfs.Update() 3524 3525 super().__init__(pfs.GetOutput(), c, alpha) 3526 3527 self.phong() 3528 if len(pos) == 2: 3529 pos = (pos[0], pos[1], 0) 3530 self.pos(pos) 3531 self.name = "Torus"
Build a toroidal shape.
3482 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3483 """ 3484 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3485 If `quad=True` a quad-mesh is generated. 3486 """ 3487 if utils.is_sequence(res): 3488 res_u, res_v = res 3489 else: 3490 res_u, res_v = 3 * res, res 3491 3492 if quads: 3493 # https://github.com/marcomusy/vedo/issues/710 3494 3495 n = res_v 3496 m = res_u 3497 3498 theta = np.linspace(0, 2.0 * np.pi, n) 3499 phi = np.linspace(0, 2.0 * np.pi, m) 3500 theta, phi = np.meshgrid(theta, phi) 3501 t = r1 + r2 * np.cos(theta) 3502 x = t * np.cos(phi) 3503 y = t * np.sin(phi) 3504 z = r2 * np.sin(theta) 3505 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3506 3507 faces = [] 3508 for j in range(m - 1): 3509 j1n = (j + 1) * n 3510 for i in range(n - 1): 3511 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3512 3513 super().__init__([pts, faces], c, alpha) 3514 3515 else: 3516 rs = vtki.new("ParametricTorus") 3517 rs.SetRingRadius(r1) 3518 rs.SetCrossSectionRadius(r2) 3519 pfs = vtki.new("ParametricFunctionSource") 3520 pfs.SetParametricFunction(rs) 3521 pfs.SetUResolution(res_u) 3522 pfs.SetVResolution(res_v) 3523 pfs.Update() 3524 3525 super().__init__(pfs.GetOutput(), c, alpha) 3526 3527 self.phong() 3528 if len(pos) == 2: 3529 pos = (pos[0], pos[1], 0) 3530 self.pos(pos) 3531 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.
3534class Paraboloid(Mesh): 3535 """ 3536 Build a paraboloid. 3537 """ 3538 3539 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3540 """ 3541 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3542 3543 Full volumetric expression is: 3544 `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` 3545 3546 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3547 """ 3548 quadric = vtki.new("Quadric") 3549 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3550 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3551 # + a3*x*y + a4*y*z + a5*x*z 3552 # + a6*x + a7*y + a8*z +a9 3553 sample = vtki.new("SampleFunction") 3554 sample.SetSampleDimensions(res, res, res) 3555 sample.SetImplicitFunction(quadric) 3556 3557 contours = vtki.new("ContourFilter") 3558 contours.SetInputConnection(sample.GetOutputPort()) 3559 contours.GenerateValues(1, 0.01, 0.01) 3560 contours.Update() 3561 3562 super().__init__(contours.GetOutput(), c, alpha) 3563 self.compute_normals().phong() 3564 self.mapper.ScalarVisibilityOff() 3565 self.pos(pos) 3566 self.name = "Paraboloid"
Build a paraboloid.
3539 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3540 """ 3541 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3542 3543 Full volumetric expression is: 3544 `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` 3545 3546 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3547 """ 3548 quadric = vtki.new("Quadric") 3549 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3550 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3551 # + a3*x*y + a4*y*z + a5*x*z 3552 # + a6*x + a7*y + a8*z +a9 3553 sample = vtki.new("SampleFunction") 3554 sample.SetSampleDimensions(res, res, res) 3555 sample.SetImplicitFunction(quadric) 3556 3557 contours = vtki.new("ContourFilter") 3558 contours.SetInputConnection(sample.GetOutputPort()) 3559 contours.GenerateValues(1, 0.01, 0.01) 3560 contours.Update() 3561 3562 super().__init__(contours.GetOutput(), c, alpha) 3563 self.compute_normals().phong() 3564 self.mapper.ScalarVisibilityOff() 3565 self.pos(pos) 3566 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
3569class Hyperboloid(Mesh): 3570 """ 3571 Build a hyperboloid. 3572 """ 3573 3574 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3575 """ 3576 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3577 3578 Full volumetric expression is: 3579 `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` 3580 """ 3581 q = vtki.new("Quadric") 3582 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3583 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3584 # + a3*x*y + a4*y*z + a5*x*z 3585 # + a6*x + a7*y + a8*z +a9 3586 sample = vtki.new("SampleFunction") 3587 sample.SetSampleDimensions(res, res, res) 3588 sample.SetImplicitFunction(q) 3589 3590 contours = vtki.new("ContourFilter") 3591 contours.SetInputConnection(sample.GetOutputPort()) 3592 contours.GenerateValues(1, value, value) 3593 contours.Update() 3594 3595 super().__init__(contours.GetOutput(), c, alpha) 3596 self.compute_normals().phong() 3597 self.mapper.ScalarVisibilityOff() 3598 self.pos(pos) 3599 self.name = "Hyperboloid"
Build a hyperboloid.
3574 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3575 """ 3576 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3577 3578 Full volumetric expression is: 3579 `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` 3580 """ 3581 q = vtki.new("Quadric") 3582 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3583 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3584 # + a3*x*y + a4*y*z + a5*x*z 3585 # + a6*x + a7*y + a8*z +a9 3586 sample = vtki.new("SampleFunction") 3587 sample.SetSampleDimensions(res, res, res) 3588 sample.SetImplicitFunction(q) 3589 3590 contours = vtki.new("ContourFilter") 3591 contours.SetInputConnection(sample.GetOutputPort()) 3592 contours.GenerateValues(1, value, value) 3593 contours.Update() 3594 3595 super().__init__(contours.GetOutput(), c, alpha) 3596 self.compute_normals().phong() 3597 self.mapper.ScalarVisibilityOff() 3598 self.pos(pos) 3599 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
4364class TextBase: 4365 "Base class." 4366 4367 def __init__(self): 4368 "Do not instantiate this base class." 4369 4370 self.rendered_at = set() 4371 self.properties = None 4372 4373 self.name = "Text" 4374 self.filename = "" 4375 self.time = 0 4376 self.info = {} 4377 4378 if isinstance(settings.default_font, int): 4379 lfonts = list(settings.font_parameters.keys()) 4380 font = settings.default_font % len(lfonts) 4381 self.fontname = lfonts[font] 4382 else: 4383 self.fontname = settings.default_font 4384 4385 def angle(self, value: float): 4386 """Orientation angle in degrees""" 4387 self.properties.SetOrientation(value) 4388 return self 4389 4390 def line_spacing(self, value: float): 4391 """Set the extra spacing between lines 4392 expressed as a text height multiplicative factor.""" 4393 self.properties.SetLineSpacing(value) 4394 return self 4395 4396 def line_offset(self, value: float): 4397 """Set/Get the vertical offset (measured in pixels).""" 4398 self.properties.SetLineOffset(value) 4399 return self 4400 4401 def bold(self, value=True): 4402 """Set bold face""" 4403 self.properties.SetBold(value) 4404 return self 4405 4406 def italic(self, value=True): 4407 """Set italic face""" 4408 self.properties.SetItalic(value) 4409 return self 4410 4411 def shadow(self, offset=(1, -1)): 4412 """Text shadowing. Set to `None` to disable it.""" 4413 if offset is None: 4414 self.properties.ShadowOff() 4415 else: 4416 self.properties.ShadowOn() 4417 self.properties.SetShadowOffset(offset) 4418 return self 4419 4420 def color(self, c=None): 4421 """Set the text color""" 4422 if c is None: 4423 return get_color(self.properties.GetColor()) 4424 self.properties.SetColor(get_color(c)) 4425 return self 4426 4427 def c(self, color=None): 4428 """Set the text color""" 4429 if color is None: 4430 return get_color(self.properties.GetColor()) 4431 return self.color(color) 4432 4433 def alpha(self, value: float): 4434 """Set the text opacity""" 4435 self.properties.SetBackgroundOpacity(value) 4436 return self 4437 4438 def background(self, color="k9", alpha=1.0): 4439 """Text background. Set to `None` to disable it.""" 4440 bg = get_color(color) 4441 if color is None: 4442 self.properties.SetBackgroundOpacity(0) 4443 else: 4444 self.properties.SetBackgroundColor(bg) 4445 if alpha: 4446 self.properties.SetBackgroundOpacity(alpha) 4447 return self 4448 4449 def frame(self, color="k1", lw=2): 4450 """Border color and width""" 4451 if color is None: 4452 self.properties.FrameOff() 4453 else: 4454 c = get_color(color) 4455 self.properties.FrameOn() 4456 self.properties.SetFrameColor(c) 4457 self.properties.SetFrameWidth(lw) 4458 return self 4459 4460 def font(self, font: str): 4461 """Text font face""" 4462 if isinstance(font, int): 4463 lfonts = list(settings.font_parameters.keys()) 4464 n = font % len(lfonts) 4465 font = lfonts[n] 4466 self.fontname = font 4467 4468 if not font: # use default font 4469 font = self.fontname 4470 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4471 elif font.startswith("https"): # user passed URL link, make it a path 4472 fpath = vedo.file_io.download(font, verbose=False, force=False) 4473 elif font.endswith(".ttf"): # user passing a local path to font file 4474 fpath = font 4475 else: # user passing name of preset font 4476 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4477 4478 if font == "Courier": self.properties.SetFontFamilyToCourier() 4479 elif font == "Times": self.properties.SetFontFamilyToTimes() 4480 elif font == "Arial": self.properties.SetFontFamilyToArial() 4481 else: 4482 fpath = utils.get_font_path(font) 4483 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4484 self.properties.SetFontFile(fpath) 4485 self.fontname = font # io.tonumpy() uses it 4486 4487 return self 4488 4489 def on(self): 4490 """Make text visible""" 4491 self.actor.SetVisibility(True) 4492 return self 4493 4494 def off(self): 4495 """Make text invisible""" 4496 self.actor.SetVisibility(False) 4497 return self
Base class.
4367 def __init__(self): 4368 "Do not instantiate this base class." 4369 4370 self.rendered_at = set() 4371 self.properties = None 4372 4373 self.name = "Text" 4374 self.filename = "" 4375 self.time = 0 4376 self.info = {} 4377 4378 if isinstance(settings.default_font, int): 4379 lfonts = list(settings.font_parameters.keys()) 4380 font = settings.default_font % len(lfonts) 4381 self.fontname = lfonts[font] 4382 else: 4383 self.fontname = settings.default_font
Do not instantiate this base class.
4385 def angle(self, value: float): 4386 """Orientation angle in degrees""" 4387 self.properties.SetOrientation(value) 4388 return self
Orientation angle in degrees
4390 def line_spacing(self, value: float): 4391 """Set the extra spacing between lines 4392 expressed as a text height multiplicative factor.""" 4393 self.properties.SetLineSpacing(value) 4394 return self
Set the extra spacing between lines expressed as a text height multiplicative factor.
4396 def line_offset(self, value: float): 4397 """Set/Get the vertical offset (measured in pixels).""" 4398 self.properties.SetLineOffset(value) 4399 return self
Set/Get the vertical offset (measured in pixels).
4401 def bold(self, value=True): 4402 """Set bold face""" 4403 self.properties.SetBold(value) 4404 return self
Set bold face
4406 def italic(self, value=True): 4407 """Set italic face""" 4408 self.properties.SetItalic(value) 4409 return self
Set italic face
4411 def shadow(self, offset=(1, -1)): 4412 """Text shadowing. Set to `None` to disable it.""" 4413 if offset is None: 4414 self.properties.ShadowOff() 4415 else: 4416 self.properties.ShadowOn() 4417 self.properties.SetShadowOffset(offset) 4418 return self
Text shadowing. Set to None
to disable it.
4420 def color(self, c=None): 4421 """Set the text color""" 4422 if c is None: 4423 return get_color(self.properties.GetColor()) 4424 self.properties.SetColor(get_color(c)) 4425 return self
Set the text color
4427 def c(self, color=None): 4428 """Set the text color""" 4429 if color is None: 4430 return get_color(self.properties.GetColor()) 4431 return self.color(color)
Set the text color
4433 def alpha(self, value: float): 4434 """Set the text opacity""" 4435 self.properties.SetBackgroundOpacity(value) 4436 return self
Set the text opacity
4438 def background(self, color="k9", alpha=1.0): 4439 """Text background. Set to `None` to disable it.""" 4440 bg = get_color(color) 4441 if color is None: 4442 self.properties.SetBackgroundOpacity(0) 4443 else: 4444 self.properties.SetBackgroundColor(bg) 4445 if alpha: 4446 self.properties.SetBackgroundOpacity(alpha) 4447 return self
Text background. Set to None
to disable it.
4449 def frame(self, color="k1", lw=2): 4450 """Border color and width""" 4451 if color is None: 4452 self.properties.FrameOff() 4453 else: 4454 c = get_color(color) 4455 self.properties.FrameOn() 4456 self.properties.SetFrameColor(c) 4457 self.properties.SetFrameWidth(lw) 4458 return self
Border color and width
4460 def font(self, font: str): 4461 """Text font face""" 4462 if isinstance(font, int): 4463 lfonts = list(settings.font_parameters.keys()) 4464 n = font % len(lfonts) 4465 font = lfonts[n] 4466 self.fontname = font 4467 4468 if not font: # use default font 4469 font = self.fontname 4470 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4471 elif font.startswith("https"): # user passed URL link, make it a path 4472 fpath = vedo.file_io.download(font, verbose=False, force=False) 4473 elif font.endswith(".ttf"): # user passing a local path to font file 4474 fpath = font 4475 else: # user passing name of preset font 4476 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4477 4478 if font == "Courier": self.properties.SetFontFamilyToCourier() 4479 elif font == "Times": self.properties.SetFontFamilyToTimes() 4480 elif font == "Arial": self.properties.SetFontFamilyToArial() 4481 else: 4482 fpath = utils.get_font_path(font) 4483 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4484 self.properties.SetFontFile(fpath) 4485 self.fontname = font # io.tonumpy() uses it 4486 4487 return self
Text font face
4025class Text3D(Mesh): 4026 """ 4027 Generate a 3D polygonal Mesh to represent a text string. 4028 """ 4029 4030 def __init__( 4031 self, 4032 txt, 4033 pos=(0, 0, 0), 4034 s=1.0, 4035 font="", 4036 hspacing=1.15, 4037 vspacing=2.15, 4038 depth=0.0, 4039 italic=False, 4040 justify="bottom-left", 4041 literal=False, 4042 c=None, 4043 alpha=1.0, 4044 ) -> None: 4045 """ 4046 Generate a 3D polygonal `Mesh` representing a text string. 4047 4048 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4049 Most Latex symbols are also supported. 4050 4051 Symbols `~ ^ _` are reserved modifiers: 4052 - use ~ to add a short space, 1/4 of the default empty space, 4053 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4054 4055 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4056 4057 More fonts at: https://vedo.embl.es/fonts/ 4058 4059 Arguments: 4060 pos : (list) 4061 position coordinates in 3D space 4062 s : (float) 4063 vertical size of the text (as scaling factor) 4064 depth : (float) 4065 text thickness (along z) 4066 italic : (bool), float 4067 italic font type (can be a signed float too) 4068 justify : (str) 4069 text justification as centering of the bounding box 4070 (bottom-left, bottom-right, top-left, top-right, centered) 4071 font : (str, int) 4072 some of the available 3D-polygonized fonts are: 4073 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4074 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4075 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4076 4077 Check for more at https://vedo.embl.es/fonts/ 4078 4079 Or type in your terminal `vedo --run fonts`. 4080 4081 Default is Normografo, which can be changed using `settings.default_font`. 4082 4083 hspacing : (float) 4084 horizontal spacing of the font 4085 vspacing : (float) 4086 vertical spacing of the font for multiple lines text 4087 literal : (bool) 4088 if set to True will ignore modifiers like _ or ^ 4089 4090 Examples: 4091 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4092 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4093 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4094 4095 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4096 4097 .. note:: Type `vedo -r fonts` for a demo. 4098 """ 4099 if len(pos) == 2: 4100 pos = (pos[0], pos[1], 0) 4101 4102 if c is None: # automatic black or white 4103 pli = vedo.plotter_instance 4104 if pli and pli.renderer: 4105 c = (0.9, 0.9, 0.9) 4106 if pli.renderer.GetGradientBackground(): 4107 bgcol = pli.renderer.GetBackground2() 4108 else: 4109 bgcol = pli.renderer.GetBackground() 4110 if np.sum(bgcol) > 1.5: 4111 c = (0.1, 0.1, 0.1) 4112 else: 4113 c = (0.6, 0.6, 0.6) 4114 4115 tpoly = self._get_text3d_poly( 4116 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4117 ) 4118 4119 super().__init__(tpoly, c, alpha) 4120 4121 self.pos(pos) 4122 self.lighting("off") 4123 4124 self.actor.PickableOff() 4125 self.actor.DragableOff() 4126 self.init_scale = s 4127 self.name = "Text3D" 4128 self.txt = txt 4129 self.justify = justify 4130 4131 def text( 4132 self, 4133 txt=None, 4134 s=1, 4135 font="", 4136 hspacing=1.15, 4137 vspacing=2.15, 4138 depth=0, 4139 italic=False, 4140 justify="", 4141 literal=False, 4142 ) -> "Text3D": 4143 """ 4144 Update the text and some of its properties. 4145 4146 Check [available fonts here](https://vedo.embl.es/fonts). 4147 """ 4148 if txt is None: 4149 return self.txt 4150 if not justify: 4151 justify = self.justify 4152 4153 poly = self._get_text3d_poly( 4154 txt, self.init_scale * s, font, hspacing, vspacing, 4155 depth, italic, justify, literal 4156 ) 4157 4158 # apply the current transformation to the new polydata 4159 tf = vtki.new("TransformPolyDataFilter") 4160 tf.SetInputData(poly) 4161 tf.SetTransform(self.transform.T) 4162 tf.Update() 4163 tpoly = tf.GetOutput() 4164 4165 self._update(tpoly) 4166 self.txt = txt 4167 return self 4168 4169 def _get_text3d_poly( 4170 self, 4171 txt, 4172 s=1, 4173 font="", 4174 hspacing=1.15, 4175 vspacing=2.15, 4176 depth=0, 4177 italic=False, 4178 justify="bottom-left", 4179 literal=False, 4180 ) -> vtki.vtkPolyData: 4181 if not font: 4182 font = settings.default_font 4183 4184 txt = str(txt) 4185 4186 if font == "VTK": ####################################### 4187 vtt = vtki.new("VectorText") 4188 vtt.SetText(txt) 4189 vtt.Update() 4190 tpoly = vtt.GetOutput() 4191 4192 else: ################################################### 4193 4194 stxt = set(txt) # check here if null or only spaces 4195 if not txt or (len(stxt) == 1 and " " in stxt): 4196 return vtki.vtkPolyData() 4197 4198 if italic is True: 4199 italic = 1 4200 4201 if isinstance(font, int): 4202 lfonts = list(settings.font_parameters.keys()) 4203 font = font % len(lfonts) 4204 font = lfonts[font] 4205 4206 if font not in settings.font_parameters.keys(): 4207 fpars = settings.font_parameters["Normografo"] 4208 else: 4209 fpars = settings.font_parameters[font] 4210 4211 # ad hoc adjustments 4212 mono = fpars["mono"] 4213 lspacing = fpars["lspacing"] 4214 hspacing *= fpars["hspacing"] 4215 fscale = fpars["fscale"] 4216 dotsep = fpars["dotsep"] 4217 4218 # replacements 4219 if ":" in txt: 4220 for r in _reps: 4221 txt = txt.replace(r[0], r[1]) 4222 4223 if not literal: 4224 reps2 = [ 4225 (r"\_", "┭"), # trick to protect ~ _ and ^ chars 4226 (r"\^", "┮"), # 4227 (r"\~", "┯"), # 4228 ("**", "^"), # order matters 4229 ("e+0", dotsep + "10^"), 4230 ("e-0", dotsep + "10^-"), 4231 ("E+0", dotsep + "10^"), 4232 ("E-0", dotsep + "10^-"), 4233 ("e+", dotsep + "10^"), 4234 ("e-", dotsep + "10^-"), 4235 ("E+", dotsep + "10^"), 4236 ("E-", dotsep + "10^-"), 4237 ] 4238 for r in reps2: 4239 txt = txt.replace(r[0], r[1]) 4240 4241 xmax, ymax, yshift, scale = 0.0, 0.0, 0.0, 1.0 4242 save_xmax = 0.0 4243 4244 notfounds = set() 4245 polyletters = [] 4246 ntxt = len(txt) 4247 for i, t in enumerate(txt): 4248 ########## 4249 if t == "┭": 4250 t = "_" 4251 elif t == "┮": 4252 t = "^" 4253 elif t == "┯": 4254 t = "~" 4255 elif t == "^" and not literal: 4256 if yshift < 0: 4257 xmax = save_xmax 4258 yshift = 0.9 * fscale 4259 scale = 0.5 4260 continue 4261 elif t == "_" and not literal: 4262 if yshift > 0: 4263 xmax = save_xmax 4264 yshift = -0.3 * fscale 4265 scale = 0.5 4266 continue 4267 elif (t in (" ", "\\n")) and yshift: 4268 yshift = 0.0 4269 scale = 1.0 4270 save_xmax = xmax 4271 if t == " ": 4272 continue 4273 elif t == "~": 4274 if i < ntxt - 1 and txt[i + 1] == "_": 4275 continue 4276 xmax += hspacing * scale * fscale / 4 4277 continue 4278 4279 ############ 4280 if t == " ": 4281 xmax += hspacing * scale * fscale 4282 4283 elif t == "\n": 4284 xmax = 0.0 4285 save_xmax = 0.0 4286 ymax -= vspacing 4287 4288 else: 4289 poly = _get_font_letter(font, t) 4290 if not poly: 4291 notfounds.add(t) 4292 xmax += hspacing * scale * fscale 4293 continue 4294 4295 if poly.GetNumberOfPoints() == 0: 4296 continue 4297 4298 tr = vtki.vtkTransform() 4299 tr.Translate(xmax, ymax + yshift, 0) 4300 pscale = scale * fscale / 1000 4301 tr.Scale(pscale, pscale, pscale) 4302 if italic: 4303 tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 4304 tf = vtki.new("TransformPolyDataFilter") 4305 tf.SetInputData(poly) 4306 tf.SetTransform(tr) 4307 tf.Update() 4308 poly = tf.GetOutput() 4309 polyletters.append(poly) 4310 4311 bx = poly.GetBounds() 4312 if mono: 4313 xmax += hspacing * scale * fscale 4314 else: 4315 xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing 4316 if yshift == 0: 4317 save_xmax = xmax 4318 4319 if len(polyletters) == 1: 4320 tpoly = polyletters[0] 4321 else: 4322 polyapp = vtki.new("AppendPolyData") 4323 for polyd in polyletters: 4324 polyapp.AddInputData(polyd) 4325 polyapp.Update() 4326 tpoly = polyapp.GetOutput() 4327 4328 if notfounds: 4329 wmsg = f"unavailable characters in font name '{font}': {notfounds}." 4330 wmsg += '\nType "vedo -r fonts" for a demo.' 4331 vedo.logger.warning(wmsg) 4332 4333 bb = tpoly.GetBounds() 4334 dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s 4335 shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2 4336 if "bottom" in justify: shift += np.array([ 0, dy, 0.]) 4337 if "top" in justify: shift += np.array([ 0,-dy, 0.]) 4338 if "left" in justify: shift += np.array([ dx, 0, 0.]) 4339 if "right" in justify: shift += np.array([-dx, 0, 0.]) 4340 4341 if tpoly.GetNumberOfPoints(): 4342 t = vtki.vtkTransform() 4343 t.PostMultiply() 4344 t.Scale(s, s, s) 4345 t.Translate(shift) 4346 tf = vtki.new("TransformPolyDataFilter") 4347 tf.SetInputData(tpoly) 4348 tf.SetTransform(t) 4349 tf.Update() 4350 tpoly = tf.GetOutput() 4351 4352 if depth: 4353 extrude = vtki.new("LinearExtrusionFilter") 4354 extrude.SetInputData(tpoly) 4355 extrude.SetExtrusionTypeToVectorExtrusion() 4356 extrude.SetVector(0, 0, 1) 4357 extrude.SetScaleFactor(depth * dy) 4358 extrude.Update() 4359 tpoly = extrude.GetOutput() 4360 4361 return tpoly
Generate a 3D polygonal Mesh to represent a text string.
4030 def __init__( 4031 self, 4032 txt, 4033 pos=(0, 0, 0), 4034 s=1.0, 4035 font="", 4036 hspacing=1.15, 4037 vspacing=2.15, 4038 depth=0.0, 4039 italic=False, 4040 justify="bottom-left", 4041 literal=False, 4042 c=None, 4043 alpha=1.0, 4044 ) -> None: 4045 """ 4046 Generate a 3D polygonal `Mesh` representing a text string. 4047 4048 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4049 Most Latex symbols are also supported. 4050 4051 Symbols `~ ^ _` are reserved modifiers: 4052 - use ~ to add a short space, 1/4 of the default empty space, 4053 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4054 4055 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4056 4057 More fonts at: https://vedo.embl.es/fonts/ 4058 4059 Arguments: 4060 pos : (list) 4061 position coordinates in 3D space 4062 s : (float) 4063 vertical size of the text (as scaling factor) 4064 depth : (float) 4065 text thickness (along z) 4066 italic : (bool), float 4067 italic font type (can be a signed float too) 4068 justify : (str) 4069 text justification as centering of the bounding box 4070 (bottom-left, bottom-right, top-left, top-right, centered) 4071 font : (str, int) 4072 some of the available 3D-polygonized fonts are: 4073 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4074 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4075 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4076 4077 Check for more at https://vedo.embl.es/fonts/ 4078 4079 Or type in your terminal `vedo --run fonts`. 4080 4081 Default is Normografo, which can be changed using `settings.default_font`. 4082 4083 hspacing : (float) 4084 horizontal spacing of the font 4085 vspacing : (float) 4086 vertical spacing of the font for multiple lines text 4087 literal : (bool) 4088 if set to True will ignore modifiers like _ or ^ 4089 4090 Examples: 4091 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4092 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4093 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4094 4095 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4096 4097 .. note:: Type `vedo -r fonts` for a demo. 4098 """ 4099 if len(pos) == 2: 4100 pos = (pos[0], pos[1], 0) 4101 4102 if c is None: # automatic black or white 4103 pli = vedo.plotter_instance 4104 if pli and pli.renderer: 4105 c = (0.9, 0.9, 0.9) 4106 if pli.renderer.GetGradientBackground(): 4107 bgcol = pli.renderer.GetBackground2() 4108 else: 4109 bgcol = pli.renderer.GetBackground() 4110 if np.sum(bgcol) > 1.5: 4111 c = (0.1, 0.1, 0.1) 4112 else: 4113 c = (0.6, 0.6, 0.6) 4114 4115 tpoly = self._get_text3d_poly( 4116 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4117 ) 4118 4119 super().__init__(tpoly, c, alpha) 4120 4121 self.pos(pos) 4122 self.lighting("off") 4123 4124 self.actor.PickableOff() 4125 self.actor.DragableOff() 4126 self.init_scale = s 4127 self.name = "Text3D" 4128 self.txt = txt 4129 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.
4131 def text( 4132 self, 4133 txt=None, 4134 s=1, 4135 font="", 4136 hspacing=1.15, 4137 vspacing=2.15, 4138 depth=0, 4139 italic=False, 4140 justify="", 4141 literal=False, 4142 ) -> "Text3D": 4143 """ 4144 Update the text and some of its properties. 4145 4146 Check [available fonts here](https://vedo.embl.es/fonts). 4147 """ 4148 if txt is None: 4149 return self.txt 4150 if not justify: 4151 justify = self.justify 4152 4153 poly = self._get_text3d_poly( 4154 txt, self.init_scale * s, font, hspacing, vspacing, 4155 depth, italic, justify, literal 4156 ) 4157 4158 # apply the current transformation to the new polydata 4159 tf = vtki.new("TransformPolyDataFilter") 4160 tf.SetInputData(poly) 4161 tf.SetTransform(self.transform.T) 4162 tf.Update() 4163 tpoly = tf.GetOutput() 4164 4165 self._update(tpoly) 4166 self.txt = txt 4167 return self
Update the text and some of its properties.
Check available fonts here.
4499class Text2D(TextBase, vedo.visual.Actor2D): 4500 """ 4501 Create a 2D text object. 4502 """ 4503 def __init__( 4504 self, 4505 txt="", 4506 pos="top-left", 4507 s=1.0, 4508 bg=None, 4509 font="", 4510 justify="", 4511 bold=False, 4512 italic=False, 4513 c=None, 4514 alpha=0.5, 4515 ) -> None: 4516 """ 4517 Create a 2D text object. 4518 4519 All properties of the text, and the text itself, can be changed after creation 4520 (which is especially useful in loops). 4521 4522 Arguments: 4523 pos : (str) 4524 text is placed in one of the 8 positions: 4525 - bottom-left 4526 - bottom-right 4527 - top-left 4528 - top-right 4529 - bottom-middle 4530 - middle-right 4531 - middle-left 4532 - top-middle 4533 4534 If a pair (x,y) is passed as input the 2D text is place at that 4535 position in the coordinate system of the 2D screen (with the 4536 origin sitting at the bottom left). 4537 4538 s : (float) 4539 size of text 4540 bg : (color) 4541 background color 4542 alpha : (float) 4543 background opacity 4544 justify : (str) 4545 text justification 4546 4547 font : (str) 4548 built-in available fonts are: 4549 - Antares 4550 - Arial 4551 - Bongas 4552 - Calco 4553 - Comae 4554 - ComicMono 4555 - Courier 4556 - Glasgo 4557 - Kanopus 4558 - LogoType 4559 - Normografo 4560 - Quikhand 4561 - SmartCouric 4562 - Theemim 4563 - Times 4564 - VictorMono 4565 - More fonts at: https://vedo.embl.es/fonts/ 4566 4567 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4568 4569 Examples: 4570 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4571 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4572 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4573 4574 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4575 """ 4576 super().__init__() 4577 self.name = "Text2D" 4578 4579 self.mapper = vtki.new("TextMapper") 4580 self.SetMapper(self.mapper) 4581 4582 self.properties = self.mapper.GetTextProperty() 4583 self.actor = self 4584 self.actor.retrieve_object = weak_ref_to(self) 4585 4586 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4587 4588 # automatic black or white 4589 if c is None: 4590 c = (0.1, 0.1, 0.1) 4591 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4592 if vedo.plotter_instance.renderer.GetGradientBackground(): 4593 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4594 else: 4595 bgcol = vedo.plotter_instance.renderer.GetBackground() 4596 c = (0.9, 0.9, 0.9) 4597 if np.sum(bgcol) > 1.5: 4598 c = (0.1, 0.1, 0.1) 4599 4600 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4601 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4602 self.PickableOff() 4603 4604 def pos(self, pos="top-left", justify=""): 4605 """ 4606 Set position of the text to draw. Keyword `pos` can be a string 4607 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4608 """ 4609 ajustify = "top-left" # autojustify 4610 if isinstance(pos, str): # corners 4611 ajustify = pos 4612 if "top" in pos: 4613 if "left" in pos: 4614 pos = (0.008, 0.994) 4615 elif "right" in pos: 4616 pos = (0.994, 0.994) 4617 elif "mid" in pos or "cent" in pos: 4618 pos = (0.5, 0.994) 4619 elif "bottom" in pos: 4620 if "left" in pos: 4621 pos = (0.008, 0.008) 4622 elif "right" in pos: 4623 pos = (0.994, 0.008) 4624 elif "mid" in pos or "cent" in pos: 4625 pos = (0.5, 0.008) 4626 elif "mid" in pos or "cent" in pos: 4627 if "left" in pos: 4628 pos = (0.008, 0.5) 4629 elif "right" in pos: 4630 pos = (0.994, 0.5) 4631 else: 4632 pos = (0.5, 0.5) 4633 4634 else: 4635 vedo.logger.warning(f"cannot understand text position {pos}") 4636 pos = (0.008, 0.994) 4637 ajustify = "top-left" 4638 4639 elif len(pos) != 2: 4640 vedo.logger.error("pos must be of length 2 or integer value or string") 4641 raise RuntimeError() 4642 4643 if not justify: 4644 justify = ajustify 4645 4646 self.properties.SetJustificationToLeft() 4647 if "top" in justify: 4648 self.properties.SetVerticalJustificationToTop() 4649 if "bottom" in justify: 4650 self.properties.SetVerticalJustificationToBottom() 4651 if "cent" in justify or "mid" in justify: 4652 self.properties.SetJustificationToCentered() 4653 if "left" in justify: 4654 self.properties.SetJustificationToLeft() 4655 if "right" in justify: 4656 self.properties.SetJustificationToRight() 4657 4658 self.SetPosition(pos) 4659 return self 4660 4661 def text(self, txt=None): 4662 """Set/get the input text string.""" 4663 if txt is None: 4664 return self.mapper.GetInput() 4665 4666 if ":" in txt: 4667 for r in _reps: 4668 txt = txt.replace(r[0], r[1]) 4669 else: 4670 txt = str(txt) 4671 4672 self.mapper.SetInput(txt) 4673 return self 4674 4675 def size(self, s): 4676 """Set the font size.""" 4677 self.properties.SetFontSize(int(s * 22.5)) 4678 return self
Create a 2D text object.
4503 def __init__( 4504 self, 4505 txt="", 4506 pos="top-left", 4507 s=1.0, 4508 bg=None, 4509 font="", 4510 justify="", 4511 bold=False, 4512 italic=False, 4513 c=None, 4514 alpha=0.5, 4515 ) -> None: 4516 """ 4517 Create a 2D text object. 4518 4519 All properties of the text, and the text itself, can be changed after creation 4520 (which is especially useful in loops). 4521 4522 Arguments: 4523 pos : (str) 4524 text is placed in one of the 8 positions: 4525 - bottom-left 4526 - bottom-right 4527 - top-left 4528 - top-right 4529 - bottom-middle 4530 - middle-right 4531 - middle-left 4532 - top-middle 4533 4534 If a pair (x,y) is passed as input the 2D text is place at that 4535 position in the coordinate system of the 2D screen (with the 4536 origin sitting at the bottom left). 4537 4538 s : (float) 4539 size of text 4540 bg : (color) 4541 background color 4542 alpha : (float) 4543 background opacity 4544 justify : (str) 4545 text justification 4546 4547 font : (str) 4548 built-in available fonts are: 4549 - Antares 4550 - Arial 4551 - Bongas 4552 - Calco 4553 - Comae 4554 - ComicMono 4555 - Courier 4556 - Glasgo 4557 - Kanopus 4558 - LogoType 4559 - Normografo 4560 - Quikhand 4561 - SmartCouric 4562 - Theemim 4563 - Times 4564 - VictorMono 4565 - More fonts at: https://vedo.embl.es/fonts/ 4566 4567 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4568 4569 Examples: 4570 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4571 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4572 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4573 4574 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4575 """ 4576 super().__init__() 4577 self.name = "Text2D" 4578 4579 self.mapper = vtki.new("TextMapper") 4580 self.SetMapper(self.mapper) 4581 4582 self.properties = self.mapper.GetTextProperty() 4583 self.actor = self 4584 self.actor.retrieve_object = weak_ref_to(self) 4585 4586 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4587 4588 # automatic black or white 4589 if c is None: 4590 c = (0.1, 0.1, 0.1) 4591 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4592 if vedo.plotter_instance.renderer.GetGradientBackground(): 4593 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4594 else: 4595 bgcol = vedo.plotter_instance.renderer.GetBackground() 4596 c = (0.9, 0.9, 0.9) 4597 if np.sum(bgcol) > 1.5: 4598 c = (0.1, 0.1, 0.1) 4599 4600 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4601 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4602 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:
555 @property 556 def mapper(self): 557 """Get the internal vtkMapper.""" 558 return self.GetMapper()
Get the internal vtkMapper.
4604 def pos(self, pos="top-left", justify=""): 4605 """ 4606 Set position of the text to draw. Keyword `pos` can be a string 4607 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4608 """ 4609 ajustify = "top-left" # autojustify 4610 if isinstance(pos, str): # corners 4611 ajustify = pos 4612 if "top" in pos: 4613 if "left" in pos: 4614 pos = (0.008, 0.994) 4615 elif "right" in pos: 4616 pos = (0.994, 0.994) 4617 elif "mid" in pos or "cent" in pos: 4618 pos = (0.5, 0.994) 4619 elif "bottom" in pos: 4620 if "left" in pos: 4621 pos = (0.008, 0.008) 4622 elif "right" in pos: 4623 pos = (0.994, 0.008) 4624 elif "mid" in pos or "cent" in pos: 4625 pos = (0.5, 0.008) 4626 elif "mid" in pos or "cent" in pos: 4627 if "left" in pos: 4628 pos = (0.008, 0.5) 4629 elif "right" in pos: 4630 pos = (0.994, 0.5) 4631 else: 4632 pos = (0.5, 0.5) 4633 4634 else: 4635 vedo.logger.warning(f"cannot understand text position {pos}") 4636 pos = (0.008, 0.994) 4637 ajustify = "top-left" 4638 4639 elif len(pos) != 2: 4640 vedo.logger.error("pos must be of length 2 or integer value or string") 4641 raise RuntimeError() 4642 4643 if not justify: 4644 justify = ajustify 4645 4646 self.properties.SetJustificationToLeft() 4647 if "top" in justify: 4648 self.properties.SetVerticalJustificationToTop() 4649 if "bottom" in justify: 4650 self.properties.SetVerticalJustificationToBottom() 4651 if "cent" in justify or "mid" in justify: 4652 self.properties.SetJustificationToCentered() 4653 if "left" in justify: 4654 self.properties.SetJustificationToLeft() 4655 if "right" in justify: 4656 self.properties.SetJustificationToRight() 4657 4658 self.SetPosition(pos) 4659 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.
4661 def text(self, txt=None): 4662 """Set/get the input text string.""" 4663 if txt is None: 4664 return self.mapper.GetInput() 4665 4666 if ":" in txt: 4667 for r in _reps: 4668 txt = txt.replace(r[0], r[1]) 4669 else: 4670 txt = str(txt) 4671 4672 self.mapper.SetInput(txt) 4673 return self
Set/get the input text string.
4681class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation): 4682 # PROBABLY USELESS given that Text2D does pretty much the same ... 4683 """ 4684 Annotate the window corner with 2D text. 4685 4686 See `Text2D` description as the basic functionality is very similar. 4687 4688 The added value of this class is the possibility to manage with one single 4689 object the all corner annotations (instead of creating 4 `Text2D` instances). 4690 4691 Examples: 4692 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 4693 """ 4694 4695 def __init__(self, c=None) -> None: 4696 4697 super().__init__() 4698 4699 self.properties = self.GetTextProperty() 4700 4701 # automatic black or white 4702 if c is None: 4703 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4704 c = (0.9, 0.9, 0.9) 4705 if vedo.plotter_instance.renderer.GetGradientBackground(): 4706 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4707 else: 4708 bgcol = vedo.plotter_instance.renderer.GetBackground() 4709 if np.sum(bgcol) > 1.5: 4710 c = (0.1, 0.1, 0.1) 4711 else: 4712 c = (0.5, 0.5, 0.5) 4713 4714 self.SetNonlinearFontScaleFactor(1 / 2.75) 4715 self.PickableOff() 4716 self.properties.SetColor(get_color(c)) 4717 self.properties.SetBold(False) 4718 self.properties.SetItalic(False) 4719 4720 def size(self, s:float, linear=False) -> "CornerAnnotation": 4721 """ 4722 The font size is calculated as the largest possible value such that the annotations 4723 for the given viewport do not overlap. 4724 4725 This font size can be scaled non-linearly with the viewport size, to maintain an 4726 acceptable readable size at larger viewport sizes, without being too big. 4727 `f' = linearScale * pow(f,nonlinearScale)` 4728 """ 4729 if linear: 4730 self.SetLinearFontScaleFactor(s * 5.5) 4731 else: 4732 self.SetNonlinearFontScaleFactor(s / 2.75) 4733 return self 4734 4735 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4736 """Set text at the assigned position""" 4737 4738 if isinstance(pos, str): # corners 4739 if "top" in pos: 4740 if "left" in pos: pos = 2 4741 elif "right" in pos: pos = 3 4742 elif "mid" in pos or "cent" in pos: pos = 7 4743 elif "bottom" in pos: 4744 if "left" in pos: pos = 0 4745 elif "right" in pos: pos = 1 4746 elif "mid" in pos or "cent" in pos: pos = 4 4747 else: 4748 if "left" in pos: pos = 6 4749 elif "right" in pos: pos = 5 4750 else: pos = 2 4751 4752 if "\\" in repr(txt): 4753 for r in _reps: 4754 txt = txt.replace(r[0], r[1]) 4755 else: 4756 txt = str(txt) 4757 4758 self.SetText(pos, txt) 4759 return self 4760 4761 def clear(self) -> "CornerAnnotation": 4762 """Remove all text from all corners""" 4763 self.ClearAllTexts() 4764 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:
4695 def __init__(self, c=None) -> None: 4696 4697 super().__init__() 4698 4699 self.properties = self.GetTextProperty() 4700 4701 # automatic black or white 4702 if c is None: 4703 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4704 c = (0.9, 0.9, 0.9) 4705 if vedo.plotter_instance.renderer.GetGradientBackground(): 4706 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4707 else: 4708 bgcol = vedo.plotter_instance.renderer.GetBackground() 4709 if np.sum(bgcol) > 1.5: 4710 c = (0.1, 0.1, 0.1) 4711 else: 4712 c = (0.5, 0.5, 0.5) 4713 4714 self.SetNonlinearFontScaleFactor(1 / 2.75) 4715 self.PickableOff() 4716 self.properties.SetColor(get_color(c)) 4717 self.properties.SetBold(False) 4718 self.properties.SetItalic(False)
Do not instantiate this base class.
4720 def size(self, s:float, linear=False) -> "CornerAnnotation": 4721 """ 4722 The font size is calculated as the largest possible value such that the annotations 4723 for the given viewport do not overlap. 4724 4725 This font size can be scaled non-linearly with the viewport size, to maintain an 4726 acceptable readable size at larger viewport sizes, without being too big. 4727 `f' = linearScale * pow(f,nonlinearScale)` 4728 """ 4729 if linear: 4730 self.SetLinearFontScaleFactor(s * 5.5) 4731 else: 4732 self.SetNonlinearFontScaleFactor(s / 2.75) 4733 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)
4735 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4736 """Set text at the assigned position""" 4737 4738 if isinstance(pos, str): # corners 4739 if "top" in pos: 4740 if "left" in pos: pos = 2 4741 elif "right" in pos: pos = 3 4742 elif "mid" in pos or "cent" in pos: pos = 7 4743 elif "bottom" in pos: 4744 if "left" in pos: pos = 0 4745 elif "right" in pos: pos = 1 4746 elif "mid" in pos or "cent" in pos: pos = 4 4747 else: 4748 if "left" in pos: pos = 6 4749 elif "right" in pos: pos = 5 4750 else: pos = 2 4751 4752 if "\\" in repr(txt): 4753 for r in _reps: 4754 txt = txt.replace(r[0], r[1]) 4755 else: 4756 txt = str(txt) 4757 4758 self.SetText(pos, txt) 4759 return self
Set text at the assigned position
4767class Latex(Image): 4768 """ 4769 Render Latex text and formulas. 4770 """ 4771 4772 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4773 """ 4774 Render Latex text and formulas. 4775 4776 Arguments: 4777 formula : (str) 4778 latex text string 4779 pos : (list) 4780 position coordinates in space 4781 bg : (color) 4782 background color box 4783 res : (int) 4784 dpi resolution 4785 usetex : (bool) 4786 use latex compiler of matplotlib if available 4787 4788 You can access the latex formula in `Latex.formula`. 4789 4790 Examples: 4791 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4792 4793 ![](https://vedo.embl.es/images/pyplot/latex.png) 4794 """ 4795 from tempfile import NamedTemporaryFile 4796 import matplotlib.pyplot as mpltib 4797 4798 def build_img_plt(formula, tfile): 4799 4800 mpltib.rc("text", usetex=usetex) 4801 4802 formula1 = "$" + formula + "$" 4803 mpltib.axis("off") 4804 col = get_color(c) 4805 if bg: 4806 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4807 else: 4808 bx = None 4809 mpltib.text( 4810 0.5, 4811 0.5, 4812 formula1, 4813 size=res, 4814 color=col, 4815 alpha=alpha, 4816 ha="center", 4817 va="center", 4818 bbox=bx, 4819 ) 4820 mpltib.savefig( 4821 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4822 ) 4823 mpltib.close() 4824 4825 if len(pos) == 2: 4826 pos = (pos[0], pos[1], 0) 4827 4828 tmp_file = NamedTemporaryFile(delete=True) 4829 tmp_file.name = tmp_file.name + ".png" 4830 4831 build_img_plt(formula, tmp_file.name) 4832 4833 super().__init__(tmp_file.name, channels=4) 4834 self.alpha(alpha) 4835 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4836 self.pos(pos) 4837 self.name = "Latex" 4838 self.formula = formula 4839 4840 # except: 4841 # printc("Error in Latex()\n", formula, c="r") 4842 # printc(" latex or dvipng not installed?", c="r") 4843 # printc(" Try: usetex=False", c="r") 4844 # printc(" Try: sudo apt install dvipng", c="r")
Render Latex text and formulas.
4772 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4773 """ 4774 Render Latex text and formulas. 4775 4776 Arguments: 4777 formula : (str) 4778 latex text string 4779 pos : (list) 4780 position coordinates in space 4781 bg : (color) 4782 background color box 4783 res : (int) 4784 dpi resolution 4785 usetex : (bool) 4786 use latex compiler of matplotlib if available 4787 4788 You can access the latex formula in `Latex.formula`. 4789 4790 Examples: 4791 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4792 4793 ![](https://vedo.embl.es/images/pyplot/latex.png) 4794 """ 4795 from tempfile import NamedTemporaryFile 4796 import matplotlib.pyplot as mpltib 4797 4798 def build_img_plt(formula, tfile): 4799 4800 mpltib.rc("text", usetex=usetex) 4801 4802 formula1 = "$" + formula + "$" 4803 mpltib.axis("off") 4804 col = get_color(c) 4805 if bg: 4806 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4807 else: 4808 bx = None 4809 mpltib.text( 4810 0.5, 4811 0.5, 4812 formula1, 4813 size=res, 4814 color=col, 4815 alpha=alpha, 4816 ha="center", 4817 va="center", 4818 bbox=bx, 4819 ) 4820 mpltib.savefig( 4821 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4822 ) 4823 mpltib.close() 4824 4825 if len(pos) == 2: 4826 pos = (pos[0], pos[1], 0) 4827 4828 tmp_file = NamedTemporaryFile(delete=True) 4829 tmp_file.name = tmp_file.name + ".png" 4830 4831 build_img_plt(formula, tmp_file.name) 4832 4833 super().__init__(tmp_file.name, channels=4) 4834 self.alpha(alpha) 4835 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4836 self.pos(pos) 4837 self.name = "Latex" 4838 self.formula = formula 4839 4840 # except: 4841 # printc("Error in Latex()\n", formula, c="r") 4842 # printc(" latex or dvipng not installed?", c="r") 4843 # printc(" Try: usetex=False", c="r") 4844 # 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:
3838class ParametricShape(Mesh): 3839 """ 3840 A set of built-in shapes mainly for illustration purposes. 3841 """ 3842 3843 def __init__(self, name, res=51, n=25, seed=1): 3844 """ 3845 A set of built-in shapes mainly for illustration purposes. 3846 3847 Name can be an integer or a string in this list: 3848 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3849 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3850 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3851 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3852 3853 Example: 3854 ```python 3855 from vedo import * 3856 settings.immediate_rendering = False 3857 plt = Plotter(N=18) 3858 for i in range(18): 3859 ps = ParametricShape(i).color(i) 3860 plt.at(i).show(ps, ps.name) 3861 plt.interactive().close() 3862 ``` 3863 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3864 """ 3865 3866 shapes = [ 3867 "Boy", 3868 "ConicSpiral", 3869 "CrossCap", 3870 "Enneper", 3871 "Figure8Klein", 3872 "Klein", 3873 "Dini", 3874 "Mobius", 3875 "RandomHills", 3876 "Roman", 3877 "SuperEllipsoid", 3878 "BohemianDome", 3879 "Bour", 3880 "CatalanMinimal", 3881 "Henneberg", 3882 "Kuen", 3883 "PluckerConoid", 3884 "Pseudosphere", 3885 ] 3886 3887 if isinstance(name, int): 3888 name = name % len(shapes) 3889 name = shapes[name] 3890 3891 if name == "Boy": 3892 ps = vtki.new("ParametricBoy") 3893 elif name == "ConicSpiral": 3894 ps = vtki.new("ParametricConicSpiral") 3895 elif name == "CrossCap": 3896 ps = vtki.new("ParametricCrossCap") 3897 elif name == "Dini": 3898 ps = vtki.new("ParametricDini") 3899 elif name == "Enneper": 3900 ps = vtki.new("ParametricEnneper") 3901 elif name == "Figure8Klein": 3902 ps = vtki.new("ParametricFigure8Klein") 3903 elif name == "Klein": 3904 ps = vtki.new("ParametricKlein") 3905 elif name == "Mobius": 3906 ps = vtki.new("ParametricMobius") 3907 ps.SetRadius(2.0) 3908 ps.SetMinimumV(-0.5) 3909 ps.SetMaximumV(0.5) 3910 elif name == "RandomHills": 3911 ps = vtki.new("ParametricRandomHills") 3912 ps.AllowRandomGenerationOn() 3913 ps.SetRandomSeed(seed) 3914 ps.SetNumberOfHills(n) 3915 elif name == "Roman": 3916 ps = vtki.new("ParametricRoman") 3917 elif name == "SuperEllipsoid": 3918 ps = vtki.new("ParametricSuperEllipsoid") 3919 ps.SetN1(0.5) 3920 ps.SetN2(0.4) 3921 elif name == "BohemianDome": 3922 ps = vtki.new("ParametricBohemianDome") 3923 ps.SetA(5.0) 3924 ps.SetB(1.0) 3925 ps.SetC(2.0) 3926 elif name == "Bour": 3927 ps = vtki.new("ParametricBour") 3928 elif name == "CatalanMinimal": 3929 ps = vtki.new("ParametricCatalanMinimal") 3930 elif name == "Henneberg": 3931 ps = vtki.new("ParametricHenneberg") 3932 elif name == "Kuen": 3933 ps = vtki.new("ParametricKuen") 3934 ps.SetDeltaV0(0.001) 3935 elif name == "PluckerConoid": 3936 ps = vtki.new("ParametricPluckerConoid") 3937 elif name == "Pseudosphere": 3938 ps = vtki.new("ParametricPseudosphere") 3939 else: 3940 vedo.logger.error(f"unknown ParametricShape {name}") 3941 return 3942 3943 pfs = vtki.new("ParametricFunctionSource") 3944 pfs.SetParametricFunction(ps) 3945 pfs.SetUResolution(res) 3946 pfs.SetVResolution(res) 3947 pfs.SetWResolution(res) 3948 pfs.SetScalarModeToZ() 3949 pfs.Update() 3950 3951 super().__init__(pfs.GetOutput()) 3952 3953 if name == "RandomHills": self.shift([0,-10,-2.25]) 3954 if name != 'Kuen': self.normalize() 3955 if name == 'Dini': self.scale(0.4) 3956 if name == 'Enneper': self.scale(0.4) 3957 if name == 'ConicSpiral': self.bc('tomato') 3958 self.name = name
A set of built-in shapes mainly for illustration purposes.
3843 def __init__(self, name, res=51, n=25, seed=1): 3844 """ 3845 A set of built-in shapes mainly for illustration purposes. 3846 3847 Name can be an integer or a string in this list: 3848 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3849 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3850 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3851 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3852 3853 Example: 3854 ```python 3855 from vedo import * 3856 settings.immediate_rendering = False 3857 plt = Plotter(N=18) 3858 for i in range(18): 3859 ps = ParametricShape(i).color(i) 3860 plt.at(i).show(ps, ps.name) 3861 plt.interactive().close() 3862 ``` 3863 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3864 """ 3865 3866 shapes = [ 3867 "Boy", 3868 "ConicSpiral", 3869 "CrossCap", 3870 "Enneper", 3871 "Figure8Klein", 3872 "Klein", 3873 "Dini", 3874 "Mobius", 3875 "RandomHills", 3876 "Roman", 3877 "SuperEllipsoid", 3878 "BohemianDome", 3879 "Bour", 3880 "CatalanMinimal", 3881 "Henneberg", 3882 "Kuen", 3883 "PluckerConoid", 3884 "Pseudosphere", 3885 ] 3886 3887 if isinstance(name, int): 3888 name = name % len(shapes) 3889 name = shapes[name] 3890 3891 if name == "Boy": 3892 ps = vtki.new("ParametricBoy") 3893 elif name == "ConicSpiral": 3894 ps = vtki.new("ParametricConicSpiral") 3895 elif name == "CrossCap": 3896 ps = vtki.new("ParametricCrossCap") 3897 elif name == "Dini": 3898 ps = vtki.new("ParametricDini") 3899 elif name == "Enneper": 3900 ps = vtki.new("ParametricEnneper") 3901 elif name == "Figure8Klein": 3902 ps = vtki.new("ParametricFigure8Klein") 3903 elif name == "Klein": 3904 ps = vtki.new("ParametricKlein") 3905 elif name == "Mobius": 3906 ps = vtki.new("ParametricMobius") 3907 ps.SetRadius(2.0) 3908 ps.SetMinimumV(-0.5) 3909 ps.SetMaximumV(0.5) 3910 elif name == "RandomHills": 3911 ps = vtki.new("ParametricRandomHills") 3912 ps.AllowRandomGenerationOn() 3913 ps.SetRandomSeed(seed) 3914 ps.SetNumberOfHills(n) 3915 elif name == "Roman": 3916 ps = vtki.new("ParametricRoman") 3917 elif name == "SuperEllipsoid": 3918 ps = vtki.new("ParametricSuperEllipsoid") 3919 ps.SetN1(0.5) 3920 ps.SetN2(0.4) 3921 elif name == "BohemianDome": 3922 ps = vtki.new("ParametricBohemianDome") 3923 ps.SetA(5.0) 3924 ps.SetB(1.0) 3925 ps.SetC(2.0) 3926 elif name == "Bour": 3927 ps = vtki.new("ParametricBour") 3928 elif name == "CatalanMinimal": 3929 ps = vtki.new("ParametricCatalanMinimal") 3930 elif name == "Henneberg": 3931 ps = vtki.new("ParametricHenneberg") 3932 elif name == "Kuen": 3933 ps = vtki.new("ParametricKuen") 3934 ps.SetDeltaV0(0.001) 3935 elif name == "PluckerConoid": 3936 ps = vtki.new("ParametricPluckerConoid") 3937 elif name == "Pseudosphere": 3938 ps = vtki.new("ParametricPseudosphere") 3939 else: 3940 vedo.logger.error(f"unknown ParametricShape {name}") 3941 return 3942 3943 pfs = vtki.new("ParametricFunctionSource") 3944 pfs.SetParametricFunction(ps) 3945 pfs.SetUResolution(res) 3946 pfs.SetVResolution(res) 3947 pfs.SetWResolution(res) 3948 pfs.SetScalarModeToZ() 3949 pfs.Update() 3950 3951 super().__init__(pfs.GetOutput()) 3952 3953 if name == "RandomHills": self.shift([0,-10,-2.25]) 3954 if name != 'Kuen': self.normalize() 3955 if name == 'Dini': self.scale(0.4) 3956 if name == 'Enneper': self.scale(0.4) 3957 if name == 'ConicSpiral': self.bc('tomato') 3958 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()
4847class ConvexHull(Mesh): 4848 """ 4849 Create the 2D/3D convex hull from a set of points. 4850 """ 4851 4852 def __init__(self, pts) -> None: 4853 """ 4854 Create the 2D/3D convex hull from a set of input points or input Mesh. 4855 4856 Examples: 4857 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4858 4859 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4860 """ 4861 if utils.is_sequence(pts): 4862 pts = utils.make3d(pts).astype(float) 4863 mesh = Points(pts) 4864 else: 4865 mesh = pts 4866 apoly = mesh.clean().dataset 4867 4868 # Create the convex hull of the pointcloud 4869 z0, z1 = mesh.zbounds() 4870 d = mesh.diagonal_size() 4871 if (z1 - z0) / d > 0.0001: 4872 delaunay = vtki.new("Delaunay3D") 4873 delaunay.SetInputData(apoly) 4874 delaunay.Update() 4875 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4876 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4877 surfaceFilter.Update() 4878 out = surfaceFilter.GetOutput() 4879 else: 4880 delaunay = vtki.new("Delaunay2D") 4881 delaunay.SetInputData(apoly) 4882 delaunay.Update() 4883 fe = vtki.new("FeatureEdges") 4884 fe.SetInputConnection(delaunay.GetOutputPort()) 4885 fe.BoundaryEdgesOn() 4886 fe.Update() 4887 out = fe.GetOutput() 4888 4889 super().__init__(out, c=mesh.color(), alpha=0.75) 4890 self.flat() 4891 self.name = "ConvexHull"
Create the 2D/3D convex hull from a set of points.
4852 def __init__(self, pts) -> None: 4853 """ 4854 Create the 2D/3D convex hull from a set of input points or input Mesh. 4855 4856 Examples: 4857 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4858 4859 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4860 """ 4861 if utils.is_sequence(pts): 4862 pts = utils.make3d(pts).astype(float) 4863 mesh = Points(pts) 4864 else: 4865 mesh = pts 4866 apoly = mesh.clean().dataset 4867 4868 # Create the convex hull of the pointcloud 4869 z0, z1 = mesh.zbounds() 4870 d = mesh.diagonal_size() 4871 if (z1 - z0) / d > 0.0001: 4872 delaunay = vtki.new("Delaunay3D") 4873 delaunay.SetInputData(apoly) 4874 delaunay.Update() 4875 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4876 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4877 surfaceFilter.Update() 4878 out = surfaceFilter.GetOutput() 4879 else: 4880 delaunay = vtki.new("Delaunay2D") 4881 delaunay.SetInputData(apoly) 4882 delaunay.Update() 4883 fe = vtki.new("FeatureEdges") 4884 fe.SetInputConnection(delaunay.GetOutputPort()) 4885 fe.BoundaryEdgesOn() 4886 fe.Update() 4887 out = fe.GetOutput() 4888 4889 super().__init__(out, c=mesh.color(), alpha=0.75) 4890 self.flat() 4891 self.name = "ConvexHull"
4894def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True) -> "vedo.Assembly": 4895 """ 4896 Create the 3D vedo logo. 4897 4898 Arguments: 4899 distance : (float) 4900 send back logo by this distance from camera 4901 version : (bool) 4902 add version text to the right end of the logo 4903 bc : (color) 4904 text back face color 4905 """ 4906 if c is None: 4907 c = (0, 0, 0) 4908 if vedo.plotter_instance: 4909 if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5: 4910 c = [0, 0, 0] 4911 else: 4912 c = "linen" 4913 4914 font = "Comae" 4915 vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8) 4916 vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc) 4917 vlogo.properties.LightingOn() 4918 4919 vr, rul = None, None 4920 if version: 4921 vr = Text3D( 4922 vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1 4923 ).scale([1, 0.7, 1]) 4924 vr.rotate_z(90).pos(2450, 50, 80) 4925 vr.bc(bc).pickable(False) 4926 elif frame: 4927 rul = vedo.RulerAxes( 4928 (-2600, 2110, 0, 1650, 0, 0), 4929 xlabel="European Molecular Biology Laboratory", 4930 ylabel=vedo.__version__, 4931 font=font, 4932 xpadding=0.09, 4933 ypadding=0.04, 4934 ) 4935 fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0) 4936 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