vedo.shapes
Submodule to generate simple and complex geometric shapes
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import os 4from functools import lru_cache 5 6import numpy as np 7 8try: 9 import vedo.vtkclasses as vtk 10except ImportError: 11 import vtkmodules.all as vtk 12 13import vedo 14from vedo import settings 15from vedo.colors import cmaps_names, color_map, get_color, printc 16from vedo import utils 17from vedo.pointcloud import Points, merge 18from vedo.mesh import Mesh 19from vedo.picture import Picture 20 21 22__docformat__ = "google" 23 24__doc__ = """ 25Submodule to generate simple and complex geometric shapes 26 27![](https://vedo.embl.es/images/basic/extrude.png) 28""" 29 30__all__ = [ 31 "Marker", 32 "Line", 33 "DashedLine", 34 "RoundedLine", 35 "Tube", 36 "ThickTube", 37 "Lines", 38 "Spline", 39 "KSpline", 40 "CSpline", 41 "Bezier", 42 "Brace", 43 "NormalLines", 44 "StreamLines", 45 "Ribbon", 46 "Arrow", 47 "Arrows", 48 "Arrow2D", 49 "Arrows2D", 50 "FlatArrow", 51 "Polygon", 52 "Triangle", 53 "Rectangle", 54 "Disc", 55 "Circle", 56 "GeoCircle", 57 "Arc", 58 "Star", 59 "Star3D", 60 "Cross3D", 61 "IcoSphere", 62 "Sphere", 63 "Spheres", 64 "Earth", 65 "Ellipsoid", 66 "Grid", 67 "TessellatedBox", 68 "Plane", 69 "Box", 70 "Cube", 71 "Spring", 72 "Cylinder", 73 "Cone", 74 "Pyramid", 75 "Torus", 76 "Paraboloid", 77 "Hyperboloid", 78 "TextBase", 79 "Text3D", 80 "Text2D", 81 "CornerAnnotation", 82 "Latex", 83 "Glyph", 84 "Tensors", 85 "ParametricShape", 86 "ConvexHull", 87 "VedoLogo", 88] 89 90############################################## 91_reps = ( 92 (":nabla", "∇"), 93 (":inf", "∞"), 94 (":rightarrow", "→"), 95 (":lefttarrow", "←"), 96 (":partial", "∂"), 97 (":sqrt", "√"), 98 (":approx", "≈"), 99 (":neq", "≠"), 100 (":leq", "≤"), 101 (":geq", "≥"), 102 (":foreach", "∀"), 103 (":permille", "‰"), 104 (":euro", "€"), 105 (":dot", "·"), 106 (":int", "∫"), 107 (":pm", "±"), 108 (":times", "×"), 109 (":Gamma", "Γ"), 110 (":Delta", "Δ"), 111 (":Theta", "Θ"), 112 (":Lambda", "Λ"), 113 (":Pi", "Π"), 114 (":Sigma", "Σ"), 115 (":Phi", "Φ"), 116 (":Chi", "X"), 117 (":Xi", "Ξ"), 118 (":Psi", "Ψ"), 119 (":Omega", "Ω"), 120 (":alpha", "α"), 121 (":beta", "β"), 122 (":gamma", "γ"), 123 (":delta", "δ"), 124 (":epsilon", "ε"), 125 (":zeta", "ζ"), 126 (":eta", "η"), 127 (":theta", "θ"), 128 (":kappa", "κ"), 129 (":lambda", "λ"), 130 (":mu", "μ"), 131 (":lowerxi", "ξ"), 132 (":nu", "ν"), 133 (":pi", "π"), 134 (":rho", "ρ"), 135 (":sigma", "σ"), 136 (":tau", "τ"), 137 (":varphi", "φ"), 138 (":phi", "φ"), 139 (":chi", "χ"), 140 (":psi", "ψ"), 141 (":omega", "ω"), 142 (":circ", "°"), 143 (":onehalf", "½"), 144 (":onefourth", "¼"), 145 (":threefourths", "¾"), 146 (":^1", "¹"), 147 (":^2", "²"), 148 (":^3", "³"), 149 (":,", "~"), 150) 151 152 153######################################################################## 154class Glyph(Mesh): 155 """ 156 At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with 157 various orientation options and coloring. 158 159 The input can also be a simple list of 2D or 3D coordinates. 160 Color can be specified as a colormap which maps the size of the orientation 161 vectors in `orientation_array`. 162 """ 163 164 def __init__( 165 self, 166 mesh, 167 glyph, 168 orientation_array=None, 169 scale_by_scalar=False, 170 scale_by_vector_size=False, 171 scale_by_vector_components=False, 172 color_by_scalar=False, 173 color_by_vector_size=False, 174 c="k8", 175 alpha=1.0, 176 **opts, 177 ): 178 """ 179 Arguments: 180 orientation_array: (list, str, vtkArray) 181 list of vectors, `vtkArray` or name of an already existing pointdata array 182 scale_by_scalar : (bool) 183 glyph mesh is scaled by the active scalars 184 scale_by_vector_size : (bool) 185 glyph mesh is scaled by the size of the vectors 186 scale_by_vector_components : (bool) 187 glyph mesh is scaled by the 3 vectors components 188 color_by_scalar : (bool) 189 glyph mesh is colored based on the scalar value 190 color_by_vector_size : (bool) 191 glyph mesh is colored based on the vector size 192 193 Examples: 194 - [glyphs1.py](]https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 195 - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py) 196 197 ![](https://vedo.embl.es/images/basic/glyphs.png) 198 """ 199 if len(opts) > 0: # Deprecations 200 printc(":noentry: Warning! In Glyph() unrecognized keywords:", opts, c="y") 201 orientation_array = opts.pop("orientationArray", orientation_array) 202 scale_by_scalar = opts.pop("scaleByScalar", scale_by_scalar) 203 scale_by_vector_size = opts.pop("scaleByVectorSize", scale_by_vector_size) 204 scale_by_vector_components = opts.pop( 205 "scaleByVectorComponents", scale_by_vector_components 206 ) 207 color_by_scalar = opts.pop("colorByScalar", color_by_scalar) 208 color_by_vector_size = opts.pop("colorByVectorSize", color_by_vector_size) 209 printc(" Please use 'snake_case' instead of 'camelCase' keywords", c="y") 210 211 lighting = None 212 if utils.is_sequence(mesh): 213 # create a cloud of points 214 poly = Points(mesh).polydata() 215 elif isinstance(mesh, vtk.vtkPolyData): 216 poly = mesh 217 else: 218 poly = mesh.polydata() 219 220 if isinstance(glyph, Points): 221 lighting = glyph.property.GetLighting() 222 glyph = glyph.polydata() 223 224 cmap = "" 225 if isinstance(c, str) and c in cmaps_names: 226 cmap = c 227 c = None 228 elif utils.is_sequence(c): # user passing an array of point colors 229 ucols = vtk.vtkUnsignedCharArray() 230 ucols.SetNumberOfComponents(3) 231 ucols.SetName("glyph_RGB") 232 for col in c: 233 cl = get_color(col) 234 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 235 poly.GetPointData().AddArray(ucols) 236 poly.GetPointData().SetActiveScalars("glyph_RGB") 237 c = None 238 239 gly = vtk.vtkGlyph3D() 240 gly.GeneratePointIdsOn() 241 gly.SetInputData(poly) 242 gly.SetSourceData(glyph) 243 244 if scale_by_scalar: 245 gly.SetScaleModeToScaleByScalar() 246 elif scale_by_vector_size: 247 gly.SetScaleModeToScaleByVector() 248 elif scale_by_vector_components: 249 gly.SetScaleModeToScaleByVectorComponents() 250 else: 251 gly.SetScaleModeToDataScalingOff() 252 253 if color_by_vector_size: 254 gly.SetVectorModeToUseVector() 255 gly.SetColorModeToColorByVector() 256 elif color_by_scalar: 257 gly.SetColorModeToColorByScalar() 258 else: 259 gly.SetColorModeToColorByScale() 260 261 if orientation_array is not None: 262 gly.OrientOn() 263 if isinstance(orientation_array, str): 264 if orientation_array.lower() == "normals": 265 gly.SetVectorModeToUseNormal() 266 else: # passing a name 267 poly.GetPointData().SetActiveVectors(orientation_array) 268 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 269 gly.SetVectorModeToUseVector() 270 elif utils.is_sequence(orientation_array): # passing a list 271 varr = vtk.vtkFloatArray() 272 varr.SetNumberOfComponents(3) 273 varr.SetName("glyph_vectors") 274 for v in orientation_array: 275 varr.InsertNextTuple(v) 276 poly.GetPointData().AddArray(varr) 277 poly.GetPointData().SetActiveVectors("glyph_vectors") 278 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 279 gly.SetVectorModeToUseVector() 280 281 gly.Update() 282 283 Mesh.__init__(self, gly.GetOutput(), c, alpha) 284 self.flat() 285 if lighting is not None: 286 self.property.SetLighting(lighting) 287 288 if cmap: 289 lut = vtk.vtkLookupTable() 290 lut.SetNumberOfTableValues(512) 291 lut.Build() 292 for i in range(512): 293 r, g, b = color_map(i, cmap, 0, 512) 294 lut.SetTableValue(i, r, g, b, 1) 295 self.mapper().SetLookupTable(lut) 296 self.mapper().ScalarVisibilityOn() 297 self.mapper().SetScalarModeToUsePointData() 298 if gly.GetOutput().GetPointData().GetScalars(): 299 rng = gly.GetOutput().GetPointData().GetScalars().GetRange() 300 self.mapper().SetScalarRange(rng[0], rng[1]) 301 302 self.name = "Glyph" 303 304 305class Tensors(Mesh): 306 """ 307 Geometric representation of tensors defined on a domain or set of points. 308 Tensors can be scaled and/or rotated according to the source at eache input point. 309 Scaling and rotation is controlled by the eigenvalues/eigenvectors of the symmetrical part 310 of the tensor as follows: 311 312 For each tensor, the eigenvalues (and associated eigenvectors) are sorted 313 to determine the major, medium, and minor eigenvalues/eigenvectors. 314 The eigenvalue decomposition only makes sense for symmetric tensors, 315 hence the need to only consider the symmetric part of the tensor, 316 which is `1/2*(T+T.transposed())`. 317 """ 318 319 def __init__( 320 self, 321 domain, 322 source="ellipsoid", 323 use_eigenvalues=True, 324 is_symmetric=True, 325 three_axes=False, 326 scale=1.0, 327 max_scale=None, 328 length=None, 329 c=None, 330 alpha=1.0, 331 ): 332 """ 333 Arguments: 334 source : (str, Mesh) 335 preset type of source shape 336 `['ellipsoid', 'cylinder', 'cube' or any specified `Mesh`]` 337 use_eigenvalues : (bool) 338 color source glyph using the eigenvalues or by scalars 339 three_axes : (bool) 340 if `False` scale the source in the x-direction, 341 the medium in the y-direction, and the minor in the z-direction. 342 Then, the source is rotated so that the glyph's local x-axis lies 343 along the major eigenvector, y-axis along the medium eigenvector, 344 and z-axis along the minor. 345 346 If `True` three sources are produced, each of them oriented along an eigenvector 347 and scaled according to the corresponding eigenvector. 348 is_symmetric : (bool) 349 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 350 The x-axis of the source glyph will correspond to the eigenvector on output. 351 length : (float) 352 distance from the origin to the tip of the source glyph along the x-axis 353 scale : (float) 354 scaling factor of the source glyph. 355 max_scale : (float) 356 clamp scaling at this factor. 357 358 Examples: 359 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 360 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 361 362 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 363 """ 364 if isinstance(source, Points): 365 src = source.normalize().polydata(False) 366 else: 367 if "ellip" in source: 368 src = vtk.vtkSphereSource() 369 src.SetPhiResolution(24) 370 src.SetThetaResolution(12) 371 elif "cyl" in source: 372 src = vtk.vtkCylinderSource() 373 src.SetResolution(48) 374 src.CappingOn() 375 elif source == "cube": 376 src = vtk.vtkCubeSource() 377 src.Update() 378 379 tg = vtk.vtkTensorGlyph() 380 if isinstance(domain, vtk.vtkPolyData): 381 tg.SetInputData(domain) 382 else: 383 tg.SetInputData(domain.GetMapper().GetInput()) 384 tg.SetSourceData(src.GetOutput()) 385 386 if c is None: 387 tg.ColorGlyphsOn() 388 else: 389 tg.ColorGlyphsOff() 390 391 tg.SetSymmetric(int(is_symmetric)) 392 393 if length is not None: 394 tg.SetLength(length) 395 if use_eigenvalues: 396 tg.ExtractEigenvaluesOn() 397 tg.SetColorModeToEigenvalues() 398 else: 399 tg.SetColorModeToScalars() 400 tg.SetThreeGlyphs(three_axes) 401 tg.ScalingOn() 402 tg.SetScaleFactor(scale) 403 if max_scale is None: 404 tg.ClampScalingOn() 405 max_scale = scale * 10 406 tg.SetMaxScaleFactor(max_scale) 407 tg.Update() 408 tgn = vtk.vtkPolyDataNormals() 409 tgn.SetInputData(tg.GetOutput()) 410 tgn.Update() 411 Mesh.__init__(self, tgn.GetOutput(), c, alpha) 412 self.name = "Tensors" 413 414 415class Line(Mesh): 416 """ 417 Build the line segment between points `p0` and `p1`. 418 419 If `p0` is already a list of points, return the line connecting them. 420 421 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 422 """ 423 424 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0): 425 """ 426 Arguments: 427 closed : (bool) 428 join last to first point 429 res : (int) 430 resolution, number of points along the line 431 (only relevant if only 2 points are specified) 432 lw : (int) 433 line width in pixel units 434 c : (color), int, str, list 435 color name, number, or list of [R,G,B] colors 436 alpha : (float) 437 opacity in range [0,1] 438 """ 439 self.slope = [] # populated by analysis.fitLine 440 self.center = [] 441 self.variances = [] 442 443 self.coefficients = [] # populated by pyplot.fit() 444 self.covariance_matrix = [] 445 self.coefficients = [] 446 self.coefficient_errors = [] 447 self.monte_carlo_coefficients = [] 448 self.reduced_chi2 = -1 449 self.ndof = 0 450 self.data_sigma = 0 451 self.error_lines = [] 452 self.error_band = None 453 self.res = res 454 455 if isinstance(p1, Points): 456 p1 = p1.GetPosition() 457 if isinstance(p0, Points): 458 p0 = p0.GetPosition() 459 if isinstance(p0, Points): 460 p0 = p0.points() 461 462 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 463 if len(p0) > 3: 464 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 465 # assume input is 2D xlist, ylist 466 p0 = np.stack((p0, p1), axis=1) 467 p1 = None 468 p0 = utils.make3d(p0) 469 470 # detect if user is passing a list of points: 471 if utils.is_sequence(p0[0]): 472 p0 = utils.make3d(p0) 473 474 ppoints = vtk.vtkPoints() # Generate the polyline 475 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 476 lines = vtk.vtkCellArray() 477 npt = len(p0) 478 if closed: 479 lines.InsertNextCell(npt + 1) 480 else: 481 lines.InsertNextCell(npt) 482 for i in range(npt): 483 lines.InsertCellPoint(i) 484 if closed: 485 lines.InsertCellPoint(0) 486 poly = vtk.vtkPolyData() 487 poly.SetPoints(ppoints) 488 poly.SetLines(lines) 489 top = p0[-1] 490 base = p0[0] 491 self.res = 2 492 493 else: # or just 2 points to link 494 495 line_source = vtk.vtkLineSource() 496 p0 = utils.make3d(p0) 497 p1 = utils.make3d(p1) 498 line_source.SetPoint1(p0) 499 line_source.SetPoint2(p1) 500 line_source.SetResolution(res - 1) 501 line_source.Update() 502 poly = line_source.GetOutput() 503 top = np.asarray(p1, dtype=float) 504 base = np.asarray(p0, dtype=float) 505 506 Mesh.__init__(self, poly, c, alpha) 507 self.lw(lw) 508 self.property.LightingOff() 509 self.PickableOff() 510 self.DragableOff() 511 self.base = base 512 self.top = top 513 self.name = "Line" 514 515 def linecolor(self, lc=None): 516 """Assign a color to the line""" 517 # overrides mesh.linecolor which would have no effect here 518 return self.color(lc) 519 520 def eval(self, x): 521 """ 522 Calculate the position of an intermediate point 523 as a fraction of the length of the line, 524 being x=0 the first point and x=1 the last point. 525 This corresponds to an imaginary point that travels along the line 526 at constant speed. 527 528 Can be used in conjunction with `lin_interpolate()` 529 to map any range to the [0,1] range. 530 """ 531 distance1 = 0.0 532 length = self.length() 533 pts = self.points() 534 for i in range(1, len(pts)): 535 p0 = pts[i - 1] 536 p1 = pts[i] 537 seg = p1 - p0 538 distance0 = distance1 539 distance1 += np.linalg.norm(seg) 540 w1 = distance1 / length 541 if w1 >= x: 542 break 543 w0 = distance0 / length 544 v = p0 + seg * (x - w0) / (w1 - w0) 545 return v 546 547 def pattern(self, stipple, repeats=10): 548 """ 549 Define a stipple pattern for dashing the line. 550 Pass the stipple pattern as a string like `'- - -'`. 551 Repeats controls the number of times the pattern repeats in a single segment. 552 553 Examples are: `'- -', '-- - --'`, etc. 554 555 The resolution of the line (nr of points) can affect how pattern will show up. 556 557 Example: 558 ```python 559 from vedo import Line 560 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 561 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 562 ln.show(axes=1).close() 563 ``` 564 ![](https://vedo.embl.es/images/feats/line_pattern.png) 565 """ 566 stipple = str(stipple) * int(2 * repeats) 567 dimension = len(stipple) 568 569 image = vtk.vtkImageData() 570 image.SetDimensions(dimension, 1, 1) 571 image.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 4) 572 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 573 i_dim = 0 574 while i_dim < dimension: 575 for i in range(dimension): 576 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 577 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 578 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 579 if stipple[i] == " ": 580 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 581 else: 582 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 583 i_dim += 1 584 585 polyData = self.polydata(False) 586 587 # Create texture coordinates 588 tcoords = vtk.vtkDoubleArray() 589 tcoords.SetName("TCoordsStippledLine") 590 tcoords.SetNumberOfComponents(1) 591 tcoords.SetNumberOfTuples(polyData.GetNumberOfPoints()) 592 for i in range(polyData.GetNumberOfPoints()): 593 tcoords.SetTypedTuple(i, [i / 2]) 594 polyData.GetPointData().SetTCoords(tcoords) 595 polyData.GetPointData().Modified() 596 texture = vtk.vtkTexture() 597 texture.SetInputData(image) 598 texture.InterpolateOff() 599 texture.RepeatOn() 600 self.SetTexture(texture) 601 return self 602 603 def length(self): 604 """Calculate length of the line.""" 605 distance = 0.0 606 pts = self.points() 607 for i in range(1, len(pts)): 608 distance += np.linalg.norm(pts[i] - pts[i - 1]) 609 return distance 610 611 def tangents(self): 612 """ 613 Compute the tangents of a line in space. 614 615 Example: 616 ```python 617 from vedo import * 618 shape = load(dataurl+"timecourse1d.npy")[58] 619 pts = shape.rotate_x(30).points() 620 tangents = Line(pts).tangents() 621 arrs = Arrows(pts, pts+tangents, c='blue9') 622 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 623 ``` 624 ![](https://vedo.embl.es/images/feats/line_tangents.png) 625 """ 626 v = np.gradient(self.points())[0] 627 ds_dt = np.linalg.norm(v, axis=1) 628 tangent = np.array([1 / ds_dt] * 3).transpose() * v 629 return tangent 630 631 def curvature(self): 632 """ 633 Compute the signed curvature of a line in space. 634 The signed is computed assuming the line is about coplanar to the xy plane. 635 636 Example: 637 ```python 638 from vedo import * 639 from vedo.pyplot import plot 640 shape = load(dataurl+"timecourse1d.npy")[55] 641 curvs = Line(shape.points()).curvature() 642 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 643 shape.render_lines_as_tubes().lw(12) 644 pp = plot(curvs, ac='white', lc='yellow5') 645 show(shape, pp, N=2, bg='bb', sharecam=False).close() 646 ``` 647 ![](https://vedo.embl.es/images/feats/line_curvature.png) 648 """ 649 v = np.gradient(self.points())[0] 650 a = np.gradient(v)[0] 651 av = np.cross(a, v) 652 mav = np.linalg.norm(av, axis=1) 653 mv = utils.mag2(v) 654 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 655 val[0] = val[1] 656 val[-1] = val[-2] 657 return val 658 659 def compute_curvature(self, method=0): 660 """ 661 Add a pointdata array named 'Curvatures' which contains 662 the curvature value at each point. 663 664 Keyword method is overridden in Mesh and has no effect here. 665 """ 666 # overrides mesh.compute_curvature 667 curvs = self.curvature() 668 vmin, vmax = np.min(curvs), np.max(curvs) 669 if vmin < 0 and vmax > 0: 670 v = max(-vmin, vmax) 671 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 672 else: 673 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 674 return self 675 676 def sweep(self, direction=(1, 0, 0), res=1): 677 """ 678 Sweep the `Line` along the specified vector direction. 679 680 Returns a `Mesh` surface. 681 Line position is updated to allow for additional sweepings. 682 683 Example: 684 ```python 685 from vedo import Line, show 686 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 687 surf1 = aline.sweep((1,0.2,0), res=3) 688 surf2 = aline.sweep((0.2,0,1)) 689 aline.color('r').linewidth(4) 690 show(surf1, surf2, aline, axes=1).close() 691 ``` 692 ![](https://vedo.embl.es/images/feats/sweepline.png) 693 """ 694 line = self.polydata() 695 rows = line.GetNumberOfPoints() 696 697 spacing = 1 / res 698 surface = vtk.vtkPolyData() 699 700 res += 1 701 npts = rows * res 702 npolys = (rows - 1) * (res - 1) 703 points = vtk.vtkPoints() 704 points.Allocate(npts) 705 706 cnt = 0 707 x = [0.0, 0.0, 0.0] 708 for row in range(rows): 709 for col in range(res): 710 p = [0.0, 0.0, 0.0] 711 line.GetPoint(row, p) 712 x[0] = p[0] + direction[0] * col * spacing 713 x[1] = p[1] + direction[1] * col * spacing 714 x[2] = p[2] + direction[2] * col * spacing 715 points.InsertPoint(cnt, x) 716 cnt += 1 717 718 # Generate the quads 719 polys = vtk.vtkCellArray() 720 polys.Allocate(npolys * 4) 721 pts = [0, 0, 0, 0] 722 for row in range(rows - 1): 723 for col in range(res - 1): 724 pts[0] = col + row * res 725 pts[1] = pts[0] + 1 726 pts[2] = pts[0] + res + 1 727 pts[3] = pts[0] + res 728 polys.InsertNextCell(4, pts) 729 surface.SetPoints(points) 730 surface.SetPolys(polys) 731 asurface = vedo.Mesh(surface) 732 prop = vtk.vtkProperty() 733 prop.DeepCopy(self.GetProperty()) 734 asurface.SetProperty(prop) 735 asurface.property = prop 736 asurface.lighting("default") 737 self.points(self.points() + direction) 738 return asurface 739 740 def reverse(self): 741 """Reverse the points sequence order.""" 742 pts = np.flip(self.points(), axis=0) 743 self.points(pts) 744 return self 745 746 747class DashedLine(Mesh): 748 """ 749 Consider using `Line.pattern()` instead. 750 751 Build a dashed line segment between points `p0` and `p1`. 752 If `p0` is a list of points returns the line connecting them. 753 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 754 """ 755 756 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0): 757 """ 758 Arguments: 759 closed : (bool) 760 join last to first point 761 spacing : (float) 762 relative size of the dash 763 lw : (int) 764 line width in pixels 765 """ 766 if isinstance(p1, vtk.vtkActor): 767 p1 = p1.GetPosition() 768 if isinstance(p0, vtk.vtkActor): 769 p0 = p0.GetPosition() 770 if isinstance(p0, Points): 771 p0 = p0.points() 772 773 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 774 if len(p0) > 3: 775 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 776 # assume input is 2D xlist, ylist 777 p0 = np.stack((p0, p1), axis=1) 778 p1 = None 779 p0 = utils.make3d(p0) 780 if closed: 781 p0 = np.append(p0, [p0[0]], axis=0) 782 783 if p1 is not None: # assume passing p0=[x,y] 784 if len(p0) == 2 and not utils.is_sequence(p0[0]): 785 p0 = (p0[0], p0[1], 0) 786 if len(p1) == 2 and not utils.is_sequence(p1[0]): 787 p1 = (p1[0], p1[1], 0) 788 789 # detect if user is passing a list of points: 790 if utils.is_sequence(p0[0]): 791 listp = p0 792 else: # or just 2 points to link 793 listp = [p0, p1] 794 795 listp = np.array(listp) 796 if listp.shape[1] == 2: 797 listp = np.c_[listp, np.zeros(listp.shape[0])] 798 799 xmn = np.min(listp, axis=0) 800 xmx = np.max(listp, axis=0) 801 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 802 if not dlen: 803 Mesh.__init__(self, vtk.vtkPolyData(), c, alpha) 804 self.name = "DashedLine (void)" 805 return 806 807 qs = [] 808 for ipt in range(len(listp) - 1): 809 p0 = listp[ipt] 810 p1 = listp[ipt + 1] 811 v = p1 - p0 812 vdist = np.linalg.norm(v) 813 n1 = int(vdist / dlen) 814 if not n1: 815 continue 816 817 res = 0 818 for i in range(n1 + 2): 819 ist = (i - 0.5) / n1 820 ist = max(ist, 0) 821 qi = p0 + v * (ist - res / vdist) 822 if ist > 1: 823 qi = p1 824 res = np.linalg.norm(qi - p1) 825 qs.append(qi) 826 break 827 qs.append(qi) 828 829 polylns = vtk.vtkAppendPolyData() 830 for i, q1 in enumerate(qs): 831 if not i % 2: 832 continue 833 q0 = qs[i - 1] 834 line_source = vtk.vtkLineSource() 835 line_source.SetPoint1(q0) 836 line_source.SetPoint2(q1) 837 line_source.Update() 838 polylns.AddInputData(line_source.GetOutput()) 839 polylns.Update() 840 841 Mesh.__init__(self, polylns.GetOutput(), c, alpha) 842 self.lw(lw).lighting("off") 843 self.base = listp[0] 844 if closed: 845 self.top = listp[-2] 846 else: 847 self.top = listp[-1] 848 self.name = "DashedLine" 849 850 851class RoundedLine(Mesh): 852 """ 853 Create a 2D line of specified thickness (in absolute units) passing through 854 a list of input points. Borders of the line are rounded. 855 """ 856 857 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0): 858 """ 859 Arguments: 860 pts : (list) 861 a list of points in 2D or 3D (z will be ignored). 862 lw : (float) 863 thickness of the line. 864 res : (int) 865 resolution of the rounded regions 866 867 Example: 868 ```python 869 from vedo import * 870 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 871 ln = Line(pts, c='r', lw=2).z(0.01) 872 rl = RoundedLine(pts, 0.6) 873 show(Points(pts), ln, rl, axes=1).close() 874 ``` 875 ![](https://vedo.embl.es/images/feats/rounded_line.png) 876 """ 877 pts = utils.make3d(pts) 878 879 def _getpts(pts, revd=False): 880 881 if revd: 882 pts = list(reversed(pts)) 883 884 if len(pts) == 2: 885 p0, p1 = pts 886 v = p1 - p0 887 dv = np.linalg.norm(v) 888 nv = np.cross(v, (0, 0, -1)) 889 nv = nv / np.linalg.norm(nv) * lw 890 return [p0 + nv, p1 + nv] 891 892 ptsnew = [] 893 for k in range(len(pts) - 2): 894 p0 = pts[k] 895 p1 = pts[k + 1] 896 p2 = pts[k + 2] 897 v = p1 - p0 898 u = p2 - p1 899 du = np.linalg.norm(u) 900 dv = np.linalg.norm(v) 901 nv = np.cross(v, (0, 0, -1)) 902 nv = nv / np.linalg.norm(nv) * lw 903 nu = np.cross(u, (0, 0, -1)) 904 nu = nu / np.linalg.norm(nu) * lw 905 uv = np.cross(u, v) 906 if k == 0: 907 ptsnew.append(p0 + nv) 908 if uv[2] <= 0: 909 alpha = np.arccos(np.dot(u, v) / du / dv) 910 db = lw * np.tan(alpha / 2) 911 p1new = p1 + nv - v / dv * db 912 ptsnew.append(p1new) 913 else: 914 p1a = p1 + nv 915 p1b = p1 + nu 916 for i in range(0, res + 1): 917 pab = p1a * (res - i) / res + p1b * i / res 918 vpab = pab - p1 919 vpab = vpab / np.linalg.norm(vpab) * lw 920 ptsnew.append(p1 + vpab) 921 if k == len(pts) - 3: 922 ptsnew.append(p2 + nu) 923 if revd: 924 ptsnew.append(p2 - nu) 925 return ptsnew 926 927 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 928 929 ppoints = vtk.vtkPoints() # Generate the polyline 930 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 931 lines = vtk.vtkCellArray() 932 npt = len(ptsnew) 933 lines.InsertNextCell(npt) 934 for i in range(npt): 935 lines.InsertCellPoint(i) 936 poly = vtk.vtkPolyData() 937 poly.SetPoints(ppoints) 938 poly.SetLines(lines) 939 vct = vtk.vtkContourTriangulator() 940 vct.SetInputData(poly) 941 vct.Update() 942 Mesh.__init__(self, vct.GetOutput(), c, alpha) 943 self.flat() 944 self.property.LightingOff() 945 self.name = "RoundedLine" 946 self.base = ptsnew[0] 947 self.top = ptsnew[-1] 948 949 950class Lines(Mesh): 951 """ 952 Build the line segments between two lists of points `start_pts` and `end_pts`. 953 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 954 """ 955 956 def __init__( 957 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 958 ): 959 """ 960 Arguments: 961 scale : (float) 962 apply a rescaling factor to the lengths. 963 c : (color, int, str, list) 964 color name, number, or list of [R,G,B] colors 965 alpha : (float) 966 opacity in range [0,1] 967 lw : (int) 968 line width in pixel units 969 res : (int) 970 resolution, number of points along the line 971 (only relevant if only 2 points are specified) 972 973 Examples: 974 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 975 976 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 977 """ 978 if isinstance(start_pts, Points): 979 start_pts = start_pts.points() 980 if isinstance(end_pts, Points): 981 end_pts = end_pts.points() 982 983 if end_pts is not None: 984 start_pts = np.stack((start_pts, end_pts), axis=1) 985 986 polylns = vtk.vtkAppendPolyData() 987 988 if not utils.is_ragged(start_pts): 989 990 for twopts in start_pts: 991 line_source = vtk.vtkLineSource() 992 line_source.SetResolution(res) 993 if len(twopts[0]) == 2: 994 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 995 else: 996 line_source.SetPoint1(twopts[0]) 997 998 if scale == 1: 999 pt2 = twopts[1] 1000 else: 1001 vers = (np.array(twopts[1]) - twopts[0]) * scale 1002 pt2 = np.array(twopts[0]) + vers 1003 1004 if len(pt2) == 2: 1005 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1006 else: 1007 line_source.SetPoint2(pt2) 1008 polylns.AddInputConnection(line_source.GetOutputPort()) 1009 1010 else: 1011 1012 polylns = vtk.vtkAppendPolyData() 1013 for t in start_pts: 1014 t = utils.make3d(t) 1015 ppoints = vtk.vtkPoints() # Generate the polyline 1016 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1017 lines = vtk.vtkCellArray() 1018 npt = len(t) 1019 lines.InsertNextCell(npt) 1020 for i in range(npt): 1021 lines.InsertCellPoint(i) 1022 poly = vtk.vtkPolyData() 1023 poly.SetPoints(ppoints) 1024 poly.SetLines(lines) 1025 polylns.AddInputData(poly) 1026 1027 polylns.Update() 1028 1029 Mesh.__init__(self, polylns.GetOutput(), c, alpha) 1030 self.lw(lw).lighting("off") 1031 if dotted: 1032 self.GetProperty().SetLineStipplePattern(0xF0F0) 1033 self.GetProperty().SetLineStippleRepeatFactor(1) 1034 1035 self.name = "Lines" 1036 1037 1038class Spline(Line): 1039 """ 1040 Find the B-Spline curve through a set of points. This curve does not necessarily 1041 pass exactly through all the input points. Needs to import `scipy`. 1042 """ 1043 1044 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing=""): 1045 """ 1046 Arguments: 1047 smooth : (float) 1048 smoothing factor. 1049 - 0 = interpolate points exactly [default]. 1050 - 1 = average point positions. 1051 degree : (int) 1052 degree of the spline (between 1 and 5). 1053 easing : (str) 1054 control sensity of points along the spline. 1055 Available options are 1056 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1057 Can be used to create animations (move objects at varying speed). 1058 See e.g.: https://easings.net 1059 res : (int) 1060 number of points on the spline 1061 1062 See also: `CSpline` and `KSpline`. 1063 1064 Examples: 1065 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1066 1067 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1068 """ 1069 from scipy.interpolate import splprep, splev 1070 1071 if isinstance(points, Points): 1072 points = points.points() 1073 1074 points = utils.make3d(points) 1075 1076 per = 0 1077 if closed: 1078 points = np.append(points, [points[0]], axis=0) 1079 per = 1 1080 1081 if res is None: 1082 res = len(points) * 10 1083 1084 points = np.array(points, dtype=float) 1085 1086 minx, miny, minz = np.min(points, axis=0) 1087 maxx, maxy, maxz = np.max(points, axis=0) 1088 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1089 smooth *= maxb / 2 # must be in absolute units 1090 1091 x = np.linspace(0, 1, res) 1092 if easing: 1093 if easing == "InSine": 1094 x = 1 - np.cos((x * np.pi) / 2) 1095 elif easing == "OutSine": 1096 x = np.sin((x * np.pi) / 2) 1097 elif easing == "Sine": 1098 x = -(np.cos(np.pi * x) - 1) / 2 1099 elif easing == "InQuad": 1100 x = x * x 1101 elif easing == "OutQuad": 1102 x = 1 - (1 - x) * (1 - x) 1103 elif easing == "InCubic": 1104 x = x * x 1105 elif easing == "OutCubic": 1106 x = 1 - np.power(1 - x, 3) 1107 elif easing == "InQuart": 1108 x = x * x * x * x 1109 elif easing == "OutQuart": 1110 x = 1 - np.power(1 - x, 4) 1111 elif easing == "InCirc": 1112 x = 1 - np.sqrt(1 - np.power(x, 2)) 1113 elif easing == "OutCirc": 1114 x = np.sqrt(1 - np.power(x - 1, 2)) 1115 else: 1116 vedo.logger.error(f"unknown ease mode {easing}") 1117 1118 # find the knots 1119 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1120 # evaluate spLine, including interpolated points: 1121 xnew, ynew, znew = splev(x, tckp) 1122 1123 Line.__init__(self, np.c_[xnew, ynew, znew], lw=2) 1124 self.name = "Spline" 1125 1126 1127class KSpline(Line): 1128 """ 1129 Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline) 1130 which runs exactly through all the input points. 1131 """ 1132 1133 def __init__(self, points, 1134 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None): 1135 """ 1136 Arguments: 1137 continuity : (float) 1138 changes the sharpness in change between tangents 1139 tension : (float) 1140 changes the length of the tangent vector 1141 bias : (float) 1142 changes the direction of the tangent vector 1143 closed : (bool) 1144 join last to first point to produce a closed curve 1145 res : (int) 1146 approximate resolution of the output line. 1147 Default is 20 times the number of input points. 1148 1149 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1150 1151 See also: `Spline` and `CSpline`. 1152 """ 1153 if isinstance(points, Points): 1154 points = points.points() 1155 1156 if not res: 1157 res = len(points) * 20 1158 1159 points = utils.make3d(points).astype(float) 1160 1161 xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1162 yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1163 zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1164 for s in [xspline, yspline, zspline]: 1165 if bias: 1166 s.SetDefaultBias(bias) 1167 if tension: 1168 s.SetDefaultTension(tension) 1169 if continuity: 1170 s.SetDefaultContinuity(continuity) 1171 s.SetClosed(closed) 1172 1173 lenp = len(points[0]) > 2 1174 1175 for i, p in enumerate(points): 1176 xspline.AddPoint(i, p[0]) 1177 yspline.AddPoint(i, p[1]) 1178 if lenp: 1179 zspline.AddPoint(i, p[2]) 1180 1181 ln = [] 1182 for pos in np.linspace(0, len(points), res): 1183 x = xspline.Evaluate(pos) 1184 y = yspline.Evaluate(pos) 1185 z = 0 1186 if lenp: 1187 z = zspline.Evaluate(pos) 1188 ln.append((x, y, z)) 1189 1190 Line.__init__(self, ln, lw=2) 1191 self.clean() 1192 self.lighting("off") 1193 self.name = "KSpline" 1194 self.base = np.array(points[0], dtype=float) 1195 self.top = np.array(points[-1], dtype=float) 1196 1197 1198class CSpline(Line): 1199 """ 1200 Return a Cardinal spline which runs exactly through all the input points. 1201 """ 1202 1203 def __init__(self, points, closed=False, res=None): 1204 """ 1205 Arguments: 1206 closed : (bool) 1207 join last to first point to produce a closed curve 1208 res : (int) 1209 approximate resolution of the output line. 1210 Default is 20 times the number of input points. 1211 1212 See also: `Spline` and `KSpline`. 1213 """ 1214 1215 if isinstance(points, Points): 1216 points = points.points() 1217 1218 if not res: 1219 res = len(points) * 20 1220 1221 points = utils.make3d(points).astype(float) 1222 1223 xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1224 yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1225 zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1226 for s in [xspline, yspline, zspline]: 1227 s.SetClosed(closed) 1228 1229 lenp = len(points[0]) > 2 1230 1231 for i, p in enumerate(points): 1232 xspline.AddPoint(i, p[0]) 1233 yspline.AddPoint(i, p[1]) 1234 if lenp: 1235 zspline.AddPoint(i, p[2]) 1236 1237 ln = [] 1238 for pos in np.linspace(0, len(points), res): 1239 x = xspline.Evaluate(pos) 1240 y = yspline.Evaluate(pos) 1241 z = 0 1242 if lenp: 1243 z = zspline.Evaluate(pos) 1244 ln.append((x, y, z)) 1245 1246 Line.__init__(self, ln, lw=2) 1247 self.clean() 1248 self.lighting("off") 1249 self.name = "CSpline" 1250 self.base = points[0] 1251 self.top = points[-1] 1252 1253 1254class Bezier(Line): 1255 """ 1256 Generate the Bezier line that links the first to the last point. 1257 """ 1258 1259 def __init__(self, points, res=None): 1260 """ 1261 Example: 1262 ```python 1263 from vedo import * 1264 import numpy as np 1265 pts = np.random.randn(25,3) 1266 for i,p in enumerate(pts): 1267 p += [5*i, 15*sin(i/2), i*i*i/200] 1268 show(Points(pts), Bezier(pts), axes=1).close() 1269 ``` 1270 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1271 """ 1272 N = len(points) 1273 if res is None: 1274 res = 10 * N 1275 t = np.linspace(0, 1, num=res) 1276 bcurve = np.zeros((res, len(points[0]))) 1277 1278 def binom(n, k): 1279 b = 1 1280 for t in range(1, min(k, n - k) + 1): 1281 b *= n / t 1282 n -= 1 1283 return b 1284 1285 def bernstein(n, k): 1286 coeff = binom(n, k) 1287 1288 def _bpoly(x): 1289 return coeff * x ** k * (1 - x) ** (n - k) 1290 1291 return _bpoly 1292 1293 for ii in range(N): 1294 b = bernstein(N - 1, ii)(t) 1295 bcurve += np.outer(b, points[ii]) 1296 Line.__init__(self, bcurve, lw=2) 1297 self.name = "BezierLine" 1298 1299 1300class NormalLines(Mesh): 1301 """ 1302 Build an `Glyph` to show the normals at cell centers or at mesh vertices. 1303 1304 Arguments: 1305 ratio : (int) 1306 show 1 normal every `ratio` cells. 1307 on : (str) 1308 either "cells" or "points". 1309 scale : (float) 1310 scale factor to control size. 1311 """ 1312 1313 def __init__(self, msh, ratio=1, on="cells", scale=1.0): 1314 1315 poly = msh.clone().compute_normals().polydata() 1316 1317 if "cell" in on: 1318 centers = vtk.vtkCellCenters() 1319 centers.SetInputData(poly) 1320 centers.Update() 1321 poly = centers.GetOutput() 1322 1323 mask_pts = vtk.vtkMaskPoints() 1324 mask_pts.SetInputData(poly) 1325 mask_pts.SetOnRatio(ratio) 1326 mask_pts.RandomModeOff() 1327 mask_pts.Update() 1328 1329 ln = vtk.vtkLineSource() 1330 ln.SetPoint1(0, 0, 0) 1331 ln.SetPoint2(1, 0, 0) 1332 ln.Update() 1333 glyph = vtk.vtkGlyph3D() 1334 glyph.SetSourceData(ln.GetOutput()) 1335 glyph.SetInputData(mask_pts.GetOutput()) 1336 glyph.SetVectorModeToUseNormal() 1337 1338 b = poly.GetBounds() 1339 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1340 glyph.SetScaleFactor(f) 1341 glyph.OrientOn() 1342 glyph.Update() 1343 1344 Mesh.__init__(self, glyph.GetOutput()) 1345 1346 self.PickableOff() 1347 prop = vtk.vtkProperty() 1348 prop.DeepCopy(msh.GetProperty()) 1349 self.SetProperty(prop) 1350 self.property = prop 1351 self.property.LightingOff() 1352 self.mapper().ScalarVisibilityOff() 1353 self.name = "NormalLines" 1354 1355 1356def _interpolate2vol(mesh, kernel=None, radius=None, bounds=None, null_value=None, dims=None): 1357 # Generate a volumetric dataset by interpolating a scalar 1358 # or vector field which is only known on a scattered set of points or mesh. 1359 # Available interpolation kernels are: shepard, gaussian, voronoi, linear. 1360 # 1361 # kernel : (str) interpolation kernel type [shepard] 1362 # radius : (float) radius of the local search 1363 # bounds : (list) bounding box of the output object 1364 # dims : (list) dimensions of the output object 1365 # null_value : (float) value to be assigned to invalid points 1366 if dims is None: 1367 dims = (25, 25, 25) 1368 1369 if bounds is None: 1370 bounds = mesh.bounds() 1371 elif isinstance(bounds, vedo.base.Base3DProp): 1372 bounds = bounds.bounds() 1373 1374 # Create a domain volume 1375 domain = vtk.vtkImageData() 1376 domain.SetDimensions(dims) 1377 domain.SetOrigin(bounds[0], bounds[2], bounds[4]) 1378 deltaZ = (bounds[5] - bounds[4]) / (dims[2] - 1) 1379 deltaY = (bounds[3] - bounds[2]) / (dims[1] - 1) 1380 deltaX = (bounds[1] - bounds[0]) / (dims[0] - 1) 1381 domain.SetSpacing(deltaX, deltaY, deltaZ) 1382 1383 if radius is None: 1384 radius = 2.5 * np.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) 1385 1386 locator = vtk.vtkStaticPointLocator() 1387 locator.SetDataSet(mesh) 1388 locator.BuildLocator() 1389 1390 if kernel == "gaussian": 1391 kern = vtk.vtkGaussianKernel() 1392 kern.SetRadius(radius) 1393 elif kernel == "voronoi": 1394 kern = vtk.vtkVoronoiKernel() 1395 elif kernel == "linear": 1396 kern = vtk.vtkLinearKernel() 1397 kern.SetRadius(radius) 1398 else: 1399 kern = vtk.vtkShepardKernel() 1400 kern.SetPowerParameter(2) 1401 kern.SetRadius(radius) 1402 1403 interpolator = vtk.vtkPointInterpolator() 1404 interpolator.SetInputData(domain) 1405 interpolator.SetSourceData(mesh) 1406 interpolator.SetKernel(kern) 1407 interpolator.SetLocator(locator) 1408 if null_value is not None: 1409 interpolator.SetNullValue(null_value) 1410 else: 1411 interpolator.SetNullPointsStrategyToClosestPoint() 1412 interpolator.Update() 1413 return interpolator.GetOutput() 1414 1415 1416def StreamLines( 1417 domain, 1418 probe, 1419 active_vectors="", 1420 integrator="rk4", 1421 direction="forward", 1422 initial_step_size=None, 1423 max_propagation=None, 1424 max_steps=10000, 1425 step_length=None, 1426 extrapolate_to_box=(), 1427 surface_constrained=False, 1428 compute_vorticity=False, 1429 ribbons=None, 1430 tubes=(), 1431 scalar_range=None, 1432 lw=None, 1433 **opts, 1434): 1435 """ 1436 Integrate a vector field on a domain (a Points/Mesh or other vtk datasets types) 1437 to generate streamlines. 1438 1439 The integration is performed using a specified integrator (Runge-Kutta). 1440 The length of a streamline is governed by specifying a maximum value either 1441 in physical arc length or in (local) cell length. 1442 Otherwise, the integration terminates upon exiting the field domain. 1443 1444 Arguments: 1445 domain : (Points, Volume, vtkDataSet) 1446 the object that contains the vector field 1447 probe : (Mesh, list) 1448 the Mesh that probes the domain. Its coordinates will 1449 be the seeds for the streamlines, can also be an array of positions. 1450 active_vectors : (str) 1451 name of the vector array to be used 1452 integrator : (str) 1453 Runge-Kutta integrator, either 'rk2', 'rk4' of 'rk45' 1454 initial_step_size : (float) 1455 initial step size of integration 1456 max_propagation : (float) 1457 maximum physical length of the streamline 1458 max_steps : (int) 1459 maximum nr of steps allowed 1460 step_length : (float) 1461 length of step integration. 1462 extrapolate_to_box : (dict) 1463 Vectors that are defined on a discrete set of points 1464 are extrapolated to a 3D domain defined by its bounding box: 1465 - bounds (list), bounding box of the domain 1466 - kernel (str), interpolation kernel `["shepard","gaussian","voronoi","linear"]` 1467 - radius (float), radius of the local search 1468 - dims (list), nr of subdivisions of the domain along x, y, and z 1469 - null_value (float), value to be assigned to invalid points 1470 1471 surface_constrained : (bool) 1472 force streamlines to be computed on a surface 1473 compute_vorticity : (bool) 1474 Turn on/off vorticity computation at streamline points 1475 (necessary for generating proper stream-ribbons) 1476 ribbons : (int) 1477 render lines as ribbons by joining them. 1478 An integer value represent the ratio of joining (e.g.: ribbons=2 groups lines 2 by 2) 1479 1480 tubes : (dict) 1481 dictionary containing the parameters for the tube representation: 1482 - ratio (int), draws tube as longitudinal stripes 1483 - res (int), tube resolution (nr. of sides, 12 by default) 1484 - max_radius_factor (float), max tube radius as a multiple of the min radius 1485 - cap (bool), capping of the tube 1486 - mode (int), radius varies based on the scalar or vector magnitude: 1487 - 0 do not vary radius 1488 - 1 vary radius by scalar 1489 - 2 vary radius by vector 1490 - 3 vary radius by absolute value of scalar 1491 - 4 vary radius by vector norm 1492 1493 scalar_range : (list) 1494 specify the scalar range for coloring 1495 1496 Examples: 1497 - [streamlines1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamlines1.py) 1498 - [streamlines2.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamlines2.py) 1499 1500 ![](https://vedo.embl.es/images/volumetric/81459343-b9210d00-919f-11ea-846c-152d62cba06e.png) 1501 1502 - [streamribbons.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamribbons.py) 1503 - [office.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/office.py) 1504 1505 ![](https://vedo.embl.es/images/volumetric/56964003-9145a500-6b5a-11e9-9d9e-9736d90e1900.png) 1506 """ 1507 if len(opts): # Deprecations 1508 printc(" Warning! In StreamLines() unrecognized keywords:", opts, c="y") 1509 initial_step_size = opts.pop("initialStepSize", initial_step_size) 1510 max_propagation = opts.pop("maxPropagation", max_propagation) 1511 max_steps = opts.pop("maxSteps", max_steps) 1512 step_length = opts.pop("stepLength", step_length) 1513 extrapolate_to_box = opts.pop("extrapolateToBox", extrapolate_to_box) 1514 surface_constrained = opts.pop("surfaceConstrained", surface_constrained) 1515 compute_vorticity = opts.pop("computeVorticity", compute_vorticity) 1516 scalar_range = opts.pop("scalarRange", scalar_range) 1517 printc(" Please use 'snake_case' instead of 'camelCase' keywords", c="y") 1518 1519 if isinstance(domain, vedo.Points): 1520 if extrapolate_to_box: 1521 grid = _interpolate2vol(domain.polydata(), **extrapolate_to_box) 1522 else: 1523 grid = domain.polydata() 1524 elif isinstance(domain, vedo.BaseVolume): 1525 grid = domain.inputdata() 1526 else: 1527 grid = domain 1528 1529 if active_vectors: 1530 grid.GetPointData().SetActiveVectors(active_vectors) 1531 1532 b = grid.GetBounds() 1533 size = (b[5] - b[4] + b[3] - b[2] + b[1] - b[0]) / 3 1534 if initial_step_size is None: 1535 initial_step_size = size / 500.0 1536 if max_propagation is None: 1537 max_propagation = size 1538 1539 if utils.is_sequence(probe): 1540 pts = utils.make3d(probe) 1541 else: 1542 pts = probe.clean().points() 1543 1544 src = vtk.vtkProgrammableSource() 1545 1546 def read_points(): 1547 output = src.GetPolyDataOutput() 1548 points = vtk.vtkPoints() 1549 for x, y, z in pts: 1550 points.InsertNextPoint(x, y, z) 1551 output.SetPoints(points) 1552 1553 src.SetExecuteMethod(read_points) 1554 src.Update() 1555 1556 st = vtk.vtkStreamTracer() 1557 st.SetInputDataObject(grid) 1558 st.SetSourceConnection(src.GetOutputPort()) 1559 1560 st.SetInitialIntegrationStep(initial_step_size) 1561 st.SetComputeVorticity(compute_vorticity) 1562 st.SetMaximumNumberOfSteps(max_steps) 1563 st.SetMaximumPropagation(max_propagation) 1564 st.SetSurfaceStreamlines(surface_constrained) 1565 if step_length: 1566 st.SetMaximumIntegrationStep(step_length) 1567 1568 if "f" in direction: 1569 st.SetIntegrationDirectionToForward() 1570 elif "back" in direction: 1571 st.SetIntegrationDirectionToBackward() 1572 elif "both" in direction: 1573 st.SetIntegrationDirectionToBoth() 1574 1575 if integrator == "rk2": 1576 st.SetIntegratorTypeToRungeKutta2() 1577 elif integrator == "rk4": 1578 st.SetIntegratorTypeToRungeKutta4() 1579 elif integrator == "rk45": 1580 st.SetIntegratorTypeToRungeKutta45() 1581 else: 1582 vedo.logger.error(f"in streamlines, unknown integrator {integrator}") 1583 1584 st.Update() 1585 output = st.GetOutput() 1586 1587 if ribbons: 1588 scalar_surface = vtk.vtkRuledSurfaceFilter() 1589 scalar_surface.SetInputConnection(st.GetOutputPort()) 1590 scalar_surface.SetOnRatio(int(ribbons)) 1591 scalar_surface.SetRuledModeToPointWalk() 1592 scalar_surface.Update() 1593 output = scalar_surface.GetOutput() 1594 1595 if tubes: 1596 radius = tubes.pop("radius", domain.GetLength() / 500) 1597 res = tubes.pop("res", 24) 1598 radfact = tubes.pop("max_radius_factor", 10) 1599 ratio = tubes.pop("ratio", 1) 1600 mode = tubes.pop("mode", 0) 1601 cap = tubes.pop("mode", False) 1602 if tubes: 1603 vedo.logger.warning(f"in StreamLines unknown 'tubes' parameters: {tubes}") 1604 1605 stream_tube = vtk.vtkTubeFilter() 1606 stream_tube.SetNumberOfSides(res) 1607 stream_tube.SetRadius(radius) 1608 stream_tube.SetCapping(cap) 1609 # max tube radius as a multiple of the min radius 1610 stream_tube.SetRadiusFactor(radfact) 1611 stream_tube.SetOnRatio(int(ratio)) 1612 stream_tube.SetVaryRadius(int(mode)) 1613 1614 stream_tube.SetInputData(output) 1615 vname = grid.GetPointData().GetVectors().GetName() 1616 stream_tube.SetInputArrayToProcess( 1617 1, 0, 0, vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS, vname 1618 ) 1619 stream_tube.Update() 1620 sta = vedo.mesh.Mesh(stream_tube.GetOutput(), c=None) 1621 1622 scals = grid.GetPointData().GetScalars() 1623 if scals: 1624 sta.mapper().SetScalarRange(scals.GetRange()) 1625 if scalar_range is not None: 1626 sta.mapper().SetScalarRange(scalar_range) 1627 1628 sta.phong() 1629 sta.name = "StreamLines" 1630 ############# 1631 return sta ############# 1632 ############# 1633 1634 sta = vedo.mesh.Mesh(output, c=None) 1635 1636 if lw is not None and len(tubes) == 0 and not ribbons: 1637 sta.lw(lw) 1638 sta.mapper().SetResolveCoincidentTopologyToPolygonOffset() 1639 sta.lighting("off") 1640 1641 scals = grid.GetPointData().GetScalars() 1642 if scals: 1643 sta.mapper().SetScalarRange(scals.GetRange()) 1644 if scalar_range is not None: 1645 sta.mapper().SetScalarRange(scalar_range) 1646 1647 sta.name = "StreamLines" 1648 return sta 1649 1650 1651class Tube(Mesh): 1652 """ 1653 Build a tube along the line defined by a set of points. 1654 """ 1655 1656 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0): 1657 """ 1658 Arguments: 1659 r : (float, list) 1660 constant radius or list of radii. 1661 res : (int) 1662 resolution, number of the sides of the tube 1663 c : (color) 1664 constant color or list of colors for each point. 1665 1666 Examples: 1667 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1668 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1669 1670 ![](https://vedo.embl.es/images/basic/tube.png) 1671 """ 1672 if isinstance(points, vedo.Points): 1673 points = points.points() 1674 1675 base = np.asarray(points[0], dtype=float) 1676 top = np.asarray(points[-1], dtype=float) 1677 1678 vpoints = vtk.vtkPoints() 1679 idx = len(points) 1680 for p in points: 1681 vpoints.InsertNextPoint(p) 1682 line = vtk.vtkPolyLine() 1683 line.GetPointIds().SetNumberOfIds(idx) 1684 for i in range(idx): 1685 line.GetPointIds().SetId(i, i) 1686 lines = vtk.vtkCellArray() 1687 lines.InsertNextCell(line) 1688 polyln = vtk.vtkPolyData() 1689 polyln.SetPoints(vpoints) 1690 polyln.SetLines(lines) 1691 1692 tuf = vtk.vtkTubeFilter() 1693 tuf.SetCapping(cap) 1694 tuf.SetNumberOfSides(res) 1695 tuf.SetInputData(polyln) 1696 if utils.is_sequence(r): 1697 arr = utils.numpy2vtk(r, dtype=float) 1698 arr.SetName("TubeRadius") 1699 polyln.GetPointData().AddArray(arr) 1700 polyln.GetPointData().SetActiveScalars("TubeRadius") 1701 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1702 else: 1703 tuf.SetRadius(r) 1704 1705 usingColScals = False 1706 if utils.is_sequence(c): 1707 usingColScals = True 1708 cc = vtk.vtkUnsignedCharArray() 1709 cc.SetName("TubeColors") 1710 cc.SetNumberOfComponents(3) 1711 cc.SetNumberOfTuples(len(c)) 1712 for i, ic in enumerate(c): 1713 r, g, b = get_color(ic) 1714 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1715 polyln.GetPointData().AddArray(cc) 1716 c = None 1717 tuf.Update() 1718 1719 Mesh.__init__(self, tuf.GetOutput(), c, alpha) 1720 self.phong() 1721 if usingColScals: 1722 self.mapper().SetScalarModeToUsePointFieldData() 1723 self.mapper().ScalarVisibilityOn() 1724 self.mapper().SelectColorArray("TubeColors") 1725 self.mapper().Modified() 1726 1727 self.base = base 1728 self.top = top 1729 self.name = "Tube" 1730 1731 1732def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0): 1733 """ 1734 Create a tube with a thickness along a line of points. 1735 1736 Example: 1737 ```python 1738 from vedo import * 1739 pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)] 1740 vline = Line(pts, lw=5, c='red5') 1741 thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1) 1742 show(vline, thick_tube, axes=1).close() 1743 ``` 1744 ![](https://vedo.embl.es/images/feats/thick_tube.png) 1745 """ 1746 1747 def make_cap(t1, t2): 1748 newpoints = t1.points().tolist() + t2.points().tolist() 1749 newfaces = [] 1750 for i in range(n - 1): 1751 newfaces.append([i, i + 1, i + n]) 1752 newfaces.append([i + n, i + 1, i + n + 1]) 1753 newfaces.append([2 * n - 1, 0, n]) 1754 newfaces.append([2 * n - 1, n - 1, 0]) 1755 capm = utils.buildPolyData(newpoints, newfaces) 1756 return capm 1757 1758 assert r1 < r2 1759 1760 t1 = Tube(pts, r=r1, cap=False, res=res) 1761 t2 = Tube(pts, r=r2, cap=False, res=res) 1762 1763 tc1a, tc1b = t1.boundaries().split() 1764 tc2a, tc2b = t2.boundaries().split() 1765 n = tc1b.npoints 1766 1767 tc1b.join(reset=True).clean() # needed because indices are flipped 1768 tc2b.join(reset=True).clean() 1769 1770 capa = make_cap(tc1a, tc2a) 1771 capb = make_cap(tc1b, tc2b) 1772 1773 thick_tube = merge(t1, t2, capa, capb).c(c).alpha(alpha) 1774 thick_tube.base = t1.base 1775 thick_tube.top = t1.top 1776 thick_tube.name = "ThickTube" 1777 return thick_tube 1778 1779 1780class Ribbon(Mesh): 1781 """ 1782 Connect two lines to generate the surface inbetween. 1783 Set the mode by which to create the ruled surface. 1784 1785 It also works with a single line in input. In this case the ribbon 1786 is formed by following the local plane of the line in space. 1787 """ 1788 1789 def __init__( 1790 self, 1791 line1, 1792 line2=None, 1793 mode=0, 1794 closed=False, 1795 width=None, 1796 res=(200, 5), 1797 c="indigo3", 1798 alpha=1.0, 1799 ): 1800 """ 1801 Arguments: 1802 mode : (int) 1803 If mode=0, resample evenly the input lines (based on length) 1804 and generates triangle strips. 1805 1806 If mode=1, use the existing points and walks around the 1807 polyline using existing points. 1808 1809 closed : (bool) 1810 if True, join the last point with the first to form a closed surface 1811 1812 res : (list) 1813 ribbon resolutions along the line and perpendicularly to it. 1814 1815 Examples: 1816 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1817 1818 ![](https://vedo.embl.es/images/basic/ribbon.png) 1819 """ 1820 1821 if isinstance(line1, Points): 1822 line1 = line1.points() 1823 1824 if isinstance(line2, Points): 1825 line2 = line2.points() 1826 1827 elif line2 is None: 1828 ############################################# 1829 ribbon_filter = vtk.vtkRibbonFilter() 1830 aline = Line(line1) 1831 ribbon_filter.SetInputData(aline.polydata()) 1832 if width is None: 1833 width = aline.diagonal_size() / 20.0 1834 ribbon_filter.SetWidth(width) 1835 ribbon_filter.Update() 1836 Mesh.__init__(self, ribbon_filter.GetOutput(), c, alpha) 1837 self.name = "Ribbon" 1838 ############################################## 1839 return ###################################### 1840 ############################################## 1841 1842 line1 = np.asarray(line1) 1843 line2 = np.asarray(line2) 1844 1845 if closed: 1846 line1 = line1.tolist() 1847 line1 += [line1[0]] 1848 line2 = line2.tolist() 1849 line2 += [line2[0]] 1850 line1 = np.array(line1) 1851 line2 = np.array(line2) 1852 1853 if len(line1[0]) == 2: 1854 line1 = np.c_[line1, np.zeros(len(line1))] 1855 if len(line2[0]) == 2: 1856 line2 = np.c_[line2, np.zeros(len(line2))] 1857 1858 ppoints1 = vtk.vtkPoints() # Generate the polyline1 1859 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1860 lines1 = vtk.vtkCellArray() 1861 lines1.InsertNextCell(len(line1)) 1862 for i in range(len(line1)): 1863 lines1.InsertCellPoint(i) 1864 poly1 = vtk.vtkPolyData() 1865 poly1.SetPoints(ppoints1) 1866 poly1.SetLines(lines1) 1867 1868 ppoints2 = vtk.vtkPoints() # Generate the polyline2 1869 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1870 lines2 = vtk.vtkCellArray() 1871 lines2.InsertNextCell(len(line2)) 1872 for i in range(len(line2)): 1873 lines2.InsertCellPoint(i) 1874 poly2 = vtk.vtkPolyData() 1875 poly2.SetPoints(ppoints2) 1876 poly2.SetLines(lines2) 1877 1878 # build the lines 1879 lines1 = vtk.vtkCellArray() 1880 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1881 for i in range(poly1.GetNumberOfPoints()): 1882 lines1.InsertCellPoint(i) 1883 1884 polygon1 = vtk.vtkPolyData() 1885 polygon1.SetPoints(ppoints1) 1886 polygon1.SetLines(lines1) 1887 1888 lines2 = vtk.vtkCellArray() 1889 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1890 for i in range(poly2.GetNumberOfPoints()): 1891 lines2.InsertCellPoint(i) 1892 1893 polygon2 = vtk.vtkPolyData() 1894 polygon2.SetPoints(ppoints2) 1895 polygon2.SetLines(lines2) 1896 1897 merged_pd = vtk.vtkAppendPolyData() 1898 merged_pd.AddInputData(polygon1) 1899 merged_pd.AddInputData(polygon2) 1900 merged_pd.Update() 1901 1902 rsf = vtk.vtkRuledSurfaceFilter() 1903 rsf.CloseSurfaceOff() 1904 rsf.SetRuledMode(mode) 1905 rsf.SetResolution(res[0], res[1]) 1906 rsf.SetInputData(merged_pd.GetOutput()) 1907 rsf.Update() 1908 1909 Mesh.__init__(self, rsf.GetOutput(), c, alpha) 1910 self.name = "Ribbon" 1911 1912 1913class Arrow(Mesh): 1914 """ 1915 Build a 3D arrow from `start_pt` to `end_pt` of section size `s`, 1916 expressed as the fraction of the window size. 1917 """ 1918 1919 def __init__( 1920 self, 1921 start_pt=(0, 0, 0), 1922 end_pt=(1, 0, 0), 1923 s=None, 1924 shaft_radius=None, 1925 head_radius=None, 1926 head_length=None, 1927 res=12, 1928 c="r4", 1929 alpha=1.0, 1930 ): 1931 """ 1932 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1933 from white to red. 1934 1935 .. note:: If `s=None` the arrow is scaled proportionally to its length 1936 1937 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1938 """ 1939 # in case user is passing meshs 1940 if isinstance(start_pt, vtk.vtkActor): 1941 start_pt = start_pt.GetPosition() 1942 if isinstance(end_pt, vtk.vtkActor): 1943 end_pt = end_pt.GetPosition() 1944 1945 axis = np.asarray(end_pt) - np.asarray(start_pt) 1946 length = np.linalg.norm(axis) 1947 if length: 1948 axis = axis / length 1949 if len(axis) < 3: # its 2d 1950 theta = np.pi / 2 1951 start_pt = [start_pt[0], start_pt[1], 0.0] 1952 end_pt = [end_pt[0], end_pt[1], 0.0] 1953 else: 1954 theta = np.arccos(axis[2]) 1955 phi = np.arctan2(axis[1], axis[0]) 1956 self.source = vtk.vtkArrowSource() 1957 self.source.SetShaftResolution(res) 1958 self.source.SetTipResolution(res) 1959 1960 if s: 1961 sz = 0.02 1962 self.source.SetTipRadius(sz) 1963 self.source.SetShaftRadius(sz / 1.75) 1964 self.source.SetTipLength(sz * 15) 1965 1966 # if s: 1967 # sz = 0.02 * s * length 1968 # tl = sz / 20 1969 # print(s, sz) 1970 # self.source.SetShaftRadius(sz) 1971 # self.source.SetTipRadius(sz*1.75) 1972 # self.source.SetTipLength(sz*15) 1973 1974 if head_length: 1975 self.source.SetTipLength(head_length) 1976 if head_radius: 1977 self.source.SetTipRadius(head_radius) 1978 if shaft_radius: 1979 self.source.SetShaftRadius(shaft_radius) 1980 1981 self.source.Update() 1982 1983 t = vtk.vtkTransform() 1984 t.RotateZ(np.rad2deg(phi)) 1985 t.RotateY(np.rad2deg(theta)) 1986 t.RotateY(-90) # put it along Z 1987 if s: 1988 sz = 800 * s 1989 t.Scale(length, sz, sz) 1990 else: 1991 t.Scale(length, length, length) 1992 tf = vtk.vtkTransformPolyDataFilter() 1993 tf.SetInputData(self.source.GetOutput()) 1994 tf.SetTransform(t) 1995 tf.Update() 1996 1997 Mesh.__init__(self, tf.GetOutput(), c, alpha) 1998 1999 self.phong().lighting("plastic") 2000 self.SetPosition(start_pt) 2001 self.PickableOff() 2002 self.DragableOff() 2003 self.base = np.array(start_pt, dtype=float) 2004 self.top = np.array(end_pt, dtype=float) 2005 self.tip_index = None 2006 self.fill = True # used by pyplot.__iadd__() 2007 self.s = s if s is not None else 1 ## used by pyplot.__iadd() 2008 self.name = "Arrow" 2009 2010 def tip_point(self, return_index=False): 2011 """Return the coordinates of the tip of the Arrow, or the point index.""" 2012 if self.tip_index is None: 2013 arrpts = utils.vtk2numpy(self.source.GetOutput().GetPoints().GetData()) 2014 self.tip_index = np.argmax(arrpts[:, 0]) 2015 if return_index: 2016 return self.tip_index 2017 return self.points()[self.tip_index] 2018 2019 2020class Arrows(Glyph): 2021 """ 2022 Build arrows between two lists of points. 2023 """ 2024 2025 def __init__( 2026 self, 2027 start_pts, 2028 end_pts=None, 2029 s=None, 2030 shaft_radius=None, 2031 head_radius=None, 2032 head_length=None, 2033 thickness=1.0, 2034 res=12, 2035 c=None, 2036 alpha=1.0, 2037 ): 2038 """ 2039 Build arrows between two lists of points `start_pts` and `end_pts`. 2040 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2041 2042 Color can be specified as a colormap which maps the size of the arrows. 2043 2044 Arguments: 2045 s : (float) 2046 fix aspect-ratio of the arrow and scale its cross section 2047 c : (color) 2048 color or color map name 2049 alpha : (float) 2050 set object opacity 2051 res : (int) 2052 set arrow resolution 2053 2054 Examples: 2055 - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py) 2056 2057 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 2058 """ 2059 if isinstance(start_pts, Points): 2060 start_pts = start_pts.points() 2061 if isinstance(end_pts, Points): 2062 end_pts = end_pts.points() 2063 2064 start_pts = np.asarray(start_pts) 2065 if end_pts is None: 2066 strt = start_pts[:, 0] 2067 end_pts = start_pts[:, 1] 2068 start_pts = strt 2069 else: 2070 end_pts = np.asarray(end_pts) 2071 2072 start_pts = utils.make3d(start_pts) 2073 end_pts = utils.make3d(end_pts) 2074 2075 arr = vtk.vtkArrowSource() 2076 arr.SetShaftResolution(res) 2077 arr.SetTipResolution(res) 2078 2079 if s: 2080 sz = 0.02 * s 2081 arr.SetTipRadius(sz * 2) 2082 arr.SetShaftRadius(sz * thickness) 2083 arr.SetTipLength(sz * 10) 2084 2085 if head_radius: 2086 arr.SetTipRadius(head_radius) 2087 if shaft_radius: 2088 arr.SetShaftRadius(shaft_radius) 2089 if head_length: 2090 arr.SetTipLength(head_length) 2091 2092 arr.Update() 2093 out = arr.GetOutput() 2094 2095 orients = end_pts - start_pts 2096 Glyph.__init__( 2097 self, 2098 start_pts, 2099 out, 2100 orientation_array=orients, 2101 scale_by_vector_size=True, 2102 color_by_vector_size=True, 2103 c=c, 2104 alpha=alpha, 2105 ) 2106 self.flat().lighting("plastic") 2107 self.name = "Arrows" 2108 2109 2110class Arrow2D(Mesh): 2111 """ 2112 Build a 2D arrow. 2113 """ 2114 2115 def __init__( 2116 self, 2117 start_pt=(0, 0, 0), 2118 end_pt=(1, 0, 0), 2119 s=1, 2120 shaft_length=0.8, 2121 shaft_width=0.05, 2122 head_length=0.225, 2123 head_width=0.175, 2124 fill=True, 2125 ): 2126 """ 2127 Build a 2D arrow from `start_pt` to `end_pt`. 2128 2129 Arguments: 2130 s : (float) 2131 a global multiplicative convenience factor controlling the arrow size 2132 shaft_length : (float) 2133 fractional shaft length 2134 shaft_width : (float) 2135 fractional shaft width 2136 head_length : (float) 2137 fractional head length 2138 head_width : (float) 2139 fractional head width 2140 fill : (bool) 2141 if False only generate the outline 2142 """ 2143 self.fill = fill ## needed by pyplot.__iadd() 2144 self.s = s # # needed by pyplot.__iadd() 2145 2146 if s != 1: 2147 shaft_width *= s 2148 head_width *= np.sqrt(s) 2149 2150 # in case user is passing meshs 2151 if isinstance(start_pt, vtk.vtkActor): 2152 start_pt = start_pt.GetPosition() 2153 if isinstance(end_pt, vtk.vtkActor): 2154 end_pt = end_pt.GetPosition() 2155 if len(start_pt) == 2: 2156 start_pt = [start_pt[0], start_pt[1], 0] 2157 if len(end_pt) == 2: 2158 end_pt = [end_pt[0], end_pt[1], 0] 2159 2160 headBase = 1 - head_length 2161 head_width = max(head_width, shaft_width) 2162 if head_length is None or headBase > shaft_length: 2163 headBase = shaft_length 2164 2165 verts = [] 2166 verts.append([0, -shaft_width / 2, 0]) 2167 verts.append([shaft_length, -shaft_width / 2, 0]) 2168 verts.append([headBase, -head_width / 2, 0]) 2169 verts.append([1, 0, 0]) 2170 verts.append([headBase, head_width / 2, 0]) 2171 verts.append([shaft_length, shaft_width / 2, 0]) 2172 verts.append([0, shaft_width / 2, 0]) 2173 if fill: 2174 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2175 poly = utils.buildPolyData(verts, faces) 2176 else: 2177 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2178 poly = utils.buildPolyData(verts, [], lines=lines) 2179 2180 axis = np.array(end_pt) - np.array(start_pt) 2181 length = np.linalg.norm(axis) 2182 if length: 2183 axis = axis / length 2184 theta = 0 2185 if len(axis) > 2: 2186 theta = np.arccos(axis[2]) 2187 phi = np.arctan2(axis[1], axis[0]) 2188 t = vtk.vtkTransform() 2189 if phi: 2190 t.RotateZ(np.rad2deg(phi)) 2191 if theta: 2192 t.RotateY(np.rad2deg(theta)) 2193 t.RotateY(-90) # put it along Z 2194 t.Scale(length, length, length) 2195 tf = vtk.vtkTransformPolyDataFilter() 2196 tf.SetInputData(poly) 2197 tf.SetTransform(t) 2198 tf.Update() 2199 2200 Mesh.__init__(self, tf.GetOutput(), c="k1") 2201 self.SetPosition(start_pt) 2202 self.lighting("off") 2203 self.DragableOff() 2204 self.PickableOff() 2205 self.base = np.array(start_pt, dtype=float) 2206 self.top = np.array(end_pt, dtype=float) 2207 self.name = "Arrow2D" 2208 2209 2210class Arrows2D(Glyph): 2211 """ 2212 Build 2D arrows between two lists of points. 2213 """ 2214 2215 def __init__( 2216 self, 2217 start_pts, 2218 end_pts=None, 2219 s=1.0, 2220 shaft_length=0.8, 2221 shaft_width=0.05, 2222 head_length=0.225, 2223 head_width=0.175, 2224 fill=True, 2225 c=None, 2226 alpha=1.0, 2227 ): 2228 """ 2229 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2230 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2231 2232 Color can be specified as a colormap which maps the size of the arrows. 2233 2234 Arguments: 2235 shaft_length : (float) 2236 fractional shaft length 2237 shaft_width : (float) 2238 fractional shaft width 2239 head_length : (float) 2240 fractional head length 2241 head_width : (float) 2242 fractional head width 2243 fill : (bool) 2244 if False only generate the outline 2245 """ 2246 if isinstance(start_pts, Points): 2247 start_pts = start_pts.points() 2248 if isinstance(end_pts, Points): 2249 end_pts = end_pts.points() 2250 2251 start_pts = np.asarray(start_pts, dtype=float) 2252 if end_pts is None: 2253 strt = start_pts[:, 0] 2254 end_pts = start_pts[:, 1] 2255 start_pts = strt 2256 else: 2257 end_pts = np.asarray(end_pts, dtype=float) 2258 2259 if head_length is None: 2260 head_length = 1 - shaft_length 2261 2262 arr = Arrow2D( 2263 (0, 0, 0), 2264 (1, 0, 0), 2265 s=s, 2266 shaft_length=shaft_length, 2267 shaft_width=shaft_width, 2268 head_length=head_length, 2269 head_width=head_width, 2270 fill=fill, 2271 ) 2272 2273 orients = end_pts - start_pts 2274 orients = utils.make3d(orients) 2275 2276 pts = Points(start_pts) 2277 Glyph.__init__( 2278 self, 2279 pts, 2280 arr.polydata(False), 2281 orientation_array=orients, 2282 scale_by_vector_size=True, 2283 c=c, 2284 alpha=alpha, 2285 ) 2286 self.flat().lighting("off") 2287 if c is not None: 2288 self.color(c) 2289 self.name = "Arrows2D" 2290 2291 2292class FlatArrow(Ribbon): 2293 """ 2294 Build a 2D arrow in 3D space by joining two close lines. 2295 """ 2296 2297 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0): 2298 """ 2299 Build a 2D arrow in 3D space by joining two close lines. 2300 2301 Examples: 2302 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2303 2304 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2305 """ 2306 if isinstance(line1, Points): 2307 line1 = line1.points() 2308 if isinstance(line2, Points): 2309 line2 = line2.points() 2310 2311 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2312 2313 v = (sm1 - sm2) / 3 * tip_width 2314 p1 = sm1 + v 2315 p2 = sm2 - v 2316 pm1 = (sm1 + sm2) / 2 2317 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2318 pm12 = pm1 - pm2 2319 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2320 2321 line1.append(p1) 2322 line1.append(tip) 2323 line2.append(p2) 2324 line2.append(tip) 2325 resm = max(100, len(line1)) 2326 2327 Ribbon.__init__(self, line1, line2, res=(resm, 1)) 2328 self.phong() 2329 self.PickableOff() 2330 self.DragableOff() 2331 self.name = "FlatArrow" 2332 2333 2334class Triangle(Mesh): 2335 """Create a triangle from 3 points in space.""" 2336 2337 def __init__(self, p1, p2, p3, c="green7", alpha=1.0): 2338 """Create a triangle from 3 points in space.""" 2339 Mesh.__init__(self, [[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2340 self.GetProperty().LightingOff() 2341 self.name = "Triangle" 2342 2343 2344class Polygon(Mesh): 2345 """ 2346 Build a polygon in the `xy` plane. 2347 """ 2348 2349 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0): 2350 """ 2351 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2352 2353 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2354 """ 2355 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2356 x, y = utils.pol2cart(np.ones_like(t) * r, t) 2357 faces = [list(range(nsides))] 2358 # do not use: vtkRegularPolygonSource 2359 Mesh.__init__(self, [np.c_[x, y], faces], c, alpha) 2360 if len(pos) == 2: 2361 pos = (pos[0], pos[1], 0) 2362 self.SetPosition(pos) 2363 self.GetProperty().LightingOff() 2364 self.name = "Polygon " + str(nsides) 2365 2366 2367class Circle(Polygon): 2368 """ 2369 Build a Circle of radius `r`. 2370 """ 2371 2372 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0): 2373 """ 2374 Build a Circle of radius `r`. 2375 """ 2376 Polygon.__init__(self, pos, nsides=res, r=r) 2377 2378 self.center = [] # filled by pointcloud.pcaEllipse 2379 self.nr_of_points = 0 2380 self.va = 0 2381 self.vb = 0 2382 self.axis1 = [] 2383 self.axis2 = [] 2384 self.alpha(alpha).c(c) 2385 self.name = "Circle" 2386 2387 2388class GeoCircle(Polygon): 2389 """ 2390 Build a Circle of radius `r`. 2391 """ 2392 2393 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0): 2394 """ 2395 Build a Circle of radius `r` as projected on a geographic map. 2396 Circles near the poles will look very squashed. 2397 2398 See example: 2399 ```bash 2400 vedo -r earthquake 2401 ``` 2402 """ 2403 coords = [] 2404 sinr, cosr = np.sin(r), np.cos(r) 2405 sinlat, coslat = np.sin(lat), np.cos(lat) 2406 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2407 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2408 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2409 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2410 2411 Polygon.__init__(self, nsides=res, c=c, alpha=alpha) 2412 self.points(coords) # warp polygon points to match geo projection 2413 self.name = "Circle" 2414 2415 2416class Star(Mesh): 2417 """ 2418 Build a 2D star shape. 2419 """ 2420 2421 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0): 2422 """ 2423 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2424 2425 If line is True then only build the outer line (no internal surface meshing). 2426 2427 Example: 2428 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2429 2430 ![](https://vedo.embl.es/images/basic/extrude.png) 2431 """ 2432 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2433 x, y = utils.pol2cart(np.ones_like(t) * r2, t) 2434 pts = np.c_[x, y, np.zeros_like(x)] 2435 2436 apts = [] 2437 for i, p in enumerate(pts): 2438 apts.append(p) 2439 if i + 1 < n: 2440 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2441 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2442 2443 if line: 2444 apts.append(pts[0]) 2445 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2446 Mesh.__init__(self, poly, c, alpha) 2447 self.lw(2) 2448 else: 2449 apts.append((0, 0, 0)) 2450 cells = [] 2451 for i in range(2 * n - 1): 2452 cell = [2 * n, i, i + 1] 2453 cells.append(cell) 2454 cells.append([2 * n, i + 1, 0]) 2455 Mesh.__init__(self, [apts, cells], c, alpha) 2456 2457 if len(pos) == 2: 2458 pos = (pos[0], pos[1], 0) 2459 self.SetPosition(pos) 2460 self.property.LightingOff() 2461 self.name = "Star" 2462 2463 2464class Disc(Mesh): 2465 """ 2466 Build a 2D disc. 2467 """ 2468 2469 def __init__( 2470 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2471 ): 2472 """ 2473 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2474 2475 Set `res` as the resolution in R and Phi (can be a list). 2476 2477 Use `angle_range` to create a disc sector between the 2 specified angles. 2478 2479 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2480 """ 2481 if utils.is_sequence(res): 2482 res_r, res_phi = res 2483 else: 2484 res_r, res_phi = res, 12 * res 2485 2486 if len(angle_range) == 0: 2487 ps = vtk.vtkDiskSource() 2488 else: 2489 ps = vtk.vtkSectorSource() 2490 ps.SetStartAngle(angle_range[0]) 2491 ps.SetEndAngle(angle_range[1]) 2492 2493 ps.SetInnerRadius(r1) 2494 ps.SetOuterRadius(r2) 2495 ps.SetRadialResolution(res_r) 2496 ps.SetCircumferentialResolution(res_phi) 2497 ps.Update() 2498 Mesh.__init__(self, ps.GetOutput(), c, alpha) 2499 self.flat() 2500 self.SetPosition(utils.make3d(pos)) 2501 self.name = "Disc" 2502 2503 2504class Arc(Mesh): 2505 """ 2506 Build a 2D circular arc between 2 points. 2507 """ 2508 2509 def __init__( 2510 self, 2511 center, 2512 point1, 2513 point2=None, 2514 normal=None, 2515 angle=None, 2516 invert=False, 2517 res=50, 2518 c="gray4", 2519 alpha=1.0, 2520 ): 2521 """ 2522 Build a 2D circular arc between 2 points `point1` and `point2`. 2523 2524 If `normal` is specified then `center` is ignored, and 2525 normal vector, a starting `point1` (polar vector) 2526 and an angle defining the arc length need to be assigned. 2527 2528 Arc spans the shortest angular sector point1 and point2, 2529 if `invert=True`, then the opposite happens. 2530 """ 2531 if len(point1) == 2: 2532 point1 = (point1[0], point1[1], 0) 2533 if point2 is not None and len(point2) == 2: 2534 point2 = (point2[0], point2[1], 0) 2535 2536 self.base = point1 2537 self.top = point2 2538 2539 ar = vtk.vtkArcSource() 2540 if point2 is not None: 2541 self.top = point2 2542 point2 = point2 - np.asarray(point1) 2543 ar.UseNormalAndAngleOff() 2544 ar.SetPoint1([0, 0, 0]) 2545 ar.SetPoint2(point2) 2546 ar.SetCenter(center) 2547 elif normal is not None and angle is not None: 2548 ar.UseNormalAndAngleOn() 2549 ar.SetAngle(angle) 2550 ar.SetPolarVector(point1) 2551 ar.SetNormal(normal) 2552 else: 2553 vedo.logger.error("incorrect input combination") 2554 return 2555 ar.SetNegative(invert) 2556 ar.SetResolution(res) 2557 ar.Update() 2558 Mesh.__init__(self, ar.GetOutput(), c, alpha) 2559 self.SetPosition(self.base) 2560 self.lw(2).lighting("off") 2561 self.name = "Arc" 2562 2563 2564class IcoSphere(Mesh): 2565 """ 2566 Create a sphere made of a uniform triangle mesh. 2567 """ 2568 2569 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=3, c="r5", alpha=1.0): 2570 """ 2571 Create a sphere made of a uniform triangle mesh 2572 (from recursive subdivision of an icosahedron). 2573 2574 Example: 2575 ```python 2576 from vedo import * 2577 icos = IcoSphere(subdivisions=3) 2578 icos.compute_quality().cmap('coolwarm') 2579 icos.show(axes=1).close() 2580 ``` 2581 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2582 """ 2583 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2584 2585 t = (1.0 + np.sqrt(5.0)) / 2.0 2586 points = np.array( 2587 [ 2588 [-1, t, 0], 2589 [1, t, 0], 2590 [-1, -t, 0], 2591 [1, -t, 0], 2592 [0, -1, t], 2593 [0, 1, t], 2594 [0, -1, -t], 2595 [0, 1, -t], 2596 [t, 0, -1], 2597 [t, 0, 1], 2598 [-t, 0, -1], 2599 [-t, 0, 1], 2600 ] 2601 ) 2602 faces = [ 2603 [0, 11, 5], 2604 [0, 5, 1], 2605 [0, 1, 7], 2606 [0, 7, 10], 2607 [0, 10, 11], 2608 [1, 5, 9], 2609 [5, 11, 4], 2610 [11, 10, 2], 2611 [10, 7, 6], 2612 [7, 1, 8], 2613 [3, 9, 4], 2614 [3, 4, 2], 2615 [3, 2, 6], 2616 [3, 6, 8], 2617 [3, 8, 9], 2618 [4, 9, 5], 2619 [2, 4, 11], 2620 [6, 2, 10], 2621 [8, 6, 7], 2622 [9, 8, 1], 2623 ] 2624 Mesh.__init__(self, [points * r, faces], c=c, alpha=alpha) 2625 2626 for _ in range(subdivisions): 2627 self.subdivide(method=1) 2628 pts = utils.versor(self.points()) * r 2629 self.points(pts) 2630 2631 self.SetPosition(pos) 2632 self.name = "IcoSphere" 2633 2634 2635class Sphere(Mesh): 2636 """ 2637 Build a sphere. 2638 """ 2639 2640 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0): 2641 """ 2642 Build a sphere at position `pos` of radius `r`. 2643 2644 Arguments: 2645 r : (float) 2646 sphere radius 2647 res : (int, list) 2648 resolution in phi, resolution in theta is by default `2*res` 2649 quads : (bool) 2650 sphere mesh will be made of quads instead of triangles 2651 2652 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2653 """ 2654 if len(pos) == 2: 2655 pos = np.asarray([pos[0], pos[1], 0]) 2656 2657 self.radius = r # used by fitSphere 2658 self.center = pos 2659 self.residue = 0 2660 2661 if quads: 2662 res = max(res, 4) 2663 img = vtk.vtkImageData() 2664 img.SetDimensions(res - 1, res - 1, res - 1) 2665 rs = 1.0 / (res - 2) 2666 img.SetSpacing(rs, rs, rs) 2667 gf = vtk.vtkGeometryFilter() 2668 gf.SetInputData(img) 2669 gf.Update() 2670 Mesh.__init__(self, gf.GetOutput(), c, alpha) 2671 self.lw(0.1) 2672 2673 cgpts = self.points() - (0.5, 0.5, 0.5) 2674 2675 x, y, z = cgpts.T 2676 x = x * (1 + x * x) / 2 2677 y = y * (1 + y * y) / 2 2678 z = z * (1 + z * z) / 2 2679 _, theta, phi = utils.cart2spher(x, y, z) 2680 2681 pts = utils.spher2cart(np.ones_like(phi) * r, theta, phi) 2682 self.points(pts) 2683 2684 else: 2685 if utils.is_sequence(res): 2686 res_t, res_phi = res 2687 else: 2688 res_t, res_phi = 2 * res, res 2689 2690 ss = vtk.vtkSphereSource() 2691 ss.SetRadius(r) 2692 ss.SetThetaResolution(res_t) 2693 ss.SetPhiResolution(res_phi) 2694 ss.Update() 2695 2696 Mesh.__init__(self, ss.GetOutput(), c, alpha) 2697 2698 self.phong() 2699 self.SetPosition(pos) 2700 self.name = "Sphere" 2701 2702 2703class Spheres(Mesh): 2704 """ 2705 Build a large set of spheres. 2706 """ 2707 2708 def __init__(self, centers, r=1.0, res=8, c="r5", alpha=1): 2709 """ 2710 Build a (possibly large) set of spheres at `centers` of radius `r`. 2711 2712 Either `c` or `r` can be a list of RGB colors or radii. 2713 2714 Examples: 2715 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2716 2717 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2718 """ 2719 2720 if isinstance(centers, Points): 2721 centers = centers.points() 2722 centers = np.asarray(centers, dtype=float) 2723 base = centers[0] 2724 2725 cisseq = False 2726 if utils.is_sequence(c): 2727 cisseq = True 2728 2729 if cisseq: 2730 if len(centers) != len(c): 2731 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2732 raise RuntimeError() 2733 2734 risseq = False 2735 if utils.is_sequence(r): 2736 risseq = True 2737 2738 if risseq: 2739 if len(centers) != len(r): 2740 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2741 raise RuntimeError() 2742 if cisseq and risseq: 2743 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2744 raise RuntimeError() 2745 2746 src = vtk.vtkSphereSource() 2747 if not risseq: 2748 src.SetRadius(r) 2749 if utils.is_sequence(res): 2750 res_t, res_phi = res 2751 else: 2752 res_t, res_phi = 2 * res, res 2753 2754 src.SetThetaResolution(res_t) 2755 src.SetPhiResolution(res_phi) 2756 src.Update() 2757 2758 psrc = vtk.vtkPointSource() 2759 psrc.SetNumberOfPoints(len(centers)) 2760 psrc.Update() 2761 pd = psrc.GetOutput() 2762 vpts = pd.GetPoints() 2763 2764 glyph = vtk.vtkGlyph3D() 2765 glyph.SetSourceConnection(src.GetOutputPort()) 2766 2767 if cisseq: 2768 glyph.SetColorModeToColorByScalar() 2769 ucols = vtk.vtkUnsignedCharArray() 2770 ucols.SetNumberOfComponents(3) 2771 ucols.SetName("Colors") 2772 for acol in c: 2773 cx, cy, cz = get_color(acol) 2774 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2775 pd.GetPointData().AddArray(ucols) 2776 pd.GetPointData().SetActiveScalars("Colors") 2777 glyph.ScalingOff() 2778 elif risseq: 2779 glyph.SetScaleModeToScaleByScalar() 2780 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2781 urads.SetName("Radii") 2782 pd.GetPointData().AddArray(urads) 2783 pd.GetPointData().SetActiveScalars("Radii") 2784 2785 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2786 2787 glyph.SetInputData(pd) 2788 glyph.Update() 2789 2790 Mesh.__init__(self, glyph.GetOutput(), alpha=alpha) 2791 self.SetPosition(base) 2792 self.base = base 2793 self.top = centers[-1] 2794 self.phong() 2795 if cisseq: 2796 self.mapper().ScalarVisibilityOn() 2797 else: 2798 self.mapper().ScalarVisibilityOff() 2799 self.GetProperty().SetColor(get_color(c)) 2800 self.name = "Spheres" 2801 2802 2803class Earth(Mesh): 2804 """ 2805 Build a textured mesh representing the Earth. 2806 """ 2807 2808 def __init__(self, style=1, r=1.0): 2809 """ 2810 Build a textured mesh representing the Earth. 2811 2812 Example: 2813 - [geodesic.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic.py) 2814 2815 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2816 """ 2817 tss = vtk.vtkTexturedSphereSource() 2818 tss.SetRadius(r) 2819 tss.SetThetaResolution(72) 2820 tss.SetPhiResolution(36) 2821 Mesh.__init__(self, tss, c="w") 2822 atext = vtk.vtkTexture() 2823 pnm_reader = vtk.vtkJPEGReader() 2824 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2825 pnm_reader.SetFileName(fn) 2826 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2827 atext.InterpolateOn() 2828 self.SetTexture(atext) 2829 self.name = "Earth" 2830 2831 2832class Ellipsoid(Mesh): 2833 """ 2834 Build a 3D ellipsoid. 2835 """ 2836 2837 def __init__( 2838 self, 2839 pos=(0, 0, 0), 2840 axis1=(1, 0, 0), 2841 axis2=(0, 2, 0), 2842 axis3=(0, 0, 3), 2843 res=24, 2844 c="cyan4", 2845 alpha=1.0, 2846 ): 2847 """ 2848 Build a 3D ellipsoid centered at position `pos`. 2849 2850 Arguments: 2851 axis1 : (list) 2852 First axis 2853 axis2 : (list) 2854 Second axis 2855 axis3 : (list) 2856 Third axis 2857 2858 .. note:: `axis1` and `axis2` are only used to define sizes and one azimuth angle. 2859 """ 2860 2861 self.center = pos 2862 self.va_error = 0 2863 self.vb_error = 0 2864 self.vc_error = 0 2865 self.axis1 = axis1 2866 self.axis2 = axis2 2867 self.axis3 = axis3 2868 self.nr_of_points = 1 # used by pcaEllipsoid 2869 2870 if utils.is_sequence(res): 2871 res_t, res_phi = res 2872 else: 2873 res_t, res_phi = 2 * res, res 2874 2875 elli_source = vtk.vtkSphereSource() 2876 elli_source.SetThetaResolution(res_t) 2877 elli_source.SetPhiResolution(res_phi) 2878 elli_source.Update() 2879 l1 = np.linalg.norm(axis1) 2880 l2 = np.linalg.norm(axis2) 2881 l3 = np.linalg.norm(axis3) 2882 self.va = l1 2883 self.vb = l2 2884 self.vc = l3 2885 axis1 = np.array(axis1) / l1 2886 axis2 = np.array(axis2) / l2 2887 axis3 = np.array(axis3) / l3 2888 angle = np.arcsin(np.dot(axis1, axis2)) 2889 theta = np.arccos(axis3[2]) 2890 phi = np.arctan2(axis3[1], axis3[0]) 2891 2892 t = vtk.vtkTransform() 2893 t.PostMultiply() 2894 t.Scale(l1, l2, l3) 2895 t.RotateX(np.rad2deg(angle)) 2896 t.RotateY(np.rad2deg(theta)) 2897 t.RotateZ(np.rad2deg(phi)) 2898 tf = vtk.vtkTransformPolyDataFilter() 2899 tf.SetInputData(elli_source.GetOutput()) 2900 tf.SetTransform(t) 2901 tf.Update() 2902 pd = tf.GetOutput() 2903 self.transformation = t 2904 2905 Mesh.__init__(self, pd, c, alpha) 2906 self.phong() 2907 if len(pos) == 2: 2908 pos = (pos[0], pos[1], 0) 2909 self.SetPosition(pos) 2910 self.name = "Ellipsoid" 2911 2912 def asphericity(self): 2913 """ 2914 Return a measure of how different an ellipsoid is froma sphere. 2915 Values close to zero correspond to a spheric object. 2916 """ 2917 a,b,c = self.va, self.vb, self.vc 2918 asp = ( ((a-b)/(a+b))**2 2919 + ((a-c)/(a+c))**2 2920 + ((b-c)/(b+c))**2 )/3. * 4. 2921 return asp 2922 2923 def asphericity_error(self): 2924 """ 2925 Calculate statistical error on the asphericity value. 2926 2927 Errors on the main axes are stored in 2928 `Ellipsoid.va_error, Ellipsoid.vb_error and Ellipsoid.vc_error`. 2929 """ 2930 a, b, c = self.va, self.vb, self.vc 2931 sqrtn = np.sqrt(self.nr_of_points) 2932 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2933 2934 # from sympy import * 2935 # init_printing(use_unicode=True) 2936 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2937 # L = ( 2938 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2939 # / 3 * 4) 2940 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2941 # print(dl2) 2942 # exit() 2943 dL2 = ( 2944 ea ** 2 2945 * ( 2946 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2947 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2948 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2949 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2950 ) ** 2 2951 + eb ** 2 2952 * ( 2953 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2954 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2955 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2956 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2957 ) ** 2 2958 + ec ** 2 2959 * ( 2960 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2961 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2962 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2963 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2964 ) ** 2 2965 ) 2966 2967 err = np.sqrt(dL2) 2968 2969 self.va_error = ea 2970 self.vb_error = eb 2971 self.vc_error = ec 2972 return err 2973 2974 2975class Grid(Mesh): 2976 """ 2977 An even or uneven 2D grid. 2978 """ 2979 2980 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0): 2981 """ 2982 Create an even or uneven 2D grid. 2983 2984 Arguments: 2985 s : (float, list) 2986 if a float is provided it is interpreted as the total size along x and y, 2987 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2988 In this case keyword `res` is ignored (see example below). 2989 res : (list) 2990 resolutions along x and y, e.i. the number of subdivisions 2991 lw : (int) 2992 line width 2993 2994 Example: 2995 ```python 2996 from vedo import * 2997 import numpy as np 2998 xcoords = np.arange(0, 2, 0.2) 2999 ycoords = np.arange(0, 1, 0.2) 3000 sqrtx = sqrt(xcoords) 3001 grid = Grid(s=(sqrtx, ycoords)).lw(2) 3002 grid.show(axes=8) 3003 3004 # can also create a grid from np.mgrid: 3005 X, Y = np.mgrid[-12:12:1000*1j, 0:15:1000*1j] 3006 vgrid = Grid(s=(X[:,0], Y[0])) 3007 vgrid.show(axes=1).close() 3008 ``` 3009 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 3010 """ 3011 resx, resy = res 3012 sx, sy = s 3013 3014 if len(pos) == 2: 3015 pos = (pos[0], pos[1], 0) 3016 3017 if utils.is_sequence(sx) and utils.is_sequence(sy): 3018 verts = [] 3019 for y in sy: 3020 for x in sx: 3021 verts.append([x, y, 0]) 3022 faces = [] 3023 n = len(sx) 3024 m = len(sy) 3025 for j in range(m - 1): 3026 j1n = (j + 1) * n 3027 for i in range(n - 1): 3028 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3029 3030 verts = np.array(verts) 3031 Mesh.__init__(self, [verts, faces], c, alpha) 3032 3033 else: 3034 ps = vtk.vtkPlaneSource() 3035 ps.SetResolution(resx, resy) 3036 ps.Update() 3037 poly0 = ps.GetOutput() 3038 t0 = vtk.vtkTransform() 3039 t0.Scale(sx, sy, 1) 3040 tf0 = vtk.vtkTransformPolyDataFilter() 3041 tf0.SetInputData(poly0) 3042 tf0.SetTransform(t0) 3043 tf0.Update() 3044 poly = tf0.GetOutput() 3045 Mesh.__init__(self, poly, c, alpha) 3046 self.SetPosition(pos) 3047 3048 self.wireframe().lw(lw) 3049 self.GetProperty().LightingOff() 3050 self.name = "Grid" 3051 3052 3053class Plane(Mesh): 3054 """ 3055 Create a plane in space. 3056 """ 3057 3058 def __init__(self, pos=(0, 0, 0), normal=(0, 0, 1), s=(1, 1), res=(1, 1), c="gray5", alpha=1.0): 3059 """ 3060 Create a plane of size `s=(xsize, ysize)` oriented perpendicular to vector `normal` 3061 and so that it passes through point `pos`. 3062 3063 Arguments: 3064 normal : (list) 3065 normal vector to the plane 3066 """ 3067 pos = utils.make3d(pos) 3068 sx, sy = s 3069 3070 self.normal = np.asarray(normal, dtype=float) 3071 self.center = np.asarray(pos, dtype=float) 3072 self.variance = 0 3073 3074 ps = vtk.vtkPlaneSource() 3075 ps.SetResolution(res[0], res[1]) 3076 tri = vtk.vtkTriangleFilter() 3077 tri.SetInputConnection(ps.GetOutputPort()) 3078 tri.Update() 3079 poly = tri.GetOutput() 3080 axis = self.normal / np.linalg.norm(normal) 3081 theta = np.arccos(axis[2]) 3082 phi = np.arctan2(axis[1], axis[0]) 3083 t = vtk.vtkTransform() 3084 t.PostMultiply() 3085 t.Scale(sx, sy, 1) 3086 t.RotateY(np.rad2deg(theta)) 3087 t.RotateZ(np.rad2deg(phi)) 3088 tf = vtk.vtkTransformPolyDataFilter() 3089 tf.SetInputData(poly) 3090 tf.SetTransform(t) 3091 tf.Update() 3092 Mesh.__init__(self, tf.GetOutput(), c, alpha) 3093 self.lighting("off") 3094 self.SetPosition(pos) 3095 self.name = "Plane" 3096 self.top = self.normal 3097 self.bottom = np.array([0.0, 0.0, 0.0]) 3098 3099 def contains(self, points): 3100 """ 3101 Check if each of the provided point lies on this plane. 3102 `points` is an array of shape (n, 3). 3103 """ 3104 points = np.array(points, dtype=float) 3105 bounds = self.points() 3106 3107 mask = np.isclose(np.dot(points - self.center, self.normal), 0) 3108 3109 for i in [1, 3]: 3110 AB = bounds[i] - bounds[0] 3111 AP = points - bounds[0] 3112 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3113 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3114 mask = np.logical_and(mask, mask_l) 3115 mask = np.logical_and(mask, mask_g) 3116 return mask 3117 3118 3119class Rectangle(Mesh): 3120 """ 3121 Build a rectangle in the xy plane. 3122 """ 3123 3124 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0): 3125 """ 3126 Build a rectangle in the xy plane identified by any two corner points. 3127 3128 Arguments: 3129 p1 : (list) 3130 bottom-left position of the corner 3131 p2 : (list) 3132 top-right position of the corner 3133 radius : (float, list) 3134 smoothing radius of the corner in world units. 3135 A list can be passed with 4 individual values. 3136 """ 3137 if len(p1) == 2: 3138 p1 = np.array([p1[0], p1[1], 0.0]) 3139 else: 3140 p1 = np.array(p1, dtype=float) 3141 if len(p2) == 2: 3142 p2 = np.array([p2[0], p2[1], 0.0]) 3143 else: 3144 p2 = np.array(p2, dtype=float) 3145 3146 self.corner1 = p1 3147 self.corner2 = p2 3148 3149 color = c 3150 smoothr = False 3151 risseq = False 3152 if utils.is_sequence(radius): 3153 risseq = True 3154 smoothr = True 3155 if max(radius) == 0: 3156 smoothr = False 3157 elif radius: 3158 smoothr = True 3159 3160 if not smoothr: 3161 radius = None 3162 self.radius = radius 3163 3164 if smoothr: 3165 r = radius 3166 if not risseq: 3167 r = [r, r, r, r] 3168 rd, ra, rb, rc = r 3169 3170 if p1[0] > p2[0]: # flip p1 - p2 3171 p1, p2 = p2, p1 3172 if p1[1] > p2[1]: # flip p1y - p2y 3173 p1[1], p2[1] = p2[1], p1[1] 3174 3175 px, py, _ = p2 - p1 3176 k = min(px / 2, py / 2) 3177 ra = min(abs(ra), k) 3178 rb = min(abs(rb), k) 3179 rc = min(abs(rc), k) 3180 rd = min(abs(rd), k) 3181 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3182 betas = np.split(beta, 4) 3183 rrx = np.cos(betas) 3184 rry = np.sin(betas) 3185 3186 q1 = (rd, 0) 3187 # q2 = (px-ra, 0) 3188 q3 = (px, ra) 3189 # q4 = (px, py-rb) 3190 q5 = (px - rb, py) 3191 # q6 = (rc, py) 3192 q7 = (0, py - rc) 3193 # q8 = (0, rd) 3194 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3195 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3196 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3197 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3198 3199 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3200 faces = [list(range(len(pts)))] 3201 else: 3202 p1r = np.array([p2[0], p1[1], 0.0]) 3203 p2l = np.array([p1[0], p2[1], 0.0]) 3204 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3205 faces = [(0, 1, 2, 3)] 3206 3207 Mesh.__init__(self, [pts, faces], color, alpha) 3208 self.SetPosition(p1) 3209 self.property.LightingOff() 3210 self.name = "Rectangle" 3211 3212 3213class Box(Mesh): 3214 """ 3215 Build a box of specified dimensions. 3216 """ 3217 3218 def __init__(self, pos=(0, 0, 0), 3219 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0): 3220 """ 3221 Build a box of dimensions `x=length, y=width and z=height`. 3222 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3223 3224 If `size` is a list of 6 numbers, this will be interpreted as the bounding box: 3225 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3226 3227 Examples: 3228 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3229 3230 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3231 """ 3232 if len(size) == 6: 3233 bounds = size 3234 length = bounds[1] - bounds[0] 3235 width = bounds[3] - bounds[2] 3236 height = bounds[5] - bounds[4] 3237 xp = (bounds[1] + bounds[0]) / 2 3238 yp = (bounds[3] + bounds[2]) / 2 3239 zp = (bounds[5] + bounds[4]) / 2 3240 pos = (xp, yp, zp) 3241 elif len(size) == 3: 3242 length, width, height = size 3243 3244 src = vtk.vtkCubeSource() 3245 src.SetXLength(length) 3246 src.SetYLength(width) 3247 src.SetZLength(height) 3248 src.Update() 3249 pd = src.GetOutput() 3250 3251 tc = [ 3252 [0.0, 0.0], 3253 [1.0, 0.0], 3254 [0.0, 1.0], 3255 [1.0, 1.0], 3256 [1.0, 0.0], 3257 [0.0, 0.0], 3258 [1.0, 1.0], 3259 [0.0, 1.0], 3260 [1.0, 1.0], 3261 [1.0, 0.0], 3262 [0.0, 1.0], 3263 [0.0, 0.0], 3264 [0.0, 1.0], 3265 [0.0, 0.0], 3266 [1.0, 1.0], 3267 [1.0, 0.0], 3268 [1.0, 0.0], 3269 [0.0, 0.0], 3270 [1.0, 1.0], 3271 [0.0, 1.0], 3272 [0.0, 0.0], 3273 [1.0, 0.0], 3274 [0.0, 1.0], 3275 [1.0, 1.0], 3276 ] 3277 vtc = utils.numpy2vtk(tc) 3278 pd.GetPointData().SetTCoords(vtc) 3279 Mesh.__init__(self, pd, c, alpha) 3280 if len(pos) == 2: 3281 pos = (pos[0], pos[1], 0) 3282 self.SetPosition(pos) 3283 self.name = "Box" 3284 3285 3286class Cube(Box): 3287 """Build a cube.""" 3288 3289 def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0): 3290 """Build a cube of size `side`.""" 3291 Box.__init__(self, pos, side, side, side, (), c, alpha) 3292 self.name = "Cube" 3293 3294 3295class TessellatedBox(Mesh): 3296 """ 3297 Build a cubic `Mesh` made of quads. 3298 """ 3299 3300 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5): 3301 """ 3302 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3303 3304 Arguments: 3305 pos : (list) 3306 position of the left bottom corner 3307 n : (int, list) 3308 number of subdivisions along each side 3309 spacing : (float) 3310 size of the side of the single quad in the 3 directions 3311 """ 3312 if utils.is_sequence(n): # slow 3313 img = vtk.vtkImageData() 3314 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3315 img.SetSpacing(spacing) 3316 gf = vtk.vtkGeometryFilter() 3317 gf.SetInputData(img) 3318 gf.Update() 3319 poly = gf.GetOutput() 3320 else: # fast 3321 n -= 1 3322 tbs = vtk.vtkTessellatedBoxSource() 3323 tbs.SetLevel(n) 3324 if len(bounds): 3325 tbs.SetBounds(bounds) 3326 else: 3327 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3328 tbs.QuadsOn() 3329 tbs.SetOutputPointsPrecision(vtk.vtkAlgorithm.SINGLE_PRECISION) 3330 tbs.Update() 3331 poly = tbs.GetOutput() 3332 Mesh.__init__(self, poly, c=c, alpha=alpha) 3333 self.SetPosition(pos) 3334 self.lw(1).lighting("off") 3335 self.base = np.array([0.5, 0.5, 0.0]) 3336 self.top = np.array([0.5, 0.5, 1.0]) 3337 self.name = "TessellatedBox" 3338 3339 3340class Spring(Mesh): 3341 """ 3342 Build a spring model. 3343 """ 3344 3345 def __init__( 3346 self, 3347 start_pt=(0, 0, 0), 3348 end_pt=(1, 0, 0), 3349 coils=20, 3350 r1=0.1, 3351 r2=None, 3352 thickness=None, 3353 c="gray5", 3354 alpha=1.0, 3355 ): 3356 """ 3357 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3358 3359 Arguments: 3360 coils : (int) 3361 number of coils 3362 r1 : (float) 3363 radius at start point 3364 r2 : (float) 3365 radius at end point 3366 thickness : (float) 3367 thickness of the coil section 3368 """ 3369 diff = end_pt - np.array(start_pt, dtype=float) 3370 length = np.linalg.norm(diff) 3371 if not length: 3372 return 3373 if not r1: 3374 r1 = length / 20 3375 trange = np.linspace(0, length, num=50 * coils) 3376 om = 6.283 * (coils - 0.5) / length 3377 if not r2: 3378 r2 = r1 3379 pts = [] 3380 for t in trange: 3381 f = (length - t) / length 3382 rd = r1 * f + r2 * (1 - f) 3383 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3384 3385 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3386 diff = diff / length 3387 theta = np.arccos(diff[2]) 3388 phi = np.arctan2(diff[1], diff[0]) 3389 sp = Line(pts).polydata(False) 3390 t = vtk.vtkTransform() 3391 t.RotateZ(np.rad2deg(phi)) 3392 t.RotateY(np.rad2deg(theta)) 3393 tf = vtk.vtkTransformPolyDataFilter() 3394 tf.SetInputData(sp) 3395 tf.SetTransform(t) 3396 tf.Update() 3397 tuf = vtk.vtkTubeFilter() 3398 tuf.SetNumberOfSides(12) 3399 tuf.CappingOn() 3400 tuf.SetInputData(tf.GetOutput()) 3401 if not thickness: 3402 thickness = r1 / 10 3403 tuf.SetRadius(thickness) 3404 tuf.Update() 3405 Mesh.__init__(self, tuf.GetOutput(), c, alpha) 3406 self.phong() 3407 self.SetPosition(start_pt) 3408 self.base = np.array(start_pt, dtype=float) 3409 self.top = np.array(end_pt, dtype=float) 3410 self.name = "Spring" 3411 3412 3413class Cylinder(Mesh): 3414 """ 3415 Build a cylinder of specified height and radius. 3416 """ 3417 3418 def __init__( 3419 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), cap=True, res=24, c="teal3", alpha=1.0 3420 ): 3421 """ 3422 Build a cylinder of specified height and radius `r`, centered at `pos`. 3423 3424 If `pos` is a list of 2 points, e.g. `pos=[v1,v2]`, build a cylinder with base 3425 centered at `v1` and top at `v2`. 3426 3427 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3428 """ 3429 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3430 base = np.array(pos[0], dtype=float) 3431 top = np.array(pos[1], dtype=float) 3432 pos = (base + top) / 2 3433 height = np.linalg.norm(top - base) 3434 axis = top - base 3435 axis = utils.versor(axis) 3436 else: 3437 axis = utils.versor(axis) 3438 base = pos - axis * height / 2 3439 top = pos + axis * height / 2 3440 3441 cyl = vtk.vtkCylinderSource() 3442 cyl.SetResolution(res) 3443 cyl.SetRadius(r) 3444 cyl.SetHeight(height) 3445 cyl.SetCapping(cap) 3446 cyl.Update() 3447 3448 theta = np.arccos(axis[2]) 3449 phi = np.arctan2(axis[1], axis[0]) 3450 t = vtk.vtkTransform() 3451 t.PostMultiply() 3452 t.RotateX(90) # put it along Z 3453 t.RotateY(np.rad2deg(theta)) 3454 t.RotateZ(np.rad2deg(phi)) 3455 tf = vtk.vtkTransformPolyDataFilter() 3456 tf.SetInputData(cyl.GetOutput()) 3457 tf.SetTransform(t) 3458 tf.Update() 3459 pd = tf.GetOutput() 3460 3461 Mesh.__init__(self, pd, c, alpha) 3462 self.phong() 3463 self.SetPosition(pos) 3464 self.base = base + pos 3465 self.top = top + pos 3466 self.name = "Cylinder" 3467 3468 3469class Cone(Mesh): 3470 """Build a cone of specified radius and height.""" 3471 3472 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3473 res=48, c="green3", alpha=1.0): 3474 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3475 con = vtk.vtkConeSource() 3476 con.SetResolution(res) 3477 con.SetRadius(r) 3478 con.SetHeight(height) 3479 con.SetDirection(axis) 3480 con.Update() 3481 Mesh.__init__(self, con.GetOutput(), c, alpha) 3482 self.phong() 3483 if len(pos) == 2: 3484 pos = (pos[0], pos[1], 0) 3485 self.SetPosition(pos) 3486 v = utils.versor(axis) * height / 2 3487 self.base = pos - v 3488 self.top = pos + v 3489 self.name = "Cone" 3490 3491 3492class Pyramid(Cone): 3493 """Build a pyramidal shape.""" 3494 3495 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3496 c="green3", alpha=1): 3497 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3498 Cone.__init__(self, pos, s, height, axis, 4, c, alpha) 3499 self.name = "Pyramid" 3500 3501 3502class Torus(Mesh): 3503 """ 3504 Build a toroidal shape. 3505 """ 3506 3507 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0): 3508 """ 3509 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3510 If `quad=True` a quad-mesh is generated. 3511 """ 3512 if utils.is_sequence(res): 3513 res_u, res_v = res 3514 else: 3515 res_u, res_v = 3 * res, res 3516 3517 if quads: 3518 # https://github.com/marcomusy/vedo/issues/710 3519 3520 n = res_v 3521 m = res_u 3522 3523 theta = np.linspace(0, 2.0 * np.pi, n) 3524 phi = np.linspace(0, 2.0 * np.pi, m) 3525 theta, phi = np.meshgrid(theta, phi) 3526 t = r1 + r2 * np.cos(theta) 3527 x = t * np.cos(phi) 3528 y = t * np.sin(phi) 3529 z = r2 * np.sin(theta) 3530 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3531 3532 faces = [] 3533 for j in range(m - 1): 3534 j1n = (j + 1) * n 3535 for i in range(n - 1): 3536 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3537 3538 Mesh.__init__(self, [pts, faces], c, alpha) 3539 3540 else: 3541 3542 rs = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricTorus() 3543 rs.SetRingRadius(r1) 3544 rs.SetCrossSectionRadius(r2) 3545 pfs = vtk.vtkParametricFunctionSource() 3546 pfs.SetParametricFunction(rs) 3547 pfs.SetUResolution(res_u) 3548 pfs.SetVResolution(res_v) 3549 pfs.Update() 3550 Mesh.__init__(self, pfs.GetOutput(), c, alpha) 3551 3552 self.phong() 3553 if len(pos) == 2: 3554 pos = (pos[0], pos[1], 0) 3555 self.SetPosition(pos) 3556 self.name = "Torus" 3557 3558 3559class Paraboloid(Mesh): 3560 """ 3561 Build a paraboloid. 3562 """ 3563 3564 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0): 3565 """ 3566 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3567 3568 Full volumetric expression is: 3569 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3570 3571 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3572 """ 3573 quadric = vtk.vtkQuadric() 3574 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3575 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3576 # + a3*x*y + a4*y*z + a5*x*z 3577 # + a6*x + a7*y + a8*z +a9 3578 sample = vtk.vtkSampleFunction() 3579 sample.SetSampleDimensions(res, res, res) 3580 sample.SetImplicitFunction(quadric) 3581 3582 contours = vtk.vtkContourFilter() 3583 contours.SetInputConnection(sample.GetOutputPort()) 3584 contours.GenerateValues(1, 0.01, 0.01) 3585 contours.Update() 3586 3587 Mesh.__init__(self, contours.GetOutput(), c, alpha) 3588 self.compute_normals().phong() 3589 self.mapper().ScalarVisibilityOff() 3590 self.SetPosition(pos) 3591 self.name = "Paraboloid" 3592 3593 3594class Hyperboloid(Mesh): 3595 """ 3596 Build a hyperboloid. 3597 """ 3598 3599 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0): 3600 """ 3601 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3602 3603 Full volumetric expression is: 3604 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3605 """ 3606 q = vtk.vtkQuadric() 3607 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3608 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3609 # + a3*x*y + a4*y*z + a5*x*z 3610 # + a6*x + a7*y + a8*z +a9 3611 sample = vtk.vtkSampleFunction() 3612 sample.SetSampleDimensions(res, res, res) 3613 sample.SetImplicitFunction(q) 3614 3615 contours = vtk.vtkContourFilter() 3616 contours.SetInputConnection(sample.GetOutputPort()) 3617 contours.GenerateValues(1, value, value) 3618 contours.Update() 3619 3620 Mesh.__init__(self, contours.GetOutput(), c, alpha) 3621 self.compute_normals().phong() 3622 self.mapper().ScalarVisibilityOff() 3623 self.SetPosition(pos) 3624 self.name = "Hyperboloid" 3625 3626 3627def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True): 3628 """ 3629 Generate a marker shape. Typically used in association with `Glyph`. 3630 """ 3631 if isinstance(symbol, Mesh): 3632 return symbol.c(c).alpha(alpha).lighting("off") 3633 3634 if isinstance(symbol, int): 3635 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 3636 symbol = symbol % len(symbs) 3637 symbol = symbs[symbol] 3638 3639 if symbol == ".": 3640 mesh = Polygon(nsides=24, r=s * 0.6) 3641 elif symbol == "o": 3642 mesh = Polygon(nsides=24, r=s * 0.75) 3643 elif symbol == "O": 3644 mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3645 elif symbol == "0": 3646 m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3647 m2 = Circle(r=s * 0.36).reverse() 3648 mesh = merge(m1, m2) 3649 elif symbol == "p": 3650 mesh = Polygon(nsides=5, r=s) 3651 elif symbol == "*": 3652 mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled) 3653 elif symbol == "h": 3654 mesh = Polygon(nsides=6, r=s) 3655 elif symbol == "D": 3656 mesh = Polygon(nsides=4, r=s) 3657 elif symbol == "d": 3658 mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1]) 3659 elif symbol == "v": 3660 mesh = Polygon(nsides=3, r=s).rotate_z(180) 3661 elif symbol == "^": 3662 mesh = Polygon(nsides=3, r=s) 3663 elif symbol == ">": 3664 mesh = Polygon(nsides=3, r=s).rotate_z(-90) 3665 elif symbol == "<": 3666 mesh = Polygon(nsides=3, r=s).rotate_z(90) 3667 elif symbol == "s": 3668 mesh = Mesh( 3669 [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]] 3670 ).scale(s / 1.4) 3671 elif symbol == "x": 3672 mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3673 # mesh.rotate_z(45) 3674 elif symbol == "a": 3675 mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3676 else: 3677 mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0) 3678 mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha) 3679 if len(pos) == 2: 3680 pos = (pos[0], pos[1], 0) 3681 mesh.SetPosition(pos) 3682 mesh.name = "Marker" 3683 return mesh 3684 3685 3686class Brace(Mesh): 3687 """ 3688 Create a brace (bracket) shape. 3689 """ 3690 3691 def __init__( 3692 self, 3693 q1, 3694 q2, 3695 style="}", 3696 padding1=0.0, 3697 font="Theemim", 3698 comment="", 3699 justify=None, 3700 angle=0.0, 3701 padding2=0.2, 3702 s=1.0, 3703 italic=0, 3704 c="k1", 3705 alpha=1.0, 3706 ): 3707 """ 3708 Create a brace (bracket) shape which spans from point q1 to point q2. 3709 3710 Arguments: 3711 q1 : (list) 3712 point 1. 3713 q2 : (list) 3714 point 2. 3715 style : (str) 3716 style of the bracket, eg. `{}, [], (), <>`. 3717 padding1 : (float) 3718 padding space in percent form the input points. 3719 font : (str) 3720 font type 3721 comment : (str) 3722 additional text to appear next to the brace symbol. 3723 justify : (str) 3724 specify the anchor point to justify text comment, e.g. "top-left". 3725 italic : float 3726 italicness of the text comment (can be a positive or negative number) 3727 angle : (float) 3728 rotation angle of text. Use `None` to keep it horizontal. 3729 padding2 : (float) 3730 padding space in percent form brace to text comment. 3731 s : (float) 3732 scale factor for the comment 3733 3734 Examples: 3735 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3736 3737 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3738 """ 3739 if isinstance(q1, vtk.vtkActor): 3740 q1 = q1.GetPosition() 3741 if isinstance(q2, vtk.vtkActor): 3742 q2 = q2.GetPosition() 3743 if len(q1) == 2: 3744 q1 = [q1[0], q1[1], 0.0] 3745 if len(q2) == 2: 3746 q2 = [q2[0], q2[1], 0.0] 3747 q1 = np.array(q1, dtype=float) 3748 q2 = np.array(q2, dtype=float) 3749 mq = (q1 + q2) / 2 3750 q1 = q1 - mq 3751 q2 = q2 - mq 3752 d = np.linalg.norm(q2 - q1) 3753 q2[2] = q1[2] 3754 3755 if style not in "{}[]()<>|I": 3756 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3757 style = "}" 3758 3759 flip = False 3760 if style in ["{", "[", "(", "<"]: 3761 flip = True 3762 i = ["{", "[", "(", "<"].index(style) 3763 style = ["}", "]", ")", ">"][i] 3764 3765 br = Text3D(style, font="Theemim", justify="center-left") 3766 br.scale([0.4, 1, 1]) 3767 3768 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3769 if flip: 3770 angler += 180 3771 3772 _, x1, y0, y1, _, _ = br.bounds() 3773 if comment: 3774 just = "center-top" 3775 if angle is None: 3776 angle = -angler + 90 3777 if not flip: 3778 angle += 180 3779 3780 if flip: 3781 angle += 180 3782 just = "center-bottom" 3783 if justify is not None: 3784 just = justify 3785 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3786 cx0, cx1 = cmt.xbounds() 3787 cmt.rotate_z(90 + angle) 3788 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3789 cmt.shift(x1 * (1 + padding2), 0, 0) 3790 poly = merge(br, cmt).polydata() 3791 3792 else: 3793 poly = br.polydata() 3794 3795 tr = vtk.vtkTransform() 3796 tr.RotateZ(angler) 3797 tr.Translate(padding1 * d, 0, 0) 3798 pscale = 1 3799 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3800 tf = vtk.vtkTransformPolyDataFilter() 3801 tf.SetInputData(poly) 3802 tf.SetTransform(tr) 3803 tf.Update() 3804 poly = tf.GetOutput() 3805 3806 Mesh.__init__(self, poly, c, alpha) 3807 self.SetPosition(mq) 3808 self.name = "Brace" 3809 self.base = q1 3810 self.top = q2 3811 3812 3813class Star3D(Mesh): 3814 """ 3815 Build a 3D starred shape. 3816 """ 3817 3818 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0): 3819 """ 3820 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3821 """ 3822 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3823 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3824 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3825 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3826 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3827 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3828 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3829 [10,1, 0],[10,11, 9]] 3830 3831 Mesh.__init__(self, [pts, fcs], c, alpha) 3832 self.RotateX(90) 3833 self.scale(r).lighting("shiny") 3834 3835 if len(pos) == 2: 3836 pos = (pos[0], pos[1], 0) 3837 self.SetPosition(pos) 3838 self.name = "Star3D" 3839 3840 3841class Cross3D(Mesh): 3842 """ 3843 Build a 3D cross shape. 3844 """ 3845 3846 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0): 3847 """ 3848 Build a 3D cross shape, mainly useful as a 3D marker. 3849 """ 3850 c1 = Cylinder(r=thickness * s, height=2 * s) 3851 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3852 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3853 poly = merge(c1, c2, c3).color(c).alpha(alpha).polydata(False) 3854 Mesh.__init__(self, poly, c, alpha) 3855 3856 if len(pos) == 2: 3857 pos = (pos[0], pos[1], 0) 3858 self.SetPosition(pos) 3859 self.name = "Cross3D" 3860 3861 3862class ParametricShape(Mesh): 3863 """ 3864 A set of built-in shapes mainly for illustration purposes. 3865 """ 3866 3867 def __init__(self, name, res=51, n=25, seed=1): 3868 """ 3869 A set of built-in shapes mainly for illustration purposes. 3870 3871 Name can be an integer or a string in this list: 3872 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3873 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3874 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3875 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3876 3877 Example: 3878 ```python 3879 from vedo import * 3880 settings.immediate_rendering = False 3881 plt = Plotter(N=18) 3882 for i in range(18): 3883 ps = ParametricShape(i).color(i) 3884 plt.at(i).show(ps, ps.name) 3885 plt.interactive().close() 3886 ``` 3887 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3888 """ 3889 3890 shapes = [ 3891 "Boy", 3892 "ConicSpiral", 3893 "CrossCap", 3894 "Enneper", 3895 "Figure8Klein", 3896 "Klein", 3897 "Dini", 3898 "Mobius", 3899 "RandomHills", 3900 "Roman", 3901 "SuperEllipsoid", 3902 "BohemianDome", 3903 "Bour", 3904 "CatalanMinimal", 3905 "Henneberg", 3906 "Kuen", 3907 "PluckerConoid", 3908 "Pseudosphere", 3909 ] 3910 3911 if isinstance(name, int): 3912 name = name % len(shapes) 3913 name = shapes[name] 3914 3915 if name == "Boy": 3916 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBoy() 3917 elif name == "ConicSpiral": 3918 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricConicSpiral() 3919 elif name == "CrossCap": 3920 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCrossCap() 3921 elif name == "Dini": 3922 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricDini() 3923 elif name == "Enneper": 3924 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricEnneper() 3925 elif name == "Figure8Klein": 3926 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricFigure8Klein() 3927 elif name == "Klein": 3928 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKlein() 3929 elif name == "Mobius": 3930 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricMobius() 3931 ps.SetRadius(2.0) 3932 ps.SetMinimumV(-0.5) 3933 ps.SetMaximumV(0.5) 3934 elif name == "RandomHills": 3935 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRandomHills() 3936 ps.AllowRandomGenerationOn() 3937 ps.SetRandomSeed(seed) 3938 ps.SetNumberOfHills(n) 3939 elif name == "Roman": 3940 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRoman() 3941 elif name == "SuperEllipsoid": 3942 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricSuperEllipsoid() 3943 ps.SetN1(0.5) 3944 ps.SetN2(0.4) 3945 elif name == "BohemianDome": 3946 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBohemianDome() 3947 ps.SetA(5.0) 3948 ps.SetB(1.0) 3949 ps.SetC(2.0) 3950 elif name == "Bour": 3951 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBour() 3952 elif name == "CatalanMinimal": 3953 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCatalanMinimal() 3954 elif name == "Henneberg": 3955 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricHenneberg() 3956 elif name == "Kuen": 3957 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKuen() 3958 ps.SetDeltaV0(0.001) 3959 elif name == "PluckerConoid": 3960 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPluckerConoid() 3961 elif name == "Pseudosphere": 3962 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPseudosphere() 3963 else: 3964 vedo.logger.error(f"unknown ParametricShape {name}") 3965 return 3966 3967 pfs = vtk.vtkParametricFunctionSource() 3968 pfs.SetParametricFunction(ps) 3969 pfs.SetUResolution(res) 3970 pfs.SetVResolution(res) 3971 pfs.SetWResolution(res) 3972 pfs.SetScalarModeToZ() 3973 pfs.Update() 3974 3975 Mesh.__init__(self, pfs.GetOutput()) 3976 3977 if name != 'Kuen': self.normalize() 3978 if name == 'Dini': self.scale(0.4) 3979 if name == 'Enneper': self.scale(0.4) 3980 if name == 'ConicSpiral': self.bc('tomato') 3981 self.name = name 3982 3983 3984@lru_cache(None) 3985def _load_font(font): 3986 # print('_load_font()', font) 3987 3988 if utils.is_number(font): 3989 font = list(settings.font_parameters.keys())[int(font)] 3990 3991 if font.endswith(".npz"): # user passed font as a local path 3992 fontfile = font 3993 font = os.path.basename(font).split(".")[0] 3994 3995 elif font.startswith("https"): # user passed URL link, make it a path 3996 try: 3997 fontfile = vedo.file_io.download(font, verbose=False, force=False) 3998 font = os.path.basename(font).split(".")[0] 3999 except: 4000 vedo.logger.warning(f"font {font} not found") 4001 font = settings.default_font 4002 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4003 4004 else: # user passed font by its standard name 4005 font = font[:1].upper() + font[1:] # capitalize first letter only 4006 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4007 4008 if font not in settings.font_parameters.keys(): 4009 font = "Normografo" 4010 vedo.logger.warning( 4011 f"Unknown font: {font}\n" 4012 f"Available 3D fonts are: " 4013 f"{list(settings.font_parameters.keys())}\n" 4014 f"Using font {font} instead." 4015 ) 4016 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4017 4018 if not settings.font_parameters[font]["islocal"]: 4019 font = "https://vedo.embl.es/fonts/" + font + ".npz" 4020 try: 4021 fontfile = vedo.file_io.download(font, verbose=False, force=False) 4022 font = os.path.basename(font).split(".")[0] 4023 except: 4024 vedo.logger.warning(f"font {font} not found") 4025 font = settings.default_font 4026 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4027 4028 ##### 4029 try: 4030 font_meshes = np.load(fontfile, allow_pickle=True)["font"][0] 4031 except: 4032 vedo.logger.warning(f"font name {font} not found.") 4033 raise RuntimeError 4034 return font_meshes 4035 4036 4037@lru_cache(None) 4038def _get_font_letter(font, letter): 4039 # print("_get_font_letter", font, letter) 4040 font_meshes = _load_font(font) 4041 try: 4042 pts, faces = font_meshes[letter] 4043 return utils.buildPolyData(pts, faces) 4044 except KeyError: 4045 return None 4046 4047 4048class Text3D(Mesh): 4049 """ 4050 Generate a 3D polygonal Mesh to represent a text string. 4051 """ 4052 4053 def __init__( 4054 self, 4055 txt, 4056 pos=(0, 0, 0), 4057 s=1.0, 4058 font="", 4059 hspacing=1.15, 4060 vspacing=2.15, 4061 depth=0.0, 4062 italic=False, 4063 justify="bottom-left", 4064 literal=False, 4065 c=None, 4066 alpha=1.0, 4067 ): 4068 """ 4069 Generate a 3D polygonal `Mesh` representing a text string. 4070 4071 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4072 Most Latex symbols are also supported. 4073 4074 Symbols `~ ^ _` are reserved modifiers: 4075 - use ~ to add a short space, 1/4 of the default empty space, 4076 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4077 4078 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4079 4080 More fonts at: https://vedo.embl.es/fonts/ 4081 4082 Arguments: 4083 pos : (list) 4084 position coordinates in 3D space 4085 s : (float) 4086 size of the text 4087 depth : (float) 4088 text thickness (along z) 4089 italic : (bool), float 4090 italic font type (can be a signed float too) 4091 justify : (str) 4092 text justification as centering of the bounding box 4093 (bottom-left, bottom-right, top-left, top-right, centered) 4094 font : (str, int) 4095 some of the available 3D-polygonized fonts are: 4096 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4097 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4098 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4099 4100 Check for more at https://vedo.embl.es/fonts/ 4101 4102 Or type in your terminal `vedo --run fonts`. 4103 4104 Default is Normografo, which can be changed using `settings.default_font`. 4105 4106 hspacing : (float) 4107 horizontal spacing of the font 4108 vspacing : (float) 4109 vertical spacing of the font for multiple lines text 4110 literal : (bool) 4111 if set to True will ignore modifiers like _ or ^ 4112 4113 Examples: 4114 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4115 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4116 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4117 4118 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4119 4120 .. note:: Type `vedo -r fonts` for a demo. 4121 """ 4122 if len(pos) == 2: 4123 pos = (pos[0], pos[1], 0) 4124 4125 if c is None: # automatic black or white 4126 pli = vedo.plotter_instance 4127 if pli and pli.renderer: 4128 c = (0.9, 0.9, 0.9) 4129 if pli.renderer.GetGradientBackground(): 4130 bgcol = pli.renderer.GetBackground2() 4131 else: 4132 bgcol = pli.renderer.GetBackground() 4133 if np.sum(bgcol) > 1.5: 4134 c = (0.1, 0.1, 0.1) 4135 else: 4136 c = (0.6, 0.6, 0.6) 4137 4138 tpoly = self._get_text3d_poly( 4139 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4140 ) 4141 4142 Mesh.__init__(self, tpoly, c, alpha) 4143 self.lighting("off") 4144 self.SetPosition(pos) 4145 self.PickableOff() 4146 self.DragableOff() 4147 self.name = "Text3D" 4148 self.txt = txt 4149 4150 def text( 4151 self, 4152 txt=None, 4153 s=1, 4154 font="", 4155 hspacing=1.15, 4156 vspacing=2.15, 4157 depth=0, 4158 italic=False, 4159 justify="bottom-left", 4160 literal=False, 4161 ): 4162 """ 4163 Update the font style of the text. 4164 Check [available fonts here](https://vedo.embl.es/fonts). 4165 """ 4166 if txt is None: 4167 return self.txt 4168 4169 tpoly = self._get_text3d_poly( 4170 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4171 ) 4172 self._update(tpoly) 4173 self.txt = txt 4174 return self 4175 4176 def _get_text3d_poly( 4177 self, 4178 txt, 4179 s=1, 4180 font="", 4181 hspacing=1.15, 4182 vspacing=2.15, 4183 depth=0, 4184 italic=False, 4185 justify="bottom-left", 4186 literal=False, 4187 ): 4188 if not font: 4189 font = settings.default_font 4190 4191 txt = str(txt) 4192 4193 if font == "VTK": ####################################### 4194 vtt = vtk.vtkVectorText() 4195 vtt.SetText(txt) 4196 vtt.Update() 4197 tpoly = vtt.GetOutput() 4198 4199 else: ################################################### 4200 4201 stxt = set(txt) # check here if null or only spaces 4202 if not txt or (len(stxt) == 1 and " " in stxt): 4203 return vtk.vtkPolyData() 4204 4205 if italic is True: 4206 italic = 1 4207 4208 if isinstance(font, int): 4209 lfonts = list(settings.font_parameters.keys()) 4210 font = font % len(lfonts) 4211 font = lfonts[font] 4212 4213 if font not in settings.font_parameters.keys(): 4214 fpars = settings.font_parameters["Normografo"] 4215 else: 4216 fpars = settings.font_parameters[font] 4217 4218 # ad hoc adjustments 4219 mono = fpars["mono"] 4220 lspacing = fpars["lspacing"] 4221 hspacing *= fpars["hspacing"] 4222 fscale = fpars["fscale"] 4223 dotsep = fpars["dotsep"] 4224 4225 # replacements 4226 if ":" in txt: 4227 for r in _reps: 4228 txt = txt.replace(r[0], r[1]) 4229 4230 if not literal: 4231 reps2 = [ 4232 ("\_", "┭"), # trick to protect ~ _ and ^ chars 4233 ("\^", "┮"), # 4234 ("\~", "┯"), # 4235 ("**", "^"), # order matters 4236 ("e+0", dotsep + "10^"), 4237 ("e-0", dotsep + "10^-"), 4238 ("E+0", dotsep + "10^"), 4239 ("E-0", dotsep + "10^-"), 4240 ("e+", dotsep + "10^"), 4241 ("e-", dotsep + "10^-"), 4242 ("E+", dotsep + "10^"), 4243 ("E-", dotsep + "10^-"), 4244 ] 4245 for r in reps2: 4246 txt = txt.replace(r[0], r[1]) 4247 4248 xmax, ymax, yshift, scale = 0, 0, 0, 1 4249 save_xmax = 0 4250 4251 notfounds = set() 4252 polyletters = [] 4253 ntxt = len(txt) 4254 for i, t in enumerate(txt): 4255 ########## 4256 if t == "┭": 4257 t = "_" 4258 elif t == "┮": 4259 t = "^" 4260 elif t == "┯": 4261 t = "~" 4262 elif t == "^" and not literal: 4263 if yshift < 0: 4264 xmax = save_xmax 4265 yshift = 0.9 * fscale 4266 scale = 0.5 4267 continue 4268 elif t == "_" and not literal: 4269 if yshift > 0: 4270 xmax = save_xmax 4271 yshift = -0.3 * fscale 4272 scale = 0.5 4273 continue 4274 elif (t in (" ", "\\n")) and yshift: 4275 yshift = 0 4276 scale = 1 4277 save_xmax = xmax 4278 if t == " ": 4279 continue 4280 elif t == "~": 4281 if i < ntxt - 1 and txt[i + 1] == "_": 4282 continue 4283 xmax += hspacing * scale * fscale / 4 4284 continue 4285 4286 ############ 4287 if t == " ": 4288 xmax += hspacing * scale * fscale 4289 4290 elif t == "\n": 4291 xmax = 0 4292 save_xmax = 0 4293 ymax -= vspacing 4294 4295 else: 4296 poly = _get_font_letter(font, t) 4297 if not poly: 4298 notfounds.add(t) 4299 xmax += hspacing * scale * fscale 4300 continue 4301 4302 tr = vtk.vtkTransform() 4303 tr.Translate(xmax, ymax + yshift, 0) 4304 pscale = scale * fscale / 1000 4305 tr.Scale(pscale, pscale, pscale) 4306 if italic: 4307 tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 4308 tf = vtk.vtkTransformPolyDataFilter() 4309 tf.SetInputData(poly) 4310 tf.SetTransform(tr) 4311 tf.Update() 4312 poly = tf.GetOutput() 4313 polyletters.append(poly) 4314 4315 bx = poly.GetBounds() 4316 if mono: 4317 xmax += hspacing * scale * fscale 4318 else: 4319 xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing 4320 if yshift == 0: 4321 save_xmax = xmax 4322 4323 if len(polyletters) == 1: 4324 tpoly = polyletters[0] 4325 else: 4326 polyapp = vtk.vtkAppendPolyData() 4327 for polyd in polyletters: 4328 polyapp.AddInputData(polyd) 4329 polyapp.Update() 4330 tpoly = polyapp.GetOutput() 4331 4332 if notfounds: 4333 wmsg = f"These characters are not available in font name {font}: {notfounds}. " 4334 wmsg += 'Type "vedo -r fonts" for a demo.' 4335 vedo.logger.warning(wmsg) 4336 4337 bb = tpoly.GetBounds() 4338 dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s 4339 shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2 4340 if "bottom" in justify: shift += np.array([ 0, dy, 0.]) 4341 if "top" in justify: shift += np.array([ 0,-dy, 0.]) 4342 if "left" in justify: shift += np.array([ dx, 0, 0.]) 4343 if "right" in justify: shift += np.array([-dx, 0, 0.]) 4344 4345 t = vtk.vtkTransform() 4346 t.PostMultiply() 4347 t.Scale(s, s, s) 4348 t.Translate(shift) 4349 tf = vtk.vtkTransformPolyDataFilter() 4350 tf.SetInputData(tpoly) 4351 tf.SetTransform(t) 4352 tf.Update() 4353 tpoly = tf.GetOutput() 4354 4355 if depth: 4356 extrude = vtk.vtkLinearExtrusionFilter() 4357 extrude.SetInputData(tpoly) 4358 extrude.SetExtrusionTypeToVectorExtrusion() 4359 extrude.SetVector(0, 0, 1) 4360 extrude.SetScaleFactor(depth * dy) 4361 extrude.Update() 4362 tpoly = extrude.GetOutput() 4363 4364 return tpoly 4365 4366 4367class TextBase: 4368 "Base class." 4369 4370 def __init__(self): 4371 "Do not instantiate this base class." 4372 4373 self.rendered_at = set() 4374 self.property = None 4375 4376 if isinstance(settings.default_font, int): 4377 lfonts = list(settings.font_parameters.keys()) 4378 font = settings.default_font % len(lfonts) 4379 self.fontname = lfonts[font] 4380 else: 4381 self.fontname = settings.default_font 4382 self.name = "Text" 4383 4384 def angle(self, a): 4385 """Orientation angle in degrees""" 4386 self.property.SetOrientation(a) 4387 return self 4388 4389 def line_spacing(self, ls): 4390 """Set the extra spacing between lines, expressed as a text height multiplication factor.""" 4391 self.property.SetLineSpacing(ls) 4392 return self 4393 4394 def line_offset(self, lo): 4395 """Set/Get the vertical offset (measured in pixels).""" 4396 self.property.SetLineOffset(lo) 4397 return self 4398 4399 def bold(self, value=True): 4400 """Set bold face""" 4401 self.property.SetBold(value) 4402 return self 4403 4404 def italic(self, value=True): 4405 """Set italic face""" 4406 self.property.SetItalic(value) 4407 return self 4408 4409 def shadow(self, offset=(1, -1)): 4410 """Text shadowing. Set to `None` to disable it.""" 4411 if offset is None: 4412 self.property.ShadowOff() 4413 else: 4414 self.property.ShadowOn() 4415 self.property.SetShadowOffset(offset) 4416 return self 4417 4418 def color(self, c=None): 4419 """Set the text color""" 4420 if c is None: 4421 return get_color(self.property.GetColor()) 4422 self.property.SetColor(get_color(c)) 4423 return self 4424 4425 def c(self, color=None): 4426 """Set the text color""" 4427 if color is None: 4428 return get_color(self.property.GetColor()) 4429 return self.color(color) 4430 4431 def alpha(self, value): 4432 """Set the text opacity""" 4433 self.property.SetBackgroundOpacity(value) 4434 return self 4435 4436 def background(self, color="k9", alpha=1.0): 4437 """Text background. Set to `None` to disable it.""" 4438 bg = get_color(color) 4439 if color is None: 4440 self.property.SetBackgroundOpacity(0) 4441 else: 4442 self.property.SetBackgroundColor(bg) 4443 if alpha: 4444 self.property.SetBackgroundOpacity(alpha) 4445 return self 4446 4447 def frame(self, color="k1", lw=2): 4448 """Border color and width""" 4449 if color is None: 4450 self.property.FrameOff() 4451 else: 4452 c = get_color(color) 4453 self.property.FrameOn() 4454 self.property.SetFrameColor(c) 4455 self.property.SetFrameWidth(lw) 4456 return self 4457 4458 def font(self, font): 4459 """Text font face""" 4460 if isinstance(font, int): 4461 lfonts = list(settings.font_parameters.keys()) 4462 n = font % len(lfonts) 4463 font = lfonts[n] 4464 self.fontname = font 4465 4466 if not font: # use default font 4467 font = self.fontname 4468 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4469 elif font.startswith("https"): # user passed URL link, make it a path 4470 fpath = vedo.file_io.download(font, verbose=False, force=False) 4471 elif font.endswith(".ttf"): # user passing a local path to font file 4472 fpath = font 4473 else: # user passing name of preset font 4474 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4475 4476 if font == "Courier": self.property.SetFontFamilyToCourier() 4477 elif font == "Times": self.property.SetFontFamilyToTimes() 4478 elif font == "Arial": self.property.SetFontFamilyToArial() 4479 else: 4480 fpath = utils.get_font_path(font) 4481 self.property.SetFontFamily(vtk.VTK_FONT_FILE) 4482 self.property.SetFontFile(fpath) 4483 self.fontname = font # io.tonumpy() uses it 4484 4485 return self 4486 4487 4488class Text2D(TextBase, vtk.vtkActor2D): 4489 """ 4490 Create a 2D text object. 4491 """ 4492 def __init__( 4493 self, 4494 txt="", 4495 pos="top-left", 4496 s=1.0, 4497 bg=None, 4498 font="", 4499 justify="", 4500 bold=False, 4501 italic=False, 4502 c=None, 4503 alpha=0.2, 4504 ): 4505 """ 4506 Create a 2D text object. 4507 4508 All properties of the text, and the text itself, can be changed after creation 4509 (which is especially useful in loops). 4510 4511 Arguments: 4512 pos : (str) 4513 text is placed in one of the 8 positions: 4514 - bottom-left 4515 - bottom-right 4516 - top-left 4517 - top-right 4518 - bottom-middle 4519 - middle-right 4520 - middle-left 4521 - top-middle 4522 4523 If a pair (x,y) is passed as input the 2D text is place at that 4524 position in the coordinate system of the 2D screen (with the 4525 origin sitting at the bottom left). 4526 4527 s : (float) 4528 size of text 4529 bg : (color) 4530 background color 4531 alpha : (float) 4532 background opacity 4533 justify : (str) 4534 text justification 4535 4536 font : (str) 4537 built-in available fonts are: 4538 - Arial 4539 - Bongas 4540 - Calco 4541 - Comae 4542 - ComicMono 4543 - Courier 4544 - Glasgo 4545 - Kanopus 4546 - LogoType 4547 - Normografo 4548 - Quikhand 4549 - SmartCouric 4550 - Theemim 4551 - Times 4552 - VictorMono 4553 - More fonts at: https://vedo.embl.es/fonts/ 4554 4555 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4556 4557 Examples: 4558 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4559 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4560 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4561 4562 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4563 """ 4564 vtk.vtkActor2D.__init__(self) 4565 TextBase.__init__(self) 4566 4567 self._mapper = vtk.vtkTextMapper() 4568 self.SetMapper(self._mapper) 4569 4570 self.property = self._mapper.GetTextProperty() 4571 4572 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4573 4574 # automatic black or white 4575 if c is None: 4576 c = (0.1, 0.1, 0.1) 4577 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4578 if vedo.plotter_instance.renderer.GetGradientBackground(): 4579 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4580 else: 4581 bgcol = vedo.plotter_instance.renderer.GetBackground() 4582 c = (0.9, 0.9, 0.9) 4583 if np.sum(bgcol) > 1.5: 4584 c = (0.1, 0.1, 0.1) 4585 4586 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4587 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4588 self.PickableOff() 4589 4590 def pos(self, pos="top-left", justify=""): 4591 """ 4592 Set position of the text to draw. Keyword `pos` can be a string 4593 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4594 """ 4595 ajustify = "top-left" # autojustify 4596 if isinstance(pos, str): # corners 4597 ajustify = pos 4598 if "top" in pos: 4599 if "left" in pos: 4600 pos = (0.008, 0.994) 4601 elif "right" in pos: 4602 pos = (0.994, 0.994) 4603 elif "mid" in pos or "cent" in pos: 4604 pos = (0.5, 0.994) 4605 elif "bottom" in pos: 4606 if "left" in pos: 4607 pos = (0.008, 0.008) 4608 elif "right" in pos: 4609 pos = (0.994, 0.008) 4610 elif "mid" in pos or "cent" in pos: 4611 pos = (0.5, 0.008) 4612 elif "mid" in pos or "cent" in pos: 4613 if "left" in pos: 4614 pos = (0.008, 0.5) 4615 elif "right" in pos: 4616 pos = (0.994, 0.5) 4617 else: 4618 pos = (0.5, 0.5) 4619 4620 else: 4621 vedo.logger.warning(f"cannot understand text position {pos}") 4622 pos = (0.008, 0.994) 4623 ajustify = "top-left" 4624 4625 elif len(pos) != 2: 4626 vedo.logger.error("pos must be of length 2 or integer value or string") 4627 raise RuntimeError() 4628 4629 if not justify: 4630 justify = ajustify 4631 4632 self.property.SetJustificationToLeft() 4633 if "top" in justify: 4634 self.property.SetVerticalJustificationToTop() 4635 if "bottom" in justify: 4636 self.property.SetVerticalJustificationToBottom() 4637 if "cent" in justify or "mid" in justify: 4638 self.property.SetJustificationToCentered() 4639 if "left" in justify: 4640 self.property.SetJustificationToLeft() 4641 if "right" in justify: 4642 self.property.SetJustificationToRight() 4643 4644 self.SetPosition(pos) 4645 return self 4646 4647 def text(self, txt=None): 4648 """Set/get the input text string.""" 4649 if txt is None: 4650 return self._mapper.GetInput() 4651 4652 if ":" in txt: 4653 for r in _reps: 4654 txt = txt.replace(r[0], r[1]) 4655 else: 4656 txt = str(txt) 4657 4658 self._mapper.SetInput(txt) 4659 return self 4660 4661 def size(self, s): 4662 """Set the font size.""" 4663 self.property.SetFontSize(int(s * 22.5)) 4664 return self 4665 4666 4667class CornerAnnotation(vtk.vtkCornerAnnotation, TextBase): 4668 # PROBABLY USELESS given that Text2D does pretty much the same ... 4669 """ 4670 Annotate the window corner with 2D text. 4671 4672 See `Text2D` description as the basic functionality is very similar. 4673 4674 The added value of this class is the possibility to manage with one single 4675 object the all corner annotations (instead of creating 4 `Text2D` instances). 4676 4677 Examples: 4678 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 4679 """ 4680 4681 def __init__(self, c=None): 4682 vtk.vtkCornerAnnotation.__init__(self) 4683 TextBase.__init__(self) 4684 4685 self.property = self.GetTextProperty() 4686 4687 # automatic black or white 4688 if c is None: 4689 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4690 c = (0.9, 0.9, 0.9) 4691 if vedo.plotter_instance.renderer.GetGradientBackground(): 4692 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4693 else: 4694 bgcol = vedo.plotter_instance.renderer.GetBackground() 4695 if np.sum(bgcol) > 1.5: 4696 c = (0.1, 0.1, 0.1) 4697 else: 4698 c = (0.5, 0.5, 0.5) 4699 4700 self.SetNonlinearFontScaleFactor(1 / 2.75) 4701 self.PickableOff() 4702 self.property.SetColor(get_color(c)) 4703 self.property.SetBold(False) 4704 self.property.SetItalic(False) 4705 4706 def size(self, s, linear=False): 4707 """ 4708 The font size is calculated as the largest possible value such that the annotations 4709 for the given viewport do not overlap. 4710 4711 This font size can be scaled non-linearly with the viewport size, to maintain an 4712 acceptable readable size at larger viewport sizes, without being too big. 4713 `f' = linearScale * pow(f,nonlinearScale)` 4714 """ 4715 if linear: 4716 self.SetLinearFontScaleFactor(s * 5.5) 4717 else: 4718 self.SetNonlinearFontScaleFactor(s / 2.75) 4719 return self 4720 4721 def text(self, txt, pos=2): 4722 """Set text at the assigned position""" 4723 4724 if isinstance(pos, str): # corners 4725 if "top" in pos: 4726 if "left" in pos: pos = 2 4727 elif "right" in pos: pos = 3 4728 elif "mid" in pos or "cent" in pos: pos = 7 4729 elif "bottom" in pos: 4730 if "left" in pos: pos = 0 4731 elif "right" in pos: pos = 1 4732 elif "mid" in pos or "cent" in pos: pos = 4 4733 else: 4734 if "left" in pos: pos = 6 4735 elif "right" in pos: pos = 5 4736 else: pos = 2 4737 4738 if "\\" in repr(txt): 4739 for r in _reps: 4740 txt = txt.replace(r[0], r[1]) 4741 else: 4742 txt = str(txt) 4743 4744 self.SetText(pos, txt) 4745 return self 4746 4747 def clear(self): 4748 """Remove all text from all corners""" 4749 self.ClearAllTexts() 4750 return self 4751 4752 4753class Latex(Picture): 4754 """ 4755 Render Latex text and formulas. 4756 """ 4757 4758 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0): 4759 """ 4760 Render Latex text and formulas. 4761 4762 Arguments: 4763 formula : (str) 4764 latex text string 4765 pos : (list) 4766 position coordinates in space 4767 bg : (color) 4768 background color box 4769 res : (int) 4770 dpi resolution 4771 usetex : (bool) 4772 use latex compiler of matplotlib if available 4773 4774 You can access the latex formula in `Latex.formula`. 4775 4776 Examples: 4777 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4778 4779 ![](https://vedo.embl.es/images/pyplot/latex.png) 4780 """ 4781 self.formula = formula 4782 4783 try: 4784 from tempfile import NamedTemporaryFile 4785 import matplotlib.pyplot as mpltib 4786 4787 def build_img_plt(formula, tfile): 4788 4789 mpltib.rc("text", usetex=usetex) 4790 4791 formula1 = "$" + formula + "$" 4792 mpltib.axis("off") 4793 col = get_color(c) 4794 if bg: 4795 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4796 else: 4797 bx = None 4798 mpltib.text( 4799 0.5, 4800 0.5, 4801 formula1, 4802 size=res, 4803 color=col, 4804 alpha=alpha, 4805 ha="center", 4806 va="center", 4807 bbox=bx, 4808 ) 4809 mpltib.savefig( 4810 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4811 ) 4812 mpltib.close() 4813 4814 if len(pos) == 2: 4815 pos = (pos[0], pos[1], 0) 4816 4817 tmp_file = NamedTemporaryFile(delete=True) 4818 tmp_file.name = tmp_file.name + ".png" 4819 4820 build_img_plt(formula, tmp_file.name) 4821 4822 Picture.__init__(self, tmp_file.name, channels=4) 4823 self.alpha(alpha) 4824 self.SetScale(0.25 / res * s, 0.25 / res * s, 0.25 / res * s) 4825 self.SetPosition(pos) 4826 self.name = "Latex" 4827 4828 except: 4829 printc("Error in Latex()\n", formula, c="r") 4830 printc(" latex or dvipng not installed?", c="r") 4831 printc(" Try: usetex=False", c="r") 4832 printc(" Try: sudo apt install dvipng", c="r") 4833 4834 4835class ConvexHull(Mesh): 4836 """ 4837 Create the 2D/3D convex hull from a set of points. 4838 """ 4839 4840 def __init__(self, pts): 4841 """ 4842 Create the 2D/3D convex hull from a set of input points or input Mesh. 4843 4844 Examples: 4845 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4846 4847 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4848 """ 4849 if utils.is_sequence(pts): 4850 pts = utils.make3d(pts).astype(float) 4851 mesh = Points(pts) 4852 else: 4853 mesh = pts 4854 apoly = mesh.clean().polydata() 4855 4856 # Create the convex hull of the pointcloud 4857 z0, z1 = mesh.zbounds() 4858 d = mesh.diagonal_size() 4859 if (z1 - z0) / d > 0.0001: 4860 delaunay = vtk.vtkDelaunay3D() 4861 delaunay.SetInputData(apoly) 4862 delaunay.Update() 4863 surfaceFilter = vtk.vtkDataSetSurfaceFilter() 4864 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4865 surfaceFilter.Update() 4866 out = surfaceFilter.GetOutput() 4867 else: 4868 delaunay = vtk.vtkDelaunay2D() 4869 delaunay.SetInputData(apoly) 4870 delaunay.Update() 4871 fe = vtk.vtkFeatureEdges() 4872 fe.SetInputConnection(delaunay.GetOutputPort()) 4873 fe.BoundaryEdgesOn() 4874 fe.Update() 4875 out = fe.GetOutput() 4876 4877 Mesh.__init__(self, out, c=mesh.color(), alpha=0.75) 4878 # self.triangulate() 4879 self.flat() 4880 self.name = "ConvexHull" 4881 4882 4883def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True): 4884 """ 4885 Create the 3D vedo logo. 4886 4887 Arguments: 4888 distance : (float) 4889 send back logo by this distance from camera 4890 version : (bool) 4891 add version text to the right end of the logo 4892 bc : (color) 4893 text back face color 4894 """ 4895 if c is None: 4896 c = (0, 0, 0) 4897 if vedo.plotter_instance: 4898 if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5: 4899 c = [0, 0, 0] 4900 else: 4901 c = "linen" 4902 4903 font = "Comae" 4904 vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8) 4905 vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc) 4906 vlogo.GetProperty().LightingOn() 4907 4908 vr, rul = None, None 4909 if version: 4910 vr = Text3D( 4911 vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1 4912 ).scale([1, 0.7, 1]) 4913 vr.RotateZ(90) 4914 vr.pos(2450, 50, 80).bc(bc).pickable(False) 4915 elif frame: 4916 rul = vedo.RulerAxes( 4917 (-2600, 2110, 0, 1650, 0, 0), 4918 xlabel="European Molecular Biology Laboratory", 4919 ylabel=vedo.__version__, 4920 font=font, 4921 xpadding=0.09, 4922 ypadding=0.04, 4923 ) 4924 fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0) 4925 return vedo.Assembly([vlogo, vr, fakept, rul]).scale(1 / 1725)
3628def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True): 3629 """ 3630 Generate a marker shape. Typically used in association with `Glyph`. 3631 """ 3632 if isinstance(symbol, Mesh): 3633 return symbol.c(c).alpha(alpha).lighting("off") 3634 3635 if isinstance(symbol, int): 3636 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 3637 symbol = symbol % len(symbs) 3638 symbol = symbs[symbol] 3639 3640 if symbol == ".": 3641 mesh = Polygon(nsides=24, r=s * 0.6) 3642 elif symbol == "o": 3643 mesh = Polygon(nsides=24, r=s * 0.75) 3644 elif symbol == "O": 3645 mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3646 elif symbol == "0": 3647 m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3648 m2 = Circle(r=s * 0.36).reverse() 3649 mesh = merge(m1, m2) 3650 elif symbol == "p": 3651 mesh = Polygon(nsides=5, r=s) 3652 elif symbol == "*": 3653 mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled) 3654 elif symbol == "h": 3655 mesh = Polygon(nsides=6, r=s) 3656 elif symbol == "D": 3657 mesh = Polygon(nsides=4, r=s) 3658 elif symbol == "d": 3659 mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1]) 3660 elif symbol == "v": 3661 mesh = Polygon(nsides=3, r=s).rotate_z(180) 3662 elif symbol == "^": 3663 mesh = Polygon(nsides=3, r=s) 3664 elif symbol == ">": 3665 mesh = Polygon(nsides=3, r=s).rotate_z(-90) 3666 elif symbol == "<": 3667 mesh = Polygon(nsides=3, r=s).rotate_z(90) 3668 elif symbol == "s": 3669 mesh = Mesh( 3670 [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]] 3671 ).scale(s / 1.4) 3672 elif symbol == "x": 3673 mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3674 # mesh.rotate_z(45) 3675 elif symbol == "a": 3676 mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3677 else: 3678 mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0) 3679 mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha) 3680 if len(pos) == 2: 3681 pos = (pos[0], pos[1], 0) 3682 mesh.SetPosition(pos) 3683 mesh.name = "Marker" 3684 return mesh
Generate a marker shape. Typically used in association with Glyph
.
416class Line(Mesh): 417 """ 418 Build the line segment between points `p0` and `p1`. 419 420 If `p0` is already a list of points, return the line connecting them. 421 422 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 423 """ 424 425 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0): 426 """ 427 Arguments: 428 closed : (bool) 429 join last to first point 430 res : (int) 431 resolution, number of points along the line 432 (only relevant if only 2 points are specified) 433 lw : (int) 434 line width in pixel units 435 c : (color), int, str, list 436 color name, number, or list of [R,G,B] colors 437 alpha : (float) 438 opacity in range [0,1] 439 """ 440 self.slope = [] # populated by analysis.fitLine 441 self.center = [] 442 self.variances = [] 443 444 self.coefficients = [] # populated by pyplot.fit() 445 self.covariance_matrix = [] 446 self.coefficients = [] 447 self.coefficient_errors = [] 448 self.monte_carlo_coefficients = [] 449 self.reduced_chi2 = -1 450 self.ndof = 0 451 self.data_sigma = 0 452 self.error_lines = [] 453 self.error_band = None 454 self.res = res 455 456 if isinstance(p1, Points): 457 p1 = p1.GetPosition() 458 if isinstance(p0, Points): 459 p0 = p0.GetPosition() 460 if isinstance(p0, Points): 461 p0 = p0.points() 462 463 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 464 if len(p0) > 3: 465 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 466 # assume input is 2D xlist, ylist 467 p0 = np.stack((p0, p1), axis=1) 468 p1 = None 469 p0 = utils.make3d(p0) 470 471 # detect if user is passing a list of points: 472 if utils.is_sequence(p0[0]): 473 p0 = utils.make3d(p0) 474 475 ppoints = vtk.vtkPoints() # Generate the polyline 476 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 477 lines = vtk.vtkCellArray() 478 npt = len(p0) 479 if closed: 480 lines.InsertNextCell(npt + 1) 481 else: 482 lines.InsertNextCell(npt) 483 for i in range(npt): 484 lines.InsertCellPoint(i) 485 if closed: 486 lines.InsertCellPoint(0) 487 poly = vtk.vtkPolyData() 488 poly.SetPoints(ppoints) 489 poly.SetLines(lines) 490 top = p0[-1] 491 base = p0[0] 492 self.res = 2 493 494 else: # or just 2 points to link 495 496 line_source = vtk.vtkLineSource() 497 p0 = utils.make3d(p0) 498 p1 = utils.make3d(p1) 499 line_source.SetPoint1(p0) 500 line_source.SetPoint2(p1) 501 line_source.SetResolution(res - 1) 502 line_source.Update() 503 poly = line_source.GetOutput() 504 top = np.asarray(p1, dtype=float) 505 base = np.asarray(p0, dtype=float) 506 507 Mesh.__init__(self, poly, c, alpha) 508 self.lw(lw) 509 self.property.LightingOff() 510 self.PickableOff() 511 self.DragableOff() 512 self.base = base 513 self.top = top 514 self.name = "Line" 515 516 def linecolor(self, lc=None): 517 """Assign a color to the line""" 518 # overrides mesh.linecolor which would have no effect here 519 return self.color(lc) 520 521 def eval(self, x): 522 """ 523 Calculate the position of an intermediate point 524 as a fraction of the length of the line, 525 being x=0 the first point and x=1 the last point. 526 This corresponds to an imaginary point that travels along the line 527 at constant speed. 528 529 Can be used in conjunction with `lin_interpolate()` 530 to map any range to the [0,1] range. 531 """ 532 distance1 = 0.0 533 length = self.length() 534 pts = self.points() 535 for i in range(1, len(pts)): 536 p0 = pts[i - 1] 537 p1 = pts[i] 538 seg = p1 - p0 539 distance0 = distance1 540 distance1 += np.linalg.norm(seg) 541 w1 = distance1 / length 542 if w1 >= x: 543 break 544 w0 = distance0 / length 545 v = p0 + seg * (x - w0) / (w1 - w0) 546 return v 547 548 def pattern(self, stipple, repeats=10): 549 """ 550 Define a stipple pattern for dashing the line. 551 Pass the stipple pattern as a string like `'- - -'`. 552 Repeats controls the number of times the pattern repeats in a single segment. 553 554 Examples are: `'- -', '-- - --'`, etc. 555 556 The resolution of the line (nr of points) can affect how pattern will show up. 557 558 Example: 559 ```python 560 from vedo import Line 561 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 562 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 563 ln.show(axes=1).close() 564 ``` 565 ![](https://vedo.embl.es/images/feats/line_pattern.png) 566 """ 567 stipple = str(stipple) * int(2 * repeats) 568 dimension = len(stipple) 569 570 image = vtk.vtkImageData() 571 image.SetDimensions(dimension, 1, 1) 572 image.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 4) 573 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 574 i_dim = 0 575 while i_dim < dimension: 576 for i in range(dimension): 577 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 578 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 579 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 580 if stipple[i] == " ": 581 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 582 else: 583 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 584 i_dim += 1 585 586 polyData = self.polydata(False) 587 588 # Create texture coordinates 589 tcoords = vtk.vtkDoubleArray() 590 tcoords.SetName("TCoordsStippledLine") 591 tcoords.SetNumberOfComponents(1) 592 tcoords.SetNumberOfTuples(polyData.GetNumberOfPoints()) 593 for i in range(polyData.GetNumberOfPoints()): 594 tcoords.SetTypedTuple(i, [i / 2]) 595 polyData.GetPointData().SetTCoords(tcoords) 596 polyData.GetPointData().Modified() 597 texture = vtk.vtkTexture() 598 texture.SetInputData(image) 599 texture.InterpolateOff() 600 texture.RepeatOn() 601 self.SetTexture(texture) 602 return self 603 604 def length(self): 605 """Calculate length of the line.""" 606 distance = 0.0 607 pts = self.points() 608 for i in range(1, len(pts)): 609 distance += np.linalg.norm(pts[i] - pts[i - 1]) 610 return distance 611 612 def tangents(self): 613 """ 614 Compute the tangents of a line in space. 615 616 Example: 617 ```python 618 from vedo import * 619 shape = load(dataurl+"timecourse1d.npy")[58] 620 pts = shape.rotate_x(30).points() 621 tangents = Line(pts).tangents() 622 arrs = Arrows(pts, pts+tangents, c='blue9') 623 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 624 ``` 625 ![](https://vedo.embl.es/images/feats/line_tangents.png) 626 """ 627 v = np.gradient(self.points())[0] 628 ds_dt = np.linalg.norm(v, axis=1) 629 tangent = np.array([1 / ds_dt] * 3).transpose() * v 630 return tangent 631 632 def curvature(self): 633 """ 634 Compute the signed curvature of a line in space. 635 The signed is computed assuming the line is about coplanar to the xy plane. 636 637 Example: 638 ```python 639 from vedo import * 640 from vedo.pyplot import plot 641 shape = load(dataurl+"timecourse1d.npy")[55] 642 curvs = Line(shape.points()).curvature() 643 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 644 shape.render_lines_as_tubes().lw(12) 645 pp = plot(curvs, ac='white', lc='yellow5') 646 show(shape, pp, N=2, bg='bb', sharecam=False).close() 647 ``` 648 ![](https://vedo.embl.es/images/feats/line_curvature.png) 649 """ 650 v = np.gradient(self.points())[0] 651 a = np.gradient(v)[0] 652 av = np.cross(a, v) 653 mav = np.linalg.norm(av, axis=1) 654 mv = utils.mag2(v) 655 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 656 val[0] = val[1] 657 val[-1] = val[-2] 658 return val 659 660 def compute_curvature(self, method=0): 661 """ 662 Add a pointdata array named 'Curvatures' which contains 663 the curvature value at each point. 664 665 Keyword method is overridden in Mesh and has no effect here. 666 """ 667 # overrides mesh.compute_curvature 668 curvs = self.curvature() 669 vmin, vmax = np.min(curvs), np.max(curvs) 670 if vmin < 0 and vmax > 0: 671 v = max(-vmin, vmax) 672 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 673 else: 674 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 675 return self 676 677 def sweep(self, direction=(1, 0, 0), res=1): 678 """ 679 Sweep the `Line` along the specified vector direction. 680 681 Returns a `Mesh` surface. 682 Line position is updated to allow for additional sweepings. 683 684 Example: 685 ```python 686 from vedo import Line, show 687 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 688 surf1 = aline.sweep((1,0.2,0), res=3) 689 surf2 = aline.sweep((0.2,0,1)) 690 aline.color('r').linewidth(4) 691 show(surf1, surf2, aline, axes=1).close() 692 ``` 693 ![](https://vedo.embl.es/images/feats/sweepline.png) 694 """ 695 line = self.polydata() 696 rows = line.GetNumberOfPoints() 697 698 spacing = 1 / res 699 surface = vtk.vtkPolyData() 700 701 res += 1 702 npts = rows * res 703 npolys = (rows - 1) * (res - 1) 704 points = vtk.vtkPoints() 705 points.Allocate(npts) 706 707 cnt = 0 708 x = [0.0, 0.0, 0.0] 709 for row in range(rows): 710 for col in range(res): 711 p = [0.0, 0.0, 0.0] 712 line.GetPoint(row, p) 713 x[0] = p[0] + direction[0] * col * spacing 714 x[1] = p[1] + direction[1] * col * spacing 715 x[2] = p[2] + direction[2] * col * spacing 716 points.InsertPoint(cnt, x) 717 cnt += 1 718 719 # Generate the quads 720 polys = vtk.vtkCellArray() 721 polys.Allocate(npolys * 4) 722 pts = [0, 0, 0, 0] 723 for row in range(rows - 1): 724 for col in range(res - 1): 725 pts[0] = col + row * res 726 pts[1] = pts[0] + 1 727 pts[2] = pts[0] + res + 1 728 pts[3] = pts[0] + res 729 polys.InsertNextCell(4, pts) 730 surface.SetPoints(points) 731 surface.SetPolys(polys) 732 asurface = vedo.Mesh(surface) 733 prop = vtk.vtkProperty() 734 prop.DeepCopy(self.GetProperty()) 735 asurface.SetProperty(prop) 736 asurface.property = prop 737 asurface.lighting("default") 738 self.points(self.points() + direction) 739 return asurface 740 741 def reverse(self): 742 """Reverse the points sequence order.""" 743 pts = np.flip(self.points(), axis=0) 744 self.points(pts) 745 return self
Build the line segment between points p0
and p1
.
If p0
is already a list of points, return the line connecting them.
A 2D set of coords can also be passed as p0=[x..], p1=[y..]
.
425 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0): 426 """ 427 Arguments: 428 closed : (bool) 429 join last to first point 430 res : (int) 431 resolution, number of points along the line 432 (only relevant if only 2 points are specified) 433 lw : (int) 434 line width in pixel units 435 c : (color), int, str, list 436 color name, number, or list of [R,G,B] colors 437 alpha : (float) 438 opacity in range [0,1] 439 """ 440 self.slope = [] # populated by analysis.fitLine 441 self.center = [] 442 self.variances = [] 443 444 self.coefficients = [] # populated by pyplot.fit() 445 self.covariance_matrix = [] 446 self.coefficients = [] 447 self.coefficient_errors = [] 448 self.monte_carlo_coefficients = [] 449 self.reduced_chi2 = -1 450 self.ndof = 0 451 self.data_sigma = 0 452 self.error_lines = [] 453 self.error_band = None 454 self.res = res 455 456 if isinstance(p1, Points): 457 p1 = p1.GetPosition() 458 if isinstance(p0, Points): 459 p0 = p0.GetPosition() 460 if isinstance(p0, Points): 461 p0 = p0.points() 462 463 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 464 if len(p0) > 3: 465 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 466 # assume input is 2D xlist, ylist 467 p0 = np.stack((p0, p1), axis=1) 468 p1 = None 469 p0 = utils.make3d(p0) 470 471 # detect if user is passing a list of points: 472 if utils.is_sequence(p0[0]): 473 p0 = utils.make3d(p0) 474 475 ppoints = vtk.vtkPoints() # Generate the polyline 476 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 477 lines = vtk.vtkCellArray() 478 npt = len(p0) 479 if closed: 480 lines.InsertNextCell(npt + 1) 481 else: 482 lines.InsertNextCell(npt) 483 for i in range(npt): 484 lines.InsertCellPoint(i) 485 if closed: 486 lines.InsertCellPoint(0) 487 poly = vtk.vtkPolyData() 488 poly.SetPoints(ppoints) 489 poly.SetLines(lines) 490 top = p0[-1] 491 base = p0[0] 492 self.res = 2 493 494 else: # or just 2 points to link 495 496 line_source = vtk.vtkLineSource() 497 p0 = utils.make3d(p0) 498 p1 = utils.make3d(p1) 499 line_source.SetPoint1(p0) 500 line_source.SetPoint2(p1) 501 line_source.SetResolution(res - 1) 502 line_source.Update() 503 poly = line_source.GetOutput() 504 top = np.asarray(p1, dtype=float) 505 base = np.asarray(p0, dtype=float) 506 507 Mesh.__init__(self, poly, c, alpha) 508 self.lw(lw) 509 self.property.LightingOff() 510 self.PickableOff() 511 self.DragableOff() 512 self.base = base 513 self.top = top 514 self.name = "Line"
Arguments:
- closed : (bool) join last to first point
- res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
- lw : (int) line width in pixel units
- c : (color), int, str, list color name, number, or list of [R,G,B] colors
- alpha : (float) opacity in range [0,1]
516 def linecolor(self, lc=None): 517 """Assign a color to the line""" 518 # overrides mesh.linecolor which would have no effect here 519 return self.color(lc)
Assign a color to the line
521 def eval(self, x): 522 """ 523 Calculate the position of an intermediate point 524 as a fraction of the length of the line, 525 being x=0 the first point and x=1 the last point. 526 This corresponds to an imaginary point that travels along the line 527 at constant speed. 528 529 Can be used in conjunction with `lin_interpolate()` 530 to map any range to the [0,1] range. 531 """ 532 distance1 = 0.0 533 length = self.length() 534 pts = self.points() 535 for i in range(1, len(pts)): 536 p0 = pts[i - 1] 537 p1 = pts[i] 538 seg = p1 - p0 539 distance0 = distance1 540 distance1 += np.linalg.norm(seg) 541 w1 = distance1 / length 542 if w1 >= x: 543 break 544 w0 = distance0 / length 545 v = p0 + seg * (x - w0) / (w1 - w0) 546 return v
Calculate the position of an intermediate point as a fraction of the length of the line, being x=0 the first point and x=1 the last point. This corresponds to an imaginary point that travels along the line at constant speed.
Can be used in conjunction with lin_interpolate()
to map any range to the [0,1] range.
548 def pattern(self, stipple, repeats=10): 549 """ 550 Define a stipple pattern for dashing the line. 551 Pass the stipple pattern as a string like `'- - -'`. 552 Repeats controls the number of times the pattern repeats in a single segment. 553 554 Examples are: `'- -', '-- - --'`, etc. 555 556 The resolution of the line (nr of points) can affect how pattern will show up. 557 558 Example: 559 ```python 560 from vedo import Line 561 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 562 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 563 ln.show(axes=1).close() 564 ``` 565 ![](https://vedo.embl.es/images/feats/line_pattern.png) 566 """ 567 stipple = str(stipple) * int(2 * repeats) 568 dimension = len(stipple) 569 570 image = vtk.vtkImageData() 571 image.SetDimensions(dimension, 1, 1) 572 image.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 4) 573 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 574 i_dim = 0 575 while i_dim < dimension: 576 for i in range(dimension): 577 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 578 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 579 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 580 if stipple[i] == " ": 581 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 582 else: 583 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 584 i_dim += 1 585 586 polyData = self.polydata(False) 587 588 # Create texture coordinates 589 tcoords = vtk.vtkDoubleArray() 590 tcoords.SetName("TCoordsStippledLine") 591 tcoords.SetNumberOfComponents(1) 592 tcoords.SetNumberOfTuples(polyData.GetNumberOfPoints()) 593 for i in range(polyData.GetNumberOfPoints()): 594 tcoords.SetTypedTuple(i, [i / 2]) 595 polyData.GetPointData().SetTCoords(tcoords) 596 polyData.GetPointData().Modified() 597 texture = vtk.vtkTexture() 598 texture.SetInputData(image) 599 texture.InterpolateOff() 600 texture.RepeatOn() 601 self.SetTexture(texture) 602 return self
Define a stipple pattern for dashing the line.
Pass the stipple pattern as a string like '- - -'
.
Repeats controls the number of times the pattern repeats in a single segment.
Examples are: '- -', '-- - --'
, etc.
The resolution of the line (nr of points) can affect how pattern will show up.
Example:
from vedo import Line pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) ln.show(axes=1).close()
604 def length(self): 605 """Calculate length of the line.""" 606 distance = 0.0 607 pts = self.points() 608 for i in range(1, len(pts)): 609 distance += np.linalg.norm(pts[i] - pts[i - 1]) 610 return distance
Calculate length of the line.
612 def tangents(self): 613 """ 614 Compute the tangents of a line in space. 615 616 Example: 617 ```python 618 from vedo import * 619 shape = load(dataurl+"timecourse1d.npy")[58] 620 pts = shape.rotate_x(30).points() 621 tangents = Line(pts).tangents() 622 arrs = Arrows(pts, pts+tangents, c='blue9') 623 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 624 ``` 625 ![](https://vedo.embl.es/images/feats/line_tangents.png) 626 """ 627 v = np.gradient(self.points())[0] 628 ds_dt = np.linalg.norm(v, axis=1) 629 tangent = np.array([1 / ds_dt] * 3).transpose() * v 630 return tangent
Compute the tangents of a line in space.
Example:
from vedo import * shape = load(dataurl+"timecourse1d.npy")[58] pts = shape.rotate_x(30).points() tangents = Line(pts).tangents() arrs = Arrows(pts, pts+tangents, c='blue9') show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()
632 def curvature(self): 633 """ 634 Compute the signed curvature of a line in space. 635 The signed is computed assuming the line is about coplanar to the xy plane. 636 637 Example: 638 ```python 639 from vedo import * 640 from vedo.pyplot import plot 641 shape = load(dataurl+"timecourse1d.npy")[55] 642 curvs = Line(shape.points()).curvature() 643 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 644 shape.render_lines_as_tubes().lw(12) 645 pp = plot(curvs, ac='white', lc='yellow5') 646 show(shape, pp, N=2, bg='bb', sharecam=False).close() 647 ``` 648 ![](https://vedo.embl.es/images/feats/line_curvature.png) 649 """ 650 v = np.gradient(self.points())[0] 651 a = np.gradient(v)[0] 652 av = np.cross(a, v) 653 mav = np.linalg.norm(av, axis=1) 654 mv = utils.mag2(v) 655 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 656 val[0] = val[1] 657 val[-1] = val[-2] 658 return val
Compute the signed curvature of a line in space. The signed is computed assuming the line is about coplanar to the xy plane.
Example:
from vedo import * from vedo.pyplot import plot shape = load(dataurl+"timecourse1d.npy")[55] curvs = Line(shape.points()).curvature() shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') shape.render_lines_as_tubes().lw(12) pp = plot(curvs, ac='white', lc='yellow5') show(shape, pp, N=2, bg='bb', sharecam=False).close()
660 def compute_curvature(self, method=0): 661 """ 662 Add a pointdata array named 'Curvatures' which contains 663 the curvature value at each point. 664 665 Keyword method is overridden in Mesh and has no effect here. 666 """ 667 # overrides mesh.compute_curvature 668 curvs = self.curvature() 669 vmin, vmax = np.min(curvs), np.max(curvs) 670 if vmin < 0 and vmax > 0: 671 v = max(-vmin, vmax) 672 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 673 else: 674 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 675 return self
Add a pointdata array named 'Curvatures' which contains the curvature value at each point.
Keyword method is overridden in Mesh and has no effect here.
677 def sweep(self, direction=(1, 0, 0), res=1): 678 """ 679 Sweep the `Line` along the specified vector direction. 680 681 Returns a `Mesh` surface. 682 Line position is updated to allow for additional sweepings. 683 684 Example: 685 ```python 686 from vedo import Line, show 687 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 688 surf1 = aline.sweep((1,0.2,0), res=3) 689 surf2 = aline.sweep((0.2,0,1)) 690 aline.color('r').linewidth(4) 691 show(surf1, surf2, aline, axes=1).close() 692 ``` 693 ![](https://vedo.embl.es/images/feats/sweepline.png) 694 """ 695 line = self.polydata() 696 rows = line.GetNumberOfPoints() 697 698 spacing = 1 / res 699 surface = vtk.vtkPolyData() 700 701 res += 1 702 npts = rows * res 703 npolys = (rows - 1) * (res - 1) 704 points = vtk.vtkPoints() 705 points.Allocate(npts) 706 707 cnt = 0 708 x = [0.0, 0.0, 0.0] 709 for row in range(rows): 710 for col in range(res): 711 p = [0.0, 0.0, 0.0] 712 line.GetPoint(row, p) 713 x[0] = p[0] + direction[0] * col * spacing 714 x[1] = p[1] + direction[1] * col * spacing 715 x[2] = p[2] + direction[2] * col * spacing 716 points.InsertPoint(cnt, x) 717 cnt += 1 718 719 # Generate the quads 720 polys = vtk.vtkCellArray() 721 polys.Allocate(npolys * 4) 722 pts = [0, 0, 0, 0] 723 for row in range(rows - 1): 724 for col in range(res - 1): 725 pts[0] = col + row * res 726 pts[1] = pts[0] + 1 727 pts[2] = pts[0] + res + 1 728 pts[3] = pts[0] + res 729 polys.InsertNextCell(4, pts) 730 surface.SetPoints(points) 731 surface.SetPolys(polys) 732 asurface = vedo.Mesh(surface) 733 prop = vtk.vtkProperty() 734 prop.DeepCopy(self.GetProperty()) 735 asurface.SetProperty(prop) 736 asurface.property = prop 737 asurface.lighting("default") 738 self.points(self.points() + direction) 739 return asurface
Sweep the Line
along the specified vector direction.
Returns a Mesh
surface.
Line position is updated to allow for additional sweepings.
Example:
from vedo import Line, show aline = Line([(0,0,0),(1,3,0),(2,4,0)]) surf1 = aline.sweep((1,0.2,0), res=3) surf2 = aline.sweep((0.2,0,1)) aline.color('r').linewidth(4) show(surf1, surf2, aline, axes=1).close()
748class DashedLine(Mesh): 749 """ 750 Consider using `Line.pattern()` instead. 751 752 Build a dashed line segment between points `p0` and `p1`. 753 If `p0` is a list of points returns the line connecting them. 754 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 755 """ 756 757 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0): 758 """ 759 Arguments: 760 closed : (bool) 761 join last to first point 762 spacing : (float) 763 relative size of the dash 764 lw : (int) 765 line width in pixels 766 """ 767 if isinstance(p1, vtk.vtkActor): 768 p1 = p1.GetPosition() 769 if isinstance(p0, vtk.vtkActor): 770 p0 = p0.GetPosition() 771 if isinstance(p0, Points): 772 p0 = p0.points() 773 774 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 775 if len(p0) > 3: 776 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 777 # assume input is 2D xlist, ylist 778 p0 = np.stack((p0, p1), axis=1) 779 p1 = None 780 p0 = utils.make3d(p0) 781 if closed: 782 p0 = np.append(p0, [p0[0]], axis=0) 783 784 if p1 is not None: # assume passing p0=[x,y] 785 if len(p0) == 2 and not utils.is_sequence(p0[0]): 786 p0 = (p0[0], p0[1], 0) 787 if len(p1) == 2 and not utils.is_sequence(p1[0]): 788 p1 = (p1[0], p1[1], 0) 789 790 # detect if user is passing a list of points: 791 if utils.is_sequence(p0[0]): 792 listp = p0 793 else: # or just 2 points to link 794 listp = [p0, p1] 795 796 listp = np.array(listp) 797 if listp.shape[1] == 2: 798 listp = np.c_[listp, np.zeros(listp.shape[0])] 799 800 xmn = np.min(listp, axis=0) 801 xmx = np.max(listp, axis=0) 802 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 803 if not dlen: 804 Mesh.__init__(self, vtk.vtkPolyData(), c, alpha) 805 self.name = "DashedLine (void)" 806 return 807 808 qs = [] 809 for ipt in range(len(listp) - 1): 810 p0 = listp[ipt] 811 p1 = listp[ipt + 1] 812 v = p1 - p0 813 vdist = np.linalg.norm(v) 814 n1 = int(vdist / dlen) 815 if not n1: 816 continue 817 818 res = 0 819 for i in range(n1 + 2): 820 ist = (i - 0.5) / n1 821 ist = max(ist, 0) 822 qi = p0 + v * (ist - res / vdist) 823 if ist > 1: 824 qi = p1 825 res = np.linalg.norm(qi - p1) 826 qs.append(qi) 827 break 828 qs.append(qi) 829 830 polylns = vtk.vtkAppendPolyData() 831 for i, q1 in enumerate(qs): 832 if not i % 2: 833 continue 834 q0 = qs[i - 1] 835 line_source = vtk.vtkLineSource() 836 line_source.SetPoint1(q0) 837 line_source.SetPoint2(q1) 838 line_source.Update() 839 polylns.AddInputData(line_source.GetOutput()) 840 polylns.Update() 841 842 Mesh.__init__(self, polylns.GetOutput(), c, alpha) 843 self.lw(lw).lighting("off") 844 self.base = listp[0] 845 if closed: 846 self.top = listp[-2] 847 else: 848 self.top = listp[-1] 849 self.name = "DashedLine"
Consider using Line.pattern()
instead.
Build a dashed line segment between points p0
and p1
.
If p0
is a list of points returns the line connecting them.
A 2D set of coords can also be passed as p0=[x..], p1=[y..]
.
757 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0): 758 """ 759 Arguments: 760 closed : (bool) 761 join last to first point 762 spacing : (float) 763 relative size of the dash 764 lw : (int) 765 line width in pixels 766 """ 767 if isinstance(p1, vtk.vtkActor): 768 p1 = p1.GetPosition() 769 if isinstance(p0, vtk.vtkActor): 770 p0 = p0.GetPosition() 771 if isinstance(p0, Points): 772 p0 = p0.points() 773 774 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 775 if len(p0) > 3: 776 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 777 # assume input is 2D xlist, ylist 778 p0 = np.stack((p0, p1), axis=1) 779 p1 = None 780 p0 = utils.make3d(p0) 781 if closed: 782 p0 = np.append(p0, [p0[0]], axis=0) 783 784 if p1 is not None: # assume passing p0=[x,y] 785 if len(p0) == 2 and not utils.is_sequence(p0[0]): 786 p0 = (p0[0], p0[1], 0) 787 if len(p1) == 2 and not utils.is_sequence(p1[0]): 788 p1 = (p1[0], p1[1], 0) 789 790 # detect if user is passing a list of points: 791 if utils.is_sequence(p0[0]): 792 listp = p0 793 else: # or just 2 points to link 794 listp = [p0, p1] 795 796 listp = np.array(listp) 797 if listp.shape[1] == 2: 798 listp = np.c_[listp, np.zeros(listp.shape[0])] 799 800 xmn = np.min(listp, axis=0) 801 xmx = np.max(listp, axis=0) 802 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 803 if not dlen: 804 Mesh.__init__(self, vtk.vtkPolyData(), c, alpha) 805 self.name = "DashedLine (void)" 806 return 807 808 qs = [] 809 for ipt in range(len(listp) - 1): 810 p0 = listp[ipt] 811 p1 = listp[ipt + 1] 812 v = p1 - p0 813 vdist = np.linalg.norm(v) 814 n1 = int(vdist / dlen) 815 if not n1: 816 continue 817 818 res = 0 819 for i in range(n1 + 2): 820 ist = (i - 0.5) / n1 821 ist = max(ist, 0) 822 qi = p0 + v * (ist - res / vdist) 823 if ist > 1: 824 qi = p1 825 res = np.linalg.norm(qi - p1) 826 qs.append(qi) 827 break 828 qs.append(qi) 829 830 polylns = vtk.vtkAppendPolyData() 831 for i, q1 in enumerate(qs): 832 if not i % 2: 833 continue 834 q0 = qs[i - 1] 835 line_source = vtk.vtkLineSource() 836 line_source.SetPoint1(q0) 837 line_source.SetPoint2(q1) 838 line_source.Update() 839 polylns.AddInputData(line_source.GetOutput()) 840 polylns.Update() 841 842 Mesh.__init__(self, polylns.GetOutput(), c, alpha) 843 self.lw(lw).lighting("off") 844 self.base = listp[0] 845 if closed: 846 self.top = listp[-2] 847 else: 848 self.top = listp[-1] 849 self.name = "DashedLine"
Arguments:
- closed : (bool) join last to first point
- spacing : (float) relative size of the dash
- lw : (int) line width in pixels
852class RoundedLine(Mesh): 853 """ 854 Create a 2D line of specified thickness (in absolute units) passing through 855 a list of input points. Borders of the line are rounded. 856 """ 857 858 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0): 859 """ 860 Arguments: 861 pts : (list) 862 a list of points in 2D or 3D (z will be ignored). 863 lw : (float) 864 thickness of the line. 865 res : (int) 866 resolution of the rounded regions 867 868 Example: 869 ```python 870 from vedo import * 871 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 872 ln = Line(pts, c='r', lw=2).z(0.01) 873 rl = RoundedLine(pts, 0.6) 874 show(Points(pts), ln, rl, axes=1).close() 875 ``` 876 ![](https://vedo.embl.es/images/feats/rounded_line.png) 877 """ 878 pts = utils.make3d(pts) 879 880 def _getpts(pts, revd=False): 881 882 if revd: 883 pts = list(reversed(pts)) 884 885 if len(pts) == 2: 886 p0, p1 = pts 887 v = p1 - p0 888 dv = np.linalg.norm(v) 889 nv = np.cross(v, (0, 0, -1)) 890 nv = nv / np.linalg.norm(nv) * lw 891 return [p0 + nv, p1 + nv] 892 893 ptsnew = [] 894 for k in range(len(pts) - 2): 895 p0 = pts[k] 896 p1 = pts[k + 1] 897 p2 = pts[k + 2] 898 v = p1 - p0 899 u = p2 - p1 900 du = np.linalg.norm(u) 901 dv = np.linalg.norm(v) 902 nv = np.cross(v, (0, 0, -1)) 903 nv = nv / np.linalg.norm(nv) * lw 904 nu = np.cross(u, (0, 0, -1)) 905 nu = nu / np.linalg.norm(nu) * lw 906 uv = np.cross(u, v) 907 if k == 0: 908 ptsnew.append(p0 + nv) 909 if uv[2] <= 0: 910 alpha = np.arccos(np.dot(u, v) / du / dv) 911 db = lw * np.tan(alpha / 2) 912 p1new = p1 + nv - v / dv * db 913 ptsnew.append(p1new) 914 else: 915 p1a = p1 + nv 916 p1b = p1 + nu 917 for i in range(0, res + 1): 918 pab = p1a * (res - i) / res + p1b * i / res 919 vpab = pab - p1 920 vpab = vpab / np.linalg.norm(vpab) * lw 921 ptsnew.append(p1 + vpab) 922 if k == len(pts) - 3: 923 ptsnew.append(p2 + nu) 924 if revd: 925 ptsnew.append(p2 - nu) 926 return ptsnew 927 928 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 929 930 ppoints = vtk.vtkPoints() # Generate the polyline 931 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 932 lines = vtk.vtkCellArray() 933 npt = len(ptsnew) 934 lines.InsertNextCell(npt) 935 for i in range(npt): 936 lines.InsertCellPoint(i) 937 poly = vtk.vtkPolyData() 938 poly.SetPoints(ppoints) 939 poly.SetLines(lines) 940 vct = vtk.vtkContourTriangulator() 941 vct.SetInputData(poly) 942 vct.Update() 943 Mesh.__init__(self, vct.GetOutput(), c, alpha) 944 self.flat() 945 self.property.LightingOff() 946 self.name = "RoundedLine" 947 self.base = ptsnew[0] 948 self.top = ptsnew[-1]
Create a 2D line of specified thickness (in absolute units) passing through a list of input points. Borders of the line are rounded.
858 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0): 859 """ 860 Arguments: 861 pts : (list) 862 a list of points in 2D or 3D (z will be ignored). 863 lw : (float) 864 thickness of the line. 865 res : (int) 866 resolution of the rounded regions 867 868 Example: 869 ```python 870 from vedo import * 871 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 872 ln = Line(pts, c='r', lw=2).z(0.01) 873 rl = RoundedLine(pts, 0.6) 874 show(Points(pts), ln, rl, axes=1).close() 875 ``` 876 ![](https://vedo.embl.es/images/feats/rounded_line.png) 877 """ 878 pts = utils.make3d(pts) 879 880 def _getpts(pts, revd=False): 881 882 if revd: 883 pts = list(reversed(pts)) 884 885 if len(pts) == 2: 886 p0, p1 = pts 887 v = p1 - p0 888 dv = np.linalg.norm(v) 889 nv = np.cross(v, (0, 0, -1)) 890 nv = nv / np.linalg.norm(nv) * lw 891 return [p0 + nv, p1 + nv] 892 893 ptsnew = [] 894 for k in range(len(pts) - 2): 895 p0 = pts[k] 896 p1 = pts[k + 1] 897 p2 = pts[k + 2] 898 v = p1 - p0 899 u = p2 - p1 900 du = np.linalg.norm(u) 901 dv = np.linalg.norm(v) 902 nv = np.cross(v, (0, 0, -1)) 903 nv = nv / np.linalg.norm(nv) * lw 904 nu = np.cross(u, (0, 0, -1)) 905 nu = nu / np.linalg.norm(nu) * lw 906 uv = np.cross(u, v) 907 if k == 0: 908 ptsnew.append(p0 + nv) 909 if uv[2] <= 0: 910 alpha = np.arccos(np.dot(u, v) / du / dv) 911 db = lw * np.tan(alpha / 2) 912 p1new = p1 + nv - v / dv * db 913 ptsnew.append(p1new) 914 else: 915 p1a = p1 + nv 916 p1b = p1 + nu 917 for i in range(0, res + 1): 918 pab = p1a * (res - i) / res + p1b * i / res 919 vpab = pab - p1 920 vpab = vpab / np.linalg.norm(vpab) * lw 921 ptsnew.append(p1 + vpab) 922 if k == len(pts) - 3: 923 ptsnew.append(p2 + nu) 924 if revd: 925 ptsnew.append(p2 - nu) 926 return ptsnew 927 928 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 929 930 ppoints = vtk.vtkPoints() # Generate the polyline 931 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 932 lines = vtk.vtkCellArray() 933 npt = len(ptsnew) 934 lines.InsertNextCell(npt) 935 for i in range(npt): 936 lines.InsertCellPoint(i) 937 poly = vtk.vtkPolyData() 938 poly.SetPoints(ppoints) 939 poly.SetLines(lines) 940 vct = vtk.vtkContourTriangulator() 941 vct.SetInputData(poly) 942 vct.Update() 943 Mesh.__init__(self, vct.GetOutput(), c, alpha) 944 self.flat() 945 self.property.LightingOff() 946 self.name = "RoundedLine" 947 self.base = ptsnew[0] 948 self.top = ptsnew[-1]
Arguments:
- pts : (list) a list of points in 2D or 3D (z will be ignored).
- lw : (float) thickness of the line.
- res : (int) resolution of the rounded regions
Example:
from vedo import * pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] ln = Line(pts, c='r', lw=2).z(0.01) rl = RoundedLine(pts, 0.6) show(Points(pts), ln, rl, axes=1).close()
1652class Tube(Mesh): 1653 """ 1654 Build a tube along the line defined by a set of points. 1655 """ 1656 1657 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0): 1658 """ 1659 Arguments: 1660 r : (float, list) 1661 constant radius or list of radii. 1662 res : (int) 1663 resolution, number of the sides of the tube 1664 c : (color) 1665 constant color or list of colors for each point. 1666 1667 Examples: 1668 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1669 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1670 1671 ![](https://vedo.embl.es/images/basic/tube.png) 1672 """ 1673 if isinstance(points, vedo.Points): 1674 points = points.points() 1675 1676 base = np.asarray(points[0], dtype=float) 1677 top = np.asarray(points[-1], dtype=float) 1678 1679 vpoints = vtk.vtkPoints() 1680 idx = len(points) 1681 for p in points: 1682 vpoints.InsertNextPoint(p) 1683 line = vtk.vtkPolyLine() 1684 line.GetPointIds().SetNumberOfIds(idx) 1685 for i in range(idx): 1686 line.GetPointIds().SetId(i, i) 1687 lines = vtk.vtkCellArray() 1688 lines.InsertNextCell(line) 1689 polyln = vtk.vtkPolyData() 1690 polyln.SetPoints(vpoints) 1691 polyln.SetLines(lines) 1692 1693 tuf = vtk.vtkTubeFilter() 1694 tuf.SetCapping(cap) 1695 tuf.SetNumberOfSides(res) 1696 tuf.SetInputData(polyln) 1697 if utils.is_sequence(r): 1698 arr = utils.numpy2vtk(r, dtype=float) 1699 arr.SetName("TubeRadius") 1700 polyln.GetPointData().AddArray(arr) 1701 polyln.GetPointData().SetActiveScalars("TubeRadius") 1702 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1703 else: 1704 tuf.SetRadius(r) 1705 1706 usingColScals = False 1707 if utils.is_sequence(c): 1708 usingColScals = True 1709 cc = vtk.vtkUnsignedCharArray() 1710 cc.SetName("TubeColors") 1711 cc.SetNumberOfComponents(3) 1712 cc.SetNumberOfTuples(len(c)) 1713 for i, ic in enumerate(c): 1714 r, g, b = get_color(ic) 1715 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1716 polyln.GetPointData().AddArray(cc) 1717 c = None 1718 tuf.Update() 1719 1720 Mesh.__init__(self, tuf.GetOutput(), c, alpha) 1721 self.phong() 1722 if usingColScals: 1723 self.mapper().SetScalarModeToUsePointFieldData() 1724 self.mapper().ScalarVisibilityOn() 1725 self.mapper().SelectColorArray("TubeColors") 1726 self.mapper().Modified() 1727 1728 self.base = base 1729 self.top = top 1730 self.name = "Tube"
Build a tube along the line defined by a set of points.
1657 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0): 1658 """ 1659 Arguments: 1660 r : (float, list) 1661 constant radius or list of radii. 1662 res : (int) 1663 resolution, number of the sides of the tube 1664 c : (color) 1665 constant color or list of colors for each point. 1666 1667 Examples: 1668 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1669 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1670 1671 ![](https://vedo.embl.es/images/basic/tube.png) 1672 """ 1673 if isinstance(points, vedo.Points): 1674 points = points.points() 1675 1676 base = np.asarray(points[0], dtype=float) 1677 top = np.asarray(points[-1], dtype=float) 1678 1679 vpoints = vtk.vtkPoints() 1680 idx = len(points) 1681 for p in points: 1682 vpoints.InsertNextPoint(p) 1683 line = vtk.vtkPolyLine() 1684 line.GetPointIds().SetNumberOfIds(idx) 1685 for i in range(idx): 1686 line.GetPointIds().SetId(i, i) 1687 lines = vtk.vtkCellArray() 1688 lines.InsertNextCell(line) 1689 polyln = vtk.vtkPolyData() 1690 polyln.SetPoints(vpoints) 1691 polyln.SetLines(lines) 1692 1693 tuf = vtk.vtkTubeFilter() 1694 tuf.SetCapping(cap) 1695 tuf.SetNumberOfSides(res) 1696 tuf.SetInputData(polyln) 1697 if utils.is_sequence(r): 1698 arr = utils.numpy2vtk(r, dtype=float) 1699 arr.SetName("TubeRadius") 1700 polyln.GetPointData().AddArray(arr) 1701 polyln.GetPointData().SetActiveScalars("TubeRadius") 1702 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1703 else: 1704 tuf.SetRadius(r) 1705 1706 usingColScals = False 1707 if utils.is_sequence(c): 1708 usingColScals = True 1709 cc = vtk.vtkUnsignedCharArray() 1710 cc.SetName("TubeColors") 1711 cc.SetNumberOfComponents(3) 1712 cc.SetNumberOfTuples(len(c)) 1713 for i, ic in enumerate(c): 1714 r, g, b = get_color(ic) 1715 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1716 polyln.GetPointData().AddArray(cc) 1717 c = None 1718 tuf.Update() 1719 1720 Mesh.__init__(self, tuf.GetOutput(), c, alpha) 1721 self.phong() 1722 if usingColScals: 1723 self.mapper().SetScalarModeToUsePointFieldData() 1724 self.mapper().ScalarVisibilityOn() 1725 self.mapper().SelectColorArray("TubeColors") 1726 self.mapper().Modified() 1727 1728 self.base = base 1729 self.top = top 1730 self.name = "Tube"
Arguments:
- r : (float, list) constant radius or list of radii.
- res : (int) resolution, number of the sides of the tube
- c : (color) constant color or list of colors for each point.
Examples:
1733def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0): 1734 """ 1735 Create a tube with a thickness along a line of points. 1736 1737 Example: 1738 ```python 1739 from vedo import * 1740 pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)] 1741 vline = Line(pts, lw=5, c='red5') 1742 thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1) 1743 show(vline, thick_tube, axes=1).close() 1744 ``` 1745 ![](https://vedo.embl.es/images/feats/thick_tube.png) 1746 """ 1747 1748 def make_cap(t1, t2): 1749 newpoints = t1.points().tolist() + t2.points().tolist() 1750 newfaces = [] 1751 for i in range(n - 1): 1752 newfaces.append([i, i + 1, i + n]) 1753 newfaces.append([i + n, i + 1, i + n + 1]) 1754 newfaces.append([2 * n - 1, 0, n]) 1755 newfaces.append([2 * n - 1, n - 1, 0]) 1756 capm = utils.buildPolyData(newpoints, newfaces) 1757 return capm 1758 1759 assert r1 < r2 1760 1761 t1 = Tube(pts, r=r1, cap=False, res=res) 1762 t2 = Tube(pts, r=r2, cap=False, res=res) 1763 1764 tc1a, tc1b = t1.boundaries().split() 1765 tc2a, tc2b = t2.boundaries().split() 1766 n = tc1b.npoints 1767 1768 tc1b.join(reset=True).clean() # needed because indices are flipped 1769 tc2b.join(reset=True).clean() 1770 1771 capa = make_cap(tc1a, tc2a) 1772 capb = make_cap(tc1b, tc2b) 1773 1774 thick_tube = merge(t1, t2, capa, capb).c(c).alpha(alpha) 1775 thick_tube.base = t1.base 1776 thick_tube.top = t1.top 1777 thick_tube.name = "ThickTube" 1778 return thick_tube
Create a tube with a thickness along a line of points.
Example:
from vedo import *
pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)]
vline = Line(pts, lw=5, c='red5')
thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1)
show(vline, thick_tube, axes=1).close()
951class Lines(Mesh): 952 """ 953 Build the line segments between two lists of points `start_pts` and `end_pts`. 954 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 955 """ 956 957 def __init__( 958 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 959 ): 960 """ 961 Arguments: 962 scale : (float) 963 apply a rescaling factor to the lengths. 964 c : (color, int, str, list) 965 color name, number, or list of [R,G,B] colors 966 alpha : (float) 967 opacity in range [0,1] 968 lw : (int) 969 line width in pixel units 970 res : (int) 971 resolution, number of points along the line 972 (only relevant if only 2 points are specified) 973 974 Examples: 975 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 976 977 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 978 """ 979 if isinstance(start_pts, Points): 980 start_pts = start_pts.points() 981 if isinstance(end_pts, Points): 982 end_pts = end_pts.points() 983 984 if end_pts is not None: 985 start_pts = np.stack((start_pts, end_pts), axis=1) 986 987 polylns = vtk.vtkAppendPolyData() 988 989 if not utils.is_ragged(start_pts): 990 991 for twopts in start_pts: 992 line_source = vtk.vtkLineSource() 993 line_source.SetResolution(res) 994 if len(twopts[0]) == 2: 995 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 996 else: 997 line_source.SetPoint1(twopts[0]) 998 999 if scale == 1: 1000 pt2 = twopts[1] 1001 else: 1002 vers = (np.array(twopts[1]) - twopts[0]) * scale 1003 pt2 = np.array(twopts[0]) + vers 1004 1005 if len(pt2) == 2: 1006 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1007 else: 1008 line_source.SetPoint2(pt2) 1009 polylns.AddInputConnection(line_source.GetOutputPort()) 1010 1011 else: 1012 1013 polylns = vtk.vtkAppendPolyData() 1014 for t in start_pts: 1015 t = utils.make3d(t) 1016 ppoints = vtk.vtkPoints() # Generate the polyline 1017 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1018 lines = vtk.vtkCellArray() 1019 npt = len(t) 1020 lines.InsertNextCell(npt) 1021 for i in range(npt): 1022 lines.InsertCellPoint(i) 1023 poly = vtk.vtkPolyData() 1024 poly.SetPoints(ppoints) 1025 poly.SetLines(lines) 1026 polylns.AddInputData(poly) 1027 1028 polylns.Update() 1029 1030 Mesh.__init__(self, polylns.GetOutput(), c, alpha) 1031 self.lw(lw).lighting("off") 1032 if dotted: 1033 self.GetProperty().SetLineStipplePattern(0xF0F0) 1034 self.GetProperty().SetLineStippleRepeatFactor(1) 1035 1036 self.name = "Lines"
Build the line segments between two lists of points start_pts
and end_pts
.
start_pts
can be also passed in the form [[point1, point2], ...]
.
957 def __init__( 958 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 959 ): 960 """ 961 Arguments: 962 scale : (float) 963 apply a rescaling factor to the lengths. 964 c : (color, int, str, list) 965 color name, number, or list of [R,G,B] colors 966 alpha : (float) 967 opacity in range [0,1] 968 lw : (int) 969 line width in pixel units 970 res : (int) 971 resolution, number of points along the line 972 (only relevant if only 2 points are specified) 973 974 Examples: 975 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 976 977 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 978 """ 979 if isinstance(start_pts, Points): 980 start_pts = start_pts.points() 981 if isinstance(end_pts, Points): 982 end_pts = end_pts.points() 983 984 if end_pts is not None: 985 start_pts = np.stack((start_pts, end_pts), axis=1) 986 987 polylns = vtk.vtkAppendPolyData() 988 989 if not utils.is_ragged(start_pts): 990 991 for twopts in start_pts: 992 line_source = vtk.vtkLineSource() 993 line_source.SetResolution(res) 994 if len(twopts[0]) == 2: 995 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 996 else: 997 line_source.SetPoint1(twopts[0]) 998 999 if scale == 1: 1000 pt2 = twopts[1] 1001 else: 1002 vers = (np.array(twopts[1]) - twopts[0]) * scale 1003 pt2 = np.array(twopts[0]) + vers 1004 1005 if len(pt2) == 2: 1006 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1007 else: 1008 line_source.SetPoint2(pt2) 1009 polylns.AddInputConnection(line_source.GetOutputPort()) 1010 1011 else: 1012 1013 polylns = vtk.vtkAppendPolyData() 1014 for t in start_pts: 1015 t = utils.make3d(t) 1016 ppoints = vtk.vtkPoints() # Generate the polyline 1017 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1018 lines = vtk.vtkCellArray() 1019 npt = len(t) 1020 lines.InsertNextCell(npt) 1021 for i in range(npt): 1022 lines.InsertCellPoint(i) 1023 poly = vtk.vtkPolyData() 1024 poly.SetPoints(ppoints) 1025 poly.SetLines(lines) 1026 polylns.AddInputData(poly) 1027 1028 polylns.Update() 1029 1030 Mesh.__init__(self, polylns.GetOutput(), c, alpha) 1031 self.lw(lw).lighting("off") 1032 if dotted: 1033 self.GetProperty().SetLineStipplePattern(0xF0F0) 1034 self.GetProperty().SetLineStippleRepeatFactor(1) 1035 1036 self.name = "Lines"
Arguments:
- scale : (float) apply a rescaling factor to the lengths.
- c : (color, int, str, list) color name, number, or list of [R,G,B] colors
- alpha : (float) opacity in range [0,1]
- lw : (int) line width in pixel units
- res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
Examples:
1039class Spline(Line): 1040 """ 1041 Find the B-Spline curve through a set of points. This curve does not necessarily 1042 pass exactly through all the input points. Needs to import `scipy`. 1043 """ 1044 1045 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing=""): 1046 """ 1047 Arguments: 1048 smooth : (float) 1049 smoothing factor. 1050 - 0 = interpolate points exactly [default]. 1051 - 1 = average point positions. 1052 degree : (int) 1053 degree of the spline (between 1 and 5). 1054 easing : (str) 1055 control sensity of points along the spline. 1056 Available options are 1057 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1058 Can be used to create animations (move objects at varying speed). 1059 See e.g.: https://easings.net 1060 res : (int) 1061 number of points on the spline 1062 1063 See also: `CSpline` and `KSpline`. 1064 1065 Examples: 1066 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1067 1068 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1069 """ 1070 from scipy.interpolate import splprep, splev 1071 1072 if isinstance(points, Points): 1073 points = points.points() 1074 1075 points = utils.make3d(points) 1076 1077 per = 0 1078 if closed: 1079 points = np.append(points, [points[0]], axis=0) 1080 per = 1 1081 1082 if res is None: 1083 res = len(points) * 10 1084 1085 points = np.array(points, dtype=float) 1086 1087 minx, miny, minz = np.min(points, axis=0) 1088 maxx, maxy, maxz = np.max(points, axis=0) 1089 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1090 smooth *= maxb / 2 # must be in absolute units 1091 1092 x = np.linspace(0, 1, res) 1093 if easing: 1094 if easing == "InSine": 1095 x = 1 - np.cos((x * np.pi) / 2) 1096 elif easing == "OutSine": 1097 x = np.sin((x * np.pi) / 2) 1098 elif easing == "Sine": 1099 x = -(np.cos(np.pi * x) - 1) / 2 1100 elif easing == "InQuad": 1101 x = x * x 1102 elif easing == "OutQuad": 1103 x = 1 - (1 - x) * (1 - x) 1104 elif easing == "InCubic": 1105 x = x * x 1106 elif easing == "OutCubic": 1107 x = 1 - np.power(1 - x, 3) 1108 elif easing == "InQuart": 1109 x = x * x * x * x 1110 elif easing == "OutQuart": 1111 x = 1 - np.power(1 - x, 4) 1112 elif easing == "InCirc": 1113 x = 1 - np.sqrt(1 - np.power(x, 2)) 1114 elif easing == "OutCirc": 1115 x = np.sqrt(1 - np.power(x - 1, 2)) 1116 else: 1117 vedo.logger.error(f"unknown ease mode {easing}") 1118 1119 # find the knots 1120 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1121 # evaluate spLine, including interpolated points: 1122 xnew, ynew, znew = splev(x, tckp) 1123 1124 Line.__init__(self, np.c_[xnew, ynew, znew], lw=2) 1125 self.name = "Spline"
Find the B-Spline curve through a set of points. This curve does not necessarily
pass exactly through all the input points. Needs to import scipy
.
1045 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing=""): 1046 """ 1047 Arguments: 1048 smooth : (float) 1049 smoothing factor. 1050 - 0 = interpolate points exactly [default]. 1051 - 1 = average point positions. 1052 degree : (int) 1053 degree of the spline (between 1 and 5). 1054 easing : (str) 1055 control sensity of points along the spline. 1056 Available options are 1057 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1058 Can be used to create animations (move objects at varying speed). 1059 See e.g.: https://easings.net 1060 res : (int) 1061 number of points on the spline 1062 1063 See also: `CSpline` and `KSpline`. 1064 1065 Examples: 1066 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1067 1068 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1069 """ 1070 from scipy.interpolate import splprep, splev 1071 1072 if isinstance(points, Points): 1073 points = points.points() 1074 1075 points = utils.make3d(points) 1076 1077 per = 0 1078 if closed: 1079 points = np.append(points, [points[0]], axis=0) 1080 per = 1 1081 1082 if res is None: 1083 res = len(points) * 10 1084 1085 points = np.array(points, dtype=float) 1086 1087 minx, miny, minz = np.min(points, axis=0) 1088 maxx, maxy, maxz = np.max(points, axis=0) 1089 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1090 smooth *= maxb / 2 # must be in absolute units 1091 1092 x = np.linspace(0, 1, res) 1093 if easing: 1094 if easing == "InSine": 1095 x = 1 - np.cos((x * np.pi) / 2) 1096 elif easing == "OutSine": 1097 x = np.sin((x * np.pi) / 2) 1098 elif easing == "Sine": 1099 x = -(np.cos(np.pi * x) - 1) / 2 1100 elif easing == "InQuad": 1101 x = x * x 1102 elif easing == "OutQuad": 1103 x = 1 - (1 - x) * (1 - x) 1104 elif easing == "InCubic": 1105 x = x * x 1106 elif easing == "OutCubic": 1107 x = 1 - np.power(1 - x, 3) 1108 elif easing == "InQuart": 1109 x = x * x * x * x 1110 elif easing == "OutQuart": 1111 x = 1 - np.power(1 - x, 4) 1112 elif easing == "InCirc": 1113 x = 1 - np.sqrt(1 - np.power(x, 2)) 1114 elif easing == "OutCirc": 1115 x = np.sqrt(1 - np.power(x - 1, 2)) 1116 else: 1117 vedo.logger.error(f"unknown ease mode {easing}") 1118 1119 # find the knots 1120 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1121 # evaluate spLine, including interpolated points: 1122 xnew, ynew, znew = splev(x, tckp) 1123 1124 Line.__init__(self, np.c_[xnew, ynew, znew], lw=2) 1125 self.name = "Spline"
Arguments:
- smooth : (float)
smoothing factor.
- 0 = interpolate points exactly [default].
- 1 = average point positions.
- degree : (int) degree of the spline (between 1 and 5).
- easing : (str)
control sensity of points along the spline.
Available options are
[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].
Can be used to create animations (move objects at varying speed). See e.g.: https://easings.net - res : (int) number of points on the spline
See also: CSpline
and KSpline
.
Examples:
1128class KSpline(Line): 1129 """ 1130 Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline) 1131 which runs exactly through all the input points. 1132 """ 1133 1134 def __init__(self, points, 1135 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None): 1136 """ 1137 Arguments: 1138 continuity : (float) 1139 changes the sharpness in change between tangents 1140 tension : (float) 1141 changes the length of the tangent vector 1142 bias : (float) 1143 changes the direction of the tangent vector 1144 closed : (bool) 1145 join last to first point to produce a closed curve 1146 res : (int) 1147 approximate resolution of the output line. 1148 Default is 20 times the number of input points. 1149 1150 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1151 1152 See also: `Spline` and `CSpline`. 1153 """ 1154 if isinstance(points, Points): 1155 points = points.points() 1156 1157 if not res: 1158 res = len(points) * 20 1159 1160 points = utils.make3d(points).astype(float) 1161 1162 xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1163 yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1164 zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1165 for s in [xspline, yspline, zspline]: 1166 if bias: 1167 s.SetDefaultBias(bias) 1168 if tension: 1169 s.SetDefaultTension(tension) 1170 if continuity: 1171 s.SetDefaultContinuity(continuity) 1172 s.SetClosed(closed) 1173 1174 lenp = len(points[0]) > 2 1175 1176 for i, p in enumerate(points): 1177 xspline.AddPoint(i, p[0]) 1178 yspline.AddPoint(i, p[1]) 1179 if lenp: 1180 zspline.AddPoint(i, p[2]) 1181 1182 ln = [] 1183 for pos in np.linspace(0, len(points), res): 1184 x = xspline.Evaluate(pos) 1185 y = yspline.Evaluate(pos) 1186 z = 0 1187 if lenp: 1188 z = zspline.Evaluate(pos) 1189 ln.append((x, y, z)) 1190 1191 Line.__init__(self, ln, lw=2) 1192 self.clean() 1193 self.lighting("off") 1194 self.name = "KSpline" 1195 self.base = np.array(points[0], dtype=float) 1196 self.top = np.array(points[-1], dtype=float)
Return a Kochanek spline which runs exactly through all the input points.
1134 def __init__(self, points, 1135 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None): 1136 """ 1137 Arguments: 1138 continuity : (float) 1139 changes the sharpness in change between tangents 1140 tension : (float) 1141 changes the length of the tangent vector 1142 bias : (float) 1143 changes the direction of the tangent vector 1144 closed : (bool) 1145 join last to first point to produce a closed curve 1146 res : (int) 1147 approximate resolution of the output line. 1148 Default is 20 times the number of input points. 1149 1150 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1151 1152 See also: `Spline` and `CSpline`. 1153 """ 1154 if isinstance(points, Points): 1155 points = points.points() 1156 1157 if not res: 1158 res = len(points) * 20 1159 1160 points = utils.make3d(points).astype(float) 1161 1162 xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1163 yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1164 zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkKochanekSpline() 1165 for s in [xspline, yspline, zspline]: 1166 if bias: 1167 s.SetDefaultBias(bias) 1168 if tension: 1169 s.SetDefaultTension(tension) 1170 if continuity: 1171 s.SetDefaultContinuity(continuity) 1172 s.SetClosed(closed) 1173 1174 lenp = len(points[0]) > 2 1175 1176 for i, p in enumerate(points): 1177 xspline.AddPoint(i, p[0]) 1178 yspline.AddPoint(i, p[1]) 1179 if lenp: 1180 zspline.AddPoint(i, p[2]) 1181 1182 ln = [] 1183 for pos in np.linspace(0, len(points), res): 1184 x = xspline.Evaluate(pos) 1185 y = yspline.Evaluate(pos) 1186 z = 0 1187 if lenp: 1188 z = zspline.Evaluate(pos) 1189 ln.append((x, y, z)) 1190 1191 Line.__init__(self, ln, lw=2) 1192 self.clean() 1193 self.lighting("off") 1194 self.name = "KSpline" 1195 self.base = np.array(points[0], dtype=float) 1196 self.top = np.array(points[-1], dtype=float)
Arguments:
- continuity : (float) changes the sharpness in change between tangents
- tension : (float) changes the length of the tangent vector
- bias : (float) changes the direction of the tangent vector
- closed : (bool) join last to first point to produce a closed curve
- res : (int) approximate resolution of the output line. Default is 20 times the number of input points.
1199class CSpline(Line): 1200 """ 1201 Return a Cardinal spline which runs exactly through all the input points. 1202 """ 1203 1204 def __init__(self, points, closed=False, res=None): 1205 """ 1206 Arguments: 1207 closed : (bool) 1208 join last to first point to produce a closed curve 1209 res : (int) 1210 approximate resolution of the output line. 1211 Default is 20 times the number of input points. 1212 1213 See also: `Spline` and `KSpline`. 1214 """ 1215 1216 if isinstance(points, Points): 1217 points = points.points() 1218 1219 if not res: 1220 res = len(points) * 20 1221 1222 points = utils.make3d(points).astype(float) 1223 1224 xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1225 yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1226 zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1227 for s in [xspline, yspline, zspline]: 1228 s.SetClosed(closed) 1229 1230 lenp = len(points[0]) > 2 1231 1232 for i, p in enumerate(points): 1233 xspline.AddPoint(i, p[0]) 1234 yspline.AddPoint(i, p[1]) 1235 if lenp: 1236 zspline.AddPoint(i, p[2]) 1237 1238 ln = [] 1239 for pos in np.linspace(0, len(points), res): 1240 x = xspline.Evaluate(pos) 1241 y = yspline.Evaluate(pos) 1242 z = 0 1243 if lenp: 1244 z = zspline.Evaluate(pos) 1245 ln.append((x, y, z)) 1246 1247 Line.__init__(self, ln, lw=2) 1248 self.clean() 1249 self.lighting("off") 1250 self.name = "CSpline" 1251 self.base = points[0] 1252 self.top = points[-1]
Return a Cardinal spline which runs exactly through all the input points.
1204 def __init__(self, points, closed=False, res=None): 1205 """ 1206 Arguments: 1207 closed : (bool) 1208 join last to first point to produce a closed curve 1209 res : (int) 1210 approximate resolution of the output line. 1211 Default is 20 times the number of input points. 1212 1213 See also: `Spline` and `KSpline`. 1214 """ 1215 1216 if isinstance(points, Points): 1217 points = points.points() 1218 1219 if not res: 1220 res = len(points) * 20 1221 1222 points = utils.make3d(points).astype(float) 1223 1224 xspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1225 yspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1226 zspline = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkCardinalSpline() 1227 for s in [xspline, yspline, zspline]: 1228 s.SetClosed(closed) 1229 1230 lenp = len(points[0]) > 2 1231 1232 for i, p in enumerate(points): 1233 xspline.AddPoint(i, p[0]) 1234 yspline.AddPoint(i, p[1]) 1235 if lenp: 1236 zspline.AddPoint(i, p[2]) 1237 1238 ln = [] 1239 for pos in np.linspace(0, len(points), res): 1240 x = xspline.Evaluate(pos) 1241 y = yspline.Evaluate(pos) 1242 z = 0 1243 if lenp: 1244 z = zspline.Evaluate(pos) 1245 ln.append((x, y, z)) 1246 1247 Line.__init__(self, ln, lw=2) 1248 self.clean() 1249 self.lighting("off") 1250 self.name = "CSpline" 1251 self.base = points[0] 1252 self.top = points[-1]
1255class Bezier(Line): 1256 """ 1257 Generate the Bezier line that links the first to the last point. 1258 """ 1259 1260 def __init__(self, points, res=None): 1261 """ 1262 Example: 1263 ```python 1264 from vedo import * 1265 import numpy as np 1266 pts = np.random.randn(25,3) 1267 for i,p in enumerate(pts): 1268 p += [5*i, 15*sin(i/2), i*i*i/200] 1269 show(Points(pts), Bezier(pts), axes=1).close() 1270 ``` 1271 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1272 """ 1273 N = len(points) 1274 if res is None: 1275 res = 10 * N 1276 t = np.linspace(0, 1, num=res) 1277 bcurve = np.zeros((res, len(points[0]))) 1278 1279 def binom(n, k): 1280 b = 1 1281 for t in range(1, min(k, n - k) + 1): 1282 b *= n / t 1283 n -= 1 1284 return b 1285 1286 def bernstein(n, k): 1287 coeff = binom(n, k) 1288 1289 def _bpoly(x): 1290 return coeff * x ** k * (1 - x) ** (n - k) 1291 1292 return _bpoly 1293 1294 for ii in range(N): 1295 b = bernstein(N - 1, ii)(t) 1296 bcurve += np.outer(b, points[ii]) 1297 Line.__init__(self, bcurve, lw=2) 1298 self.name = "BezierLine"
Generate the Bezier line that links the first to the last point.
1260 def __init__(self, points, res=None): 1261 """ 1262 Example: 1263 ```python 1264 from vedo import * 1265 import numpy as np 1266 pts = np.random.randn(25,3) 1267 for i,p in enumerate(pts): 1268 p += [5*i, 15*sin(i/2), i*i*i/200] 1269 show(Points(pts), Bezier(pts), axes=1).close() 1270 ``` 1271 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1272 """ 1273 N = len(points) 1274 if res is None: 1275 res = 10 * N 1276 t = np.linspace(0, 1, num=res) 1277 bcurve = np.zeros((res, len(points[0]))) 1278 1279 def binom(n, k): 1280 b = 1 1281 for t in range(1, min(k, n - k) + 1): 1282 b *= n / t 1283 n -= 1 1284 return b 1285 1286 def bernstein(n, k): 1287 coeff = binom(n, k) 1288 1289 def _bpoly(x): 1290 return coeff * x ** k * (1 - x) ** (n - k) 1291 1292 return _bpoly 1293 1294 for ii in range(N): 1295 b = bernstein(N - 1, ii)(t) 1296 bcurve += np.outer(b, points[ii]) 1297 Line.__init__(self, bcurve, lw=2) 1298 self.name = "BezierLine"
Example:
from vedo import * import numpy as np pts = np.random.randn(25,3) for i,p in enumerate(pts): p += [5*i, 15*sin(i/2), i*i*i/200] show(Points(pts), Bezier(pts), axes=1).close()
3687class Brace(Mesh): 3688 """ 3689 Create a brace (bracket) shape. 3690 """ 3691 3692 def __init__( 3693 self, 3694 q1, 3695 q2, 3696 style="}", 3697 padding1=0.0, 3698 font="Theemim", 3699 comment="", 3700 justify=None, 3701 angle=0.0, 3702 padding2=0.2, 3703 s=1.0, 3704 italic=0, 3705 c="k1", 3706 alpha=1.0, 3707 ): 3708 """ 3709 Create a brace (bracket) shape which spans from point q1 to point q2. 3710 3711 Arguments: 3712 q1 : (list) 3713 point 1. 3714 q2 : (list) 3715 point 2. 3716 style : (str) 3717 style of the bracket, eg. `{}, [], (), <>`. 3718 padding1 : (float) 3719 padding space in percent form the input points. 3720 font : (str) 3721 font type 3722 comment : (str) 3723 additional text to appear next to the brace symbol. 3724 justify : (str) 3725 specify the anchor point to justify text comment, e.g. "top-left". 3726 italic : float 3727 italicness of the text comment (can be a positive or negative number) 3728 angle : (float) 3729 rotation angle of text. Use `None` to keep it horizontal. 3730 padding2 : (float) 3731 padding space in percent form brace to text comment. 3732 s : (float) 3733 scale factor for the comment 3734 3735 Examples: 3736 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3737 3738 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3739 """ 3740 if isinstance(q1, vtk.vtkActor): 3741 q1 = q1.GetPosition() 3742 if isinstance(q2, vtk.vtkActor): 3743 q2 = q2.GetPosition() 3744 if len(q1) == 2: 3745 q1 = [q1[0], q1[1], 0.0] 3746 if len(q2) == 2: 3747 q2 = [q2[0], q2[1], 0.0] 3748 q1 = np.array(q1, dtype=float) 3749 q2 = np.array(q2, dtype=float) 3750 mq = (q1 + q2) / 2 3751 q1 = q1 - mq 3752 q2 = q2 - mq 3753 d = np.linalg.norm(q2 - q1) 3754 q2[2] = q1[2] 3755 3756 if style not in "{}[]()<>|I": 3757 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3758 style = "}" 3759 3760 flip = False 3761 if style in ["{", "[", "(", "<"]: 3762 flip = True 3763 i = ["{", "[", "(", "<"].index(style) 3764 style = ["}", "]", ")", ">"][i] 3765 3766 br = Text3D(style, font="Theemim", justify="center-left") 3767 br.scale([0.4, 1, 1]) 3768 3769 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3770 if flip: 3771 angler += 180 3772 3773 _, x1, y0, y1, _, _ = br.bounds() 3774 if comment: 3775 just = "center-top" 3776 if angle is None: 3777 angle = -angler + 90 3778 if not flip: 3779 angle += 180 3780 3781 if flip: 3782 angle += 180 3783 just = "center-bottom" 3784 if justify is not None: 3785 just = justify 3786 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3787 cx0, cx1 = cmt.xbounds() 3788 cmt.rotate_z(90 + angle) 3789 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3790 cmt.shift(x1 * (1 + padding2), 0, 0) 3791 poly = merge(br, cmt).polydata() 3792 3793 else: 3794 poly = br.polydata() 3795 3796 tr = vtk.vtkTransform() 3797 tr.RotateZ(angler) 3798 tr.Translate(padding1 * d, 0, 0) 3799 pscale = 1 3800 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3801 tf = vtk.vtkTransformPolyDataFilter() 3802 tf.SetInputData(poly) 3803 tf.SetTransform(tr) 3804 tf.Update() 3805 poly = tf.GetOutput() 3806 3807 Mesh.__init__(self, poly, c, alpha) 3808 self.SetPosition(mq) 3809 self.name = "Brace" 3810 self.base = q1 3811 self.top = q2
Create a brace (bracket) shape.
3692 def __init__( 3693 self, 3694 q1, 3695 q2, 3696 style="}", 3697 padding1=0.0, 3698 font="Theemim", 3699 comment="", 3700 justify=None, 3701 angle=0.0, 3702 padding2=0.2, 3703 s=1.0, 3704 italic=0, 3705 c="k1", 3706 alpha=1.0, 3707 ): 3708 """ 3709 Create a brace (bracket) shape which spans from point q1 to point q2. 3710 3711 Arguments: 3712 q1 : (list) 3713 point 1. 3714 q2 : (list) 3715 point 2. 3716 style : (str) 3717 style of the bracket, eg. `{}, [], (), <>`. 3718 padding1 : (float) 3719 padding space in percent form the input points. 3720 font : (str) 3721 font type 3722 comment : (str) 3723 additional text to appear next to the brace symbol. 3724 justify : (str) 3725 specify the anchor point to justify text comment, e.g. "top-left". 3726 italic : float 3727 italicness of the text comment (can be a positive or negative number) 3728 angle : (float) 3729 rotation angle of text. Use `None` to keep it horizontal. 3730 padding2 : (float) 3731 padding space in percent form brace to text comment. 3732 s : (float) 3733 scale factor for the comment 3734 3735 Examples: 3736 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3737 3738 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3739 """ 3740 if isinstance(q1, vtk.vtkActor): 3741 q1 = q1.GetPosition() 3742 if isinstance(q2, vtk.vtkActor): 3743 q2 = q2.GetPosition() 3744 if len(q1) == 2: 3745 q1 = [q1[0], q1[1], 0.0] 3746 if len(q2) == 2: 3747 q2 = [q2[0], q2[1], 0.0] 3748 q1 = np.array(q1, dtype=float) 3749 q2 = np.array(q2, dtype=float) 3750 mq = (q1 + q2) / 2 3751 q1 = q1 - mq 3752 q2 = q2 - mq 3753 d = np.linalg.norm(q2 - q1) 3754 q2[2] = q1[2] 3755 3756 if style not in "{}[]()<>|I": 3757 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3758 style = "}" 3759 3760 flip = False 3761 if style in ["{", "[", "(", "<"]: 3762 flip = True 3763 i = ["{", "[", "(", "<"].index(style) 3764 style = ["}", "]", ")", ">"][i] 3765 3766 br = Text3D(style, font="Theemim", justify="center-left") 3767 br.scale([0.4, 1, 1]) 3768 3769 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3770 if flip: 3771 angler += 180 3772 3773 _, x1, y0, y1, _, _ = br.bounds() 3774 if comment: 3775 just = "center-top" 3776 if angle is None: 3777 angle = -angler + 90 3778 if not flip: 3779 angle += 180 3780 3781 if flip: 3782 angle += 180 3783 just = "center-bottom" 3784 if justify is not None: 3785 just = justify 3786 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3787 cx0, cx1 = cmt.xbounds() 3788 cmt.rotate_z(90 + angle) 3789 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3790 cmt.shift(x1 * (1 + padding2), 0, 0) 3791 poly = merge(br, cmt).polydata() 3792 3793 else: 3794 poly = br.polydata() 3795 3796 tr = vtk.vtkTransform() 3797 tr.RotateZ(angler) 3798 tr.Translate(padding1 * d, 0, 0) 3799 pscale = 1 3800 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3801 tf = vtk.vtkTransformPolyDataFilter() 3802 tf.SetInputData(poly) 3803 tf.SetTransform(tr) 3804 tf.Update() 3805 poly = tf.GetOutput() 3806 3807 Mesh.__init__(self, poly, c, alpha) 3808 self.SetPosition(mq) 3809 self.name = "Brace" 3810 self.base = q1 3811 self.top = q2
Create a brace (bracket) shape which spans from point q1 to point q2.
Arguments:
- q1 : (list) point 1.
- q2 : (list) point 2.
- style : (str)
style of the bracket, eg.
{}, [], (), <>
. - padding1 : (float) padding space in percent form the input points.
- font : (str) font type
- comment : (str) additional text to appear next to the brace symbol.
- justify : (str) specify the anchor point to justify text comment, e.g. "top-left".
- italic : float italicness of the text comment (can be a positive or negative number)
- angle : (float)
rotation angle of text. Use
None
to keep it horizontal. - padding2 : (float) padding space in percent form brace to text comment.
- s : (float) scale factor for the comment
Examples:
1301class NormalLines(Mesh): 1302 """ 1303 Build an `Glyph` to show the normals at cell centers or at mesh vertices. 1304 1305 Arguments: 1306 ratio : (int) 1307 show 1 normal every `ratio` cells. 1308 on : (str) 1309 either "cells" or "points". 1310 scale : (float) 1311 scale factor to control size. 1312 """ 1313 1314 def __init__(self, msh, ratio=1, on="cells", scale=1.0): 1315 1316 poly = msh.clone().compute_normals().polydata() 1317 1318 if "cell" in on: 1319 centers = vtk.vtkCellCenters() 1320 centers.SetInputData(poly) 1321 centers.Update() 1322 poly = centers.GetOutput() 1323 1324 mask_pts = vtk.vtkMaskPoints() 1325 mask_pts.SetInputData(poly) 1326 mask_pts.SetOnRatio(ratio) 1327 mask_pts.RandomModeOff() 1328 mask_pts.Update() 1329 1330 ln = vtk.vtkLineSource() 1331 ln.SetPoint1(0, 0, 0) 1332 ln.SetPoint2(1, 0, 0) 1333 ln.Update() 1334 glyph = vtk.vtkGlyph3D() 1335 glyph.SetSourceData(ln.GetOutput()) 1336 glyph.SetInputData(mask_pts.GetOutput()) 1337 glyph.SetVectorModeToUseNormal() 1338 1339 b = poly.GetBounds() 1340 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1341 glyph.SetScaleFactor(f) 1342 glyph.OrientOn() 1343 glyph.Update() 1344 1345 Mesh.__init__(self, glyph.GetOutput()) 1346 1347 self.PickableOff() 1348 prop = vtk.vtkProperty() 1349 prop.DeepCopy(msh.GetProperty()) 1350 self.SetProperty(prop) 1351 self.property = prop 1352 self.property.LightingOff() 1353 self.mapper().ScalarVisibilityOff() 1354 self.name = "NormalLines"
Build an Glyph
to show the normals at cell centers or at mesh vertices.
Arguments:
- ratio : (int)
show 1 normal every
ratio
cells. - on : (str) either "cells" or "points".
- scale : (float) scale factor to control size.
1314 def __init__(self, msh, ratio=1, on="cells", scale=1.0): 1315 1316 poly = msh.clone().compute_normals().polydata() 1317 1318 if "cell" in on: 1319 centers = vtk.vtkCellCenters() 1320 centers.SetInputData(poly) 1321 centers.Update() 1322 poly = centers.GetOutput() 1323 1324 mask_pts = vtk.vtkMaskPoints() 1325 mask_pts.SetInputData(poly) 1326 mask_pts.SetOnRatio(ratio) 1327 mask_pts.RandomModeOff() 1328 mask_pts.Update() 1329 1330 ln = vtk.vtkLineSource() 1331 ln.SetPoint1(0, 0, 0) 1332 ln.SetPoint2(1, 0, 0) 1333 ln.Update() 1334 glyph = vtk.vtkGlyph3D() 1335 glyph.SetSourceData(ln.GetOutput()) 1336 glyph.SetInputData(mask_pts.GetOutput()) 1337 glyph.SetVectorModeToUseNormal() 1338 1339 b = poly.GetBounds() 1340 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1341 glyph.SetScaleFactor(f) 1342 glyph.OrientOn() 1343 glyph.Update() 1344 1345 Mesh.__init__(self, glyph.GetOutput()) 1346 1347 self.PickableOff() 1348 prop = vtk.vtkProperty() 1349 prop.DeepCopy(msh.GetProperty()) 1350 self.SetProperty(prop) 1351 self.property = prop 1352 self.property.LightingOff() 1353 self.mapper().ScalarVisibilityOff() 1354 self.name = "NormalLines"
Input can be a list of vertices and their connectivity (faces of the polygonal mesh),
or directly a vtkPolydata
object.
For point clouds - e.i. no faces - just substitute the faces
list with None
.
Example:
Mesh( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )
Arguments:
- c : (color) color in RGB format, hex, symbol or name
- alpha : (float) mesh opacity [0,1]
Examples:
- buildmesh.py (and many others!)
1417def StreamLines( 1418 domain, 1419 probe, 1420 active_vectors="", 1421 integrator="rk4", 1422 direction="forward", 1423 initial_step_size=None, 1424 max_propagation=None, 1425 max_steps=10000, 1426 step_length=None, 1427 extrapolate_to_box=(), 1428 surface_constrained=False, 1429 compute_vorticity=False, 1430 ribbons=None, 1431 tubes=(), 1432 scalar_range=None, 1433 lw=None, 1434 **opts, 1435): 1436 """ 1437 Integrate a vector field on a domain (a Points/Mesh or other vtk datasets types) 1438 to generate streamlines. 1439 1440 The integration is performed using a specified integrator (Runge-Kutta). 1441 The length of a streamline is governed by specifying a maximum value either 1442 in physical arc length or in (local) cell length. 1443 Otherwise, the integration terminates upon exiting the field domain. 1444 1445 Arguments: 1446 domain : (Points, Volume, vtkDataSet) 1447 the object that contains the vector field 1448 probe : (Mesh, list) 1449 the Mesh that probes the domain. Its coordinates will 1450 be the seeds for the streamlines, can also be an array of positions. 1451 active_vectors : (str) 1452 name of the vector array to be used 1453 integrator : (str) 1454 Runge-Kutta integrator, either 'rk2', 'rk4' of 'rk45' 1455 initial_step_size : (float) 1456 initial step size of integration 1457 max_propagation : (float) 1458 maximum physical length of the streamline 1459 max_steps : (int) 1460 maximum nr of steps allowed 1461 step_length : (float) 1462 length of step integration. 1463 extrapolate_to_box : (dict) 1464 Vectors that are defined on a discrete set of points 1465 are extrapolated to a 3D domain defined by its bounding box: 1466 - bounds (list), bounding box of the domain 1467 - kernel (str), interpolation kernel `["shepard","gaussian","voronoi","linear"]` 1468 - radius (float), radius of the local search 1469 - dims (list), nr of subdivisions of the domain along x, y, and z 1470 - null_value (float), value to be assigned to invalid points 1471 1472 surface_constrained : (bool) 1473 force streamlines to be computed on a surface 1474 compute_vorticity : (bool) 1475 Turn on/off vorticity computation at streamline points 1476 (necessary for generating proper stream-ribbons) 1477 ribbons : (int) 1478 render lines as ribbons by joining them. 1479 An integer value represent the ratio of joining (e.g.: ribbons=2 groups lines 2 by 2) 1480 1481 tubes : (dict) 1482 dictionary containing the parameters for the tube representation: 1483 - ratio (int), draws tube as longitudinal stripes 1484 - res (int), tube resolution (nr. of sides, 12 by default) 1485 - max_radius_factor (float), max tube radius as a multiple of the min radius 1486 - cap (bool), capping of the tube 1487 - mode (int), radius varies based on the scalar or vector magnitude: 1488 - 0 do not vary radius 1489 - 1 vary radius by scalar 1490 - 2 vary radius by vector 1491 - 3 vary radius by absolute value of scalar 1492 - 4 vary radius by vector norm 1493 1494 scalar_range : (list) 1495 specify the scalar range for coloring 1496 1497 Examples: 1498 - [streamlines1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamlines1.py) 1499 - [streamlines2.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamlines2.py) 1500 1501 ![](https://vedo.embl.es/images/volumetric/81459343-b9210d00-919f-11ea-846c-152d62cba06e.png) 1502 1503 - [streamribbons.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/streamribbons.py) 1504 - [office.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/office.py) 1505 1506 ![](https://vedo.embl.es/images/volumetric/56964003-9145a500-6b5a-11e9-9d9e-9736d90e1900.png) 1507 """ 1508 if len(opts): # Deprecations 1509 printc(" Warning! In StreamLines() unrecognized keywords:", opts, c="y") 1510 initial_step_size = opts.pop("initialStepSize", initial_step_size) 1511 max_propagation = opts.pop("maxPropagation", max_propagation) 1512 max_steps = opts.pop("maxSteps", max_steps) 1513 step_length = opts.pop("stepLength", step_length) 1514 extrapolate_to_box = opts.pop("extrapolateToBox", extrapolate_to_box) 1515 surface_constrained = opts.pop("surfaceConstrained", surface_constrained) 1516 compute_vorticity = opts.pop("computeVorticity", compute_vorticity) 1517 scalar_range = opts.pop("scalarRange", scalar_range) 1518 printc(" Please use 'snake_case' instead of 'camelCase' keywords", c="y") 1519 1520 if isinstance(domain, vedo.Points): 1521 if extrapolate_to_box: 1522 grid = _interpolate2vol(domain.polydata(), **extrapolate_to_box) 1523 else: 1524 grid = domain.polydata() 1525 elif isinstance(domain, vedo.BaseVolume): 1526 grid = domain.inputdata() 1527 else: 1528 grid = domain 1529 1530 if active_vectors: 1531 grid.GetPointData().SetActiveVectors(active_vectors) 1532 1533 b = grid.GetBounds() 1534 size = (b[5] - b[4] + b[3] - b[2] + b[1] - b[0]) / 3 1535 if initial_step_size is None: 1536 initial_step_size = size / 500.0 1537 if max_propagation is None: 1538 max_propagation = size 1539 1540 if utils.is_sequence(probe): 1541 pts = utils.make3d(probe) 1542 else: 1543 pts = probe.clean().points() 1544 1545 src = vtk.vtkProgrammableSource() 1546 1547 def read_points(): 1548 output = src.GetPolyDataOutput() 1549 points = vtk.vtkPoints() 1550 for x, y, z in pts: 1551 points.InsertNextPoint(x, y, z) 1552 output.SetPoints(points) 1553 1554 src.SetExecuteMethod(read_points) 1555 src.Update() 1556 1557 st = vtk.vtkStreamTracer() 1558 st.SetInputDataObject(grid) 1559 st.SetSourceConnection(src.GetOutputPort()) 1560 1561 st.SetInitialIntegrationStep(initial_step_size) 1562 st.SetComputeVorticity(compute_vorticity) 1563 st.SetMaximumNumberOfSteps(max_steps) 1564 st.SetMaximumPropagation(max_propagation) 1565 st.SetSurfaceStreamlines(surface_constrained) 1566 if step_length: 1567 st.SetMaximumIntegrationStep(step_length) 1568 1569 if "f" in direction: 1570 st.SetIntegrationDirectionToForward() 1571 elif "back" in direction: 1572 st.SetIntegrationDirectionToBackward() 1573 elif "both" in direction: 1574 st.SetIntegrationDirectionToBoth() 1575 1576 if integrator == "rk2": 1577 st.SetIntegratorTypeToRungeKutta2() 1578 elif integrator == "rk4": 1579 st.SetIntegratorTypeToRungeKutta4() 1580 elif integrator == "rk45": 1581 st.SetIntegratorTypeToRungeKutta45() 1582 else: 1583 vedo.logger.error(f"in streamlines, unknown integrator {integrator}") 1584 1585 st.Update() 1586 output = st.GetOutput() 1587 1588 if ribbons: 1589 scalar_surface = vtk.vtkRuledSurfaceFilter() 1590 scalar_surface.SetInputConnection(st.GetOutputPort()) 1591 scalar_surface.SetOnRatio(int(ribbons)) 1592 scalar_surface.SetRuledModeToPointWalk() 1593 scalar_surface.Update() 1594 output = scalar_surface.GetOutput() 1595 1596 if tubes: 1597 radius = tubes.pop("radius", domain.GetLength() / 500) 1598 res = tubes.pop("res", 24) 1599 radfact = tubes.pop("max_radius_factor", 10) 1600 ratio = tubes.pop("ratio", 1) 1601 mode = tubes.pop("mode", 0) 1602 cap = tubes.pop("mode", False) 1603 if tubes: 1604 vedo.logger.warning(f"in StreamLines unknown 'tubes' parameters: {tubes}") 1605 1606 stream_tube = vtk.vtkTubeFilter() 1607 stream_tube.SetNumberOfSides(res) 1608 stream_tube.SetRadius(radius) 1609 stream_tube.SetCapping(cap) 1610 # max tube radius as a multiple of the min radius 1611 stream_tube.SetRadiusFactor(radfact) 1612 stream_tube.SetOnRatio(int(ratio)) 1613 stream_tube.SetVaryRadius(int(mode)) 1614 1615 stream_tube.SetInputData(output) 1616 vname = grid.GetPointData().GetVectors().GetName() 1617 stream_tube.SetInputArrayToProcess( 1618 1, 0, 0, vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS, vname 1619 ) 1620 stream_tube.Update() 1621 sta = vedo.mesh.Mesh(stream_tube.GetOutput(), c=None) 1622 1623 scals = grid.GetPointData().GetScalars() 1624 if scals: 1625 sta.mapper().SetScalarRange(scals.GetRange()) 1626 if scalar_range is not None: 1627 sta.mapper().SetScalarRange(scalar_range) 1628 1629 sta.phong() 1630 sta.name = "StreamLines" 1631 ############# 1632 return sta ############# 1633 ############# 1634 1635 sta = vedo.mesh.Mesh(output, c=None) 1636 1637 if lw is not None and len(tubes) == 0 and not ribbons: 1638 sta.lw(lw) 1639 sta.mapper().SetResolveCoincidentTopologyToPolygonOffset() 1640 sta.lighting("off") 1641 1642 scals = grid.GetPointData().GetScalars() 1643 if scals: 1644 sta.mapper().SetScalarRange(scals.GetRange()) 1645 if scalar_range is not None: 1646 sta.mapper().SetScalarRange(scalar_range) 1647 1648 sta.name = "StreamLines" 1649 return sta
Integrate a vector field on a domain (a Points/Mesh or other vtk datasets types) to generate streamlines.
The integration is performed using a specified integrator (Runge-Kutta). The length of a streamline is governed by specifying a maximum value either in physical arc length or in (local) cell length. Otherwise, the integration terminates upon exiting the field domain.
Arguments:
- domain : (Points, Volume, vtkDataSet) the object that contains the vector field
- probe : (Mesh, list) the Mesh that probes the domain. Its coordinates will be the seeds for the streamlines, can also be an array of positions.
- active_vectors : (str) name of the vector array to be used
- integrator : (str) Runge-Kutta integrator, either 'rk2', 'rk4' of 'rk45'
- initial_step_size : (float) initial step size of integration
- max_propagation : (float) maximum physical length of the streamline
- max_steps : (int) maximum nr of steps allowed
- step_length : (float) length of step integration.
- extrapolate_to_box : (dict)
Vectors that are defined on a discrete set of points
are extrapolated to a 3D domain defined by its bounding box:
- bounds (list), bounding box of the domain
- kernel (str), interpolation kernel
["shepard","gaussian","voronoi","linear"]
- radius (float), radius of the local search - dims (list), nr of subdivisions of the domain along x, y, and z - null_value (float), value to be assigned to invalid points - surface_constrained : (bool) force streamlines to be computed on a surface
- compute_vorticity : (bool) Turn on/off vorticity computation at streamline points (necessary for generating proper stream-ribbons)
- ribbons : (int) render lines as ribbons by joining them. An integer value represent the ratio of joining (e.g.: ribbons=2 groups lines 2 by 2)
- tubes : (dict)
dictionary containing the parameters for the tube representation:
- ratio (int), draws tube as longitudinal stripes
- res (int), tube resolution (nr. of sides, 12 by default)
- max_radius_factor (float), max tube radius as a multiple of the min radius
- cap (bool), capping of the tube
- mode (int), radius varies based on the scalar or vector magnitude:
- 0 do not vary radius
- 1 vary radius by scalar
- 2 vary radius by vector
- 3 vary radius by absolute value of scalar
- 4 vary radius by vector norm
- scalar_range : (list) specify the scalar range for coloring
Examples:
1781class Ribbon(Mesh): 1782 """ 1783 Connect two lines to generate the surface inbetween. 1784 Set the mode by which to create the ruled surface. 1785 1786 It also works with a single line in input. In this case the ribbon 1787 is formed by following the local plane of the line in space. 1788 """ 1789 1790 def __init__( 1791 self, 1792 line1, 1793 line2=None, 1794 mode=0, 1795 closed=False, 1796 width=None, 1797 res=(200, 5), 1798 c="indigo3", 1799 alpha=1.0, 1800 ): 1801 """ 1802 Arguments: 1803 mode : (int) 1804 If mode=0, resample evenly the input lines (based on length) 1805 and generates triangle strips. 1806 1807 If mode=1, use the existing points and walks around the 1808 polyline using existing points. 1809 1810 closed : (bool) 1811 if True, join the last point with the first to form a closed surface 1812 1813 res : (list) 1814 ribbon resolutions along the line and perpendicularly to it. 1815 1816 Examples: 1817 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1818 1819 ![](https://vedo.embl.es/images/basic/ribbon.png) 1820 """ 1821 1822 if isinstance(line1, Points): 1823 line1 = line1.points() 1824 1825 if isinstance(line2, Points): 1826 line2 = line2.points() 1827 1828 elif line2 is None: 1829 ############################################# 1830 ribbon_filter = vtk.vtkRibbonFilter() 1831 aline = Line(line1) 1832 ribbon_filter.SetInputData(aline.polydata()) 1833 if width is None: 1834 width = aline.diagonal_size() / 20.0 1835 ribbon_filter.SetWidth(width) 1836 ribbon_filter.Update() 1837 Mesh.__init__(self, ribbon_filter.GetOutput(), c, alpha) 1838 self.name = "Ribbon" 1839 ############################################## 1840 return ###################################### 1841 ############################################## 1842 1843 line1 = np.asarray(line1) 1844 line2 = np.asarray(line2) 1845 1846 if closed: 1847 line1 = line1.tolist() 1848 line1 += [line1[0]] 1849 line2 = line2.tolist() 1850 line2 += [line2[0]] 1851 line1 = np.array(line1) 1852 line2 = np.array(line2) 1853 1854 if len(line1[0]) == 2: 1855 line1 = np.c_[line1, np.zeros(len(line1))] 1856 if len(line2[0]) == 2: 1857 line2 = np.c_[line2, np.zeros(len(line2))] 1858 1859 ppoints1 = vtk.vtkPoints() # Generate the polyline1 1860 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1861 lines1 = vtk.vtkCellArray() 1862 lines1.InsertNextCell(len(line1)) 1863 for i in range(len(line1)): 1864 lines1.InsertCellPoint(i) 1865 poly1 = vtk.vtkPolyData() 1866 poly1.SetPoints(ppoints1) 1867 poly1.SetLines(lines1) 1868 1869 ppoints2 = vtk.vtkPoints() # Generate the polyline2 1870 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1871 lines2 = vtk.vtkCellArray() 1872 lines2.InsertNextCell(len(line2)) 1873 for i in range(len(line2)): 1874 lines2.InsertCellPoint(i) 1875 poly2 = vtk.vtkPolyData() 1876 poly2.SetPoints(ppoints2) 1877 poly2.SetLines(lines2) 1878 1879 # build the lines 1880 lines1 = vtk.vtkCellArray() 1881 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1882 for i in range(poly1.GetNumberOfPoints()): 1883 lines1.InsertCellPoint(i) 1884 1885 polygon1 = vtk.vtkPolyData() 1886 polygon1.SetPoints(ppoints1) 1887 polygon1.SetLines(lines1) 1888 1889 lines2 = vtk.vtkCellArray() 1890 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1891 for i in range(poly2.GetNumberOfPoints()): 1892 lines2.InsertCellPoint(i) 1893 1894 polygon2 = vtk.vtkPolyData() 1895 polygon2.SetPoints(ppoints2) 1896 polygon2.SetLines(lines2) 1897 1898 merged_pd = vtk.vtkAppendPolyData() 1899 merged_pd.AddInputData(polygon1) 1900 merged_pd.AddInputData(polygon2) 1901 merged_pd.Update() 1902 1903 rsf = vtk.vtkRuledSurfaceFilter() 1904 rsf.CloseSurfaceOff() 1905 rsf.SetRuledMode(mode) 1906 rsf.SetResolution(res[0], res[1]) 1907 rsf.SetInputData(merged_pd.GetOutput()) 1908 rsf.Update() 1909 1910 Mesh.__init__(self, rsf.GetOutput(), c, alpha) 1911 self.name = "Ribbon"
Connect two lines to generate the surface inbetween. Set the mode by which to create the ruled surface.
It also works with a single line in input. In this case the ribbon is formed by following the local plane of the line in space.
1790 def __init__( 1791 self, 1792 line1, 1793 line2=None, 1794 mode=0, 1795 closed=False, 1796 width=None, 1797 res=(200, 5), 1798 c="indigo3", 1799 alpha=1.0, 1800 ): 1801 """ 1802 Arguments: 1803 mode : (int) 1804 If mode=0, resample evenly the input lines (based on length) 1805 and generates triangle strips. 1806 1807 If mode=1, use the existing points and walks around the 1808 polyline using existing points. 1809 1810 closed : (bool) 1811 if True, join the last point with the first to form a closed surface 1812 1813 res : (list) 1814 ribbon resolutions along the line and perpendicularly to it. 1815 1816 Examples: 1817 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1818 1819 ![](https://vedo.embl.es/images/basic/ribbon.png) 1820 """ 1821 1822 if isinstance(line1, Points): 1823 line1 = line1.points() 1824 1825 if isinstance(line2, Points): 1826 line2 = line2.points() 1827 1828 elif line2 is None: 1829 ############################################# 1830 ribbon_filter = vtk.vtkRibbonFilter() 1831 aline = Line(line1) 1832 ribbon_filter.SetInputData(aline.polydata()) 1833 if width is None: 1834 width = aline.diagonal_size() / 20.0 1835 ribbon_filter.SetWidth(width) 1836 ribbon_filter.Update() 1837 Mesh.__init__(self, ribbon_filter.GetOutput(), c, alpha) 1838 self.name = "Ribbon" 1839 ############################################## 1840 return ###################################### 1841 ############################################## 1842 1843 line1 = np.asarray(line1) 1844 line2 = np.asarray(line2) 1845 1846 if closed: 1847 line1 = line1.tolist() 1848 line1 += [line1[0]] 1849 line2 = line2.tolist() 1850 line2 += [line2[0]] 1851 line1 = np.array(line1) 1852 line2 = np.array(line2) 1853 1854 if len(line1[0]) == 2: 1855 line1 = np.c_[line1, np.zeros(len(line1))] 1856 if len(line2[0]) == 2: 1857 line2 = np.c_[line2, np.zeros(len(line2))] 1858 1859 ppoints1 = vtk.vtkPoints() # Generate the polyline1 1860 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1861 lines1 = vtk.vtkCellArray() 1862 lines1.InsertNextCell(len(line1)) 1863 for i in range(len(line1)): 1864 lines1.InsertCellPoint(i) 1865 poly1 = vtk.vtkPolyData() 1866 poly1.SetPoints(ppoints1) 1867 poly1.SetLines(lines1) 1868 1869 ppoints2 = vtk.vtkPoints() # Generate the polyline2 1870 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1871 lines2 = vtk.vtkCellArray() 1872 lines2.InsertNextCell(len(line2)) 1873 for i in range(len(line2)): 1874 lines2.InsertCellPoint(i) 1875 poly2 = vtk.vtkPolyData() 1876 poly2.SetPoints(ppoints2) 1877 poly2.SetLines(lines2) 1878 1879 # build the lines 1880 lines1 = vtk.vtkCellArray() 1881 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1882 for i in range(poly1.GetNumberOfPoints()): 1883 lines1.InsertCellPoint(i) 1884 1885 polygon1 = vtk.vtkPolyData() 1886 polygon1.SetPoints(ppoints1) 1887 polygon1.SetLines(lines1) 1888 1889 lines2 = vtk.vtkCellArray() 1890 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1891 for i in range(poly2.GetNumberOfPoints()): 1892 lines2.InsertCellPoint(i) 1893 1894 polygon2 = vtk.vtkPolyData() 1895 polygon2.SetPoints(ppoints2) 1896 polygon2.SetLines(lines2) 1897 1898 merged_pd = vtk.vtkAppendPolyData() 1899 merged_pd.AddInputData(polygon1) 1900 merged_pd.AddInputData(polygon2) 1901 merged_pd.Update() 1902 1903 rsf = vtk.vtkRuledSurfaceFilter() 1904 rsf.CloseSurfaceOff() 1905 rsf.SetRuledMode(mode) 1906 rsf.SetResolution(res[0], res[1]) 1907 rsf.SetInputData(merged_pd.GetOutput()) 1908 rsf.Update() 1909 1910 Mesh.__init__(self, rsf.GetOutput(), c, alpha) 1911 self.name = "Ribbon"
Arguments:
mode : (int) If mode=0, resample evenly the input lines (based on length) and generates triangle strips.
If mode=1, use the existing points and walks around the polyline using existing points.
- closed : (bool) if True, join the last point with the first to form a closed surface
- res : (list) ribbon resolutions along the line and perpendicularly to it.
Examples:
1914class Arrow(Mesh): 1915 """ 1916 Build a 3D arrow from `start_pt` to `end_pt` of section size `s`, 1917 expressed as the fraction of the window size. 1918 """ 1919 1920 def __init__( 1921 self, 1922 start_pt=(0, 0, 0), 1923 end_pt=(1, 0, 0), 1924 s=None, 1925 shaft_radius=None, 1926 head_radius=None, 1927 head_length=None, 1928 res=12, 1929 c="r4", 1930 alpha=1.0, 1931 ): 1932 """ 1933 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1934 from white to red. 1935 1936 .. note:: If `s=None` the arrow is scaled proportionally to its length 1937 1938 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1939 """ 1940 # in case user is passing meshs 1941 if isinstance(start_pt, vtk.vtkActor): 1942 start_pt = start_pt.GetPosition() 1943 if isinstance(end_pt, vtk.vtkActor): 1944 end_pt = end_pt.GetPosition() 1945 1946 axis = np.asarray(end_pt) - np.asarray(start_pt) 1947 length = np.linalg.norm(axis) 1948 if length: 1949 axis = axis / length 1950 if len(axis) < 3: # its 2d 1951 theta = np.pi / 2 1952 start_pt = [start_pt[0], start_pt[1], 0.0] 1953 end_pt = [end_pt[0], end_pt[1], 0.0] 1954 else: 1955 theta = np.arccos(axis[2]) 1956 phi = np.arctan2(axis[1], axis[0]) 1957 self.source = vtk.vtkArrowSource() 1958 self.source.SetShaftResolution(res) 1959 self.source.SetTipResolution(res) 1960 1961 if s: 1962 sz = 0.02 1963 self.source.SetTipRadius(sz) 1964 self.source.SetShaftRadius(sz / 1.75) 1965 self.source.SetTipLength(sz * 15) 1966 1967 # if s: 1968 # sz = 0.02 * s * length 1969 # tl = sz / 20 1970 # print(s, sz) 1971 # self.source.SetShaftRadius(sz) 1972 # self.source.SetTipRadius(sz*1.75) 1973 # self.source.SetTipLength(sz*15) 1974 1975 if head_length: 1976 self.source.SetTipLength(head_length) 1977 if head_radius: 1978 self.source.SetTipRadius(head_radius) 1979 if shaft_radius: 1980 self.source.SetShaftRadius(shaft_radius) 1981 1982 self.source.Update() 1983 1984 t = vtk.vtkTransform() 1985 t.RotateZ(np.rad2deg(phi)) 1986 t.RotateY(np.rad2deg(theta)) 1987 t.RotateY(-90) # put it along Z 1988 if s: 1989 sz = 800 * s 1990 t.Scale(length, sz, sz) 1991 else: 1992 t.Scale(length, length, length) 1993 tf = vtk.vtkTransformPolyDataFilter() 1994 tf.SetInputData(self.source.GetOutput()) 1995 tf.SetTransform(t) 1996 tf.Update() 1997 1998 Mesh.__init__(self, tf.GetOutput(), c, alpha) 1999 2000 self.phong().lighting("plastic") 2001 self.SetPosition(start_pt) 2002 self.PickableOff() 2003 self.DragableOff() 2004 self.base = np.array(start_pt, dtype=float) 2005 self.top = np.array(end_pt, dtype=float) 2006 self.tip_index = None 2007 self.fill = True # used by pyplot.__iadd__() 2008 self.s = s if s is not None else 1 ## used by pyplot.__iadd() 2009 self.name = "Arrow" 2010 2011 def tip_point(self, return_index=False): 2012 """Return the coordinates of the tip of the Arrow, or the point index.""" 2013 if self.tip_index is None: 2014 arrpts = utils.vtk2numpy(self.source.GetOutput().GetPoints().GetData()) 2015 self.tip_index = np.argmax(arrpts[:, 0]) 2016 if return_index: 2017 return self.tip_index 2018 return self.points()[self.tip_index]
Build a 3D arrow from start_pt
to end_pt
of section size s
,
expressed as the fraction of the window size.
1920 def __init__( 1921 self, 1922 start_pt=(0, 0, 0), 1923 end_pt=(1, 0, 0), 1924 s=None, 1925 shaft_radius=None, 1926 head_radius=None, 1927 head_length=None, 1928 res=12, 1929 c="r4", 1930 alpha=1.0, 1931 ): 1932 """ 1933 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1934 from white to red. 1935 1936 .. note:: If `s=None` the arrow is scaled proportionally to its length 1937 1938 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1939 """ 1940 # in case user is passing meshs 1941 if isinstance(start_pt, vtk.vtkActor): 1942 start_pt = start_pt.GetPosition() 1943 if isinstance(end_pt, vtk.vtkActor): 1944 end_pt = end_pt.GetPosition() 1945 1946 axis = np.asarray(end_pt) - np.asarray(start_pt) 1947 length = np.linalg.norm(axis) 1948 if length: 1949 axis = axis / length 1950 if len(axis) < 3: # its 2d 1951 theta = np.pi / 2 1952 start_pt = [start_pt[0], start_pt[1], 0.0] 1953 end_pt = [end_pt[0], end_pt[1], 0.0] 1954 else: 1955 theta = np.arccos(axis[2]) 1956 phi = np.arctan2(axis[1], axis[0]) 1957 self.source = vtk.vtkArrowSource() 1958 self.source.SetShaftResolution(res) 1959 self.source.SetTipResolution(res) 1960 1961 if s: 1962 sz = 0.02 1963 self.source.SetTipRadius(sz) 1964 self.source.SetShaftRadius(sz / 1.75) 1965 self.source.SetTipLength(sz * 15) 1966 1967 # if s: 1968 # sz = 0.02 * s * length 1969 # tl = sz / 20 1970 # print(s, sz) 1971 # self.source.SetShaftRadius(sz) 1972 # self.source.SetTipRadius(sz*1.75) 1973 # self.source.SetTipLength(sz*15) 1974 1975 if head_length: 1976 self.source.SetTipLength(head_length) 1977 if head_radius: 1978 self.source.SetTipRadius(head_radius) 1979 if shaft_radius: 1980 self.source.SetShaftRadius(shaft_radius) 1981 1982 self.source.Update() 1983 1984 t = vtk.vtkTransform() 1985 t.RotateZ(np.rad2deg(phi)) 1986 t.RotateY(np.rad2deg(theta)) 1987 t.RotateY(-90) # put it along Z 1988 if s: 1989 sz = 800 * s 1990 t.Scale(length, sz, sz) 1991 else: 1992 t.Scale(length, length, length) 1993 tf = vtk.vtkTransformPolyDataFilter() 1994 tf.SetInputData(self.source.GetOutput()) 1995 tf.SetTransform(t) 1996 tf.Update() 1997 1998 Mesh.__init__(self, tf.GetOutput(), c, alpha) 1999 2000 self.phong().lighting("plastic") 2001 self.SetPosition(start_pt) 2002 self.PickableOff() 2003 self.DragableOff() 2004 self.base = np.array(start_pt, dtype=float) 2005 self.top = np.array(end_pt, dtype=float) 2006 self.tip_index = None 2007 self.fill = True # used by pyplot.__iadd__() 2008 self.s = s if s is not None else 1 ## used by pyplot.__iadd() 2009 self.name = "Arrow"
If c
is a float
less than 1, the arrow is rendered as a in a color scale
from white to red.
If s=None
the arrow is scaled proportionally to its length
2011 def tip_point(self, return_index=False): 2012 """Return the coordinates of the tip of the Arrow, or the point index.""" 2013 if self.tip_index is None: 2014 arrpts = utils.vtk2numpy(self.source.GetOutput().GetPoints().GetData()) 2015 self.tip_index = np.argmax(arrpts[:, 0]) 2016 if return_index: 2017 return self.tip_index 2018 return self.points()[self.tip_index]
Return the coordinates of the tip of the Arrow, or the point index.
2021class Arrows(Glyph): 2022 """ 2023 Build arrows between two lists of points. 2024 """ 2025 2026 def __init__( 2027 self, 2028 start_pts, 2029 end_pts=None, 2030 s=None, 2031 shaft_radius=None, 2032 head_radius=None, 2033 head_length=None, 2034 thickness=1.0, 2035 res=12, 2036 c=None, 2037 alpha=1.0, 2038 ): 2039 """ 2040 Build arrows between two lists of points `start_pts` and `end_pts`. 2041 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2042 2043 Color can be specified as a colormap which maps the size of the arrows. 2044 2045 Arguments: 2046 s : (float) 2047 fix aspect-ratio of the arrow and scale its cross section 2048 c : (color) 2049 color or color map name 2050 alpha : (float) 2051 set object opacity 2052 res : (int) 2053 set arrow resolution 2054 2055 Examples: 2056 - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py) 2057 2058 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 2059 """ 2060 if isinstance(start_pts, Points): 2061 start_pts = start_pts.points() 2062 if isinstance(end_pts, Points): 2063 end_pts = end_pts.points() 2064 2065 start_pts = np.asarray(start_pts) 2066 if end_pts is None: 2067 strt = start_pts[:, 0] 2068 end_pts = start_pts[:, 1] 2069 start_pts = strt 2070 else: 2071 end_pts = np.asarray(end_pts) 2072 2073 start_pts = utils.make3d(start_pts) 2074 end_pts = utils.make3d(end_pts) 2075 2076 arr = vtk.vtkArrowSource() 2077 arr.SetShaftResolution(res) 2078 arr.SetTipResolution(res) 2079 2080 if s: 2081 sz = 0.02 * s 2082 arr.SetTipRadius(sz * 2) 2083 arr.SetShaftRadius(sz * thickness) 2084 arr.SetTipLength(sz * 10) 2085 2086 if head_radius: 2087 arr.SetTipRadius(head_radius) 2088 if shaft_radius: 2089 arr.SetShaftRadius(shaft_radius) 2090 if head_length: 2091 arr.SetTipLength(head_length) 2092 2093 arr.Update() 2094 out = arr.GetOutput() 2095 2096 orients = end_pts - start_pts 2097 Glyph.__init__( 2098 self, 2099 start_pts, 2100 out, 2101 orientation_array=orients, 2102 scale_by_vector_size=True, 2103 color_by_vector_size=True, 2104 c=c, 2105 alpha=alpha, 2106 ) 2107 self.flat().lighting("plastic") 2108 self.name = "Arrows"
Build arrows between two lists of points.
2026 def __init__( 2027 self, 2028 start_pts, 2029 end_pts=None, 2030 s=None, 2031 shaft_radius=None, 2032 head_radius=None, 2033 head_length=None, 2034 thickness=1.0, 2035 res=12, 2036 c=None, 2037 alpha=1.0, 2038 ): 2039 """ 2040 Build arrows between two lists of points `start_pts` and `end_pts`. 2041 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2042 2043 Color can be specified as a colormap which maps the size of the arrows. 2044 2045 Arguments: 2046 s : (float) 2047 fix aspect-ratio of the arrow and scale its cross section 2048 c : (color) 2049 color or color map name 2050 alpha : (float) 2051 set object opacity 2052 res : (int) 2053 set arrow resolution 2054 2055 Examples: 2056 - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py) 2057 2058 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 2059 """ 2060 if isinstance(start_pts, Points): 2061 start_pts = start_pts.points() 2062 if isinstance(end_pts, Points): 2063 end_pts = end_pts.points() 2064 2065 start_pts = np.asarray(start_pts) 2066 if end_pts is None: 2067 strt = start_pts[:, 0] 2068 end_pts = start_pts[:, 1] 2069 start_pts = strt 2070 else: 2071 end_pts = np.asarray(end_pts) 2072 2073 start_pts = utils.make3d(start_pts) 2074 end_pts = utils.make3d(end_pts) 2075 2076 arr = vtk.vtkArrowSource() 2077 arr.SetShaftResolution(res) 2078 arr.SetTipResolution(res) 2079 2080 if s: 2081 sz = 0.02 * s 2082 arr.SetTipRadius(sz * 2) 2083 arr.SetShaftRadius(sz * thickness) 2084 arr.SetTipLength(sz * 10) 2085 2086 if head_radius: 2087 arr.SetTipRadius(head_radius) 2088 if shaft_radius: 2089 arr.SetShaftRadius(shaft_radius) 2090 if head_length: 2091 arr.SetTipLength(head_length) 2092 2093 arr.Update() 2094 out = arr.GetOutput() 2095 2096 orients = end_pts - start_pts 2097 Glyph.__init__( 2098 self, 2099 start_pts, 2100 out, 2101 orientation_array=orients, 2102 scale_by_vector_size=True, 2103 color_by_vector_size=True, 2104 c=c, 2105 alpha=alpha, 2106 ) 2107 self.flat().lighting("plastic") 2108 self.name = "Arrows"
Build arrows between two lists of points start_pts
and end_pts
.
start_pts
can be also passed in the form [[point1, point2], ...]
.
Color can be specified as a colormap which maps the size of the arrows.
Arguments:
- s : (float) fix aspect-ratio of the arrow and scale its cross section
- c : (color) color or color map name
- alpha : (float) set object opacity
- res : (int) set arrow resolution
Examples:
2111class Arrow2D(Mesh): 2112 """ 2113 Build a 2D arrow. 2114 """ 2115 2116 def __init__( 2117 self, 2118 start_pt=(0, 0, 0), 2119 end_pt=(1, 0, 0), 2120 s=1, 2121 shaft_length=0.8, 2122 shaft_width=0.05, 2123 head_length=0.225, 2124 head_width=0.175, 2125 fill=True, 2126 ): 2127 """ 2128 Build a 2D arrow from `start_pt` to `end_pt`. 2129 2130 Arguments: 2131 s : (float) 2132 a global multiplicative convenience factor controlling the arrow size 2133 shaft_length : (float) 2134 fractional shaft length 2135 shaft_width : (float) 2136 fractional shaft width 2137 head_length : (float) 2138 fractional head length 2139 head_width : (float) 2140 fractional head width 2141 fill : (bool) 2142 if False only generate the outline 2143 """ 2144 self.fill = fill ## needed by pyplot.__iadd() 2145 self.s = s # # needed by pyplot.__iadd() 2146 2147 if s != 1: 2148 shaft_width *= s 2149 head_width *= np.sqrt(s) 2150 2151 # in case user is passing meshs 2152 if isinstance(start_pt, vtk.vtkActor): 2153 start_pt = start_pt.GetPosition() 2154 if isinstance(end_pt, vtk.vtkActor): 2155 end_pt = end_pt.GetPosition() 2156 if len(start_pt) == 2: 2157 start_pt = [start_pt[0], start_pt[1], 0] 2158 if len(end_pt) == 2: 2159 end_pt = [end_pt[0], end_pt[1], 0] 2160 2161 headBase = 1 - head_length 2162 head_width = max(head_width, shaft_width) 2163 if head_length is None or headBase > shaft_length: 2164 headBase = shaft_length 2165 2166 verts = [] 2167 verts.append([0, -shaft_width / 2, 0]) 2168 verts.append([shaft_length, -shaft_width / 2, 0]) 2169 verts.append([headBase, -head_width / 2, 0]) 2170 verts.append([1, 0, 0]) 2171 verts.append([headBase, head_width / 2, 0]) 2172 verts.append([shaft_length, shaft_width / 2, 0]) 2173 verts.append([0, shaft_width / 2, 0]) 2174 if fill: 2175 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2176 poly = utils.buildPolyData(verts, faces) 2177 else: 2178 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2179 poly = utils.buildPolyData(verts, [], lines=lines) 2180 2181 axis = np.array(end_pt) - np.array(start_pt) 2182 length = np.linalg.norm(axis) 2183 if length: 2184 axis = axis / length 2185 theta = 0 2186 if len(axis) > 2: 2187 theta = np.arccos(axis[2]) 2188 phi = np.arctan2(axis[1], axis[0]) 2189 t = vtk.vtkTransform() 2190 if phi: 2191 t.RotateZ(np.rad2deg(phi)) 2192 if theta: 2193 t.RotateY(np.rad2deg(theta)) 2194 t.RotateY(-90) # put it along Z 2195 t.Scale(length, length, length) 2196 tf = vtk.vtkTransformPolyDataFilter() 2197 tf.SetInputData(poly) 2198 tf.SetTransform(t) 2199 tf.Update() 2200 2201 Mesh.__init__(self, tf.GetOutput(), c="k1") 2202 self.SetPosition(start_pt) 2203 self.lighting("off") 2204 self.DragableOff() 2205 self.PickableOff() 2206 self.base = np.array(start_pt, dtype=float) 2207 self.top = np.array(end_pt, dtype=float) 2208 self.name = "Arrow2D"
Build a 2D arrow.
2116 def __init__( 2117 self, 2118 start_pt=(0, 0, 0), 2119 end_pt=(1, 0, 0), 2120 s=1, 2121 shaft_length=0.8, 2122 shaft_width=0.05, 2123 head_length=0.225, 2124 head_width=0.175, 2125 fill=True, 2126 ): 2127 """ 2128 Build a 2D arrow from `start_pt` to `end_pt`. 2129 2130 Arguments: 2131 s : (float) 2132 a global multiplicative convenience factor controlling the arrow size 2133 shaft_length : (float) 2134 fractional shaft length 2135 shaft_width : (float) 2136 fractional shaft width 2137 head_length : (float) 2138 fractional head length 2139 head_width : (float) 2140 fractional head width 2141 fill : (bool) 2142 if False only generate the outline 2143 """ 2144 self.fill = fill ## needed by pyplot.__iadd() 2145 self.s = s # # needed by pyplot.__iadd() 2146 2147 if s != 1: 2148 shaft_width *= s 2149 head_width *= np.sqrt(s) 2150 2151 # in case user is passing meshs 2152 if isinstance(start_pt, vtk.vtkActor): 2153 start_pt = start_pt.GetPosition() 2154 if isinstance(end_pt, vtk.vtkActor): 2155 end_pt = end_pt.GetPosition() 2156 if len(start_pt) == 2: 2157 start_pt = [start_pt[0], start_pt[1], 0] 2158 if len(end_pt) == 2: 2159 end_pt = [end_pt[0], end_pt[1], 0] 2160 2161 headBase = 1 - head_length 2162 head_width = max(head_width, shaft_width) 2163 if head_length is None or headBase > shaft_length: 2164 headBase = shaft_length 2165 2166 verts = [] 2167 verts.append([0, -shaft_width / 2, 0]) 2168 verts.append([shaft_length, -shaft_width / 2, 0]) 2169 verts.append([headBase, -head_width / 2, 0]) 2170 verts.append([1, 0, 0]) 2171 verts.append([headBase, head_width / 2, 0]) 2172 verts.append([shaft_length, shaft_width / 2, 0]) 2173 verts.append([0, shaft_width / 2, 0]) 2174 if fill: 2175 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2176 poly = utils.buildPolyData(verts, faces) 2177 else: 2178 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2179 poly = utils.buildPolyData(verts, [], lines=lines) 2180 2181 axis = np.array(end_pt) - np.array(start_pt) 2182 length = np.linalg.norm(axis) 2183 if length: 2184 axis = axis / length 2185 theta = 0 2186 if len(axis) > 2: 2187 theta = np.arccos(axis[2]) 2188 phi = np.arctan2(axis[1], axis[0]) 2189 t = vtk.vtkTransform() 2190 if phi: 2191 t.RotateZ(np.rad2deg(phi)) 2192 if theta: 2193 t.RotateY(np.rad2deg(theta)) 2194 t.RotateY(-90) # put it along Z 2195 t.Scale(length, length, length) 2196 tf = vtk.vtkTransformPolyDataFilter() 2197 tf.SetInputData(poly) 2198 tf.SetTransform(t) 2199 tf.Update() 2200 2201 Mesh.__init__(self, tf.GetOutput(), c="k1") 2202 self.SetPosition(start_pt) 2203 self.lighting("off") 2204 self.DragableOff() 2205 self.PickableOff() 2206 self.base = np.array(start_pt, dtype=float) 2207 self.top = np.array(end_pt, dtype=float) 2208 self.name = "Arrow2D"
Build a 2D arrow from start_pt
to end_pt
.
Arguments:
- s : (float) a global multiplicative convenience factor controlling the arrow size
- shaft_length : (float) fractional shaft length
- shaft_width : (float) fractional shaft width
- head_length : (float) fractional head length
- head_width : (float) fractional head width
- fill : (bool) if False only generate the outline
2211class Arrows2D(Glyph): 2212 """ 2213 Build 2D arrows between two lists of points. 2214 """ 2215 2216 def __init__( 2217 self, 2218 start_pts, 2219 end_pts=None, 2220 s=1.0, 2221 shaft_length=0.8, 2222 shaft_width=0.05, 2223 head_length=0.225, 2224 head_width=0.175, 2225 fill=True, 2226 c=None, 2227 alpha=1.0, 2228 ): 2229 """ 2230 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2231 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2232 2233 Color can be specified as a colormap which maps the size of the arrows. 2234 2235 Arguments: 2236 shaft_length : (float) 2237 fractional shaft length 2238 shaft_width : (float) 2239 fractional shaft width 2240 head_length : (float) 2241 fractional head length 2242 head_width : (float) 2243 fractional head width 2244 fill : (bool) 2245 if False only generate the outline 2246 """ 2247 if isinstance(start_pts, Points): 2248 start_pts = start_pts.points() 2249 if isinstance(end_pts, Points): 2250 end_pts = end_pts.points() 2251 2252 start_pts = np.asarray(start_pts, dtype=float) 2253 if end_pts is None: 2254 strt = start_pts[:, 0] 2255 end_pts = start_pts[:, 1] 2256 start_pts = strt 2257 else: 2258 end_pts = np.asarray(end_pts, dtype=float) 2259 2260 if head_length is None: 2261 head_length = 1 - shaft_length 2262 2263 arr = Arrow2D( 2264 (0, 0, 0), 2265 (1, 0, 0), 2266 s=s, 2267 shaft_length=shaft_length, 2268 shaft_width=shaft_width, 2269 head_length=head_length, 2270 head_width=head_width, 2271 fill=fill, 2272 ) 2273 2274 orients = end_pts - start_pts 2275 orients = utils.make3d(orients) 2276 2277 pts = Points(start_pts) 2278 Glyph.__init__( 2279 self, 2280 pts, 2281 arr.polydata(False), 2282 orientation_array=orients, 2283 scale_by_vector_size=True, 2284 c=c, 2285 alpha=alpha, 2286 ) 2287 self.flat().lighting("off") 2288 if c is not None: 2289 self.color(c) 2290 self.name = "Arrows2D"
Build 2D arrows between two lists of points.
2216 def __init__( 2217 self, 2218 start_pts, 2219 end_pts=None, 2220 s=1.0, 2221 shaft_length=0.8, 2222 shaft_width=0.05, 2223 head_length=0.225, 2224 head_width=0.175, 2225 fill=True, 2226 c=None, 2227 alpha=1.0, 2228 ): 2229 """ 2230 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2231 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2232 2233 Color can be specified as a colormap which maps the size of the arrows. 2234 2235 Arguments: 2236 shaft_length : (float) 2237 fractional shaft length 2238 shaft_width : (float) 2239 fractional shaft width 2240 head_length : (float) 2241 fractional head length 2242 head_width : (float) 2243 fractional head width 2244 fill : (bool) 2245 if False only generate the outline 2246 """ 2247 if isinstance(start_pts, Points): 2248 start_pts = start_pts.points() 2249 if isinstance(end_pts, Points): 2250 end_pts = end_pts.points() 2251 2252 start_pts = np.asarray(start_pts, dtype=float) 2253 if end_pts is None: 2254 strt = start_pts[:, 0] 2255 end_pts = start_pts[:, 1] 2256 start_pts = strt 2257 else: 2258 end_pts = np.asarray(end_pts, dtype=float) 2259 2260 if head_length is None: 2261 head_length = 1 - shaft_length 2262 2263 arr = Arrow2D( 2264 (0, 0, 0), 2265 (1, 0, 0), 2266 s=s, 2267 shaft_length=shaft_length, 2268 shaft_width=shaft_width, 2269 head_length=head_length, 2270 head_width=head_width, 2271 fill=fill, 2272 ) 2273 2274 orients = end_pts - start_pts 2275 orients = utils.make3d(orients) 2276 2277 pts = Points(start_pts) 2278 Glyph.__init__( 2279 self, 2280 pts, 2281 arr.polydata(False), 2282 orientation_array=orients, 2283 scale_by_vector_size=True, 2284 c=c, 2285 alpha=alpha, 2286 ) 2287 self.flat().lighting("off") 2288 if c is not None: 2289 self.color(c) 2290 self.name = "Arrows2D"
Build 2D arrows between two lists of points start_pts
and end_pts
.
start_pts
can be also passed in the form [[point1, point2], ...]
.
Color can be specified as a colormap which maps the size of the arrows.
Arguments:
- shaft_length : (float) fractional shaft length
- shaft_width : (float) fractional shaft width
- head_length : (float) fractional head length
- head_width : (float) fractional head width
- fill : (bool) if False only generate the outline
2293class FlatArrow(Ribbon): 2294 """ 2295 Build a 2D arrow in 3D space by joining two close lines. 2296 """ 2297 2298 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0): 2299 """ 2300 Build a 2D arrow in 3D space by joining two close lines. 2301 2302 Examples: 2303 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2304 2305 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2306 """ 2307 if isinstance(line1, Points): 2308 line1 = line1.points() 2309 if isinstance(line2, Points): 2310 line2 = line2.points() 2311 2312 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2313 2314 v = (sm1 - sm2) / 3 * tip_width 2315 p1 = sm1 + v 2316 p2 = sm2 - v 2317 pm1 = (sm1 + sm2) / 2 2318 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2319 pm12 = pm1 - pm2 2320 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2321 2322 line1.append(p1) 2323 line1.append(tip) 2324 line2.append(p2) 2325 line2.append(tip) 2326 resm = max(100, len(line1)) 2327 2328 Ribbon.__init__(self, line1, line2, res=(resm, 1)) 2329 self.phong() 2330 self.PickableOff() 2331 self.DragableOff() 2332 self.name = "FlatArrow"
Build a 2D arrow in 3D space by joining two close lines.
2298 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0): 2299 """ 2300 Build a 2D arrow in 3D space by joining two close lines. 2301 2302 Examples: 2303 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2304 2305 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2306 """ 2307 if isinstance(line1, Points): 2308 line1 = line1.points() 2309 if isinstance(line2, Points): 2310 line2 = line2.points() 2311 2312 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2313 2314 v = (sm1 - sm2) / 3 * tip_width 2315 p1 = sm1 + v 2316 p2 = sm2 - v 2317 pm1 = (sm1 + sm2) / 2 2318 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2319 pm12 = pm1 - pm2 2320 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2321 2322 line1.append(p1) 2323 line1.append(tip) 2324 line2.append(p2) 2325 line2.append(tip) 2326 resm = max(100, len(line1)) 2327 2328 Ribbon.__init__(self, line1, line2, res=(resm, 1)) 2329 self.phong() 2330 self.PickableOff() 2331 self.DragableOff() 2332 self.name = "FlatArrow"
2345class Polygon(Mesh): 2346 """ 2347 Build a polygon in the `xy` plane. 2348 """ 2349 2350 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0): 2351 """ 2352 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2353 2354 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2355 """ 2356 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2357 x, y = utils.pol2cart(np.ones_like(t) * r, t) 2358 faces = [list(range(nsides))] 2359 # do not use: vtkRegularPolygonSource 2360 Mesh.__init__(self, [np.c_[x, y], faces], c, alpha) 2361 if len(pos) == 2: 2362 pos = (pos[0], pos[1], 0) 2363 self.SetPosition(pos) 2364 self.GetProperty().LightingOff() 2365 self.name = "Polygon " + str(nsides)
Build a polygon in the xy
plane.
2350 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0): 2351 """ 2352 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2353 2354 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2355 """ 2356 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2357 x, y = utils.pol2cart(np.ones_like(t) * r, t) 2358 faces = [list(range(nsides))] 2359 # do not use: vtkRegularPolygonSource 2360 Mesh.__init__(self, [np.c_[x, y], faces], c, alpha) 2361 if len(pos) == 2: 2362 pos = (pos[0], pos[1], 0) 2363 self.SetPosition(pos) 2364 self.GetProperty().LightingOff() 2365 self.name = "Polygon " + str(nsides)
Build a polygon in the xy
plane of nsides
of radius r
.
2335class Triangle(Mesh): 2336 """Create a triangle from 3 points in space.""" 2337 2338 def __init__(self, p1, p2, p3, c="green7", alpha=1.0): 2339 """Create a triangle from 3 points in space.""" 2340 Mesh.__init__(self, [[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2341 self.GetProperty().LightingOff() 2342 self.name = "Triangle"
Create a triangle from 3 points in space.
2338 def __init__(self, p1, p2, p3, c="green7", alpha=1.0): 2339 """Create a triangle from 3 points in space.""" 2340 Mesh.__init__(self, [[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2341 self.GetProperty().LightingOff() 2342 self.name = "Triangle"
Create a triangle from 3 points in space.
3120class Rectangle(Mesh): 3121 """ 3122 Build a rectangle in the xy plane. 3123 """ 3124 3125 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0): 3126 """ 3127 Build a rectangle in the xy plane identified by any two corner points. 3128 3129 Arguments: 3130 p1 : (list) 3131 bottom-left position of the corner 3132 p2 : (list) 3133 top-right position of the corner 3134 radius : (float, list) 3135 smoothing radius of the corner in world units. 3136 A list can be passed with 4 individual values. 3137 """ 3138 if len(p1) == 2: 3139 p1 = np.array([p1[0], p1[1], 0.0]) 3140 else: 3141 p1 = np.array(p1, dtype=float) 3142 if len(p2) == 2: 3143 p2 = np.array([p2[0], p2[1], 0.0]) 3144 else: 3145 p2 = np.array(p2, dtype=float) 3146 3147 self.corner1 = p1 3148 self.corner2 = p2 3149 3150 color = c 3151 smoothr = False 3152 risseq = False 3153 if utils.is_sequence(radius): 3154 risseq = True 3155 smoothr = True 3156 if max(radius) == 0: 3157 smoothr = False 3158 elif radius: 3159 smoothr = True 3160 3161 if not smoothr: 3162 radius = None 3163 self.radius = radius 3164 3165 if smoothr: 3166 r = radius 3167 if not risseq: 3168 r = [r, r, r, r] 3169 rd, ra, rb, rc = r 3170 3171 if p1[0] > p2[0]: # flip p1 - p2 3172 p1, p2 = p2, p1 3173 if p1[1] > p2[1]: # flip p1y - p2y 3174 p1[1], p2[1] = p2[1], p1[1] 3175 3176 px, py, _ = p2 - p1 3177 k = min(px / 2, py / 2) 3178 ra = min(abs(ra), k) 3179 rb = min(abs(rb), k) 3180 rc = min(abs(rc), k) 3181 rd = min(abs(rd), k) 3182 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3183 betas = np.split(beta, 4) 3184 rrx = np.cos(betas) 3185 rry = np.sin(betas) 3186 3187 q1 = (rd, 0) 3188 # q2 = (px-ra, 0) 3189 q3 = (px, ra) 3190 # q4 = (px, py-rb) 3191 q5 = (px - rb, py) 3192 # q6 = (rc, py) 3193 q7 = (0, py - rc) 3194 # q8 = (0, rd) 3195 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3196 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3197 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3198 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3199 3200 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3201 faces = [list(range(len(pts)))] 3202 else: 3203 p1r = np.array([p2[0], p1[1], 0.0]) 3204 p2l = np.array([p1[0], p2[1], 0.0]) 3205 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3206 faces = [(0, 1, 2, 3)] 3207 3208 Mesh.__init__(self, [pts, faces], color, alpha) 3209 self.SetPosition(p1) 3210 self.property.LightingOff() 3211 self.name = "Rectangle"
Build a rectangle in the xy plane.
3125 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0): 3126 """ 3127 Build a rectangle in the xy plane identified by any two corner points. 3128 3129 Arguments: 3130 p1 : (list) 3131 bottom-left position of the corner 3132 p2 : (list) 3133 top-right position of the corner 3134 radius : (float, list) 3135 smoothing radius of the corner in world units. 3136 A list can be passed with 4 individual values. 3137 """ 3138 if len(p1) == 2: 3139 p1 = np.array([p1[0], p1[1], 0.0]) 3140 else: 3141 p1 = np.array(p1, dtype=float) 3142 if len(p2) == 2: 3143 p2 = np.array([p2[0], p2[1], 0.0]) 3144 else: 3145 p2 = np.array(p2, dtype=float) 3146 3147 self.corner1 = p1 3148 self.corner2 = p2 3149 3150 color = c 3151 smoothr = False 3152 risseq = False 3153 if utils.is_sequence(radius): 3154 risseq = True 3155 smoothr = True 3156 if max(radius) == 0: 3157 smoothr = False 3158 elif radius: 3159 smoothr = True 3160 3161 if not smoothr: 3162 radius = None 3163 self.radius = radius 3164 3165 if smoothr: 3166 r = radius 3167 if not risseq: 3168 r = [r, r, r, r] 3169 rd, ra, rb, rc = r 3170 3171 if p1[0] > p2[0]: # flip p1 - p2 3172 p1, p2 = p2, p1 3173 if p1[1] > p2[1]: # flip p1y - p2y 3174 p1[1], p2[1] = p2[1], p1[1] 3175 3176 px, py, _ = p2 - p1 3177 k = min(px / 2, py / 2) 3178 ra = min(abs(ra), k) 3179 rb = min(abs(rb), k) 3180 rc = min(abs(rc), k) 3181 rd = min(abs(rd), k) 3182 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3183 betas = np.split(beta, 4) 3184 rrx = np.cos(betas) 3185 rry = np.sin(betas) 3186 3187 q1 = (rd, 0) 3188 # q2 = (px-ra, 0) 3189 q3 = (px, ra) 3190 # q4 = (px, py-rb) 3191 q5 = (px - rb, py) 3192 # q6 = (rc, py) 3193 q7 = (0, py - rc) 3194 # q8 = (0, rd) 3195 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3196 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3197 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3198 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3199 3200 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3201 faces = [list(range(len(pts)))] 3202 else: 3203 p1r = np.array([p2[0], p1[1], 0.0]) 3204 p2l = np.array([p1[0], p2[1], 0.0]) 3205 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3206 faces = [(0, 1, 2, 3)] 3207 3208 Mesh.__init__(self, [pts, faces], color, alpha) 3209 self.SetPosition(p1) 3210 self.property.LightingOff() 3211 self.name = "Rectangle"
Build a rectangle in the xy plane identified by any two corner points.
Arguments:
- p1 : (list) bottom-left position of the corner
- p2 : (list) top-right position of the corner
- radius : (float, list) smoothing radius of the corner in world units. A list can be passed with 4 individual values.
2465class Disc(Mesh): 2466 """ 2467 Build a 2D disc. 2468 """ 2469 2470 def __init__( 2471 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2472 ): 2473 """ 2474 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2475 2476 Set `res` as the resolution in R and Phi (can be a list). 2477 2478 Use `angle_range` to create a disc sector between the 2 specified angles. 2479 2480 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2481 """ 2482 if utils.is_sequence(res): 2483 res_r, res_phi = res 2484 else: 2485 res_r, res_phi = res, 12 * res 2486 2487 if len(angle_range) == 0: 2488 ps = vtk.vtkDiskSource() 2489 else: 2490 ps = vtk.vtkSectorSource() 2491 ps.SetStartAngle(angle_range[0]) 2492 ps.SetEndAngle(angle_range[1]) 2493 2494 ps.SetInnerRadius(r1) 2495 ps.SetOuterRadius(r2) 2496 ps.SetRadialResolution(res_r) 2497 ps.SetCircumferentialResolution(res_phi) 2498 ps.Update() 2499 Mesh.__init__(self, ps.GetOutput(), c, alpha) 2500 self.flat() 2501 self.SetPosition(utils.make3d(pos)) 2502 self.name = "Disc"
Build a 2D disc.
2470 def __init__( 2471 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2472 ): 2473 """ 2474 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2475 2476 Set `res` as the resolution in R and Phi (can be a list). 2477 2478 Use `angle_range` to create a disc sector between the 2 specified angles. 2479 2480 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2481 """ 2482 if utils.is_sequence(res): 2483 res_r, res_phi = res 2484 else: 2485 res_r, res_phi = res, 12 * res 2486 2487 if len(angle_range) == 0: 2488 ps = vtk.vtkDiskSource() 2489 else: 2490 ps = vtk.vtkSectorSource() 2491 ps.SetStartAngle(angle_range[0]) 2492 ps.SetEndAngle(angle_range[1]) 2493 2494 ps.SetInnerRadius(r1) 2495 ps.SetOuterRadius(r2) 2496 ps.SetRadialResolution(res_r) 2497 ps.SetCircumferentialResolution(res_phi) 2498 ps.Update() 2499 Mesh.__init__(self, ps.GetOutput(), c, alpha) 2500 self.flat() 2501 self.SetPosition(utils.make3d(pos)) 2502 self.name = "Disc"
Build a 2D disc of inner radius r1
and outer radius r2
.
Set res
as the resolution in R and Phi (can be a list).
Use angle_range
to create a disc sector between the 2 specified angles.
2368class Circle(Polygon): 2369 """ 2370 Build a Circle of radius `r`. 2371 """ 2372 2373 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0): 2374 """ 2375 Build a Circle of radius `r`. 2376 """ 2377 Polygon.__init__(self, pos, nsides=res, r=r) 2378 2379 self.center = [] # filled by pointcloud.pcaEllipse 2380 self.nr_of_points = 0 2381 self.va = 0 2382 self.vb = 0 2383 self.axis1 = [] 2384 self.axis2 = [] 2385 self.alpha(alpha).c(c) 2386 self.name = "Circle"
Build a Circle of radius r
.
2373 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0): 2374 """ 2375 Build a Circle of radius `r`. 2376 """ 2377 Polygon.__init__(self, pos, nsides=res, r=r) 2378 2379 self.center = [] # filled by pointcloud.pcaEllipse 2380 self.nr_of_points = 0 2381 self.va = 0 2382 self.vb = 0 2383 self.axis1 = [] 2384 self.axis2 = [] 2385 self.alpha(alpha).c(c) 2386 self.name = "Circle"
Build a Circle of radius r
.
2389class GeoCircle(Polygon): 2390 """ 2391 Build a Circle of radius `r`. 2392 """ 2393 2394 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0): 2395 """ 2396 Build a Circle of radius `r` as projected on a geographic map. 2397 Circles near the poles will look very squashed. 2398 2399 See example: 2400 ```bash 2401 vedo -r earthquake 2402 ``` 2403 """ 2404 coords = [] 2405 sinr, cosr = np.sin(r), np.cos(r) 2406 sinlat, coslat = np.sin(lat), np.cos(lat) 2407 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2408 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2409 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2410 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2411 2412 Polygon.__init__(self, nsides=res, c=c, alpha=alpha) 2413 self.points(coords) # warp polygon points to match geo projection 2414 self.name = "Circle"
Build a Circle of radius r
.
2394 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0): 2395 """ 2396 Build a Circle of radius `r` as projected on a geographic map. 2397 Circles near the poles will look very squashed. 2398 2399 See example: 2400 ```bash 2401 vedo -r earthquake 2402 ``` 2403 """ 2404 coords = [] 2405 sinr, cosr = np.sin(r), np.cos(r) 2406 sinlat, coslat = np.sin(lat), np.cos(lat) 2407 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2408 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2409 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2410 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2411 2412 Polygon.__init__(self, nsides=res, c=c, alpha=alpha) 2413 self.points(coords) # warp polygon points to match geo projection 2414 self.name = "Circle"
Build a Circle of radius r
as projected on a geographic map.
Circles near the poles will look very squashed.
See example:
vedo -r earthquake
2505class Arc(Mesh): 2506 """ 2507 Build a 2D circular arc between 2 points. 2508 """ 2509 2510 def __init__( 2511 self, 2512 center, 2513 point1, 2514 point2=None, 2515 normal=None, 2516 angle=None, 2517 invert=False, 2518 res=50, 2519 c="gray4", 2520 alpha=1.0, 2521 ): 2522 """ 2523 Build a 2D circular arc between 2 points `point1` and `point2`. 2524 2525 If `normal` is specified then `center` is ignored, and 2526 normal vector, a starting `point1` (polar vector) 2527 and an angle defining the arc length need to be assigned. 2528 2529 Arc spans the shortest angular sector point1 and point2, 2530 if `invert=True`, then the opposite happens. 2531 """ 2532 if len(point1) == 2: 2533 point1 = (point1[0], point1[1], 0) 2534 if point2 is not None and len(point2) == 2: 2535 point2 = (point2[0], point2[1], 0) 2536 2537 self.base = point1 2538 self.top = point2 2539 2540 ar = vtk.vtkArcSource() 2541 if point2 is not None: 2542 self.top = point2 2543 point2 = point2 - np.asarray(point1) 2544 ar.UseNormalAndAngleOff() 2545 ar.SetPoint1([0, 0, 0]) 2546 ar.SetPoint2(point2) 2547 ar.SetCenter(center) 2548 elif normal is not None and angle is not None: 2549 ar.UseNormalAndAngleOn() 2550 ar.SetAngle(angle) 2551 ar.SetPolarVector(point1) 2552 ar.SetNormal(normal) 2553 else: 2554 vedo.logger.error("incorrect input combination") 2555 return 2556 ar.SetNegative(invert) 2557 ar.SetResolution(res) 2558 ar.Update() 2559 Mesh.__init__(self, ar.GetOutput(), c, alpha) 2560 self.SetPosition(self.base) 2561 self.lw(2).lighting("off") 2562 self.name = "Arc"
Build a 2D circular arc between 2 points.
2510 def __init__( 2511 self, 2512 center, 2513 point1, 2514 point2=None, 2515 normal=None, 2516 angle=None, 2517 invert=False, 2518 res=50, 2519 c="gray4", 2520 alpha=1.0, 2521 ): 2522 """ 2523 Build a 2D circular arc between 2 points `point1` and `point2`. 2524 2525 If `normal` is specified then `center` is ignored, and 2526 normal vector, a starting `point1` (polar vector) 2527 and an angle defining the arc length need to be assigned. 2528 2529 Arc spans the shortest angular sector point1 and point2, 2530 if `invert=True`, then the opposite happens. 2531 """ 2532 if len(point1) == 2: 2533 point1 = (point1[0], point1[1], 0) 2534 if point2 is not None and len(point2) == 2: 2535 point2 = (point2[0], point2[1], 0) 2536 2537 self.base = point1 2538 self.top = point2 2539 2540 ar = vtk.vtkArcSource() 2541 if point2 is not None: 2542 self.top = point2 2543 point2 = point2 - np.asarray(point1) 2544 ar.UseNormalAndAngleOff() 2545 ar.SetPoint1([0, 0, 0]) 2546 ar.SetPoint2(point2) 2547 ar.SetCenter(center) 2548 elif normal is not None and angle is not None: 2549 ar.UseNormalAndAngleOn() 2550 ar.SetAngle(angle) 2551 ar.SetPolarVector(point1) 2552 ar.SetNormal(normal) 2553 else: 2554 vedo.logger.error("incorrect input combination") 2555 return 2556 ar.SetNegative(invert) 2557 ar.SetResolution(res) 2558 ar.Update() 2559 Mesh.__init__(self, ar.GetOutput(), c, alpha) 2560 self.SetPosition(self.base) 2561 self.lw(2).lighting("off") 2562 self.name = "Arc"
Build a 2D circular arc between 2 points point1
and point2
.
If normal
is specified then center
is ignored, and
normal vector, a starting point1
(polar vector)
and an angle defining the arc length need to be assigned.
Arc spans the shortest angular sector point1 and point2,
if invert=True
, then the opposite happens.
2417class Star(Mesh): 2418 """ 2419 Build a 2D star shape. 2420 """ 2421 2422 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0): 2423 """ 2424 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2425 2426 If line is True then only build the outer line (no internal surface meshing). 2427 2428 Example: 2429 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2430 2431 ![](https://vedo.embl.es/images/basic/extrude.png) 2432 """ 2433 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2434 x, y = utils.pol2cart(np.ones_like(t) * r2, t) 2435 pts = np.c_[x, y, np.zeros_like(x)] 2436 2437 apts = [] 2438 for i, p in enumerate(pts): 2439 apts.append(p) 2440 if i + 1 < n: 2441 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2442 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2443 2444 if line: 2445 apts.append(pts[0]) 2446 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2447 Mesh.__init__(self, poly, c, alpha) 2448 self.lw(2) 2449 else: 2450 apts.append((0, 0, 0)) 2451 cells = [] 2452 for i in range(2 * n - 1): 2453 cell = [2 * n, i, i + 1] 2454 cells.append(cell) 2455 cells.append([2 * n, i + 1, 0]) 2456 Mesh.__init__(self, [apts, cells], c, alpha) 2457 2458 if len(pos) == 2: 2459 pos = (pos[0], pos[1], 0) 2460 self.SetPosition(pos) 2461 self.property.LightingOff() 2462 self.name = "Star"
Build a 2D star shape.
2422 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0): 2423 """ 2424 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2425 2426 If line is True then only build the outer line (no internal surface meshing). 2427 2428 Example: 2429 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2430 2431 ![](https://vedo.embl.es/images/basic/extrude.png) 2432 """ 2433 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2434 x, y = utils.pol2cart(np.ones_like(t) * r2, t) 2435 pts = np.c_[x, y, np.zeros_like(x)] 2436 2437 apts = [] 2438 for i, p in enumerate(pts): 2439 apts.append(p) 2440 if i + 1 < n: 2441 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2442 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2443 2444 if line: 2445 apts.append(pts[0]) 2446 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2447 Mesh.__init__(self, poly, c, alpha) 2448 self.lw(2) 2449 else: 2450 apts.append((0, 0, 0)) 2451 cells = [] 2452 for i in range(2 * n - 1): 2453 cell = [2 * n, i, i + 1] 2454 cells.append(cell) 2455 cells.append([2 * n, i + 1, 0]) 2456 Mesh.__init__(self, [apts, cells], c, alpha) 2457 2458 if len(pos) == 2: 2459 pos = (pos[0], pos[1], 0) 2460 self.SetPosition(pos) 2461 self.property.LightingOff() 2462 self.name = "Star"
Build a 2D star shape of n
cusps of inner radius r1
and outer radius r2
.
If line is True then only build the outer line (no internal surface meshing).
Example:
3814class Star3D(Mesh): 3815 """ 3816 Build a 3D starred shape. 3817 """ 3818 3819 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0): 3820 """ 3821 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3822 """ 3823 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3824 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3825 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3826 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3827 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3828 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3829 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3830 [10,1, 0],[10,11, 9]] 3831 3832 Mesh.__init__(self, [pts, fcs], c, alpha) 3833 self.RotateX(90) 3834 self.scale(r).lighting("shiny") 3835 3836 if len(pos) == 2: 3837 pos = (pos[0], pos[1], 0) 3838 self.SetPosition(pos) 3839 self.name = "Star3D"
Build a 3D starred shape.
3819 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0): 3820 """ 3821 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3822 """ 3823 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3824 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3825 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3826 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3827 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3828 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3829 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3830 [10,1, 0],[10,11, 9]] 3831 3832 Mesh.__init__(self, [pts, fcs], c, alpha) 3833 self.RotateX(90) 3834 self.scale(r).lighting("shiny") 3835 3836 if len(pos) == 2: 3837 pos = (pos[0], pos[1], 0) 3838 self.SetPosition(pos) 3839 self.name = "Star3D"
Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3842class Cross3D(Mesh): 3843 """ 3844 Build a 3D cross shape. 3845 """ 3846 3847 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0): 3848 """ 3849 Build a 3D cross shape, mainly useful as a 3D marker. 3850 """ 3851 c1 = Cylinder(r=thickness * s, height=2 * s) 3852 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3853 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3854 poly = merge(c1, c2, c3).color(c).alpha(alpha).polydata(False) 3855 Mesh.__init__(self, poly, c, alpha) 3856 3857 if len(pos) == 2: 3858 pos = (pos[0], pos[1], 0) 3859 self.SetPosition(pos) 3860 self.name = "Cross3D"
Build a 3D cross shape.
3847 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0): 3848 """ 3849 Build a 3D cross shape, mainly useful as a 3D marker. 3850 """ 3851 c1 = Cylinder(r=thickness * s, height=2 * s) 3852 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3853 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3854 poly = merge(c1, c2, c3).color(c).alpha(alpha).polydata(False) 3855 Mesh.__init__(self, poly, c, alpha) 3856 3857 if len(pos) == 2: 3858 pos = (pos[0], pos[1], 0) 3859 self.SetPosition(pos) 3860 self.name = "Cross3D"
Build a 3D cross shape, mainly useful as a 3D marker.
2565class IcoSphere(Mesh): 2566 """ 2567 Create a sphere made of a uniform triangle mesh. 2568 """ 2569 2570 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=3, c="r5", alpha=1.0): 2571 """ 2572 Create a sphere made of a uniform triangle mesh 2573 (from recursive subdivision of an icosahedron). 2574 2575 Example: 2576 ```python 2577 from vedo import * 2578 icos = IcoSphere(subdivisions=3) 2579 icos.compute_quality().cmap('coolwarm') 2580 icos.show(axes=1).close() 2581 ``` 2582 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2583 """ 2584 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2585 2586 t = (1.0 + np.sqrt(5.0)) / 2.0 2587 points = np.array( 2588 [ 2589 [-1, t, 0], 2590 [1, t, 0], 2591 [-1, -t, 0], 2592 [1, -t, 0], 2593 [0, -1, t], 2594 [0, 1, t], 2595 [0, -1, -t], 2596 [0, 1, -t], 2597 [t, 0, -1], 2598 [t, 0, 1], 2599 [-t, 0, -1], 2600 [-t, 0, 1], 2601 ] 2602 ) 2603 faces = [ 2604 [0, 11, 5], 2605 [0, 5, 1], 2606 [0, 1, 7], 2607 [0, 7, 10], 2608 [0, 10, 11], 2609 [1, 5, 9], 2610 [5, 11, 4], 2611 [11, 10, 2], 2612 [10, 7, 6], 2613 [7, 1, 8], 2614 [3, 9, 4], 2615 [3, 4, 2], 2616 [3, 2, 6], 2617 [3, 6, 8], 2618 [3, 8, 9], 2619 [4, 9, 5], 2620 [2, 4, 11], 2621 [6, 2, 10], 2622 [8, 6, 7], 2623 [9, 8, 1], 2624 ] 2625 Mesh.__init__(self, [points * r, faces], c=c, alpha=alpha) 2626 2627 for _ in range(subdivisions): 2628 self.subdivide(method=1) 2629 pts = utils.versor(self.points()) * r 2630 self.points(pts) 2631 2632 self.SetPosition(pos) 2633 self.name = "IcoSphere"
Create a sphere made of a uniform triangle mesh.
2570 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=3, c="r5", alpha=1.0): 2571 """ 2572 Create a sphere made of a uniform triangle mesh 2573 (from recursive subdivision of an icosahedron). 2574 2575 Example: 2576 ```python 2577 from vedo import * 2578 icos = IcoSphere(subdivisions=3) 2579 icos.compute_quality().cmap('coolwarm') 2580 icos.show(axes=1).close() 2581 ``` 2582 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2583 """ 2584 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2585 2586 t = (1.0 + np.sqrt(5.0)) / 2.0 2587 points = np.array( 2588 [ 2589 [-1, t, 0], 2590 [1, t, 0], 2591 [-1, -t, 0], 2592 [1, -t, 0], 2593 [0, -1, t], 2594 [0, 1, t], 2595 [0, -1, -t], 2596 [0, 1, -t], 2597 [t, 0, -1], 2598 [t, 0, 1], 2599 [-t, 0, -1], 2600 [-t, 0, 1], 2601 ] 2602 ) 2603 faces = [ 2604 [0, 11, 5], 2605 [0, 5, 1], 2606 [0, 1, 7], 2607 [0, 7, 10], 2608 [0, 10, 11], 2609 [1, 5, 9], 2610 [5, 11, 4], 2611 [11, 10, 2], 2612 [10, 7, 6], 2613 [7, 1, 8], 2614 [3, 9, 4], 2615 [3, 4, 2], 2616 [3, 2, 6], 2617 [3, 6, 8], 2618 [3, 8, 9], 2619 [4, 9, 5], 2620 [2, 4, 11], 2621 [6, 2, 10], 2622 [8, 6, 7], 2623 [9, 8, 1], 2624 ] 2625 Mesh.__init__(self, [points * r, faces], c=c, alpha=alpha) 2626 2627 for _ in range(subdivisions): 2628 self.subdivide(method=1) 2629 pts = utils.versor(self.points()) * r 2630 self.points(pts) 2631 2632 self.SetPosition(pos) 2633 self.name = "IcoSphere"
Create a sphere made of a uniform triangle mesh (from recursive subdivision of an icosahedron).
Example:
from vedo import *
icos = IcoSphere(subdivisions=3)
icos.compute_quality().cmap('coolwarm')
icos.show(axes=1).close()
2636class Sphere(Mesh): 2637 """ 2638 Build a sphere. 2639 """ 2640 2641 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0): 2642 """ 2643 Build a sphere at position `pos` of radius `r`. 2644 2645 Arguments: 2646 r : (float) 2647 sphere radius 2648 res : (int, list) 2649 resolution in phi, resolution in theta is by default `2*res` 2650 quads : (bool) 2651 sphere mesh will be made of quads instead of triangles 2652 2653 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2654 """ 2655 if len(pos) == 2: 2656 pos = np.asarray([pos[0], pos[1], 0]) 2657 2658 self.radius = r # used by fitSphere 2659 self.center = pos 2660 self.residue = 0 2661 2662 if quads: 2663 res = max(res, 4) 2664 img = vtk.vtkImageData() 2665 img.SetDimensions(res - 1, res - 1, res - 1) 2666 rs = 1.0 / (res - 2) 2667 img.SetSpacing(rs, rs, rs) 2668 gf = vtk.vtkGeometryFilter() 2669 gf.SetInputData(img) 2670 gf.Update() 2671 Mesh.__init__(self, gf.GetOutput(), c, alpha) 2672 self.lw(0.1) 2673 2674 cgpts = self.points() - (0.5, 0.5, 0.5) 2675 2676 x, y, z = cgpts.T 2677 x = x * (1 + x * x) / 2 2678 y = y * (1 + y * y) / 2 2679 z = z * (1 + z * z) / 2 2680 _, theta, phi = utils.cart2spher(x, y, z) 2681 2682 pts = utils.spher2cart(np.ones_like(phi) * r, theta, phi) 2683 self.points(pts) 2684 2685 else: 2686 if utils.is_sequence(res): 2687 res_t, res_phi = res 2688 else: 2689 res_t, res_phi = 2 * res, res 2690 2691 ss = vtk.vtkSphereSource() 2692 ss.SetRadius(r) 2693 ss.SetThetaResolution(res_t) 2694 ss.SetPhiResolution(res_phi) 2695 ss.Update() 2696 2697 Mesh.__init__(self, ss.GetOutput(), c, alpha) 2698 2699 self.phong() 2700 self.SetPosition(pos) 2701 self.name = "Sphere"
Build a sphere.
2641 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0): 2642 """ 2643 Build a sphere at position `pos` of radius `r`. 2644 2645 Arguments: 2646 r : (float) 2647 sphere radius 2648 res : (int, list) 2649 resolution in phi, resolution in theta is by default `2*res` 2650 quads : (bool) 2651 sphere mesh will be made of quads instead of triangles 2652 2653 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2654 """ 2655 if len(pos) == 2: 2656 pos = np.asarray([pos[0], pos[1], 0]) 2657 2658 self.radius = r # used by fitSphere 2659 self.center = pos 2660 self.residue = 0 2661 2662 if quads: 2663 res = max(res, 4) 2664 img = vtk.vtkImageData() 2665 img.SetDimensions(res - 1, res - 1, res - 1) 2666 rs = 1.0 / (res - 2) 2667 img.SetSpacing(rs, rs, rs) 2668 gf = vtk.vtkGeometryFilter() 2669 gf.SetInputData(img) 2670 gf.Update() 2671 Mesh.__init__(self, gf.GetOutput(), c, alpha) 2672 self.lw(0.1) 2673 2674 cgpts = self.points() - (0.5, 0.5, 0.5) 2675 2676 x, y, z = cgpts.T 2677 x = x * (1 + x * x) / 2 2678 y = y * (1 + y * y) / 2 2679 z = z * (1 + z * z) / 2 2680 _, theta, phi = utils.cart2spher(x, y, z) 2681 2682 pts = utils.spher2cart(np.ones_like(phi) * r, theta, phi) 2683 self.points(pts) 2684 2685 else: 2686 if utils.is_sequence(res): 2687 res_t, res_phi = res 2688 else: 2689 res_t, res_phi = 2 * res, res 2690 2691 ss = vtk.vtkSphereSource() 2692 ss.SetRadius(r) 2693 ss.SetThetaResolution(res_t) 2694 ss.SetPhiResolution(res_phi) 2695 ss.Update() 2696 2697 Mesh.__init__(self, ss.GetOutput(), c, alpha) 2698 2699 self.phong() 2700 self.SetPosition(pos) 2701 self.name = "Sphere"
Build a sphere at position pos
of radius r
.
Arguments:
- r : (float) sphere radius
- res : (int, list)
resolution in phi, resolution in theta is by default
2*res
- quads : (bool) sphere mesh will be made of quads instead of triangles
2704class Spheres(Mesh): 2705 """ 2706 Build a large set of spheres. 2707 """ 2708 2709 def __init__(self, centers, r=1.0, res=8, c="r5", alpha=1): 2710 """ 2711 Build a (possibly large) set of spheres at `centers` of radius `r`. 2712 2713 Either `c` or `r` can be a list of RGB colors or radii. 2714 2715 Examples: 2716 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2717 2718 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2719 """ 2720 2721 if isinstance(centers, Points): 2722 centers = centers.points() 2723 centers = np.asarray(centers, dtype=float) 2724 base = centers[0] 2725 2726 cisseq = False 2727 if utils.is_sequence(c): 2728 cisseq = True 2729 2730 if cisseq: 2731 if len(centers) != len(c): 2732 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2733 raise RuntimeError() 2734 2735 risseq = False 2736 if utils.is_sequence(r): 2737 risseq = True 2738 2739 if risseq: 2740 if len(centers) != len(r): 2741 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2742 raise RuntimeError() 2743 if cisseq and risseq: 2744 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2745 raise RuntimeError() 2746 2747 src = vtk.vtkSphereSource() 2748 if not risseq: 2749 src.SetRadius(r) 2750 if utils.is_sequence(res): 2751 res_t, res_phi = res 2752 else: 2753 res_t, res_phi = 2 * res, res 2754 2755 src.SetThetaResolution(res_t) 2756 src.SetPhiResolution(res_phi) 2757 src.Update() 2758 2759 psrc = vtk.vtkPointSource() 2760 psrc.SetNumberOfPoints(len(centers)) 2761 psrc.Update() 2762 pd = psrc.GetOutput() 2763 vpts = pd.GetPoints() 2764 2765 glyph = vtk.vtkGlyph3D() 2766 glyph.SetSourceConnection(src.GetOutputPort()) 2767 2768 if cisseq: 2769 glyph.SetColorModeToColorByScalar() 2770 ucols = vtk.vtkUnsignedCharArray() 2771 ucols.SetNumberOfComponents(3) 2772 ucols.SetName("Colors") 2773 for acol in c: 2774 cx, cy, cz = get_color(acol) 2775 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2776 pd.GetPointData().AddArray(ucols) 2777 pd.GetPointData().SetActiveScalars("Colors") 2778 glyph.ScalingOff() 2779 elif risseq: 2780 glyph.SetScaleModeToScaleByScalar() 2781 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2782 urads.SetName("Radii") 2783 pd.GetPointData().AddArray(urads) 2784 pd.GetPointData().SetActiveScalars("Radii") 2785 2786 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2787 2788 glyph.SetInputData(pd) 2789 glyph.Update() 2790 2791 Mesh.__init__(self, glyph.GetOutput(), alpha=alpha) 2792 self.SetPosition(base) 2793 self.base = base 2794 self.top = centers[-1] 2795 self.phong() 2796 if cisseq: 2797 self.mapper().ScalarVisibilityOn() 2798 else: 2799 self.mapper().ScalarVisibilityOff() 2800 self.GetProperty().SetColor(get_color(c)) 2801 self.name = "Spheres"
Build a large set of spheres.
2709 def __init__(self, centers, r=1.0, res=8, c="r5", alpha=1): 2710 """ 2711 Build a (possibly large) set of spheres at `centers` of radius `r`. 2712 2713 Either `c` or `r` can be a list of RGB colors or radii. 2714 2715 Examples: 2716 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2717 2718 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2719 """ 2720 2721 if isinstance(centers, Points): 2722 centers = centers.points() 2723 centers = np.asarray(centers, dtype=float) 2724 base = centers[0] 2725 2726 cisseq = False 2727 if utils.is_sequence(c): 2728 cisseq = True 2729 2730 if cisseq: 2731 if len(centers) != len(c): 2732 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2733 raise RuntimeError() 2734 2735 risseq = False 2736 if utils.is_sequence(r): 2737 risseq = True 2738 2739 if risseq: 2740 if len(centers) != len(r): 2741 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2742 raise RuntimeError() 2743 if cisseq and risseq: 2744 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2745 raise RuntimeError() 2746 2747 src = vtk.vtkSphereSource() 2748 if not risseq: 2749 src.SetRadius(r) 2750 if utils.is_sequence(res): 2751 res_t, res_phi = res 2752 else: 2753 res_t, res_phi = 2 * res, res 2754 2755 src.SetThetaResolution(res_t) 2756 src.SetPhiResolution(res_phi) 2757 src.Update() 2758 2759 psrc = vtk.vtkPointSource() 2760 psrc.SetNumberOfPoints(len(centers)) 2761 psrc.Update() 2762 pd = psrc.GetOutput() 2763 vpts = pd.GetPoints() 2764 2765 glyph = vtk.vtkGlyph3D() 2766 glyph.SetSourceConnection(src.GetOutputPort()) 2767 2768 if cisseq: 2769 glyph.SetColorModeToColorByScalar() 2770 ucols = vtk.vtkUnsignedCharArray() 2771 ucols.SetNumberOfComponents(3) 2772 ucols.SetName("Colors") 2773 for acol in c: 2774 cx, cy, cz = get_color(acol) 2775 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2776 pd.GetPointData().AddArray(ucols) 2777 pd.GetPointData().SetActiveScalars("Colors") 2778 glyph.ScalingOff() 2779 elif risseq: 2780 glyph.SetScaleModeToScaleByScalar() 2781 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2782 urads.SetName("Radii") 2783 pd.GetPointData().AddArray(urads) 2784 pd.GetPointData().SetActiveScalars("Radii") 2785 2786 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2787 2788 glyph.SetInputData(pd) 2789 glyph.Update() 2790 2791 Mesh.__init__(self, glyph.GetOutput(), alpha=alpha) 2792 self.SetPosition(base) 2793 self.base = base 2794 self.top = centers[-1] 2795 self.phong() 2796 if cisseq: 2797 self.mapper().ScalarVisibilityOn() 2798 else: 2799 self.mapper().ScalarVisibilityOff() 2800 self.GetProperty().SetColor(get_color(c)) 2801 self.name = "Spheres"
Build a (possibly large) set of spheres at centers
of radius r
.
Either c
or r
can be a list of RGB colors or radii.
Examples:
2804class Earth(Mesh): 2805 """ 2806 Build a textured mesh representing the Earth. 2807 """ 2808 2809 def __init__(self, style=1, r=1.0): 2810 """ 2811 Build a textured mesh representing the Earth. 2812 2813 Example: 2814 - [geodesic.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic.py) 2815 2816 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2817 """ 2818 tss = vtk.vtkTexturedSphereSource() 2819 tss.SetRadius(r) 2820 tss.SetThetaResolution(72) 2821 tss.SetPhiResolution(36) 2822 Mesh.__init__(self, tss, c="w") 2823 atext = vtk.vtkTexture() 2824 pnm_reader = vtk.vtkJPEGReader() 2825 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2826 pnm_reader.SetFileName(fn) 2827 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2828 atext.InterpolateOn() 2829 self.SetTexture(atext) 2830 self.name = "Earth"
Build a textured mesh representing the Earth.
2809 def __init__(self, style=1, r=1.0): 2810 """ 2811 Build a textured mesh representing the Earth. 2812 2813 Example: 2814 - [geodesic.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic.py) 2815 2816 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2817 """ 2818 tss = vtk.vtkTexturedSphereSource() 2819 tss.SetRadius(r) 2820 tss.SetThetaResolution(72) 2821 tss.SetPhiResolution(36) 2822 Mesh.__init__(self, tss, c="w") 2823 atext = vtk.vtkTexture() 2824 pnm_reader = vtk.vtkJPEGReader() 2825 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2826 pnm_reader.SetFileName(fn) 2827 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2828 atext.InterpolateOn() 2829 self.SetTexture(atext) 2830 self.name = "Earth"
2833class Ellipsoid(Mesh): 2834 """ 2835 Build a 3D ellipsoid. 2836 """ 2837 2838 def __init__( 2839 self, 2840 pos=(0, 0, 0), 2841 axis1=(1, 0, 0), 2842 axis2=(0, 2, 0), 2843 axis3=(0, 0, 3), 2844 res=24, 2845 c="cyan4", 2846 alpha=1.0, 2847 ): 2848 """ 2849 Build a 3D ellipsoid centered at position `pos`. 2850 2851 Arguments: 2852 axis1 : (list) 2853 First axis 2854 axis2 : (list) 2855 Second axis 2856 axis3 : (list) 2857 Third axis 2858 2859 .. note:: `axis1` and `axis2` are only used to define sizes and one azimuth angle. 2860 """ 2861 2862 self.center = pos 2863 self.va_error = 0 2864 self.vb_error = 0 2865 self.vc_error = 0 2866 self.axis1 = axis1 2867 self.axis2 = axis2 2868 self.axis3 = axis3 2869 self.nr_of_points = 1 # used by pcaEllipsoid 2870 2871 if utils.is_sequence(res): 2872 res_t, res_phi = res 2873 else: 2874 res_t, res_phi = 2 * res, res 2875 2876 elli_source = vtk.vtkSphereSource() 2877 elli_source.SetThetaResolution(res_t) 2878 elli_source.SetPhiResolution(res_phi) 2879 elli_source.Update() 2880 l1 = np.linalg.norm(axis1) 2881 l2 = np.linalg.norm(axis2) 2882 l3 = np.linalg.norm(axis3) 2883 self.va = l1 2884 self.vb = l2 2885 self.vc = l3 2886 axis1 = np.array(axis1) / l1 2887 axis2 = np.array(axis2) / l2 2888 axis3 = np.array(axis3) / l3 2889 angle = np.arcsin(np.dot(axis1, axis2)) 2890 theta = np.arccos(axis3[2]) 2891 phi = np.arctan2(axis3[1], axis3[0]) 2892 2893 t = vtk.vtkTransform() 2894 t.PostMultiply() 2895 t.Scale(l1, l2, l3) 2896 t.RotateX(np.rad2deg(angle)) 2897 t.RotateY(np.rad2deg(theta)) 2898 t.RotateZ(np.rad2deg(phi)) 2899 tf = vtk.vtkTransformPolyDataFilter() 2900 tf.SetInputData(elli_source.GetOutput()) 2901 tf.SetTransform(t) 2902 tf.Update() 2903 pd = tf.GetOutput() 2904 self.transformation = t 2905 2906 Mesh.__init__(self, pd, c, alpha) 2907 self.phong() 2908 if len(pos) == 2: 2909 pos = (pos[0], pos[1], 0) 2910 self.SetPosition(pos) 2911 self.name = "Ellipsoid" 2912 2913 def asphericity(self): 2914 """ 2915 Return a measure of how different an ellipsoid is froma sphere. 2916 Values close to zero correspond to a spheric object. 2917 """ 2918 a,b,c = self.va, self.vb, self.vc 2919 asp = ( ((a-b)/(a+b))**2 2920 + ((a-c)/(a+c))**2 2921 + ((b-c)/(b+c))**2 )/3. * 4. 2922 return asp 2923 2924 def asphericity_error(self): 2925 """ 2926 Calculate statistical error on the asphericity value. 2927 2928 Errors on the main axes are stored in 2929 `Ellipsoid.va_error, Ellipsoid.vb_error and Ellipsoid.vc_error`. 2930 """ 2931 a, b, c = self.va, self.vb, self.vc 2932 sqrtn = np.sqrt(self.nr_of_points) 2933 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2934 2935 # from sympy import * 2936 # init_printing(use_unicode=True) 2937 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2938 # L = ( 2939 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2940 # / 3 * 4) 2941 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2942 # print(dl2) 2943 # exit() 2944 dL2 = ( 2945 ea ** 2 2946 * ( 2947 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2948 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2949 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2950 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2951 ) ** 2 2952 + eb ** 2 2953 * ( 2954 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2955 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2956 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2957 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2958 ) ** 2 2959 + ec ** 2 2960 * ( 2961 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2962 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2963 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2964 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2965 ) ** 2 2966 ) 2967 2968 err = np.sqrt(dL2) 2969 2970 self.va_error = ea 2971 self.vb_error = eb 2972 self.vc_error = ec 2973 return err
Build a 3D ellipsoid.
2838 def __init__( 2839 self, 2840 pos=(0, 0, 0), 2841 axis1=(1, 0, 0), 2842 axis2=(0, 2, 0), 2843 axis3=(0, 0, 3), 2844 res=24, 2845 c="cyan4", 2846 alpha=1.0, 2847 ): 2848 """ 2849 Build a 3D ellipsoid centered at position `pos`. 2850 2851 Arguments: 2852 axis1 : (list) 2853 First axis 2854 axis2 : (list) 2855 Second axis 2856 axis3 : (list) 2857 Third axis 2858 2859 .. note:: `axis1` and `axis2` are only used to define sizes and one azimuth angle. 2860 """ 2861 2862 self.center = pos 2863 self.va_error = 0 2864 self.vb_error = 0 2865 self.vc_error = 0 2866 self.axis1 = axis1 2867 self.axis2 = axis2 2868 self.axis3 = axis3 2869 self.nr_of_points = 1 # used by pcaEllipsoid 2870 2871 if utils.is_sequence(res): 2872 res_t, res_phi = res 2873 else: 2874 res_t, res_phi = 2 * res, res 2875 2876 elli_source = vtk.vtkSphereSource() 2877 elli_source.SetThetaResolution(res_t) 2878 elli_source.SetPhiResolution(res_phi) 2879 elli_source.Update() 2880 l1 = np.linalg.norm(axis1) 2881 l2 = np.linalg.norm(axis2) 2882 l3 = np.linalg.norm(axis3) 2883 self.va = l1 2884 self.vb = l2 2885 self.vc = l3 2886 axis1 = np.array(axis1) / l1 2887 axis2 = np.array(axis2) / l2 2888 axis3 = np.array(axis3) / l3 2889 angle = np.arcsin(np.dot(axis1, axis2)) 2890 theta = np.arccos(axis3[2]) 2891 phi = np.arctan2(axis3[1], axis3[0]) 2892 2893 t = vtk.vtkTransform() 2894 t.PostMultiply() 2895 t.Scale(l1, l2, l3) 2896 t.RotateX(np.rad2deg(angle)) 2897 t.RotateY(np.rad2deg(theta)) 2898 t.RotateZ(np.rad2deg(phi)) 2899 tf = vtk.vtkTransformPolyDataFilter() 2900 tf.SetInputData(elli_source.GetOutput()) 2901 tf.SetTransform(t) 2902 tf.Update() 2903 pd = tf.GetOutput() 2904 self.transformation = t 2905 2906 Mesh.__init__(self, pd, c, alpha) 2907 self.phong() 2908 if len(pos) == 2: 2909 pos = (pos[0], pos[1], 0) 2910 self.SetPosition(pos) 2911 self.name = "Ellipsoid"
Build a 3D ellipsoid centered at position pos
.
Arguments:
- axis1 : (list) First axis
- axis2 : (list) Second axis
- axis3 : (list) Third axis
axis1
and axis2
are only used to define sizes and one azimuth angle.
2913 def asphericity(self): 2914 """ 2915 Return a measure of how different an ellipsoid is froma sphere. 2916 Values close to zero correspond to a spheric object. 2917 """ 2918 a,b,c = self.va, self.vb, self.vc 2919 asp = ( ((a-b)/(a+b))**2 2920 + ((a-c)/(a+c))**2 2921 + ((b-c)/(b+c))**2 )/3. * 4. 2922 return asp
Return a measure of how different an ellipsoid is froma sphere. Values close to zero correspond to a spheric object.
2924 def asphericity_error(self): 2925 """ 2926 Calculate statistical error on the asphericity value. 2927 2928 Errors on the main axes are stored in 2929 `Ellipsoid.va_error, Ellipsoid.vb_error and Ellipsoid.vc_error`. 2930 """ 2931 a, b, c = self.va, self.vb, self.vc 2932 sqrtn = np.sqrt(self.nr_of_points) 2933 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2934 2935 # from sympy import * 2936 # init_printing(use_unicode=True) 2937 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2938 # L = ( 2939 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2940 # / 3 * 4) 2941 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2942 # print(dl2) 2943 # exit() 2944 dL2 = ( 2945 ea ** 2 2946 * ( 2947 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2948 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2949 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2950 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2951 ) ** 2 2952 + eb ** 2 2953 * ( 2954 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2955 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2956 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2957 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2958 ) ** 2 2959 + ec ** 2 2960 * ( 2961 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2962 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2963 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2964 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2965 ) ** 2 2966 ) 2967 2968 err = np.sqrt(dL2) 2969 2970 self.va_error = ea 2971 self.vb_error = eb 2972 self.vc_error = ec 2973 return err
Calculate statistical error on the asphericity value.
Errors on the main axes are stored in
Ellipsoid.va_error, Ellipsoid.vb_error and Ellipsoid.vc_error
.
2976class Grid(Mesh): 2977 """ 2978 An even or uneven 2D grid. 2979 """ 2980 2981 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0): 2982 """ 2983 Create an even or uneven 2D grid. 2984 2985 Arguments: 2986 s : (float, list) 2987 if a float is provided it is interpreted as the total size along x and y, 2988 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2989 In this case keyword `res` is ignored (see example below). 2990 res : (list) 2991 resolutions along x and y, e.i. the number of subdivisions 2992 lw : (int) 2993 line width 2994 2995 Example: 2996 ```python 2997 from vedo import * 2998 import numpy as np 2999 xcoords = np.arange(0, 2, 0.2) 3000 ycoords = np.arange(0, 1, 0.2) 3001 sqrtx = sqrt(xcoords) 3002 grid = Grid(s=(sqrtx, ycoords)).lw(2) 3003 grid.show(axes=8) 3004 3005 # can also create a grid from np.mgrid: 3006 X, Y = np.mgrid[-12:12:1000*1j, 0:15:1000*1j] 3007 vgrid = Grid(s=(X[:,0], Y[0])) 3008 vgrid.show(axes=1).close() 3009 ``` 3010 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 3011 """ 3012 resx, resy = res 3013 sx, sy = s 3014 3015 if len(pos) == 2: 3016 pos = (pos[0], pos[1], 0) 3017 3018 if utils.is_sequence(sx) and utils.is_sequence(sy): 3019 verts = [] 3020 for y in sy: 3021 for x in sx: 3022 verts.append([x, y, 0]) 3023 faces = [] 3024 n = len(sx) 3025 m = len(sy) 3026 for j in range(m - 1): 3027 j1n = (j + 1) * n 3028 for i in range(n - 1): 3029 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3030 3031 verts = np.array(verts) 3032 Mesh.__init__(self, [verts, faces], c, alpha) 3033 3034 else: 3035 ps = vtk.vtkPlaneSource() 3036 ps.SetResolution(resx, resy) 3037 ps.Update() 3038 poly0 = ps.GetOutput() 3039 t0 = vtk.vtkTransform() 3040 t0.Scale(sx, sy, 1) 3041 tf0 = vtk.vtkTransformPolyDataFilter() 3042 tf0.SetInputData(poly0) 3043 tf0.SetTransform(t0) 3044 tf0.Update() 3045 poly = tf0.GetOutput() 3046 Mesh.__init__(self, poly, c, alpha) 3047 self.SetPosition(pos) 3048 3049 self.wireframe().lw(lw) 3050 self.GetProperty().LightingOff() 3051 self.name = "Grid"
An even or uneven 2D grid.
2981 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0): 2982 """ 2983 Create an even or uneven 2D grid. 2984 2985 Arguments: 2986 s : (float, list) 2987 if a float is provided it is interpreted as the total size along x and y, 2988 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2989 In this case keyword `res` is ignored (see example below). 2990 res : (list) 2991 resolutions along x and y, e.i. the number of subdivisions 2992 lw : (int) 2993 line width 2994 2995 Example: 2996 ```python 2997 from vedo import * 2998 import numpy as np 2999 xcoords = np.arange(0, 2, 0.2) 3000 ycoords = np.arange(0, 1, 0.2) 3001 sqrtx = sqrt(xcoords) 3002 grid = Grid(s=(sqrtx, ycoords)).lw(2) 3003 grid.show(axes=8) 3004 3005 # can also create a grid from np.mgrid: 3006 X, Y = np.mgrid[-12:12:1000*1j, 0:15:1000*1j] 3007 vgrid = Grid(s=(X[:,0], Y[0])) 3008 vgrid.show(axes=1).close() 3009 ``` 3010 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 3011 """ 3012 resx, resy = res 3013 sx, sy = s 3014 3015 if len(pos) == 2: 3016 pos = (pos[0], pos[1], 0) 3017 3018 if utils.is_sequence(sx) and utils.is_sequence(sy): 3019 verts = [] 3020 for y in sy: 3021 for x in sx: 3022 verts.append([x, y, 0]) 3023 faces = [] 3024 n = len(sx) 3025 m = len(sy) 3026 for j in range(m - 1): 3027 j1n = (j + 1) * n 3028 for i in range(n - 1): 3029 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3030 3031 verts = np.array(verts) 3032 Mesh.__init__(self, [verts, faces], c, alpha) 3033 3034 else: 3035 ps = vtk.vtkPlaneSource() 3036 ps.SetResolution(resx, resy) 3037 ps.Update() 3038 poly0 = ps.GetOutput() 3039 t0 = vtk.vtkTransform() 3040 t0.Scale(sx, sy, 1) 3041 tf0 = vtk.vtkTransformPolyDataFilter() 3042 tf0.SetInputData(poly0) 3043 tf0.SetTransform(t0) 3044 tf0.Update() 3045 poly = tf0.GetOutput() 3046 Mesh.__init__(self, poly, c, alpha) 3047 self.SetPosition(pos) 3048 3049 self.wireframe().lw(lw) 3050 self.GetProperty().LightingOff() 3051 self.name = "Grid"
Create an even or uneven 2D grid.
Arguments:
- s : (float, list)
if a float is provided it is interpreted as the total size along x and y,
if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
In this case keyword
res
is ignored (see example below). - res : (list) resolutions along x and y, e.i. the number of subdivisions
- lw : (int) line width
Example:
from vedo import * import numpy as np xcoords = np.arange(0, 2, 0.2) ycoords = np.arange(0, 1, 0.2) sqrtx = sqrt(xcoords) grid = Grid(s=(sqrtx, ycoords)).lw(2) grid.show(axes=8) # can also create a grid from np.mgrid: X, Y = np.mgrid[-12:12:1000*1j, 0:15:1000*1j] vgrid = Grid(s=(X[:,0], Y[0])) vgrid.show(axes=1).close()
3296class TessellatedBox(Mesh): 3297 """ 3298 Build a cubic `Mesh` made of quads. 3299 """ 3300 3301 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5): 3302 """ 3303 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3304 3305 Arguments: 3306 pos : (list) 3307 position of the left bottom corner 3308 n : (int, list) 3309 number of subdivisions along each side 3310 spacing : (float) 3311 size of the side of the single quad in the 3 directions 3312 """ 3313 if utils.is_sequence(n): # slow 3314 img = vtk.vtkImageData() 3315 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3316 img.SetSpacing(spacing) 3317 gf = vtk.vtkGeometryFilter() 3318 gf.SetInputData(img) 3319 gf.Update() 3320 poly = gf.GetOutput() 3321 else: # fast 3322 n -= 1 3323 tbs = vtk.vtkTessellatedBoxSource() 3324 tbs.SetLevel(n) 3325 if len(bounds): 3326 tbs.SetBounds(bounds) 3327 else: 3328 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3329 tbs.QuadsOn() 3330 tbs.SetOutputPointsPrecision(vtk.vtkAlgorithm.SINGLE_PRECISION) 3331 tbs.Update() 3332 poly = tbs.GetOutput() 3333 Mesh.__init__(self, poly, c=c, alpha=alpha) 3334 self.SetPosition(pos) 3335 self.lw(1).lighting("off") 3336 self.base = np.array([0.5, 0.5, 0.0]) 3337 self.top = np.array([0.5, 0.5, 1.0]) 3338 self.name = "TessellatedBox"
Build a cubic Mesh
made of quads.
3301 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5): 3302 """ 3303 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3304 3305 Arguments: 3306 pos : (list) 3307 position of the left bottom corner 3308 n : (int, list) 3309 number of subdivisions along each side 3310 spacing : (float) 3311 size of the side of the single quad in the 3 directions 3312 """ 3313 if utils.is_sequence(n): # slow 3314 img = vtk.vtkImageData() 3315 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3316 img.SetSpacing(spacing) 3317 gf = vtk.vtkGeometryFilter() 3318 gf.SetInputData(img) 3319 gf.Update() 3320 poly = gf.GetOutput() 3321 else: # fast 3322 n -= 1 3323 tbs = vtk.vtkTessellatedBoxSource() 3324 tbs.SetLevel(n) 3325 if len(bounds): 3326 tbs.SetBounds(bounds) 3327 else: 3328 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3329 tbs.QuadsOn() 3330 tbs.SetOutputPointsPrecision(vtk.vtkAlgorithm.SINGLE_PRECISION) 3331 tbs.Update() 3332 poly = tbs.GetOutput() 3333 Mesh.__init__(self, poly, c=c, alpha=alpha) 3334 self.SetPosition(pos) 3335 self.lw(1).lighting("off") 3336 self.base = np.array([0.5, 0.5, 0.0]) 3337 self.top = np.array([0.5, 0.5, 1.0]) 3338 self.name = "TessellatedBox"
Build a cubic Mesh
made of n
small quads in the 3 axis directions.
Arguments:
- pos : (list) position of the left bottom corner
- n : (int, list) number of subdivisions along each side
- spacing : (float) size of the side of the single quad in the 3 directions
3054class Plane(Mesh): 3055 """ 3056 Create a plane in space. 3057 """ 3058 3059 def __init__(self, pos=(0, 0, 0), normal=(0, 0, 1), s=(1, 1), res=(1, 1), c="gray5", alpha=1.0): 3060 """ 3061 Create a plane of size `s=(xsize, ysize)` oriented perpendicular to vector `normal` 3062 and so that it passes through point `pos`. 3063 3064 Arguments: 3065 normal : (list) 3066 normal vector to the plane 3067 """ 3068 pos = utils.make3d(pos) 3069 sx, sy = s 3070 3071 self.normal = np.asarray(normal, dtype=float) 3072 self.center = np.asarray(pos, dtype=float) 3073 self.variance = 0 3074 3075 ps = vtk.vtkPlaneSource() 3076 ps.SetResolution(res[0], res[1]) 3077 tri = vtk.vtkTriangleFilter() 3078 tri.SetInputConnection(ps.GetOutputPort()) 3079 tri.Update() 3080 poly = tri.GetOutput() 3081 axis = self.normal / np.linalg.norm(normal) 3082 theta = np.arccos(axis[2]) 3083 phi = np.arctan2(axis[1], axis[0]) 3084 t = vtk.vtkTransform() 3085 t.PostMultiply() 3086 t.Scale(sx, sy, 1) 3087 t.RotateY(np.rad2deg(theta)) 3088 t.RotateZ(np.rad2deg(phi)) 3089 tf = vtk.vtkTransformPolyDataFilter() 3090 tf.SetInputData(poly) 3091 tf.SetTransform(t) 3092 tf.Update() 3093 Mesh.__init__(self, tf.GetOutput(), c, alpha) 3094 self.lighting("off") 3095 self.SetPosition(pos) 3096 self.name = "Plane" 3097 self.top = self.normal 3098 self.bottom = np.array([0.0, 0.0, 0.0]) 3099 3100 def contains(self, points): 3101 """ 3102 Check if each of the provided point lies on this plane. 3103 `points` is an array of shape (n, 3). 3104 """ 3105 points = np.array(points, dtype=float) 3106 bounds = self.points() 3107 3108 mask = np.isclose(np.dot(points - self.center, self.normal), 0) 3109 3110 for i in [1, 3]: 3111 AB = bounds[i] - bounds[0] 3112 AP = points - bounds[0] 3113 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3114 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3115 mask = np.logical_and(mask, mask_l) 3116 mask = np.logical_and(mask, mask_g) 3117 return mask
Create a plane in space.
3059 def __init__(self, pos=(0, 0, 0), normal=(0, 0, 1), s=(1, 1), res=(1, 1), c="gray5", alpha=1.0): 3060 """ 3061 Create a plane of size `s=(xsize, ysize)` oriented perpendicular to vector `normal` 3062 and so that it passes through point `pos`. 3063 3064 Arguments: 3065 normal : (list) 3066 normal vector to the plane 3067 """ 3068 pos = utils.make3d(pos) 3069 sx, sy = s 3070 3071 self.normal = np.asarray(normal, dtype=float) 3072 self.center = np.asarray(pos, dtype=float) 3073 self.variance = 0 3074 3075 ps = vtk.vtkPlaneSource() 3076 ps.SetResolution(res[0], res[1]) 3077 tri = vtk.vtkTriangleFilter() 3078 tri.SetInputConnection(ps.GetOutputPort()) 3079 tri.Update() 3080 poly = tri.GetOutput() 3081 axis = self.normal / np.linalg.norm(normal) 3082 theta = np.arccos(axis[2]) 3083 phi = np.arctan2(axis[1], axis[0]) 3084 t = vtk.vtkTransform() 3085 t.PostMultiply() 3086 t.Scale(sx, sy, 1) 3087 t.RotateY(np.rad2deg(theta)) 3088 t.RotateZ(np.rad2deg(phi)) 3089 tf = vtk.vtkTransformPolyDataFilter() 3090 tf.SetInputData(poly) 3091 tf.SetTransform(t) 3092 tf.Update() 3093 Mesh.__init__(self, tf.GetOutput(), c, alpha) 3094 self.lighting("off") 3095 self.SetPosition(pos) 3096 self.name = "Plane" 3097 self.top = self.normal 3098 self.bottom = np.array([0.0, 0.0, 0.0])
Create a plane of size s=(xsize, ysize)
oriented perpendicular to vector normal
and so that it passes through point pos
.
Arguments:
- normal : (list) normal vector to the plane
3100 def contains(self, points): 3101 """ 3102 Check if each of the provided point lies on this plane. 3103 `points` is an array of shape (n, 3). 3104 """ 3105 points = np.array(points, dtype=float) 3106 bounds = self.points() 3107 3108 mask = np.isclose(np.dot(points - self.center, self.normal), 0) 3109 3110 for i in [1, 3]: 3111 AB = bounds[i] - bounds[0] 3112 AP = points - bounds[0] 3113 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3114 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3115 mask = np.logical_and(mask, mask_l) 3116 mask = np.logical_and(mask, mask_g) 3117 return mask
Check if each of the provided point lies on this plane.
points
is an array of shape (n, 3).
3214class Box(Mesh): 3215 """ 3216 Build a box of specified dimensions. 3217 """ 3218 3219 def __init__(self, pos=(0, 0, 0), 3220 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0): 3221 """ 3222 Build a box of dimensions `x=length, y=width and z=height`. 3223 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3224 3225 If `size` is a list of 6 numbers, this will be interpreted as the bounding box: 3226 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3227 3228 Examples: 3229 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3230 3231 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3232 """ 3233 if len(size) == 6: 3234 bounds = size 3235 length = bounds[1] - bounds[0] 3236 width = bounds[3] - bounds[2] 3237 height = bounds[5] - bounds[4] 3238 xp = (bounds[1] + bounds[0]) / 2 3239 yp = (bounds[3] + bounds[2]) / 2 3240 zp = (bounds[5] + bounds[4]) / 2 3241 pos = (xp, yp, zp) 3242 elif len(size) == 3: 3243 length, width, height = size 3244 3245 src = vtk.vtkCubeSource() 3246 src.SetXLength(length) 3247 src.SetYLength(width) 3248 src.SetZLength(height) 3249 src.Update() 3250 pd = src.GetOutput() 3251 3252 tc = [ 3253 [0.0, 0.0], 3254 [1.0, 0.0], 3255 [0.0, 1.0], 3256 [1.0, 1.0], 3257 [1.0, 0.0], 3258 [0.0, 0.0], 3259 [1.0, 1.0], 3260 [0.0, 1.0], 3261 [1.0, 1.0], 3262 [1.0, 0.0], 3263 [0.0, 1.0], 3264 [0.0, 0.0], 3265 [0.0, 1.0], 3266 [0.0, 0.0], 3267 [1.0, 1.0], 3268 [1.0, 0.0], 3269 [1.0, 0.0], 3270 [0.0, 0.0], 3271 [1.0, 1.0], 3272 [0.0, 1.0], 3273 [0.0, 0.0], 3274 [1.0, 0.0], 3275 [0.0, 1.0], 3276 [1.0, 1.0], 3277 ] 3278 vtc = utils.numpy2vtk(tc) 3279 pd.GetPointData().SetTCoords(vtc) 3280 Mesh.__init__(self, pd, c, alpha) 3281 if len(pos) == 2: 3282 pos = (pos[0], pos[1], 0) 3283 self.SetPosition(pos) 3284 self.name = "Box"
Build a box of specified dimensions.
3219 def __init__(self, pos=(0, 0, 0), 3220 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0): 3221 """ 3222 Build a box of dimensions `x=length, y=width and z=height`. 3223 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3224 3225 If `size` is a list of 6 numbers, this will be interpreted as the bounding box: 3226 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3227 3228 Examples: 3229 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3230 3231 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3232 """ 3233 if len(size) == 6: 3234 bounds = size 3235 length = bounds[1] - bounds[0] 3236 width = bounds[3] - bounds[2] 3237 height = bounds[5] - bounds[4] 3238 xp = (bounds[1] + bounds[0]) / 2 3239 yp = (bounds[3] + bounds[2]) / 2 3240 zp = (bounds[5] + bounds[4]) / 2 3241 pos = (xp, yp, zp) 3242 elif len(size) == 3: 3243 length, width, height = size 3244 3245 src = vtk.vtkCubeSource() 3246 src.SetXLength(length) 3247 src.SetYLength(width) 3248 src.SetZLength(height) 3249 src.Update() 3250 pd = src.GetOutput() 3251 3252 tc = [ 3253 [0.0, 0.0], 3254 [1.0, 0.0], 3255 [0.0, 1.0], 3256 [1.0, 1.0], 3257 [1.0, 0.0], 3258 [0.0, 0.0], 3259 [1.0, 1.0], 3260 [0.0, 1.0], 3261 [1.0, 1.0], 3262 [1.0, 0.0], 3263 [0.0, 1.0], 3264 [0.0, 0.0], 3265 [0.0, 1.0], 3266 [0.0, 0.0], 3267 [1.0, 1.0], 3268 [1.0, 0.0], 3269 [1.0, 0.0], 3270 [0.0, 0.0], 3271 [1.0, 1.0], 3272 [0.0, 1.0], 3273 [0.0, 0.0], 3274 [1.0, 0.0], 3275 [0.0, 1.0], 3276 [1.0, 1.0], 3277 ] 3278 vtc = utils.numpy2vtk(tc) 3279 pd.GetPointData().SetTCoords(vtc) 3280 Mesh.__init__(self, pd, c, alpha) 3281 if len(pos) == 2: 3282 pos = (pos[0], pos[1], 0) 3283 self.SetPosition(pos) 3284 self.name = "Box"
Build a box of dimensions x=length, y=width and z=height
.
Alternatively dimensions can be defined by setting size
keyword with a tuple.
If size
is a list of 6 numbers, this will be interpreted as the bounding box:
[xmin,xmax, ymin,ymax, zmin,zmax]
Examples:
3287class Cube(Box): 3288 """Build a cube.""" 3289 3290 def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0): 3291 """Build a cube of size `side`.""" 3292 Box.__init__(self, pos, side, side, side, (), c, alpha) 3293 self.name = "Cube"
Build a cube.
3341class Spring(Mesh): 3342 """ 3343 Build a spring model. 3344 """ 3345 3346 def __init__( 3347 self, 3348 start_pt=(0, 0, 0), 3349 end_pt=(1, 0, 0), 3350 coils=20, 3351 r1=0.1, 3352 r2=None, 3353 thickness=None, 3354 c="gray5", 3355 alpha=1.0, 3356 ): 3357 """ 3358 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3359 3360 Arguments: 3361 coils : (int) 3362 number of coils 3363 r1 : (float) 3364 radius at start point 3365 r2 : (float) 3366 radius at end point 3367 thickness : (float) 3368 thickness of the coil section 3369 """ 3370 diff = end_pt - np.array(start_pt, dtype=float) 3371 length = np.linalg.norm(diff) 3372 if not length: 3373 return 3374 if not r1: 3375 r1 = length / 20 3376 trange = np.linspace(0, length, num=50 * coils) 3377 om = 6.283 * (coils - 0.5) / length 3378 if not r2: 3379 r2 = r1 3380 pts = [] 3381 for t in trange: 3382 f = (length - t) / length 3383 rd = r1 * f + r2 * (1 - f) 3384 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3385 3386 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3387 diff = diff / length 3388 theta = np.arccos(diff[2]) 3389 phi = np.arctan2(diff[1], diff[0]) 3390 sp = Line(pts).polydata(False) 3391 t = vtk.vtkTransform() 3392 t.RotateZ(np.rad2deg(phi)) 3393 t.RotateY(np.rad2deg(theta)) 3394 tf = vtk.vtkTransformPolyDataFilter() 3395 tf.SetInputData(sp) 3396 tf.SetTransform(t) 3397 tf.Update() 3398 tuf = vtk.vtkTubeFilter() 3399 tuf.SetNumberOfSides(12) 3400 tuf.CappingOn() 3401 tuf.SetInputData(tf.GetOutput()) 3402 if not thickness: 3403 thickness = r1 / 10 3404 tuf.SetRadius(thickness) 3405 tuf.Update() 3406 Mesh.__init__(self, tuf.GetOutput(), c, alpha) 3407 self.phong() 3408 self.SetPosition(start_pt) 3409 self.base = np.array(start_pt, dtype=float) 3410 self.top = np.array(end_pt, dtype=float) 3411 self.name = "Spring"
Build a spring model.
3346 def __init__( 3347 self, 3348 start_pt=(0, 0, 0), 3349 end_pt=(1, 0, 0), 3350 coils=20, 3351 r1=0.1, 3352 r2=None, 3353 thickness=None, 3354 c="gray5", 3355 alpha=1.0, 3356 ): 3357 """ 3358 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3359 3360 Arguments: 3361 coils : (int) 3362 number of coils 3363 r1 : (float) 3364 radius at start point 3365 r2 : (float) 3366 radius at end point 3367 thickness : (float) 3368 thickness of the coil section 3369 """ 3370 diff = end_pt - np.array(start_pt, dtype=float) 3371 length = np.linalg.norm(diff) 3372 if not length: 3373 return 3374 if not r1: 3375 r1 = length / 20 3376 trange = np.linspace(0, length, num=50 * coils) 3377 om = 6.283 * (coils - 0.5) / length 3378 if not r2: 3379 r2 = r1 3380 pts = [] 3381 for t in trange: 3382 f = (length - t) / length 3383 rd = r1 * f + r2 * (1 - f) 3384 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3385 3386 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3387 diff = diff / length 3388 theta = np.arccos(diff[2]) 3389 phi = np.arctan2(diff[1], diff[0]) 3390 sp = Line(pts).polydata(False) 3391 t = vtk.vtkTransform() 3392 t.RotateZ(np.rad2deg(phi)) 3393 t.RotateY(np.rad2deg(theta)) 3394 tf = vtk.vtkTransformPolyDataFilter() 3395 tf.SetInputData(sp) 3396 tf.SetTransform(t) 3397 tf.Update() 3398 tuf = vtk.vtkTubeFilter() 3399 tuf.SetNumberOfSides(12) 3400 tuf.CappingOn() 3401 tuf.SetInputData(tf.GetOutput()) 3402 if not thickness: 3403 thickness = r1 / 10 3404 tuf.SetRadius(thickness) 3405 tuf.Update() 3406 Mesh.__init__(self, tuf.GetOutput(), c, alpha) 3407 self.phong() 3408 self.SetPosition(start_pt) 3409 self.base = np.array(start_pt, dtype=float) 3410 self.top = np.array(end_pt, dtype=float) 3411 self.name = "Spring"
Build a spring of specified nr of coils
between start_pt
and end_pt
.
Arguments:
- coils : (int) number of coils
- r1 : (float) radius at start point
- r2 : (float) radius at end point
- thickness : (float) thickness of the coil section
3414class Cylinder(Mesh): 3415 """ 3416 Build a cylinder of specified height and radius. 3417 """ 3418 3419 def __init__( 3420 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), cap=True, res=24, c="teal3", alpha=1.0 3421 ): 3422 """ 3423 Build a cylinder of specified height and radius `r`, centered at `pos`. 3424 3425 If `pos` is a list of 2 points, e.g. `pos=[v1,v2]`, build a cylinder with base 3426 centered at `v1` and top at `v2`. 3427 3428 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3429 """ 3430 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3431 base = np.array(pos[0], dtype=float) 3432 top = np.array(pos[1], dtype=float) 3433 pos = (base + top) / 2 3434 height = np.linalg.norm(top - base) 3435 axis = top - base 3436 axis = utils.versor(axis) 3437 else: 3438 axis = utils.versor(axis) 3439 base = pos - axis * height / 2 3440 top = pos + axis * height / 2 3441 3442 cyl = vtk.vtkCylinderSource() 3443 cyl.SetResolution(res) 3444 cyl.SetRadius(r) 3445 cyl.SetHeight(height) 3446 cyl.SetCapping(cap) 3447 cyl.Update() 3448 3449 theta = np.arccos(axis[2]) 3450 phi = np.arctan2(axis[1], axis[0]) 3451 t = vtk.vtkTransform() 3452 t.PostMultiply() 3453 t.RotateX(90) # put it along Z 3454 t.RotateY(np.rad2deg(theta)) 3455 t.RotateZ(np.rad2deg(phi)) 3456 tf = vtk.vtkTransformPolyDataFilter() 3457 tf.SetInputData(cyl.GetOutput()) 3458 tf.SetTransform(t) 3459 tf.Update() 3460 pd = tf.GetOutput() 3461 3462 Mesh.__init__(self, pd, c, alpha) 3463 self.phong() 3464 self.SetPosition(pos) 3465 self.base = base + pos 3466 self.top = top + pos 3467 self.name = "Cylinder"
Build a cylinder of specified height and radius.
3419 def __init__( 3420 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), cap=True, res=24, c="teal3", alpha=1.0 3421 ): 3422 """ 3423 Build a cylinder of specified height and radius `r`, centered at `pos`. 3424 3425 If `pos` is a list of 2 points, e.g. `pos=[v1,v2]`, build a cylinder with base 3426 centered at `v1` and top at `v2`. 3427 3428 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3429 """ 3430 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3431 base = np.array(pos[0], dtype=float) 3432 top = np.array(pos[1], dtype=float) 3433 pos = (base + top) / 2 3434 height = np.linalg.norm(top - base) 3435 axis = top - base 3436 axis = utils.versor(axis) 3437 else: 3438 axis = utils.versor(axis) 3439 base = pos - axis * height / 2 3440 top = pos + axis * height / 2 3441 3442 cyl = vtk.vtkCylinderSource() 3443 cyl.SetResolution(res) 3444 cyl.SetRadius(r) 3445 cyl.SetHeight(height) 3446 cyl.SetCapping(cap) 3447 cyl.Update() 3448 3449 theta = np.arccos(axis[2]) 3450 phi = np.arctan2(axis[1], axis[0]) 3451 t = vtk.vtkTransform() 3452 t.PostMultiply() 3453 t.RotateX(90) # put it along Z 3454 t.RotateY(np.rad2deg(theta)) 3455 t.RotateZ(np.rad2deg(phi)) 3456 tf = vtk.vtkTransformPolyDataFilter() 3457 tf.SetInputData(cyl.GetOutput()) 3458 tf.SetTransform(t) 3459 tf.Update() 3460 pd = tf.GetOutput() 3461 3462 Mesh.__init__(self, pd, c, alpha) 3463 self.phong() 3464 self.SetPosition(pos) 3465 self.base = base + pos 3466 self.top = top + pos 3467 self.name = "Cylinder"
3470class Cone(Mesh): 3471 """Build a cone of specified radius and height.""" 3472 3473 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3474 res=48, c="green3", alpha=1.0): 3475 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3476 con = vtk.vtkConeSource() 3477 con.SetResolution(res) 3478 con.SetRadius(r) 3479 con.SetHeight(height) 3480 con.SetDirection(axis) 3481 con.Update() 3482 Mesh.__init__(self, con.GetOutput(), c, alpha) 3483 self.phong() 3484 if len(pos) == 2: 3485 pos = (pos[0], pos[1], 0) 3486 self.SetPosition(pos) 3487 v = utils.versor(axis) * height / 2 3488 self.base = pos - v 3489 self.top = pos + v 3490 self.name = "Cone"
Build a cone of specified radius and height.
3473 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3474 res=48, c="green3", alpha=1.0): 3475 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3476 con = vtk.vtkConeSource() 3477 con.SetResolution(res) 3478 con.SetRadius(r) 3479 con.SetHeight(height) 3480 con.SetDirection(axis) 3481 con.Update() 3482 Mesh.__init__(self, con.GetOutput(), c, alpha) 3483 self.phong() 3484 if len(pos) == 2: 3485 pos = (pos[0], pos[1], 0) 3486 self.SetPosition(pos) 3487 v = utils.versor(axis) * height / 2 3488 self.base = pos - v 3489 self.top = pos + v 3490 self.name = "Cone"
Build a cone of specified radius r
and height
, centered at pos
.
3493class Pyramid(Cone): 3494 """Build a pyramidal shape.""" 3495 3496 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3497 c="green3", alpha=1): 3498 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3499 Cone.__init__(self, pos, s, height, axis, 4, c, alpha) 3500 self.name = "Pyramid"
Build a pyramidal shape.
3496 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3497 c="green3", alpha=1): 3498 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3499 Cone.__init__(self, pos, s, height, axis, 4, c, alpha) 3500 self.name = "Pyramid"
Build a pyramid of specified base size s
and height
, centered at pos
.
3503class Torus(Mesh): 3504 """ 3505 Build a toroidal shape. 3506 """ 3507 3508 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0): 3509 """ 3510 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3511 If `quad=True` a quad-mesh is generated. 3512 """ 3513 if utils.is_sequence(res): 3514 res_u, res_v = res 3515 else: 3516 res_u, res_v = 3 * res, res 3517 3518 if quads: 3519 # https://github.com/marcomusy/vedo/issues/710 3520 3521 n = res_v 3522 m = res_u 3523 3524 theta = np.linspace(0, 2.0 * np.pi, n) 3525 phi = np.linspace(0, 2.0 * np.pi, m) 3526 theta, phi = np.meshgrid(theta, phi) 3527 t = r1 + r2 * np.cos(theta) 3528 x = t * np.cos(phi) 3529 y = t * np.sin(phi) 3530 z = r2 * np.sin(theta) 3531 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3532 3533 faces = [] 3534 for j in range(m - 1): 3535 j1n = (j + 1) * n 3536 for i in range(n - 1): 3537 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3538 3539 Mesh.__init__(self, [pts, faces], c, alpha) 3540 3541 else: 3542 3543 rs = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricTorus() 3544 rs.SetRingRadius(r1) 3545 rs.SetCrossSectionRadius(r2) 3546 pfs = vtk.vtkParametricFunctionSource() 3547 pfs.SetParametricFunction(rs) 3548 pfs.SetUResolution(res_u) 3549 pfs.SetVResolution(res_v) 3550 pfs.Update() 3551 Mesh.__init__(self, pfs.GetOutput(), c, alpha) 3552 3553 self.phong() 3554 if len(pos) == 2: 3555 pos = (pos[0], pos[1], 0) 3556 self.SetPosition(pos) 3557 self.name = "Torus"
Build a toroidal shape.
3508 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0): 3509 """ 3510 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3511 If `quad=True` a quad-mesh is generated. 3512 """ 3513 if utils.is_sequence(res): 3514 res_u, res_v = res 3515 else: 3516 res_u, res_v = 3 * res, res 3517 3518 if quads: 3519 # https://github.com/marcomusy/vedo/issues/710 3520 3521 n = res_v 3522 m = res_u 3523 3524 theta = np.linspace(0, 2.0 * np.pi, n) 3525 phi = np.linspace(0, 2.0 * np.pi, m) 3526 theta, phi = np.meshgrid(theta, phi) 3527 t = r1 + r2 * np.cos(theta) 3528 x = t * np.cos(phi) 3529 y = t * np.sin(phi) 3530 z = r2 * np.sin(theta) 3531 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3532 3533 faces = [] 3534 for j in range(m - 1): 3535 j1n = (j + 1) * n 3536 for i in range(n - 1): 3537 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3538 3539 Mesh.__init__(self, [pts, faces], c, alpha) 3540 3541 else: 3542 3543 rs = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricTorus() 3544 rs.SetRingRadius(r1) 3545 rs.SetCrossSectionRadius(r2) 3546 pfs = vtk.vtkParametricFunctionSource() 3547 pfs.SetParametricFunction(rs) 3548 pfs.SetUResolution(res_u) 3549 pfs.SetVResolution(res_v) 3550 pfs.Update() 3551 Mesh.__init__(self, pfs.GetOutput(), c, alpha) 3552 3553 self.phong() 3554 if len(pos) == 2: 3555 pos = (pos[0], pos[1], 0) 3556 self.SetPosition(pos) 3557 self.name = "Torus"
Build a torus of specified outer radius r1
internal radius r2
, centered at pos
.
If quad=True
a quad-mesh is generated.
3560class Paraboloid(Mesh): 3561 """ 3562 Build a paraboloid. 3563 """ 3564 3565 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0): 3566 """ 3567 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3568 3569 Full volumetric expression is: 3570 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3571 3572 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3573 """ 3574 quadric = vtk.vtkQuadric() 3575 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3576 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3577 # + a3*x*y + a4*y*z + a5*x*z 3578 # + a6*x + a7*y + a8*z +a9 3579 sample = vtk.vtkSampleFunction() 3580 sample.SetSampleDimensions(res, res, res) 3581 sample.SetImplicitFunction(quadric) 3582 3583 contours = vtk.vtkContourFilter() 3584 contours.SetInputConnection(sample.GetOutputPort()) 3585 contours.GenerateValues(1, 0.01, 0.01) 3586 contours.Update() 3587 3588 Mesh.__init__(self, contours.GetOutput(), c, alpha) 3589 self.compute_normals().phong() 3590 self.mapper().ScalarVisibilityOff() 3591 self.SetPosition(pos) 3592 self.name = "Paraboloid"
Build a paraboloid.
3565 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0): 3566 """ 3567 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3568 3569 Full volumetric expression is: 3570 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3571 3572 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3573 """ 3574 quadric = vtk.vtkQuadric() 3575 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3576 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3577 # + a3*x*y + a4*y*z + a5*x*z 3578 # + a6*x + a7*y + a8*z +a9 3579 sample = vtk.vtkSampleFunction() 3580 sample.SetSampleDimensions(res, res, res) 3581 sample.SetImplicitFunction(quadric) 3582 3583 contours = vtk.vtkContourFilter() 3584 contours.SetInputConnection(sample.GetOutputPort()) 3585 contours.GenerateValues(1, 0.01, 0.01) 3586 contours.Update() 3587 3588 Mesh.__init__(self, contours.GetOutput(), c, alpha) 3589 self.compute_normals().phong() 3590 self.mapper().ScalarVisibilityOff() 3591 self.SetPosition(pos) 3592 self.name = "Paraboloid"
Build a paraboloid of specified height and radius r
, centered at pos
.
Full volumetric expression is:
F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9
3595class Hyperboloid(Mesh): 3596 """ 3597 Build a hyperboloid. 3598 """ 3599 3600 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0): 3601 """ 3602 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3603 3604 Full volumetric expression is: 3605 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3606 """ 3607 q = vtk.vtkQuadric() 3608 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3609 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3610 # + a3*x*y + a4*y*z + a5*x*z 3611 # + a6*x + a7*y + a8*z +a9 3612 sample = vtk.vtkSampleFunction() 3613 sample.SetSampleDimensions(res, res, res) 3614 sample.SetImplicitFunction(q) 3615 3616 contours = vtk.vtkContourFilter() 3617 contours.SetInputConnection(sample.GetOutputPort()) 3618 contours.GenerateValues(1, value, value) 3619 contours.Update() 3620 3621 Mesh.__init__(self, contours.GetOutput(), c, alpha) 3622 self.compute_normals().phong() 3623 self.mapper().ScalarVisibilityOff() 3624 self.SetPosition(pos) 3625 self.name = "Hyperboloid"
Build a hyperboloid.
3600 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0): 3601 """ 3602 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3603 3604 Full volumetric expression is: 3605 `F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` 3606 """ 3607 q = vtk.vtkQuadric() 3608 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3609 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3610 # + a3*x*y + a4*y*z + a5*x*z 3611 # + a6*x + a7*y + a8*z +a9 3612 sample = vtk.vtkSampleFunction() 3613 sample.SetSampleDimensions(res, res, res) 3614 sample.SetImplicitFunction(q) 3615 3616 contours = vtk.vtkContourFilter() 3617 contours.SetInputConnection(sample.GetOutputPort()) 3618 contours.GenerateValues(1, value, value) 3619 contours.Update() 3620 3621 Mesh.__init__(self, contours.GetOutput(), c, alpha) 3622 self.compute_normals().phong() 3623 self.mapper().ScalarVisibilityOff() 3624 self.SetPosition(pos) 3625 self.name = "Hyperboloid"
Build a hyperboloid of specified aperture a2
and height
, centered at pos
.
Full volumetric expression is:
F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9
4368class TextBase: 4369 "Base class." 4370 4371 def __init__(self): 4372 "Do not instantiate this base class." 4373 4374 self.rendered_at = set() 4375 self.property = None 4376 4377 if isinstance(settings.default_font, int): 4378 lfonts = list(settings.font_parameters.keys()) 4379 font = settings.default_font % len(lfonts) 4380 self.fontname = lfonts[font] 4381 else: 4382 self.fontname = settings.default_font 4383 self.name = "Text" 4384 4385 def angle(self, a): 4386 """Orientation angle in degrees""" 4387 self.property.SetOrientation(a) 4388 return self 4389 4390 def line_spacing(self, ls): 4391 """Set the extra spacing between lines, expressed as a text height multiplication factor.""" 4392 self.property.SetLineSpacing(ls) 4393 return self 4394 4395 def line_offset(self, lo): 4396 """Set/Get the vertical offset (measured in pixels).""" 4397 self.property.SetLineOffset(lo) 4398 return self 4399 4400 def bold(self, value=True): 4401 """Set bold face""" 4402 self.property.SetBold(value) 4403 return self 4404 4405 def italic(self, value=True): 4406 """Set italic face""" 4407 self.property.SetItalic(value) 4408 return self 4409 4410 def shadow(self, offset=(1, -1)): 4411 """Text shadowing. Set to `None` to disable it.""" 4412 if offset is None: 4413 self.property.ShadowOff() 4414 else: 4415 self.property.ShadowOn() 4416 self.property.SetShadowOffset(offset) 4417 return self 4418 4419 def color(self, c=None): 4420 """Set the text color""" 4421 if c is None: 4422 return get_color(self.property.GetColor()) 4423 self.property.SetColor(get_color(c)) 4424 return self 4425 4426 def c(self, color=None): 4427 """Set the text color""" 4428 if color is None: 4429 return get_color(self.property.GetColor()) 4430 return self.color(color) 4431 4432 def alpha(self, value): 4433 """Set the text opacity""" 4434 self.property.SetBackgroundOpacity(value) 4435 return self 4436 4437 def background(self, color="k9", alpha=1.0): 4438 """Text background. Set to `None` to disable it.""" 4439 bg = get_color(color) 4440 if color is None: 4441 self.property.SetBackgroundOpacity(0) 4442 else: 4443 self.property.SetBackgroundColor(bg) 4444 if alpha: 4445 self.property.SetBackgroundOpacity(alpha) 4446 return self 4447 4448 def frame(self, color="k1", lw=2): 4449 """Border color and width""" 4450 if color is None: 4451 self.property.FrameOff() 4452 else: 4453 c = get_color(color) 4454 self.property.FrameOn() 4455 self.property.SetFrameColor(c) 4456 self.property.SetFrameWidth(lw) 4457 return self 4458 4459 def font(self, font): 4460 """Text font face""" 4461 if isinstance(font, int): 4462 lfonts = list(settings.font_parameters.keys()) 4463 n = font % len(lfonts) 4464 font = lfonts[n] 4465 self.fontname = font 4466 4467 if not font: # use default font 4468 font = self.fontname 4469 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4470 elif font.startswith("https"): # user passed URL link, make it a path 4471 fpath = vedo.file_io.download(font, verbose=False, force=False) 4472 elif font.endswith(".ttf"): # user passing a local path to font file 4473 fpath = font 4474 else: # user passing name of preset font 4475 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4476 4477 if font == "Courier": self.property.SetFontFamilyToCourier() 4478 elif font == "Times": self.property.SetFontFamilyToTimes() 4479 elif font == "Arial": self.property.SetFontFamilyToArial() 4480 else: 4481 fpath = utils.get_font_path(font) 4482 self.property.SetFontFamily(vtk.VTK_FONT_FILE) 4483 self.property.SetFontFile(fpath) 4484 self.fontname = font # io.tonumpy() uses it 4485 4486 return self
Base class.
4371 def __init__(self): 4372 "Do not instantiate this base class." 4373 4374 self.rendered_at = set() 4375 self.property = None 4376 4377 if isinstance(settings.default_font, int): 4378 lfonts = list(settings.font_parameters.keys()) 4379 font = settings.default_font % len(lfonts) 4380 self.fontname = lfonts[font] 4381 else: 4382 self.fontname = settings.default_font 4383 self.name = "Text"
Do not instantiate this base class.
4385 def angle(self, a): 4386 """Orientation angle in degrees""" 4387 self.property.SetOrientation(a) 4388 return self
Orientation angle in degrees
4390 def line_spacing(self, ls): 4391 """Set the extra spacing between lines, expressed as a text height multiplication factor.""" 4392 self.property.SetLineSpacing(ls) 4393 return self
Set the extra spacing between lines, expressed as a text height multiplication factor.
4395 def line_offset(self, lo): 4396 """Set/Get the vertical offset (measured in pixels).""" 4397 self.property.SetLineOffset(lo) 4398 return self
Set/Get the vertical offset (measured in pixels).
4400 def bold(self, value=True): 4401 """Set bold face""" 4402 self.property.SetBold(value) 4403 return self
Set bold face
4405 def italic(self, value=True): 4406 """Set italic face""" 4407 self.property.SetItalic(value) 4408 return self
Set italic face
4410 def shadow(self, offset=(1, -1)): 4411 """Text shadowing. Set to `None` to disable it.""" 4412 if offset is None: 4413 self.property.ShadowOff() 4414 else: 4415 self.property.ShadowOn() 4416 self.property.SetShadowOffset(offset) 4417 return self
Text shadowing. Set to None
to disable it.
4419 def color(self, c=None): 4420 """Set the text color""" 4421 if c is None: 4422 return get_color(self.property.GetColor()) 4423 self.property.SetColor(get_color(c)) 4424 return self
Set the text color
4426 def c(self, color=None): 4427 """Set the text color""" 4428 if color is None: 4429 return get_color(self.property.GetColor()) 4430 return self.color(color)
Set the text color
4432 def alpha(self, value): 4433 """Set the text opacity""" 4434 self.property.SetBackgroundOpacity(value) 4435 return self
Set the text opacity
4437 def background(self, color="k9", alpha=1.0): 4438 """Text background. Set to `None` to disable it.""" 4439 bg = get_color(color) 4440 if color is None: 4441 self.property.SetBackgroundOpacity(0) 4442 else: 4443 self.property.SetBackgroundColor(bg) 4444 if alpha: 4445 self.property.SetBackgroundOpacity(alpha) 4446 return self
Text background. Set to None
to disable it.
4448 def frame(self, color="k1", lw=2): 4449 """Border color and width""" 4450 if color is None: 4451 self.property.FrameOff() 4452 else: 4453 c = get_color(color) 4454 self.property.FrameOn() 4455 self.property.SetFrameColor(c) 4456 self.property.SetFrameWidth(lw) 4457 return self
Border color and width
4459 def font(self, font): 4460 """Text font face""" 4461 if isinstance(font, int): 4462 lfonts = list(settings.font_parameters.keys()) 4463 n = font % len(lfonts) 4464 font = lfonts[n] 4465 self.fontname = font 4466 4467 if not font: # use default font 4468 font = self.fontname 4469 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4470 elif font.startswith("https"): # user passed URL link, make it a path 4471 fpath = vedo.file_io.download(font, verbose=False, force=False) 4472 elif font.endswith(".ttf"): # user passing a local path to font file 4473 fpath = font 4474 else: # user passing name of preset font 4475 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4476 4477 if font == "Courier": self.property.SetFontFamilyToCourier() 4478 elif font == "Times": self.property.SetFontFamilyToTimes() 4479 elif font == "Arial": self.property.SetFontFamilyToArial() 4480 else: 4481 fpath = utils.get_font_path(font) 4482 self.property.SetFontFamily(vtk.VTK_FONT_FILE) 4483 self.property.SetFontFile(fpath) 4484 self.fontname = font # io.tonumpy() uses it 4485 4486 return self
Text font face
4049class Text3D(Mesh): 4050 """ 4051 Generate a 3D polygonal Mesh to represent a text string. 4052 """ 4053 4054 def __init__( 4055 self, 4056 txt, 4057 pos=(0, 0, 0), 4058 s=1.0, 4059 font="", 4060 hspacing=1.15, 4061 vspacing=2.15, 4062 depth=0.0, 4063 italic=False, 4064 justify="bottom-left", 4065 literal=False, 4066 c=None, 4067 alpha=1.0, 4068 ): 4069 """ 4070 Generate a 3D polygonal `Mesh` representing a text string. 4071 4072 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4073 Most Latex symbols are also supported. 4074 4075 Symbols `~ ^ _` are reserved modifiers: 4076 - use ~ to add a short space, 1/4 of the default empty space, 4077 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4078 4079 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4080 4081 More fonts at: https://vedo.embl.es/fonts/ 4082 4083 Arguments: 4084 pos : (list) 4085 position coordinates in 3D space 4086 s : (float) 4087 size of the text 4088 depth : (float) 4089 text thickness (along z) 4090 italic : (bool), float 4091 italic font type (can be a signed float too) 4092 justify : (str) 4093 text justification as centering of the bounding box 4094 (bottom-left, bottom-right, top-left, top-right, centered) 4095 font : (str, int) 4096 some of the available 3D-polygonized fonts are: 4097 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4098 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4099 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4100 4101 Check for more at https://vedo.embl.es/fonts/ 4102 4103 Or type in your terminal `vedo --run fonts`. 4104 4105 Default is Normografo, which can be changed using `settings.default_font`. 4106 4107 hspacing : (float) 4108 horizontal spacing of the font 4109 vspacing : (float) 4110 vertical spacing of the font for multiple lines text 4111 literal : (bool) 4112 if set to True will ignore modifiers like _ or ^ 4113 4114 Examples: 4115 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4116 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4117 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4118 4119 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4120 4121 .. note:: Type `vedo -r fonts` for a demo. 4122 """ 4123 if len(pos) == 2: 4124 pos = (pos[0], pos[1], 0) 4125 4126 if c is None: # automatic black or white 4127 pli = vedo.plotter_instance 4128 if pli and pli.renderer: 4129 c = (0.9, 0.9, 0.9) 4130 if pli.renderer.GetGradientBackground(): 4131 bgcol = pli.renderer.GetBackground2() 4132 else: 4133 bgcol = pli.renderer.GetBackground() 4134 if np.sum(bgcol) > 1.5: 4135 c = (0.1, 0.1, 0.1) 4136 else: 4137 c = (0.6, 0.6, 0.6) 4138 4139 tpoly = self._get_text3d_poly( 4140 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4141 ) 4142 4143 Mesh.__init__(self, tpoly, c, alpha) 4144 self.lighting("off") 4145 self.SetPosition(pos) 4146 self.PickableOff() 4147 self.DragableOff() 4148 self.name = "Text3D" 4149 self.txt = txt 4150 4151 def text( 4152 self, 4153 txt=None, 4154 s=1, 4155 font="", 4156 hspacing=1.15, 4157 vspacing=2.15, 4158 depth=0, 4159 italic=False, 4160 justify="bottom-left", 4161 literal=False, 4162 ): 4163 """ 4164 Update the font style of the text. 4165 Check [available fonts here](https://vedo.embl.es/fonts). 4166 """ 4167 if txt is None: 4168 return self.txt 4169 4170 tpoly = self._get_text3d_poly( 4171 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4172 ) 4173 self._update(tpoly) 4174 self.txt = txt 4175 return self 4176 4177 def _get_text3d_poly( 4178 self, 4179 txt, 4180 s=1, 4181 font="", 4182 hspacing=1.15, 4183 vspacing=2.15, 4184 depth=0, 4185 italic=False, 4186 justify="bottom-left", 4187 literal=False, 4188 ): 4189 if not font: 4190 font = settings.default_font 4191 4192 txt = str(txt) 4193 4194 if font == "VTK": ####################################### 4195 vtt = vtk.vtkVectorText() 4196 vtt.SetText(txt) 4197 vtt.Update() 4198 tpoly = vtt.GetOutput() 4199 4200 else: ################################################### 4201 4202 stxt = set(txt) # check here if null or only spaces 4203 if not txt or (len(stxt) == 1 and " " in stxt): 4204 return vtk.vtkPolyData() 4205 4206 if italic is True: 4207 italic = 1 4208 4209 if isinstance(font, int): 4210 lfonts = list(settings.font_parameters.keys()) 4211 font = font % len(lfonts) 4212 font = lfonts[font] 4213 4214 if font not in settings.font_parameters.keys(): 4215 fpars = settings.font_parameters["Normografo"] 4216 else: 4217 fpars = settings.font_parameters[font] 4218 4219 # ad hoc adjustments 4220 mono = fpars["mono"] 4221 lspacing = fpars["lspacing"] 4222 hspacing *= fpars["hspacing"] 4223 fscale = fpars["fscale"] 4224 dotsep = fpars["dotsep"] 4225 4226 # replacements 4227 if ":" in txt: 4228 for r in _reps: 4229 txt = txt.replace(r[0], r[1]) 4230 4231 if not literal: 4232 reps2 = [ 4233 ("\_", "┭"), # trick to protect ~ _ and ^ chars 4234 ("\^", "┮"), # 4235 ("\~", "┯"), # 4236 ("**", "^"), # order matters 4237 ("e+0", dotsep + "10^"), 4238 ("e-0", dotsep + "10^-"), 4239 ("E+0", dotsep + "10^"), 4240 ("E-0", dotsep + "10^-"), 4241 ("e+", dotsep + "10^"), 4242 ("e-", dotsep + "10^-"), 4243 ("E+", dotsep + "10^"), 4244 ("E-", dotsep + "10^-"), 4245 ] 4246 for r in reps2: 4247 txt = txt.replace(r[0], r[1]) 4248 4249 xmax, ymax, yshift, scale = 0, 0, 0, 1 4250 save_xmax = 0 4251 4252 notfounds = set() 4253 polyletters = [] 4254 ntxt = len(txt) 4255 for i, t in enumerate(txt): 4256 ########## 4257 if t == "┭": 4258 t = "_" 4259 elif t == "┮": 4260 t = "^" 4261 elif t == "┯": 4262 t = "~" 4263 elif t == "^" and not literal: 4264 if yshift < 0: 4265 xmax = save_xmax 4266 yshift = 0.9 * fscale 4267 scale = 0.5 4268 continue 4269 elif t == "_" and not literal: 4270 if yshift > 0: 4271 xmax = save_xmax 4272 yshift = -0.3 * fscale 4273 scale = 0.5 4274 continue 4275 elif (t in (" ", "\\n")) and yshift: 4276 yshift = 0 4277 scale = 1 4278 save_xmax = xmax 4279 if t == " ": 4280 continue 4281 elif t == "~": 4282 if i < ntxt - 1 and txt[i + 1] == "_": 4283 continue 4284 xmax += hspacing * scale * fscale / 4 4285 continue 4286 4287 ############ 4288 if t == " ": 4289 xmax += hspacing * scale * fscale 4290 4291 elif t == "\n": 4292 xmax = 0 4293 save_xmax = 0 4294 ymax -= vspacing 4295 4296 else: 4297 poly = _get_font_letter(font, t) 4298 if not poly: 4299 notfounds.add(t) 4300 xmax += hspacing * scale * fscale 4301 continue 4302 4303 tr = vtk.vtkTransform() 4304 tr.Translate(xmax, ymax + yshift, 0) 4305 pscale = scale * fscale / 1000 4306 tr.Scale(pscale, pscale, pscale) 4307 if italic: 4308 tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 4309 tf = vtk.vtkTransformPolyDataFilter() 4310 tf.SetInputData(poly) 4311 tf.SetTransform(tr) 4312 tf.Update() 4313 poly = tf.GetOutput() 4314 polyletters.append(poly) 4315 4316 bx = poly.GetBounds() 4317 if mono: 4318 xmax += hspacing * scale * fscale 4319 else: 4320 xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing 4321 if yshift == 0: 4322 save_xmax = xmax 4323 4324 if len(polyletters) == 1: 4325 tpoly = polyletters[0] 4326 else: 4327 polyapp = vtk.vtkAppendPolyData() 4328 for polyd in polyletters: 4329 polyapp.AddInputData(polyd) 4330 polyapp.Update() 4331 tpoly = polyapp.GetOutput() 4332 4333 if notfounds: 4334 wmsg = f"These characters are not available in font name {font}: {notfounds}. " 4335 wmsg += 'Type "vedo -r fonts" for a demo.' 4336 vedo.logger.warning(wmsg) 4337 4338 bb = tpoly.GetBounds() 4339 dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s 4340 shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2 4341 if "bottom" in justify: shift += np.array([ 0, dy, 0.]) 4342 if "top" in justify: shift += np.array([ 0,-dy, 0.]) 4343 if "left" in justify: shift += np.array([ dx, 0, 0.]) 4344 if "right" in justify: shift += np.array([-dx, 0, 0.]) 4345 4346 t = vtk.vtkTransform() 4347 t.PostMultiply() 4348 t.Scale(s, s, s) 4349 t.Translate(shift) 4350 tf = vtk.vtkTransformPolyDataFilter() 4351 tf.SetInputData(tpoly) 4352 tf.SetTransform(t) 4353 tf.Update() 4354 tpoly = tf.GetOutput() 4355 4356 if depth: 4357 extrude = vtk.vtkLinearExtrusionFilter() 4358 extrude.SetInputData(tpoly) 4359 extrude.SetExtrusionTypeToVectorExtrusion() 4360 extrude.SetVector(0, 0, 1) 4361 extrude.SetScaleFactor(depth * dy) 4362 extrude.Update() 4363 tpoly = extrude.GetOutput() 4364 4365 return tpoly
Generate a 3D polygonal Mesh to represent a text string.
4054 def __init__( 4055 self, 4056 txt, 4057 pos=(0, 0, 0), 4058 s=1.0, 4059 font="", 4060 hspacing=1.15, 4061 vspacing=2.15, 4062 depth=0.0, 4063 italic=False, 4064 justify="bottom-left", 4065 literal=False, 4066 c=None, 4067 alpha=1.0, 4068 ): 4069 """ 4070 Generate a 3D polygonal `Mesh` representing a text string. 4071 4072 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4073 Most Latex symbols are also supported. 4074 4075 Symbols `~ ^ _` are reserved modifiers: 4076 - use ~ to add a short space, 1/4 of the default empty space, 4077 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4078 4079 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4080 4081 More fonts at: https://vedo.embl.es/fonts/ 4082 4083 Arguments: 4084 pos : (list) 4085 position coordinates in 3D space 4086 s : (float) 4087 size of the text 4088 depth : (float) 4089 text thickness (along z) 4090 italic : (bool), float 4091 italic font type (can be a signed float too) 4092 justify : (str) 4093 text justification as centering of the bounding box 4094 (bottom-left, bottom-right, top-left, top-right, centered) 4095 font : (str, int) 4096 some of the available 3D-polygonized fonts are: 4097 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4098 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4099 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4100 4101 Check for more at https://vedo.embl.es/fonts/ 4102 4103 Or type in your terminal `vedo --run fonts`. 4104 4105 Default is Normografo, which can be changed using `settings.default_font`. 4106 4107 hspacing : (float) 4108 horizontal spacing of the font 4109 vspacing : (float) 4110 vertical spacing of the font for multiple lines text 4111 literal : (bool) 4112 if set to True will ignore modifiers like _ or ^ 4113 4114 Examples: 4115 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4116 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4117 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4118 4119 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4120 4121 .. note:: Type `vedo -r fonts` for a demo. 4122 """ 4123 if len(pos) == 2: 4124 pos = (pos[0], pos[1], 0) 4125 4126 if c is None: # automatic black or white 4127 pli = vedo.plotter_instance 4128 if pli and pli.renderer: 4129 c = (0.9, 0.9, 0.9) 4130 if pli.renderer.GetGradientBackground(): 4131 bgcol = pli.renderer.GetBackground2() 4132 else: 4133 bgcol = pli.renderer.GetBackground() 4134 if np.sum(bgcol) > 1.5: 4135 c = (0.1, 0.1, 0.1) 4136 else: 4137 c = (0.6, 0.6, 0.6) 4138 4139 tpoly = self._get_text3d_poly( 4140 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4141 ) 4142 4143 Mesh.__init__(self, tpoly, c, alpha) 4144 self.lighting("off") 4145 self.SetPosition(pos) 4146 self.PickableOff() 4147 self.DragableOff() 4148 self.name = "Text3D" 4149 self.txt = txt
Generate a 3D polygonal Mesh
representing a text string.
Can render strings like 3.7 10^9
or H_2 O
with subscripts and superscripts.
Most Latex symbols are also supported.
Symbols ~ ^ _
are reserved modifiers:
- use ~ to add a short space, 1/4 of the default empty space,
- use ^ and _ to start up/sub scripting, a space terminates their effect.
Monospaced fonts are: Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino
.
More fonts at: https://vedo.embl.es/fonts/
Arguments:
- pos : (list) position coordinates in 3D space
- s : (float) size of the text
- depth : (float) text thickness (along z)
- italic : (bool), float italic font type (can be a signed float too)
- justify : (str) text justification as centering of the bounding box (bottom-left, bottom-right, top-left, top-right, centered)
font : (str, int) some of the available 3D-polygonized fonts are: Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, Capsmall, Cartoons123, Vega, Justino, Spears, Meson.
Check for more at https://vedo.embl.es/fonts/
Or type in your terminal
vedo --run fonts
.Default is Normografo, which can be changed using
settings.default_font
.- hspacing : (float) horizontal spacing of the font
- vspacing : (float) vertical spacing of the font for multiple lines text
- literal : (bool) if set to True will ignore modifiers like _ or ^
Examples:
Type vedo -r fonts
for a demo.
4151 def text( 4152 self, 4153 txt=None, 4154 s=1, 4155 font="", 4156 hspacing=1.15, 4157 vspacing=2.15, 4158 depth=0, 4159 italic=False, 4160 justify="bottom-left", 4161 literal=False, 4162 ): 4163 """ 4164 Update the font style of the text. 4165 Check [available fonts here](https://vedo.embl.es/fonts). 4166 """ 4167 if txt is None: 4168 return self.txt 4169 4170 tpoly = self._get_text3d_poly( 4171 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4172 ) 4173 self._update(tpoly) 4174 self.txt = txt 4175 return self
Update the font style of the text. Check available fonts here.
4489class Text2D(TextBase, vtk.vtkActor2D): 4490 """ 4491 Create a 2D text object. 4492 """ 4493 def __init__( 4494 self, 4495 txt="", 4496 pos="top-left", 4497 s=1.0, 4498 bg=None, 4499 font="", 4500 justify="", 4501 bold=False, 4502 italic=False, 4503 c=None, 4504 alpha=0.2, 4505 ): 4506 """ 4507 Create a 2D text object. 4508 4509 All properties of the text, and the text itself, can be changed after creation 4510 (which is especially useful in loops). 4511 4512 Arguments: 4513 pos : (str) 4514 text is placed in one of the 8 positions: 4515 - bottom-left 4516 - bottom-right 4517 - top-left 4518 - top-right 4519 - bottom-middle 4520 - middle-right 4521 - middle-left 4522 - top-middle 4523 4524 If a pair (x,y) is passed as input the 2D text is place at that 4525 position in the coordinate system of the 2D screen (with the 4526 origin sitting at the bottom left). 4527 4528 s : (float) 4529 size of text 4530 bg : (color) 4531 background color 4532 alpha : (float) 4533 background opacity 4534 justify : (str) 4535 text justification 4536 4537 font : (str) 4538 built-in available fonts are: 4539 - Arial 4540 - Bongas 4541 - Calco 4542 - Comae 4543 - ComicMono 4544 - Courier 4545 - Glasgo 4546 - Kanopus 4547 - LogoType 4548 - Normografo 4549 - Quikhand 4550 - SmartCouric 4551 - Theemim 4552 - Times 4553 - VictorMono 4554 - More fonts at: https://vedo.embl.es/fonts/ 4555 4556 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4557 4558 Examples: 4559 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4560 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4561 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4562 4563 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4564 """ 4565 vtk.vtkActor2D.__init__(self) 4566 TextBase.__init__(self) 4567 4568 self._mapper = vtk.vtkTextMapper() 4569 self.SetMapper(self._mapper) 4570 4571 self.property = self._mapper.GetTextProperty() 4572 4573 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4574 4575 # automatic black or white 4576 if c is None: 4577 c = (0.1, 0.1, 0.1) 4578 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4579 if vedo.plotter_instance.renderer.GetGradientBackground(): 4580 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4581 else: 4582 bgcol = vedo.plotter_instance.renderer.GetBackground() 4583 c = (0.9, 0.9, 0.9) 4584 if np.sum(bgcol) > 1.5: 4585 c = (0.1, 0.1, 0.1) 4586 4587 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4588 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4589 self.PickableOff() 4590 4591 def pos(self, pos="top-left", justify=""): 4592 """ 4593 Set position of the text to draw. Keyword `pos` can be a string 4594 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4595 """ 4596 ajustify = "top-left" # autojustify 4597 if isinstance(pos, str): # corners 4598 ajustify = pos 4599 if "top" in pos: 4600 if "left" in pos: 4601 pos = (0.008, 0.994) 4602 elif "right" in pos: 4603 pos = (0.994, 0.994) 4604 elif "mid" in pos or "cent" in pos: 4605 pos = (0.5, 0.994) 4606 elif "bottom" in pos: 4607 if "left" in pos: 4608 pos = (0.008, 0.008) 4609 elif "right" in pos: 4610 pos = (0.994, 0.008) 4611 elif "mid" in pos or "cent" in pos: 4612 pos = (0.5, 0.008) 4613 elif "mid" in pos or "cent" in pos: 4614 if "left" in pos: 4615 pos = (0.008, 0.5) 4616 elif "right" in pos: 4617 pos = (0.994, 0.5) 4618 else: 4619 pos = (0.5, 0.5) 4620 4621 else: 4622 vedo.logger.warning(f"cannot understand text position {pos}") 4623 pos = (0.008, 0.994) 4624 ajustify = "top-left" 4625 4626 elif len(pos) != 2: 4627 vedo.logger.error("pos must be of length 2 or integer value or string") 4628 raise RuntimeError() 4629 4630 if not justify: 4631 justify = ajustify 4632 4633 self.property.SetJustificationToLeft() 4634 if "top" in justify: 4635 self.property.SetVerticalJustificationToTop() 4636 if "bottom" in justify: 4637 self.property.SetVerticalJustificationToBottom() 4638 if "cent" in justify or "mid" in justify: 4639 self.property.SetJustificationToCentered() 4640 if "left" in justify: 4641 self.property.SetJustificationToLeft() 4642 if "right" in justify: 4643 self.property.SetJustificationToRight() 4644 4645 self.SetPosition(pos) 4646 return self 4647 4648 def text(self, txt=None): 4649 """Set/get the input text string.""" 4650 if txt is None: 4651 return self._mapper.GetInput() 4652 4653 if ":" in txt: 4654 for r in _reps: 4655 txt = txt.replace(r[0], r[1]) 4656 else: 4657 txt = str(txt) 4658 4659 self._mapper.SetInput(txt) 4660 return self 4661 4662 def size(self, s): 4663 """Set the font size.""" 4664 self.property.SetFontSize(int(s * 22.5)) 4665 return self
Create a 2D text object.
4493 def __init__( 4494 self, 4495 txt="", 4496 pos="top-left", 4497 s=1.0, 4498 bg=None, 4499 font="", 4500 justify="", 4501 bold=False, 4502 italic=False, 4503 c=None, 4504 alpha=0.2, 4505 ): 4506 """ 4507 Create a 2D text object. 4508 4509 All properties of the text, and the text itself, can be changed after creation 4510 (which is especially useful in loops). 4511 4512 Arguments: 4513 pos : (str) 4514 text is placed in one of the 8 positions: 4515 - bottom-left 4516 - bottom-right 4517 - top-left 4518 - top-right 4519 - bottom-middle 4520 - middle-right 4521 - middle-left 4522 - top-middle 4523 4524 If a pair (x,y) is passed as input the 2D text is place at that 4525 position in the coordinate system of the 2D screen (with the 4526 origin sitting at the bottom left). 4527 4528 s : (float) 4529 size of text 4530 bg : (color) 4531 background color 4532 alpha : (float) 4533 background opacity 4534 justify : (str) 4535 text justification 4536 4537 font : (str) 4538 built-in available fonts are: 4539 - Arial 4540 - Bongas 4541 - Calco 4542 - Comae 4543 - ComicMono 4544 - Courier 4545 - Glasgo 4546 - Kanopus 4547 - LogoType 4548 - Normografo 4549 - Quikhand 4550 - SmartCouric 4551 - Theemim 4552 - Times 4553 - VictorMono 4554 - More fonts at: https://vedo.embl.es/fonts/ 4555 4556 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4557 4558 Examples: 4559 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4560 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4561 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4562 4563 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4564 """ 4565 vtk.vtkActor2D.__init__(self) 4566 TextBase.__init__(self) 4567 4568 self._mapper = vtk.vtkTextMapper() 4569 self.SetMapper(self._mapper) 4570 4571 self.property = self._mapper.GetTextProperty() 4572 4573 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4574 4575 # automatic black or white 4576 if c is None: 4577 c = (0.1, 0.1, 0.1) 4578 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4579 if vedo.plotter_instance.renderer.GetGradientBackground(): 4580 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4581 else: 4582 bgcol = vedo.plotter_instance.renderer.GetBackground() 4583 c = (0.9, 0.9, 0.9) 4584 if np.sum(bgcol) > 1.5: 4585 c = (0.1, 0.1, 0.1) 4586 4587 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4588 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4589 self.PickableOff()
Create a 2D text object.
All properties of the text, and the text itself, can be changed after creation (which is especially useful in loops).
Arguments:
pos : (str) text is placed in one of the 8 positions:
- bottom-left
- bottom-right
- top-left
- top-right
- bottom-middle
- middle-right
- middle-left
- top-middle
If a pair (x,y) is passed as input the 2D text is place at that position in the coordinate system of the 2D screen (with the origin sitting at the bottom left).
- s : (float) size of text
- bg : (color) background color
- alpha : (float) background opacity
- justify : (str) text justification
font : (str) built-in available fonts are:
- Arial
- Bongas
- Calco
- Comae
- ComicMono
- Courier
- Glasgo
- Kanopus
- LogoType
- Normografo
- Quikhand
- SmartCouric
- Theemim
- Times
- VictorMono
- More fonts at: https://vedo.embl.es/fonts/
A path to a
.otf
or.ttf
font-file can also be supplied as input.
Examples:
4591 def pos(self, pos="top-left", justify=""): 4592 """ 4593 Set position of the text to draw. Keyword `pos` can be a string 4594 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4595 """ 4596 ajustify = "top-left" # autojustify 4597 if isinstance(pos, str): # corners 4598 ajustify = pos 4599 if "top" in pos: 4600 if "left" in pos: 4601 pos = (0.008, 0.994) 4602 elif "right" in pos: 4603 pos = (0.994, 0.994) 4604 elif "mid" in pos or "cent" in pos: 4605 pos = (0.5, 0.994) 4606 elif "bottom" in pos: 4607 if "left" in pos: 4608 pos = (0.008, 0.008) 4609 elif "right" in pos: 4610 pos = (0.994, 0.008) 4611 elif "mid" in pos or "cent" in pos: 4612 pos = (0.5, 0.008) 4613 elif "mid" in pos or "cent" in pos: 4614 if "left" in pos: 4615 pos = (0.008, 0.5) 4616 elif "right" in pos: 4617 pos = (0.994, 0.5) 4618 else: 4619 pos = (0.5, 0.5) 4620 4621 else: 4622 vedo.logger.warning(f"cannot understand text position {pos}") 4623 pos = (0.008, 0.994) 4624 ajustify = "top-left" 4625 4626 elif len(pos) != 2: 4627 vedo.logger.error("pos must be of length 2 or integer value or string") 4628 raise RuntimeError() 4629 4630 if not justify: 4631 justify = ajustify 4632 4633 self.property.SetJustificationToLeft() 4634 if "top" in justify: 4635 self.property.SetVerticalJustificationToTop() 4636 if "bottom" in justify: 4637 self.property.SetVerticalJustificationToBottom() 4638 if "cent" in justify or "mid" in justify: 4639 self.property.SetJustificationToCentered() 4640 if "left" in justify: 4641 self.property.SetJustificationToLeft() 4642 if "right" in justify: 4643 self.property.SetJustificationToRight() 4644 4645 self.SetPosition(pos) 4646 return self
Set position of the text to draw. Keyword pos
can be a string
or 2D coordinates in the range [0,1], being (0,0) the bottom left corner.
4648 def text(self, txt=None): 4649 """Set/get the input text string.""" 4650 if txt is None: 4651 return self._mapper.GetInput() 4652 4653 if ":" in txt: 4654 for r in _reps: 4655 txt = txt.replace(r[0], r[1]) 4656 else: 4657 txt = str(txt) 4658 4659 self._mapper.SetInput(txt) 4660 return self
Set/get the input text string.
4668class CornerAnnotation(vtk.vtkCornerAnnotation, TextBase): 4669 # PROBABLY USELESS given that Text2D does pretty much the same ... 4670 """ 4671 Annotate the window corner with 2D text. 4672 4673 See `Text2D` description as the basic functionality is very similar. 4674 4675 The added value of this class is the possibility to manage with one single 4676 object the all corner annotations (instead of creating 4 `Text2D` instances). 4677 4678 Examples: 4679 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 4680 """ 4681 4682 def __init__(self, c=None): 4683 vtk.vtkCornerAnnotation.__init__(self) 4684 TextBase.__init__(self) 4685 4686 self.property = self.GetTextProperty() 4687 4688 # automatic black or white 4689 if c is None: 4690 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4691 c = (0.9, 0.9, 0.9) 4692 if vedo.plotter_instance.renderer.GetGradientBackground(): 4693 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4694 else: 4695 bgcol = vedo.plotter_instance.renderer.GetBackground() 4696 if np.sum(bgcol) > 1.5: 4697 c = (0.1, 0.1, 0.1) 4698 else: 4699 c = (0.5, 0.5, 0.5) 4700 4701 self.SetNonlinearFontScaleFactor(1 / 2.75) 4702 self.PickableOff() 4703 self.property.SetColor(get_color(c)) 4704 self.property.SetBold(False) 4705 self.property.SetItalic(False) 4706 4707 def size(self, s, linear=False): 4708 """ 4709 The font size is calculated as the largest possible value such that the annotations 4710 for the given viewport do not overlap. 4711 4712 This font size can be scaled non-linearly with the viewport size, to maintain an 4713 acceptable readable size at larger viewport sizes, without being too big. 4714 `f' = linearScale * pow(f,nonlinearScale)` 4715 """ 4716 if linear: 4717 self.SetLinearFontScaleFactor(s * 5.5) 4718 else: 4719 self.SetNonlinearFontScaleFactor(s / 2.75) 4720 return self 4721 4722 def text(self, txt, pos=2): 4723 """Set text at the assigned position""" 4724 4725 if isinstance(pos, str): # corners 4726 if "top" in pos: 4727 if "left" in pos: pos = 2 4728 elif "right" in pos: pos = 3 4729 elif "mid" in pos or "cent" in pos: pos = 7 4730 elif "bottom" in pos: 4731 if "left" in pos: pos = 0 4732 elif "right" in pos: pos = 1 4733 elif "mid" in pos or "cent" in pos: pos = 4 4734 else: 4735 if "left" in pos: pos = 6 4736 elif "right" in pos: pos = 5 4737 else: pos = 2 4738 4739 if "\\" in repr(txt): 4740 for r in _reps: 4741 txt = txt.replace(r[0], r[1]) 4742 else: 4743 txt = str(txt) 4744 4745 self.SetText(pos, txt) 4746 return self 4747 4748 def clear(self): 4749 """Remove all text from all corners""" 4750 self.ClearAllTexts() 4751 return self
Annotate the window corner with 2D text.
See Text2D
description as the basic functionality is very similar.
The added value of this class is the possibility to manage with one single
object the all corner annotations (instead of creating 4 Text2D
instances).
Examples:
4682 def __init__(self, c=None): 4683 vtk.vtkCornerAnnotation.__init__(self) 4684 TextBase.__init__(self) 4685 4686 self.property = self.GetTextProperty() 4687 4688 # automatic black or white 4689 if c is None: 4690 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4691 c = (0.9, 0.9, 0.9) 4692 if vedo.plotter_instance.renderer.GetGradientBackground(): 4693 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4694 else: 4695 bgcol = vedo.plotter_instance.renderer.GetBackground() 4696 if np.sum(bgcol) > 1.5: 4697 c = (0.1, 0.1, 0.1) 4698 else: 4699 c = (0.5, 0.5, 0.5) 4700 4701 self.SetNonlinearFontScaleFactor(1 / 2.75) 4702 self.PickableOff() 4703 self.property.SetColor(get_color(c)) 4704 self.property.SetBold(False) 4705 self.property.SetItalic(False)
Do not instantiate this base class.
4707 def size(self, s, linear=False): 4708 """ 4709 The font size is calculated as the largest possible value such that the annotations 4710 for the given viewport do not overlap. 4711 4712 This font size can be scaled non-linearly with the viewport size, to maintain an 4713 acceptable readable size at larger viewport sizes, without being too big. 4714 `f' = linearScale * pow(f,nonlinearScale)` 4715 """ 4716 if linear: 4717 self.SetLinearFontScaleFactor(s * 5.5) 4718 else: 4719 self.SetNonlinearFontScaleFactor(s / 2.75) 4720 return self
The font size is calculated as the largest possible value such that the annotations for the given viewport do not overlap.
This font size can be scaled non-linearly with the viewport size, to maintain an
acceptable readable size at larger viewport sizes, without being too big.
f' = linearScale * pow(f,nonlinearScale)
4722 def text(self, txt, pos=2): 4723 """Set text at the assigned position""" 4724 4725 if isinstance(pos, str): # corners 4726 if "top" in pos: 4727 if "left" in pos: pos = 2 4728 elif "right" in pos: pos = 3 4729 elif "mid" in pos or "cent" in pos: pos = 7 4730 elif "bottom" in pos: 4731 if "left" in pos: pos = 0 4732 elif "right" in pos: pos = 1 4733 elif "mid" in pos or "cent" in pos: pos = 4 4734 else: 4735 if "left" in pos: pos = 6 4736 elif "right" in pos: pos = 5 4737 else: pos = 2 4738 4739 if "\\" in repr(txt): 4740 for r in _reps: 4741 txt = txt.replace(r[0], r[1]) 4742 else: 4743 txt = str(txt) 4744 4745 self.SetText(pos, txt) 4746 return self
Set text at the assigned position
4754class Latex(Picture): 4755 """ 4756 Render Latex text and formulas. 4757 """ 4758 4759 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0): 4760 """ 4761 Render Latex text and formulas. 4762 4763 Arguments: 4764 formula : (str) 4765 latex text string 4766 pos : (list) 4767 position coordinates in space 4768 bg : (color) 4769 background color box 4770 res : (int) 4771 dpi resolution 4772 usetex : (bool) 4773 use latex compiler of matplotlib if available 4774 4775 You can access the latex formula in `Latex.formula`. 4776 4777 Examples: 4778 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4779 4780 ![](https://vedo.embl.es/images/pyplot/latex.png) 4781 """ 4782 self.formula = formula 4783 4784 try: 4785 from tempfile import NamedTemporaryFile 4786 import matplotlib.pyplot as mpltib 4787 4788 def build_img_plt(formula, tfile): 4789 4790 mpltib.rc("text", usetex=usetex) 4791 4792 formula1 = "$" + formula + "$" 4793 mpltib.axis("off") 4794 col = get_color(c) 4795 if bg: 4796 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4797 else: 4798 bx = None 4799 mpltib.text( 4800 0.5, 4801 0.5, 4802 formula1, 4803 size=res, 4804 color=col, 4805 alpha=alpha, 4806 ha="center", 4807 va="center", 4808 bbox=bx, 4809 ) 4810 mpltib.savefig( 4811 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4812 ) 4813 mpltib.close() 4814 4815 if len(pos) == 2: 4816 pos = (pos[0], pos[1], 0) 4817 4818 tmp_file = NamedTemporaryFile(delete=True) 4819 tmp_file.name = tmp_file.name + ".png" 4820 4821 build_img_plt(formula, tmp_file.name) 4822 4823 Picture.__init__(self, tmp_file.name, channels=4) 4824 self.alpha(alpha) 4825 self.SetScale(0.25 / res * s, 0.25 / res * s, 0.25 / res * s) 4826 self.SetPosition(pos) 4827 self.name = "Latex" 4828 4829 except: 4830 printc("Error in Latex()\n", formula, c="r") 4831 printc(" latex or dvipng not installed?", c="r") 4832 printc(" Try: usetex=False", c="r") 4833 printc(" Try: sudo apt install dvipng", c="r")
Render Latex text and formulas.
4759 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0): 4760 """ 4761 Render Latex text and formulas. 4762 4763 Arguments: 4764 formula : (str) 4765 latex text string 4766 pos : (list) 4767 position coordinates in space 4768 bg : (color) 4769 background color box 4770 res : (int) 4771 dpi resolution 4772 usetex : (bool) 4773 use latex compiler of matplotlib if available 4774 4775 You can access the latex formula in `Latex.formula`. 4776 4777 Examples: 4778 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4779 4780 ![](https://vedo.embl.es/images/pyplot/latex.png) 4781 """ 4782 self.formula = formula 4783 4784 try: 4785 from tempfile import NamedTemporaryFile 4786 import matplotlib.pyplot as mpltib 4787 4788 def build_img_plt(formula, tfile): 4789 4790 mpltib.rc("text", usetex=usetex) 4791 4792 formula1 = "$" + formula + "$" 4793 mpltib.axis("off") 4794 col = get_color(c) 4795 if bg: 4796 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4797 else: 4798 bx = None 4799 mpltib.text( 4800 0.5, 4801 0.5, 4802 formula1, 4803 size=res, 4804 color=col, 4805 alpha=alpha, 4806 ha="center", 4807 va="center", 4808 bbox=bx, 4809 ) 4810 mpltib.savefig( 4811 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4812 ) 4813 mpltib.close() 4814 4815 if len(pos) == 2: 4816 pos = (pos[0], pos[1], 0) 4817 4818 tmp_file = NamedTemporaryFile(delete=True) 4819 tmp_file.name = tmp_file.name + ".png" 4820 4821 build_img_plt(formula, tmp_file.name) 4822 4823 Picture.__init__(self, tmp_file.name, channels=4) 4824 self.alpha(alpha) 4825 self.SetScale(0.25 / res * s, 0.25 / res * s, 0.25 / res * s) 4826 self.SetPosition(pos) 4827 self.name = "Latex" 4828 4829 except: 4830 printc("Error in Latex()\n", formula, c="r") 4831 printc(" latex or dvipng not installed?", c="r") 4832 printc(" Try: usetex=False", c="r") 4833 printc(" Try: sudo apt install dvipng", c="r")
Render Latex text and formulas.
Arguments:
- formula : (str) latex text string
- pos : (list) position coordinates in space
- bg : (color) background color box
- res : (int) dpi resolution
- usetex : (bool) use latex compiler of matplotlib if available
You can access the latex formula in Latex.formula
.
Examples:
155class Glyph(Mesh): 156 """ 157 At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with 158 various orientation options and coloring. 159 160 The input can also be a simple list of 2D or 3D coordinates. 161 Color can be specified as a colormap which maps the size of the orientation 162 vectors in `orientation_array`. 163 """ 164 165 def __init__( 166 self, 167 mesh, 168 glyph, 169 orientation_array=None, 170 scale_by_scalar=False, 171 scale_by_vector_size=False, 172 scale_by_vector_components=False, 173 color_by_scalar=False, 174 color_by_vector_size=False, 175 c="k8", 176 alpha=1.0, 177 **opts, 178 ): 179 """ 180 Arguments: 181 orientation_array: (list, str, vtkArray) 182 list of vectors, `vtkArray` or name of an already existing pointdata array 183 scale_by_scalar : (bool) 184 glyph mesh is scaled by the active scalars 185 scale_by_vector_size : (bool) 186 glyph mesh is scaled by the size of the vectors 187 scale_by_vector_components : (bool) 188 glyph mesh is scaled by the 3 vectors components 189 color_by_scalar : (bool) 190 glyph mesh is colored based on the scalar value 191 color_by_vector_size : (bool) 192 glyph mesh is colored based on the vector size 193 194 Examples: 195 - [glyphs1.py](]https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 196 - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py) 197 198 ![](https://vedo.embl.es/images/basic/glyphs.png) 199 """ 200 if len(opts) > 0: # Deprecations 201 printc(":noentry: Warning! In Glyph() unrecognized keywords:", opts, c="y") 202 orientation_array = opts.pop("orientationArray", orientation_array) 203 scale_by_scalar = opts.pop("scaleByScalar", scale_by_scalar) 204 scale_by_vector_size = opts.pop("scaleByVectorSize", scale_by_vector_size) 205 scale_by_vector_components = opts.pop( 206 "scaleByVectorComponents", scale_by_vector_components 207 ) 208 color_by_scalar = opts.pop("colorByScalar", color_by_scalar) 209 color_by_vector_size = opts.pop("colorByVectorSize", color_by_vector_size) 210 printc(" Please use 'snake_case' instead of 'camelCase' keywords", c="y") 211 212 lighting = None 213 if utils.is_sequence(mesh): 214 # create a cloud of points 215 poly = Points(mesh).polydata() 216 elif isinstance(mesh, vtk.vtkPolyData): 217 poly = mesh 218 else: 219 poly = mesh.polydata() 220 221 if isinstance(glyph, Points): 222 lighting = glyph.property.GetLighting() 223 glyph = glyph.polydata() 224 225 cmap = "" 226 if isinstance(c, str) and c in cmaps_names: 227 cmap = c 228 c = None 229 elif utils.is_sequence(c): # user passing an array of point colors 230 ucols = vtk.vtkUnsignedCharArray() 231 ucols.SetNumberOfComponents(3) 232 ucols.SetName("glyph_RGB") 233 for col in c: 234 cl = get_color(col) 235 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 236 poly.GetPointData().AddArray(ucols) 237 poly.GetPointData().SetActiveScalars("glyph_RGB") 238 c = None 239 240 gly = vtk.vtkGlyph3D() 241 gly.GeneratePointIdsOn() 242 gly.SetInputData(poly) 243 gly.SetSourceData(glyph) 244 245 if scale_by_scalar: 246 gly.SetScaleModeToScaleByScalar() 247 elif scale_by_vector_size: 248 gly.SetScaleModeToScaleByVector() 249 elif scale_by_vector_components: 250 gly.SetScaleModeToScaleByVectorComponents() 251 else: 252 gly.SetScaleModeToDataScalingOff() 253 254 if color_by_vector_size: 255 gly.SetVectorModeToUseVector() 256 gly.SetColorModeToColorByVector() 257 elif color_by_scalar: 258 gly.SetColorModeToColorByScalar() 259 else: 260 gly.SetColorModeToColorByScale() 261 262 if orientation_array is not None: 263 gly.OrientOn() 264 if isinstance(orientation_array, str): 265 if orientation_array.lower() == "normals": 266 gly.SetVectorModeToUseNormal() 267 else: # passing a name 268 poly.GetPointData().SetActiveVectors(orientation_array) 269 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 270 gly.SetVectorModeToUseVector() 271 elif utils.is_sequence(orientation_array): # passing a list 272 varr = vtk.vtkFloatArray() 273 varr.SetNumberOfComponents(3) 274 varr.SetName("glyph_vectors") 275 for v in orientation_array: 276 varr.InsertNextTuple(v) 277 poly.GetPointData().AddArray(varr) 278 poly.GetPointData().SetActiveVectors("glyph_vectors") 279 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 280 gly.SetVectorModeToUseVector() 281 282 gly.Update() 283 284 Mesh.__init__(self, gly.GetOutput(), c, alpha) 285 self.flat() 286 if lighting is not None: 287 self.property.SetLighting(lighting) 288 289 if cmap: 290 lut = vtk.vtkLookupTable() 291 lut.SetNumberOfTableValues(512) 292 lut.Build() 293 for i in range(512): 294 r, g, b = color_map(i, cmap, 0, 512) 295 lut.SetTableValue(i, r, g, b, 1) 296 self.mapper().SetLookupTable(lut) 297 self.mapper().ScalarVisibilityOn() 298 self.mapper().SetScalarModeToUsePointData() 299 if gly.GetOutput().GetPointData().GetScalars(): 300 rng = gly.GetOutput().GetPointData().GetScalars().GetRange() 301 self.mapper().SetScalarRange(rng[0], rng[1]) 302 303 self.name = "Glyph"
At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with various orientation options and coloring.
The input can also be a simple list of 2D or 3D coordinates.
Color can be specified as a colormap which maps the size of the orientation
vectors in orientation_array
.
165 def __init__( 166 self, 167 mesh, 168 glyph, 169 orientation_array=None, 170 scale_by_scalar=False, 171 scale_by_vector_size=False, 172 scale_by_vector_components=False, 173 color_by_scalar=False, 174 color_by_vector_size=False, 175 c="k8", 176 alpha=1.0, 177 **opts, 178 ): 179 """ 180 Arguments: 181 orientation_array: (list, str, vtkArray) 182 list of vectors, `vtkArray` or name of an already existing pointdata array 183 scale_by_scalar : (bool) 184 glyph mesh is scaled by the active scalars 185 scale_by_vector_size : (bool) 186 glyph mesh is scaled by the size of the vectors 187 scale_by_vector_components : (bool) 188 glyph mesh is scaled by the 3 vectors components 189 color_by_scalar : (bool) 190 glyph mesh is colored based on the scalar value 191 color_by_vector_size : (bool) 192 glyph mesh is colored based on the vector size 193 194 Examples: 195 - [glyphs1.py](]https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 196 - [glyphs_arrows.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs_arrows.py) 197 198 ![](https://vedo.embl.es/images/basic/glyphs.png) 199 """ 200 if len(opts) > 0: # Deprecations 201 printc(":noentry: Warning! In Glyph() unrecognized keywords:", opts, c="y") 202 orientation_array = opts.pop("orientationArray", orientation_array) 203 scale_by_scalar = opts.pop("scaleByScalar", scale_by_scalar) 204 scale_by_vector_size = opts.pop("scaleByVectorSize", scale_by_vector_size) 205 scale_by_vector_components = opts.pop( 206 "scaleByVectorComponents", scale_by_vector_components 207 ) 208 color_by_scalar = opts.pop("colorByScalar", color_by_scalar) 209 color_by_vector_size = opts.pop("colorByVectorSize", color_by_vector_size) 210 printc(" Please use 'snake_case' instead of 'camelCase' keywords", c="y") 211 212 lighting = None 213 if utils.is_sequence(mesh): 214 # create a cloud of points 215 poly = Points(mesh).polydata() 216 elif isinstance(mesh, vtk.vtkPolyData): 217 poly = mesh 218 else: 219 poly = mesh.polydata() 220 221 if isinstance(glyph, Points): 222 lighting = glyph.property.GetLighting() 223 glyph = glyph.polydata() 224 225 cmap = "" 226 if isinstance(c, str) and c in cmaps_names: 227 cmap = c 228 c = None 229 elif utils.is_sequence(c): # user passing an array of point colors 230 ucols = vtk.vtkUnsignedCharArray() 231 ucols.SetNumberOfComponents(3) 232 ucols.SetName("glyph_RGB") 233 for col in c: 234 cl = get_color(col) 235 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 236 poly.GetPointData().AddArray(ucols) 237 poly.GetPointData().SetActiveScalars("glyph_RGB") 238 c = None 239 240 gly = vtk.vtkGlyph3D() 241 gly.GeneratePointIdsOn() 242 gly.SetInputData(poly) 243 gly.SetSourceData(glyph) 244 245 if scale_by_scalar: 246 gly.SetScaleModeToScaleByScalar() 247 elif scale_by_vector_size: 248 gly.SetScaleModeToScaleByVector() 249 elif scale_by_vector_components: 250 gly.SetScaleModeToScaleByVectorComponents() 251 else: 252 gly.SetScaleModeToDataScalingOff() 253 254 if color_by_vector_size: 255 gly.SetVectorModeToUseVector() 256 gly.SetColorModeToColorByVector() 257 elif color_by_scalar: 258 gly.SetColorModeToColorByScalar() 259 else: 260 gly.SetColorModeToColorByScale() 261 262 if orientation_array is not None: 263 gly.OrientOn() 264 if isinstance(orientation_array, str): 265 if orientation_array.lower() == "normals": 266 gly.SetVectorModeToUseNormal() 267 else: # passing a name 268 poly.GetPointData().SetActiveVectors(orientation_array) 269 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 270 gly.SetVectorModeToUseVector() 271 elif utils.is_sequence(orientation_array): # passing a list 272 varr = vtk.vtkFloatArray() 273 varr.SetNumberOfComponents(3) 274 varr.SetName("glyph_vectors") 275 for v in orientation_array: 276 varr.InsertNextTuple(v) 277 poly.GetPointData().AddArray(varr) 278 poly.GetPointData().SetActiveVectors("glyph_vectors") 279 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 280 gly.SetVectorModeToUseVector() 281 282 gly.Update() 283 284 Mesh.__init__(self, gly.GetOutput(), c, alpha) 285 self.flat() 286 if lighting is not None: 287 self.property.SetLighting(lighting) 288 289 if cmap: 290 lut = vtk.vtkLookupTable() 291 lut.SetNumberOfTableValues(512) 292 lut.Build() 293 for i in range(512): 294 r, g, b = color_map(i, cmap, 0, 512) 295 lut.SetTableValue(i, r, g, b, 1) 296 self.mapper().SetLookupTable(lut) 297 self.mapper().ScalarVisibilityOn() 298 self.mapper().SetScalarModeToUsePointData() 299 if gly.GetOutput().GetPointData().GetScalars(): 300 rng = gly.GetOutput().GetPointData().GetScalars().GetRange() 301 self.mapper().SetScalarRange(rng[0], rng[1]) 302 303 self.name = "Glyph"
Arguments:
- orientation_array: (list, str, vtkArray)
list of vectors,
vtkArray
or name of an already existing pointdata array - scale_by_scalar : (bool) glyph mesh is scaled by the active scalars
- scale_by_vector_size : (bool) glyph mesh is scaled by the size of the vectors
- scale_by_vector_components : (bool) glyph mesh is scaled by the 3 vectors components
- color_by_scalar : (bool) glyph mesh is colored based on the scalar value
- color_by_vector_size : (bool) glyph mesh is colored based on the vector size
Examples:
306class Tensors(Mesh): 307 """ 308 Geometric representation of tensors defined on a domain or set of points. 309 Tensors can be scaled and/or rotated according to the source at eache input point. 310 Scaling and rotation is controlled by the eigenvalues/eigenvectors of the symmetrical part 311 of the tensor as follows: 312 313 For each tensor, the eigenvalues (and associated eigenvectors) are sorted 314 to determine the major, medium, and minor eigenvalues/eigenvectors. 315 The eigenvalue decomposition only makes sense for symmetric tensors, 316 hence the need to only consider the symmetric part of the tensor, 317 which is `1/2*(T+T.transposed())`. 318 """ 319 320 def __init__( 321 self, 322 domain, 323 source="ellipsoid", 324 use_eigenvalues=True, 325 is_symmetric=True, 326 three_axes=False, 327 scale=1.0, 328 max_scale=None, 329 length=None, 330 c=None, 331 alpha=1.0, 332 ): 333 """ 334 Arguments: 335 source : (str, Mesh) 336 preset type of source shape 337 `['ellipsoid', 'cylinder', 'cube' or any specified `Mesh`]` 338 use_eigenvalues : (bool) 339 color source glyph using the eigenvalues or by scalars 340 three_axes : (bool) 341 if `False` scale the source in the x-direction, 342 the medium in the y-direction, and the minor in the z-direction. 343 Then, the source is rotated so that the glyph's local x-axis lies 344 along the major eigenvector, y-axis along the medium eigenvector, 345 and z-axis along the minor. 346 347 If `True` three sources are produced, each of them oriented along an eigenvector 348 and scaled according to the corresponding eigenvector. 349 is_symmetric : (bool) 350 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 351 The x-axis of the source glyph will correspond to the eigenvector on output. 352 length : (float) 353 distance from the origin to the tip of the source glyph along the x-axis 354 scale : (float) 355 scaling factor of the source glyph. 356 max_scale : (float) 357 clamp scaling at this factor. 358 359 Examples: 360 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 361 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 362 363 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 364 """ 365 if isinstance(source, Points): 366 src = source.normalize().polydata(False) 367 else: 368 if "ellip" in source: 369 src = vtk.vtkSphereSource() 370 src.SetPhiResolution(24) 371 src.SetThetaResolution(12) 372 elif "cyl" in source: 373 src = vtk.vtkCylinderSource() 374 src.SetResolution(48) 375 src.CappingOn() 376 elif source == "cube": 377 src = vtk.vtkCubeSource() 378 src.Update() 379 380 tg = vtk.vtkTensorGlyph() 381 if isinstance(domain, vtk.vtkPolyData): 382 tg.SetInputData(domain) 383 else: 384 tg.SetInputData(domain.GetMapper().GetInput()) 385 tg.SetSourceData(src.GetOutput()) 386 387 if c is None: 388 tg.ColorGlyphsOn() 389 else: 390 tg.ColorGlyphsOff() 391 392 tg.SetSymmetric(int(is_symmetric)) 393 394 if length is not None: 395 tg.SetLength(length) 396 if use_eigenvalues: 397 tg.ExtractEigenvaluesOn() 398 tg.SetColorModeToEigenvalues() 399 else: 400 tg.SetColorModeToScalars() 401 tg.SetThreeGlyphs(three_axes) 402 tg.ScalingOn() 403 tg.SetScaleFactor(scale) 404 if max_scale is None: 405 tg.ClampScalingOn() 406 max_scale = scale * 10 407 tg.SetMaxScaleFactor(max_scale) 408 tg.Update() 409 tgn = vtk.vtkPolyDataNormals() 410 tgn.SetInputData(tg.GetOutput()) 411 tgn.Update() 412 Mesh.__init__(self, tgn.GetOutput(), c, alpha) 413 self.name = "Tensors"
Geometric representation of tensors defined on a domain or set of points. Tensors can be scaled and/or rotated according to the source at eache input point. Scaling and rotation is controlled by the eigenvalues/eigenvectors of the symmetrical part of the tensor as follows:
For each tensor, the eigenvalues (and associated eigenvectors) are sorted
to determine the major, medium, and minor eigenvalues/eigenvectors.
The eigenvalue decomposition only makes sense for symmetric tensors,
hence the need to only consider the symmetric part of the tensor,
which is 1/2*(T+T.transposed())
.
320 def __init__( 321 self, 322 domain, 323 source="ellipsoid", 324 use_eigenvalues=True, 325 is_symmetric=True, 326 three_axes=False, 327 scale=1.0, 328 max_scale=None, 329 length=None, 330 c=None, 331 alpha=1.0, 332 ): 333 """ 334 Arguments: 335 source : (str, Mesh) 336 preset type of source shape 337 `['ellipsoid', 'cylinder', 'cube' or any specified `Mesh`]` 338 use_eigenvalues : (bool) 339 color source glyph using the eigenvalues or by scalars 340 three_axes : (bool) 341 if `False` scale the source in the x-direction, 342 the medium in the y-direction, and the minor in the z-direction. 343 Then, the source is rotated so that the glyph's local x-axis lies 344 along the major eigenvector, y-axis along the medium eigenvector, 345 and z-axis along the minor. 346 347 If `True` three sources are produced, each of them oriented along an eigenvector 348 and scaled according to the corresponding eigenvector. 349 is_symmetric : (bool) 350 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 351 The x-axis of the source glyph will correspond to the eigenvector on output. 352 length : (float) 353 distance from the origin to the tip of the source glyph along the x-axis 354 scale : (float) 355 scaling factor of the source glyph. 356 max_scale : (float) 357 clamp scaling at this factor. 358 359 Examples: 360 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 361 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 362 363 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 364 """ 365 if isinstance(source, Points): 366 src = source.normalize().polydata(False) 367 else: 368 if "ellip" in source: 369 src = vtk.vtkSphereSource() 370 src.SetPhiResolution(24) 371 src.SetThetaResolution(12) 372 elif "cyl" in source: 373 src = vtk.vtkCylinderSource() 374 src.SetResolution(48) 375 src.CappingOn() 376 elif source == "cube": 377 src = vtk.vtkCubeSource() 378 src.Update() 379 380 tg = vtk.vtkTensorGlyph() 381 if isinstance(domain, vtk.vtkPolyData): 382 tg.SetInputData(domain) 383 else: 384 tg.SetInputData(domain.GetMapper().GetInput()) 385 tg.SetSourceData(src.GetOutput()) 386 387 if c is None: 388 tg.ColorGlyphsOn() 389 else: 390 tg.ColorGlyphsOff() 391 392 tg.SetSymmetric(int(is_symmetric)) 393 394 if length is not None: 395 tg.SetLength(length) 396 if use_eigenvalues: 397 tg.ExtractEigenvaluesOn() 398 tg.SetColorModeToEigenvalues() 399 else: 400 tg.SetColorModeToScalars() 401 tg.SetThreeGlyphs(three_axes) 402 tg.ScalingOn() 403 tg.SetScaleFactor(scale) 404 if max_scale is None: 405 tg.ClampScalingOn() 406 max_scale = scale * 10 407 tg.SetMaxScaleFactor(max_scale) 408 tg.Update() 409 tgn = vtk.vtkPolyDataNormals() 410 tgn.SetInputData(tg.GetOutput()) 411 tgn.Update() 412 Mesh.__init__(self, tgn.GetOutput(), c, alpha) 413 self.name = "Tensors"
Arguments:
- source : (str, Mesh)
preset type of source shape
['ellipsoid', 'cylinder', 'cube' or any specified
Mesh]
- 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:
3863class ParametricShape(Mesh): 3864 """ 3865 A set of built-in shapes mainly for illustration purposes. 3866 """ 3867 3868 def __init__(self, name, res=51, n=25, seed=1): 3869 """ 3870 A set of built-in shapes mainly for illustration purposes. 3871 3872 Name can be an integer or a string in this list: 3873 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3874 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3875 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3876 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3877 3878 Example: 3879 ```python 3880 from vedo import * 3881 settings.immediate_rendering = False 3882 plt = Plotter(N=18) 3883 for i in range(18): 3884 ps = ParametricShape(i).color(i) 3885 plt.at(i).show(ps, ps.name) 3886 plt.interactive().close() 3887 ``` 3888 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3889 """ 3890 3891 shapes = [ 3892 "Boy", 3893 "ConicSpiral", 3894 "CrossCap", 3895 "Enneper", 3896 "Figure8Klein", 3897 "Klein", 3898 "Dini", 3899 "Mobius", 3900 "RandomHills", 3901 "Roman", 3902 "SuperEllipsoid", 3903 "BohemianDome", 3904 "Bour", 3905 "CatalanMinimal", 3906 "Henneberg", 3907 "Kuen", 3908 "PluckerConoid", 3909 "Pseudosphere", 3910 ] 3911 3912 if isinstance(name, int): 3913 name = name % len(shapes) 3914 name = shapes[name] 3915 3916 if name == "Boy": 3917 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBoy() 3918 elif name == "ConicSpiral": 3919 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricConicSpiral() 3920 elif name == "CrossCap": 3921 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCrossCap() 3922 elif name == "Dini": 3923 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricDini() 3924 elif name == "Enneper": 3925 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricEnneper() 3926 elif name == "Figure8Klein": 3927 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricFigure8Klein() 3928 elif name == "Klein": 3929 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKlein() 3930 elif name == "Mobius": 3931 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricMobius() 3932 ps.SetRadius(2.0) 3933 ps.SetMinimumV(-0.5) 3934 ps.SetMaximumV(0.5) 3935 elif name == "RandomHills": 3936 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRandomHills() 3937 ps.AllowRandomGenerationOn() 3938 ps.SetRandomSeed(seed) 3939 ps.SetNumberOfHills(n) 3940 elif name == "Roman": 3941 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRoman() 3942 elif name == "SuperEllipsoid": 3943 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricSuperEllipsoid() 3944 ps.SetN1(0.5) 3945 ps.SetN2(0.4) 3946 elif name == "BohemianDome": 3947 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBohemianDome() 3948 ps.SetA(5.0) 3949 ps.SetB(1.0) 3950 ps.SetC(2.0) 3951 elif name == "Bour": 3952 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBour() 3953 elif name == "CatalanMinimal": 3954 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCatalanMinimal() 3955 elif name == "Henneberg": 3956 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricHenneberg() 3957 elif name == "Kuen": 3958 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKuen() 3959 ps.SetDeltaV0(0.001) 3960 elif name == "PluckerConoid": 3961 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPluckerConoid() 3962 elif name == "Pseudosphere": 3963 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPseudosphere() 3964 else: 3965 vedo.logger.error(f"unknown ParametricShape {name}") 3966 return 3967 3968 pfs = vtk.vtkParametricFunctionSource() 3969 pfs.SetParametricFunction(ps) 3970 pfs.SetUResolution(res) 3971 pfs.SetVResolution(res) 3972 pfs.SetWResolution(res) 3973 pfs.SetScalarModeToZ() 3974 pfs.Update() 3975 3976 Mesh.__init__(self, pfs.GetOutput()) 3977 3978 if name != 'Kuen': self.normalize() 3979 if name == 'Dini': self.scale(0.4) 3980 if name == 'Enneper': self.scale(0.4) 3981 if name == 'ConicSpiral': self.bc('tomato') 3982 self.name = name
A set of built-in shapes mainly for illustration purposes.
3868 def __init__(self, name, res=51, n=25, seed=1): 3869 """ 3870 A set of built-in shapes mainly for illustration purposes. 3871 3872 Name can be an integer or a string in this list: 3873 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3874 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3875 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3876 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3877 3878 Example: 3879 ```python 3880 from vedo import * 3881 settings.immediate_rendering = False 3882 plt = Plotter(N=18) 3883 for i in range(18): 3884 ps = ParametricShape(i).color(i) 3885 plt.at(i).show(ps, ps.name) 3886 plt.interactive().close() 3887 ``` 3888 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3889 """ 3890 3891 shapes = [ 3892 "Boy", 3893 "ConicSpiral", 3894 "CrossCap", 3895 "Enneper", 3896 "Figure8Klein", 3897 "Klein", 3898 "Dini", 3899 "Mobius", 3900 "RandomHills", 3901 "Roman", 3902 "SuperEllipsoid", 3903 "BohemianDome", 3904 "Bour", 3905 "CatalanMinimal", 3906 "Henneberg", 3907 "Kuen", 3908 "PluckerConoid", 3909 "Pseudosphere", 3910 ] 3911 3912 if isinstance(name, int): 3913 name = name % len(shapes) 3914 name = shapes[name] 3915 3916 if name == "Boy": 3917 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBoy() 3918 elif name == "ConicSpiral": 3919 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricConicSpiral() 3920 elif name == "CrossCap": 3921 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCrossCap() 3922 elif name == "Dini": 3923 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricDini() 3924 elif name == "Enneper": 3925 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricEnneper() 3926 elif name == "Figure8Klein": 3927 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricFigure8Klein() 3928 elif name == "Klein": 3929 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKlein() 3930 elif name == "Mobius": 3931 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricMobius() 3932 ps.SetRadius(2.0) 3933 ps.SetMinimumV(-0.5) 3934 ps.SetMaximumV(0.5) 3935 elif name == "RandomHills": 3936 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRandomHills() 3937 ps.AllowRandomGenerationOn() 3938 ps.SetRandomSeed(seed) 3939 ps.SetNumberOfHills(n) 3940 elif name == "Roman": 3941 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricRoman() 3942 elif name == "SuperEllipsoid": 3943 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricSuperEllipsoid() 3944 ps.SetN1(0.5) 3945 ps.SetN2(0.4) 3946 elif name == "BohemianDome": 3947 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBohemianDome() 3948 ps.SetA(5.0) 3949 ps.SetB(1.0) 3950 ps.SetC(2.0) 3951 elif name == "Bour": 3952 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricBour() 3953 elif name == "CatalanMinimal": 3954 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricCatalanMinimal() 3955 elif name == "Henneberg": 3956 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricHenneberg() 3957 elif name == "Kuen": 3958 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricKuen() 3959 ps.SetDeltaV0(0.001) 3960 elif name == "PluckerConoid": 3961 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPluckerConoid() 3962 elif name == "Pseudosphere": 3963 ps = vtk.vtkmodules.vtkCommonComputationalGeometry.vtkParametricPseudosphere() 3964 else: 3965 vedo.logger.error(f"unknown ParametricShape {name}") 3966 return 3967 3968 pfs = vtk.vtkParametricFunctionSource() 3969 pfs.SetParametricFunction(ps) 3970 pfs.SetUResolution(res) 3971 pfs.SetVResolution(res) 3972 pfs.SetWResolution(res) 3973 pfs.SetScalarModeToZ() 3974 pfs.Update() 3975 3976 Mesh.__init__(self, pfs.GetOutput()) 3977 3978 if name != 'Kuen': self.normalize() 3979 if name == 'Dini': self.scale(0.4) 3980 if name == 'Enneper': self.scale(0.4) 3981 if name == 'ConicSpiral': self.bc('tomato') 3982 self.name = name
A set of built-in shapes mainly for illustration purposes.
Name can be an integer or a string in this list:
['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']
.
Example:
from vedo import * settings.immediate_rendering = False plt = Plotter(N=18) for i in range(18): ps = ParametricShape(i).color(i) plt.at(i).show(ps, ps.name) plt.interactive().close()
4836class ConvexHull(Mesh): 4837 """ 4838 Create the 2D/3D convex hull from a set of points. 4839 """ 4840 4841 def __init__(self, pts): 4842 """ 4843 Create the 2D/3D convex hull from a set of input points or input Mesh. 4844 4845 Examples: 4846 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4847 4848 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4849 """ 4850 if utils.is_sequence(pts): 4851 pts = utils.make3d(pts).astype(float) 4852 mesh = Points(pts) 4853 else: 4854 mesh = pts 4855 apoly = mesh.clean().polydata() 4856 4857 # Create the convex hull of the pointcloud 4858 z0, z1 = mesh.zbounds() 4859 d = mesh.diagonal_size() 4860 if (z1 - z0) / d > 0.0001: 4861 delaunay = vtk.vtkDelaunay3D() 4862 delaunay.SetInputData(apoly) 4863 delaunay.Update() 4864 surfaceFilter = vtk.vtkDataSetSurfaceFilter() 4865 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4866 surfaceFilter.Update() 4867 out = surfaceFilter.GetOutput() 4868 else: 4869 delaunay = vtk.vtkDelaunay2D() 4870 delaunay.SetInputData(apoly) 4871 delaunay.Update() 4872 fe = vtk.vtkFeatureEdges() 4873 fe.SetInputConnection(delaunay.GetOutputPort()) 4874 fe.BoundaryEdgesOn() 4875 fe.Update() 4876 out = fe.GetOutput() 4877 4878 Mesh.__init__(self, out, c=mesh.color(), alpha=0.75) 4879 # self.triangulate() 4880 self.flat() 4881 self.name = "ConvexHull"
Create the 2D/3D convex hull from a set of points.
4841 def __init__(self, pts): 4842 """ 4843 Create the 2D/3D convex hull from a set of input points or input Mesh. 4844 4845 Examples: 4846 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4847 4848 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4849 """ 4850 if utils.is_sequence(pts): 4851 pts = utils.make3d(pts).astype(float) 4852 mesh = Points(pts) 4853 else: 4854 mesh = pts 4855 apoly = mesh.clean().polydata() 4856 4857 # Create the convex hull of the pointcloud 4858 z0, z1 = mesh.zbounds() 4859 d = mesh.diagonal_size() 4860 if (z1 - z0) / d > 0.0001: 4861 delaunay = vtk.vtkDelaunay3D() 4862 delaunay.SetInputData(apoly) 4863 delaunay.Update() 4864 surfaceFilter = vtk.vtkDataSetSurfaceFilter() 4865 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4866 surfaceFilter.Update() 4867 out = surfaceFilter.GetOutput() 4868 else: 4869 delaunay = vtk.vtkDelaunay2D() 4870 delaunay.SetInputData(apoly) 4871 delaunay.Update() 4872 fe = vtk.vtkFeatureEdges() 4873 fe.SetInputConnection(delaunay.GetOutputPort()) 4874 fe.BoundaryEdgesOn() 4875 fe.Update() 4876 out = fe.GetOutput() 4877 4878 Mesh.__init__(self, out, c=mesh.color(), alpha=0.75) 4879 # self.triangulate() 4880 self.flat() 4881 self.name = "ConvexHull"
4884def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True): 4885 """ 4886 Create the 3D vedo logo. 4887 4888 Arguments: 4889 distance : (float) 4890 send back logo by this distance from camera 4891 version : (bool) 4892 add version text to the right end of the logo 4893 bc : (color) 4894 text back face color 4895 """ 4896 if c is None: 4897 c = (0, 0, 0) 4898 if vedo.plotter_instance: 4899 if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5: 4900 c = [0, 0, 0] 4901 else: 4902 c = "linen" 4903 4904 font = "Comae" 4905 vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8) 4906 vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc) 4907 vlogo.GetProperty().LightingOn() 4908 4909 vr, rul = None, None 4910 if version: 4911 vr = Text3D( 4912 vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1 4913 ).scale([1, 0.7, 1]) 4914 vr.RotateZ(90) 4915 vr.pos(2450, 50, 80).bc(bc).pickable(False) 4916 elif frame: 4917 rul = vedo.RulerAxes( 4918 (-2600, 2110, 0, 1650, 0, 0), 4919 xlabel="European Molecular Biology Laboratory", 4920 ylabel=vedo.__version__, 4921 font=font, 4922 xpadding=0.09, 4923 ypadding=0.04, 4924 ) 4925 fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0) 4926 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