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