vedo.shapes
Submodule to generate simple and complex geometric shapes
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import os 4from typing import List, Union, Any 5from functools import lru_cache 6from weakref import ref as weak_ref_to 7 8import numpy as np 9import vedo.vtkclasses as vtki 10 11import vedo 12from vedo import settings 13from vedo.transformations import LinearTransform, pol2cart, cart2spher, spher2cart 14from vedo.colors import cmaps_names, get_color, printc 15from vedo import utils 16from vedo.pointcloud import Points, merge 17from vedo.mesh import Mesh 18from vedo.image import Image 19 20__docformat__ = "google" 21 22__doc__ = """ 23Submodule to generate simple and complex geometric shapes 24 25![](https://vedo.embl.es/images/basic/extrude.png) 26""" 27 28__all__ = [ 29 "Marker", 30 "Line", 31 "DashedLine", 32 "RoundedLine", 33 "Tube", 34 "Tubes", 35 "ThickTube", 36 "Lines", 37 "Spline", 38 "KSpline", 39 "CSpline", 40 "Bezier", 41 "Brace", 42 "NormalLines", 43 "Ribbon", 44 "Arrow", 45 "Arrows", 46 "Arrow2D", 47 "Arrows2D", 48 "FlatArrow", 49 "Polygon", 50 "Triangle", 51 "Rectangle", 52 "Disc", 53 "Circle", 54 "GeoCircle", 55 "Arc", 56 "Star", 57 "Star3D", 58 "Cross3D", 59 "IcoSphere", 60 "Sphere", 61 "Spheres", 62 "Earth", 63 "Ellipsoid", 64 "Grid", 65 "TessellatedBox", 66 "Plane", 67 "Box", 68 "Cube", 69 "Spring", 70 "Cylinder", 71 "Cone", 72 "Pyramid", 73 "Torus", 74 "Paraboloid", 75 "Hyperboloid", 76 "TextBase", 77 "Text3D", 78 "Text2D", 79 "CornerAnnotation", 80 "Latex", 81 "Glyph", 82 "Tensors", 83 "ParametricShape", 84 "ConvexHull", 85 "VedoLogo", 86] 87 88############################################## 89_reps = ( 90 (":nabla", "∇"), 91 (":inf", "∞"), 92 (":rightarrow", "→"), 93 (":leftarrow", "←"), 94 (":partial", "∂"), 95 (":sqrt", "√"), 96 (":approx", "≈"), 97 (":neq", "≠"), 98 (":leq", "≤"), 99 (":geq", "≥"), 100 (":foreach", "∀"), 101 (":permille", "‰"), 102 (":euro", "€"), 103 (":dot", "·"), 104 (":int", "∫"), 105 (":pm", "±"), 106 (":times", "×"), 107 (":Gamma", "Γ"), 108 (":Delta", "Δ"), 109 (":Theta", "Θ"), 110 (":Lambda", "Λ"), 111 (":Pi", "Π"), 112 (":Sigma", "Σ"), 113 (":Phi", "Φ"), 114 (":Chi", "X"), 115 (":Xi", "Ξ"), 116 (":Psi", "Ψ"), 117 (":Omega", "Ω"), 118 (":alpha", "α"), 119 (":beta", "β"), 120 (":gamma", "γ"), 121 (":delta", "δ"), 122 (":epsilon", "ε"), 123 (":zeta", "ζ"), 124 (":eta", "η"), 125 (":theta", "θ"), 126 (":kappa", "κ"), 127 (":lambda", "λ"), 128 (":mu", "μ"), 129 (":lowerxi", "ξ"), 130 (":nu", "ν"), 131 (":pi", "π"), 132 (":rho", "ρ"), 133 (":sigma", "σ"), 134 (":tau", "τ"), 135 (":varphi", "φ"), 136 (":phi", "φ"), 137 (":chi", "χ"), 138 (":psi", "ψ"), 139 (":omega", "ω"), 140 (":circ", "°"), 141 (":onehalf", "½"), 142 (":onefourth", "¼"), 143 (":threefourths", "¾"), 144 (":^1", "¹"), 145 (":^2", "²"), 146 (":^3", "³"), 147 (":,", "~"), 148) 149 150 151######################################################################## 152class Glyph(Mesh): 153 """ 154 At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with 155 various orientation options and coloring. 156 157 The input can also be a simple list of 2D or 3D coordinates. 158 Color can be specified as a colormap which maps the size of the orientation 159 vectors in `orientation_array`. 160 """ 161 162 def __init__( 163 self, 164 mesh, 165 glyph, 166 orientation_array=None, 167 scale_by_scalar=False, 168 scale_by_vector_size=False, 169 scale_by_vector_components=False, 170 color_by_scalar=False, 171 color_by_vector_size=False, 172 c="k8", 173 alpha=1.0, 174 ) -> None: 175 """ 176 Arguments: 177 orientation_array: (list, str, vtkArray) 178 list of vectors, `vtkArray` or name of an already existing pointdata array 179 scale_by_scalar : (bool) 180 glyph mesh is scaled by the active scalars 181 scale_by_vector_size : (bool) 182 glyph mesh is scaled by the size of the vectors 183 scale_by_vector_components : (bool) 184 glyph mesh is scaled by the 3 vectors components 185 color_by_scalar : (bool) 186 glyph mesh is colored based on the scalar value 187 color_by_vector_size : (bool) 188 glyph mesh is colored based on the vector size 189 190 Examples: 191 - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 192 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 193 194 ![](https://vedo.embl.es/images/basic/glyphs.png) 195 """ 196 if utils.is_sequence(mesh): 197 # create a cloud of points 198 poly = utils.buildPolyData(mesh) 199 else: 200 poly = mesh.dataset 201 202 cmap = "" 203 if isinstance(c, str) and c in cmaps_names: 204 cmap = c 205 c = None 206 elif utils.is_sequence(c): # user passing an array of point colors 207 ucols = vtki.vtkUnsignedCharArray() 208 ucols.SetNumberOfComponents(3) 209 ucols.SetName("GlyphRGB") 210 for col in c: 211 cl = get_color(col) 212 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 213 poly.GetPointData().AddArray(ucols) 214 poly.GetPointData().SetActiveScalars("GlyphRGB") 215 c = None 216 217 gly = vtki.vtkGlyph3D() 218 gly.GeneratePointIdsOn() 219 gly.SetInputData(poly) 220 try: 221 gly.SetSourceData(glyph) 222 except TypeError: 223 gly.SetSourceData(glyph.dataset) 224 225 if scale_by_scalar: 226 gly.SetScaleModeToScaleByScalar() 227 elif scale_by_vector_size: 228 gly.SetScaleModeToScaleByVector() 229 elif scale_by_vector_components: 230 gly.SetScaleModeToScaleByVectorComponents() 231 else: 232 gly.SetScaleModeToDataScalingOff() 233 234 if color_by_vector_size: 235 gly.SetVectorModeToUseVector() 236 gly.SetColorModeToColorByVector() 237 elif color_by_scalar: 238 gly.SetColorModeToColorByScalar() 239 else: 240 gly.SetColorModeToColorByScale() 241 242 if orientation_array is not None: 243 gly.OrientOn() 244 if isinstance(orientation_array, str): 245 if orientation_array.lower() == "normals": 246 gly.SetVectorModeToUseNormal() 247 else: # passing a name 248 poly.GetPointData().SetActiveVectors(orientation_array) 249 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 250 gly.SetVectorModeToUseVector() 251 elif utils.is_sequence(orientation_array): # passing a list 252 varr = vtki.vtkFloatArray() 253 varr.SetNumberOfComponents(3) 254 varr.SetName("glyph_vectors") 255 for v in orientation_array: 256 varr.InsertNextTuple(v) 257 poly.GetPointData().AddArray(varr) 258 poly.GetPointData().SetActiveVectors("glyph_vectors") 259 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 260 gly.SetVectorModeToUseVector() 261 262 gly.Update() 263 264 super().__init__(gly.GetOutput(), c, alpha) 265 self.flat() 266 267 if cmap: 268 self.cmap(cmap, "VectorMagnitude") 269 elif c is None: 270 self.pointdata.select("GlyphRGB") 271 272 self.name = "Glyph" 273 274 275class Tensors(Mesh): 276 """ 277 Geometric representation of tensors defined on a domain or set of points. 278 Tensors can be scaled and/or rotated according to the source at each input point. 279 Scaling and rotation is controlled by the eigenvalues/eigenvectors of the 280 symmetrical part of the tensor as follows: 281 282 For each tensor, the eigenvalues (and associated eigenvectors) are sorted 283 to determine the major, medium, and minor eigenvalues/eigenvectors. 284 The eigenvalue decomposition only makes sense for symmetric tensors, 285 hence the need to only consider the symmetric part of the tensor, 286 which is `1/2*(T+T.transposed())`. 287 """ 288 289 def __init__( 290 self, 291 domain, 292 source="ellipsoid", 293 use_eigenvalues=True, 294 is_symmetric=True, 295 three_axes=False, 296 scale=1.0, 297 max_scale=None, 298 length=None, 299 res=24, 300 c=None, 301 alpha=1.0, 302 ) -> None: 303 """ 304 Arguments: 305 source : (str, Mesh) 306 preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object. 307 use_eigenvalues : (bool) 308 color source glyph using the eigenvalues or by scalars 309 three_axes : (bool) 310 if `False` scale the source in the x-direction, 311 the medium in the y-direction, and the minor in the z-direction. 312 Then, the source is rotated so that the glyph's local x-axis lies 313 along the major eigenvector, y-axis along the medium eigenvector, 314 and z-axis along the minor. 315 316 If `True` three sources are produced, each of them oriented along an eigenvector 317 and scaled according to the corresponding eigenvector. 318 is_symmetric : (bool) 319 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 320 The x-axis of the source glyph will correspond to the eigenvector on output. 321 length : (float) 322 distance from the origin to the tip of the source glyph along the x-axis 323 scale : (float) 324 scaling factor of the source glyph. 325 max_scale : (float) 326 clamp scaling at this factor. 327 328 Examples: 329 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 330 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 331 - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py) 332 333 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 334 """ 335 if isinstance(source, Points): 336 src = source.dataset 337 else: # is string 338 if "ellip" in source: 339 src = vtki.new("SphereSource") 340 src.SetPhiResolution(res) 341 src.SetThetaResolution(res*2) 342 elif "cyl" in source: 343 src = vtki.new("CylinderSource") 344 src.SetResolution(res) 345 src.CappingOn() 346 elif source == "cube": 347 src = vtki.new("CubeSource") 348 else: 349 vedo.logger.error(f"Unknown source type {source}") 350 raise ValueError() 351 src.Update() 352 src = src.GetOutput() 353 354 tg = vtki.new("TensorGlyph") 355 if isinstance(domain, vtki.vtkPolyData): 356 tg.SetInputData(domain) 357 else: 358 tg.SetInputData(domain.dataset) 359 tg.SetSourceData(src) 360 361 if c is None: 362 tg.ColorGlyphsOn() 363 else: 364 tg.ColorGlyphsOff() 365 366 tg.SetSymmetric(int(is_symmetric)) 367 368 if length is not None: 369 tg.SetLength(length) 370 if use_eigenvalues: 371 tg.ExtractEigenvaluesOn() 372 tg.SetColorModeToEigenvalues() 373 else: 374 tg.SetColorModeToScalars() 375 376 tg.SetThreeGlyphs(three_axes) 377 tg.ScalingOn() 378 tg.SetScaleFactor(scale) 379 if max_scale is None: 380 tg.ClampScalingOn() 381 max_scale = scale * 10 382 tg.SetMaxScaleFactor(max_scale) 383 384 tg.Update() 385 tgn = vtki.new("PolyDataNormals") 386 tgn.ComputeCellNormalsOff() 387 tgn.SetInputData(tg.GetOutput()) 388 tgn.Update() 389 390 super().__init__(tgn.GetOutput(), c, alpha) 391 self.name = "Tensors" 392 393 394class Line(Mesh): 395 """ 396 Build the line segment between point `p0` and point `p1`. 397 398 If `p0` is already a list of points, return the line connecting them. 399 400 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 401 """ 402 403 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None: 404 """ 405 Arguments: 406 closed : (bool) 407 join last to first point 408 res : (int) 409 resolution, number of points along the line 410 (only relevant if only 2 points are specified) 411 lw : (int) 412 line width in pixel units 413 """ 414 415 if isinstance(p1, Points): 416 p1 = p1.pos() 417 if isinstance(p0, Points): 418 p0 = p0.pos() 419 try: 420 p0 = p0.dataset 421 except AttributeError: 422 pass 423 424 if isinstance(p0, vtki.vtkPolyData): 425 poly = p0 426 top = np.array([0,0,1]) 427 base = np.array([0,0,0]) 428 429 elif utils.is_sequence(p0[0]): # detect if user is passing a list of points 430 431 p0 = utils.make3d(p0) 432 ppoints = vtki.vtkPoints() # Generate the polyline 433 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 434 lines = vtki.vtkCellArray() 435 npt = len(p0) 436 if closed: 437 lines.InsertNextCell(npt + 1) 438 else: 439 lines.InsertNextCell(npt) 440 for i in range(npt): 441 lines.InsertCellPoint(i) 442 if closed: 443 lines.InsertCellPoint(0) 444 poly = vtki.vtkPolyData() 445 poly.SetPoints(ppoints) 446 poly.SetLines(lines) 447 top = p0[-1] 448 base = p0[0] 449 if res != 2: 450 printc(f"Warning: calling Line(res={res}), try remove []?", c='y') 451 res = 2 452 453 else: # or just 2 points to link 454 455 line_source = vtki.new("LineSource") 456 p0 = utils.make3d(p0) 457 p1 = utils.make3d(p1) 458 line_source.SetPoint1(p0) 459 line_source.SetPoint2(p1) 460 line_source.SetResolution(res - 1) 461 line_source.Update() 462 poly = line_source.GetOutput() 463 top = np.asarray(p1, dtype=float) 464 base = np.asarray(p0, dtype=float) 465 466 super().__init__(poly, c, alpha) 467 468 self.slope: List[float] = [] # populated by analysis.fit_line 469 self.center: List[float] = [] 470 self.variances: List[float] = [] 471 472 self.coefficients: List[float] = [] # populated by pyplot.fit() 473 self.covariance_matrix: List[float] = [] 474 self.coefficient_errors: List[float] = [] 475 self.monte_carlo_coefficients: List[float] = [] 476 self.reduced_chi2 = -1 477 self.ndof = 0 478 self.data_sigma = 0 479 self.error_lines: List[Any] = [] 480 self.error_band = None 481 self.res = res 482 483 self.lw(lw) 484 self.properties.LightingOff() 485 self.actor.PickableOff() 486 self.actor.DragableOff() 487 self.base = base 488 self.top = top 489 self.name = "Line" 490 491 def clone(self, deep=True) -> "Line": 492 """ 493 Return a copy of the ``Line`` object. 494 495 Example: 496 ```python 497 from vedo import * 498 ln1 = Line([1,1,1], [2,2,2], lw=3).print() 499 ln2 = ln1.clone().shift(0,0,1).c('red').print() 500 show(ln1, ln2, axes=1, viewup='z').close() 501 ``` 502 ![](https://vedo.embl.es/images/feats/line_clone.png) 503 """ 504 poly = vtki.vtkPolyData() 505 if deep: 506 poly.DeepCopy(self.dataset) 507 else: 508 poly.ShallowCopy(self.dataset) 509 ln = Line(poly) 510 ln.copy_properties_from(self) 511 ln.transform = self.transform.clone() 512 ln.name = self.name 513 ln.base = self.base 514 ln.top = self.top 515 ln.pipeline = utils.OperationNode( 516 "clone", parents=[self], shape="diamond", c="#edede9") 517 return ln 518 519 def linecolor(self, lc=None) -> "Line": 520 """Assign a color to the line""" 521 # overrides mesh.linecolor which would have no effect here 522 return self.color(lc) 523 524 def eval(self, x: float) -> np.ndarray: 525 """ 526 Calculate the position of an intermediate point 527 as a fraction of the length of the line, 528 being x=0 the first point and x=1 the last point. 529 This corresponds to an imaginary point that travels along the line 530 at constant speed. 531 532 Can be used in conjunction with `lin_interpolate()` 533 to map any range to the [0,1] range. 534 """ 535 distance1 = 0.0 536 length = self.length() 537 pts = self.coordinates 538 for i in range(1, len(pts)): 539 p0 = pts[i - 1] 540 p1 = pts[i] 541 seg = p1 - p0 542 distance0 = distance1 543 distance1 += np.linalg.norm(seg) 544 w1 = distance1 / length 545 if w1 >= x: 546 break 547 w0 = distance0 / length 548 v = p0 + seg * (x - w0) / (w1 - w0) 549 return v 550 551 def find_index_at_position(self, p) -> float: 552 """ 553 Find the index of the line vertex that is closest to the point `p`. 554 Note that the returned index can be fractional if `p` is not exactly 555 one of the vertices of the line. 556 """ 557 q = self.closest_point(p) 558 a, b = sorted(self.closest_point(q, n=2, return_point_id=True)) 559 pts = self.coordinates 560 d = np.linalg.norm(pts[a] - pts[b]) 561 t = a + np.linalg.norm(pts[a] - q) / d 562 return t 563 564 def pattern(self, stipple, repeats=10) -> "Line": 565 """ 566 Define a stipple pattern for dashing the line. 567 Pass the stipple pattern as a string like `'- - -'`. 568 Repeats controls the number of times the pattern repeats in a single segment. 569 570 Examples are: `'- -', '-- - --'`, etc. 571 572 The resolution of the line (nr of points) can affect how pattern will show up. 573 574 Example: 575 ```python 576 from vedo import Line 577 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 578 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 579 ln.show(axes=1).close() 580 ``` 581 ![](https://vedo.embl.es/images/feats/line_pattern.png) 582 """ 583 stipple = str(stipple) * int(2 * repeats) 584 dimension = len(stipple) 585 586 image = vtki.vtkImageData() 587 image.SetDimensions(dimension, 1, 1) 588 image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4) 589 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 590 i_dim = 0 591 while i_dim < dimension: 592 for i in range(dimension): 593 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 594 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 595 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 596 if stipple[i] == " ": 597 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 598 else: 599 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 600 i_dim += 1 601 602 poly = self.dataset 603 604 # Create texture coordinates 605 tcoords = vtki.vtkDoubleArray() 606 tcoords.SetName("TCoordsStippledLine") 607 tcoords.SetNumberOfComponents(1) 608 tcoords.SetNumberOfTuples(poly.GetNumberOfPoints()) 609 for i in range(poly.GetNumberOfPoints()): 610 tcoords.SetTypedTuple(i, [i / 2]) 611 poly.GetPointData().SetTCoords(tcoords) 612 poly.GetPointData().Modified() 613 texture = vtki.vtkTexture() 614 texture.SetInputData(image) 615 texture.InterpolateOff() 616 texture.RepeatOn() 617 self.actor.SetTexture(texture) 618 return self 619 620 def length(self) -> float: 621 """Calculate length of the line.""" 622 distance = 0.0 623 pts = self.coordinates 624 for i in range(1, len(pts)): 625 distance += np.linalg.norm(pts[i] - pts[i - 1]) 626 return distance 627 628 def tangents(self) -> np.ndarray: 629 """ 630 Compute the tangents of a line in space. 631 632 Example: 633 ```python 634 from vedo import * 635 shape = Assembly(dataurl+"timecourse1d.npy")[58] 636 pts = shape.rotate_x(30).coordinates 637 tangents = Line(pts).tangents() 638 arrs = Arrows(pts, pts+tangents, c='blue9') 639 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 640 ``` 641 ![](https://vedo.embl.es/images/feats/line_tangents.png) 642 """ 643 v = np.gradient(self.coordinates)[0] 644 ds_dt = np.linalg.norm(v, axis=1) 645 tangent = np.array([1 / ds_dt] * 3).transpose() * v 646 return tangent 647 648 def curvature(self) -> np.ndarray: 649 """ 650 Compute the signed curvature of a line in space. 651 The signed is computed assuming the line is about coplanar to the xy plane. 652 653 Example: 654 ```python 655 from vedo import * 656 from vedo.pyplot import plot 657 shape = Assembly(dataurl+"timecourse1d.npy")[55] 658 curvs = Line(shape.coordinates).curvature() 659 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 660 shape.render_lines_as_tubes().lw(12) 661 pp = plot(curvs, ac='white', lc='yellow5') 662 show(shape, pp, N=2, bg='bb', sharecam=False).close() 663 ``` 664 ![](https://vedo.embl.es/images/feats/line_curvature.png) 665 """ 666 v = np.gradient(self.coordinates)[0] 667 a = np.gradient(v)[0] 668 av = np.cross(a, v) 669 mav = np.linalg.norm(av, axis=1) 670 mv = utils.mag2(v) 671 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 672 val[0] = val[1] 673 val[-1] = val[-2] 674 return val 675 676 def compute_curvature(self, method=0) -> "Line": 677 """ 678 Add a pointdata array named 'Curvatures' which contains 679 the curvature value at each point. 680 681 NB: keyword `method` is overridden in Mesh and has no effect here. 682 """ 683 # overrides mesh.compute_curvature 684 curvs = self.curvature() 685 vmin, vmax = np.min(curvs), np.max(curvs) 686 if vmin < 0 and vmax > 0: 687 v = max(-vmin, vmax) 688 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 689 else: 690 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 691 return self 692 693 def plot_scalar( 694 self, 695 radius=0.0, 696 height=1.1, 697 normal=(), 698 camera=None, 699 ) -> "Line": 700 """ 701 Generate a new `Line` which plots the active scalar along the line. 702 703 Arguments: 704 radius : (float) 705 distance radius to the line 706 height: (float) 707 height of the plot 708 normal: (list) 709 normal vector to the plane of the plot 710 camera: (vtkCamera) 711 camera object to use for the plot orientation 712 713 Example: 714 ```python 715 from vedo import * 716 circle = Circle(res=360).rotate_y(20) 717 pts = circle.coordinates 718 bore = Line(pts).lw(5) 719 values = np.arctan2(pts[:,1], pts[:,0]) 720 bore.pointdata["scalars"] = values + np.random.randn(360)/5 721 vap = bore.plot_scalar(radius=0, height=1) 722 show(bore, vap, axes=1, viewup='z').close() 723 ``` 724 ![](https://vedo.embl.es/images/feats/line_plot_scalar.png) 725 """ 726 ap = vtki.new("ArcPlotter") 727 ap.SetInputData(self.dataset) 728 ap.SetCamera(camera) 729 ap.SetRadius(radius) 730 ap.SetHeight(height) 731 if len(normal)>0: 732 ap.UseDefaultNormalOn() 733 ap.SetDefaultNormal(normal) 734 ap.Update() 735 vap = Line(ap.GetOutput()) 736 vap.linewidth(3).lighting('off') 737 vap.name = "ArcPlot" 738 return vap 739 740 def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh": 741 """ 742 Sweep the `Line` along the specified vector direction. 743 744 Returns a `Mesh` surface. 745 Line position is updated to allow for additional sweepings. 746 747 Example: 748 ```python 749 from vedo import Line, show 750 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 751 surf1 = aline.sweep((1,0.2,0), res=3) 752 surf2 = aline.sweep((0.2,0,1)).alpha(0.5) 753 aline.color('r').linewidth(4) 754 show(surf1, surf2, aline, axes=1).close() 755 ``` 756 ![](https://vedo.embl.es/images/feats/sweepline.png) 757 """ 758 line = self.dataset 759 rows = line.GetNumberOfPoints() 760 761 spacing = 1 / res 762 surface = vtki.vtkPolyData() 763 764 res += 1 765 npts = rows * res 766 npolys = (rows - 1) * (res - 1) 767 points = vtki.vtkPoints() 768 points.Allocate(npts) 769 770 cnt = 0 771 x = [0.0, 0.0, 0.0] 772 for row in range(rows): 773 for col in range(res): 774 p = [0.0, 0.0, 0.0] 775 line.GetPoint(row, p) 776 x[0] = p[0] + direction[0] * col * spacing 777 x[1] = p[1] + direction[1] * col * spacing 778 x[2] = p[2] + direction[2] * col * spacing 779 points.InsertPoint(cnt, x) 780 cnt += 1 781 782 # Generate the quads 783 polys = vtki.vtkCellArray() 784 polys.Allocate(npolys * 4) 785 pts = [0, 0, 0, 0] 786 for row in range(rows - 1): 787 for col in range(res - 1): 788 pts[0] = col + row * res 789 pts[1] = pts[0] + 1 790 pts[2] = pts[0] + res + 1 791 pts[3] = pts[0] + res 792 polys.InsertNextCell(4, pts) 793 surface.SetPoints(points) 794 surface.SetPolys(polys) 795 asurface = Mesh(surface) 796 asurface.copy_properties_from(self) 797 asurface.lighting("default") 798 self.coordinates = self.coordinates + direction 799 return asurface 800 801 def reverse(self): 802 """Reverse the points sequence order.""" 803 pts = np.flip(self.coordinates, axis=0) 804 self.coordinates = pts 805 return self 806 807 808class DashedLine(Mesh): 809 """ 810 Consider using `Line.pattern()` instead. 811 812 Build a dashed line segment between points `p0` and `p1`. 813 If `p0` is a list of points returns the line connecting them. 814 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 815 """ 816 817 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None: 818 """ 819 Arguments: 820 closed : (bool) 821 join last to first point 822 spacing : (float) 823 relative size of the dash 824 lw : (int) 825 line width in pixels 826 """ 827 if isinstance(p1, vtki.vtkActor): 828 p1 = p1.GetPosition() 829 if isinstance(p0, vtki.vtkActor): 830 p0 = p0.GetPosition() 831 if isinstance(p0, Points): 832 p0 = p0.coordinates 833 834 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 835 if len(p0) > 3: 836 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 837 # assume input is 2D xlist, ylist 838 p0 = np.stack((p0, p1), axis=1) 839 p1 = None 840 p0 = utils.make3d(p0) 841 if closed: 842 p0 = np.append(p0, [p0[0]], axis=0) 843 844 if p1 is not None: # assume passing p0=[x,y] 845 if len(p0) == 2 and not utils.is_sequence(p0[0]): 846 p0 = (p0[0], p0[1], 0) 847 if len(p1) == 2 and not utils.is_sequence(p1[0]): 848 p1 = (p1[0], p1[1], 0) 849 850 # detect if user is passing a list of points: 851 if utils.is_sequence(p0[0]): 852 listp = p0 853 else: # or just 2 points to link 854 listp = [p0, p1] 855 856 listp = np.array(listp) 857 if listp.shape[1] == 2: 858 listp = np.c_[listp, np.zeros(listp.shape[0])] 859 860 xmn = np.min(listp, axis=0) 861 xmx = np.max(listp, axis=0) 862 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 863 if not dlen: 864 super().__init__(vtki.vtkPolyData(), c, alpha) 865 self.name = "DashedLine (void)" 866 return 867 868 qs = [] 869 for ipt in range(len(listp) - 1): 870 p0 = listp[ipt] 871 p1 = listp[ipt + 1] 872 v = p1 - p0 873 vdist = np.linalg.norm(v) 874 n1 = int(vdist / dlen) 875 if not n1: 876 continue 877 878 res = 0.0 879 for i in range(n1 + 2): 880 ist = (i - 0.5) / n1 881 ist = max(ist, 0) 882 qi = p0 + v * (ist - res / vdist) 883 if ist > 1: 884 qi = p1 885 res = np.linalg.norm(qi - p1) 886 qs.append(qi) 887 break 888 qs.append(qi) 889 890 polylns = vtki.new("AppendPolyData") 891 for i, q1 in enumerate(qs): 892 if not i % 2: 893 continue 894 q0 = qs[i - 1] 895 line_source = vtki.new("LineSource") 896 line_source.SetPoint1(q0) 897 line_source.SetPoint2(q1) 898 line_source.Update() 899 polylns.AddInputData(line_source.GetOutput()) 900 polylns.Update() 901 902 super().__init__(polylns.GetOutput(), c, alpha) 903 self.lw(lw).lighting("off") 904 self.base = listp[0] 905 if closed: 906 self.top = listp[-2] 907 else: 908 self.top = listp[-1] 909 self.name = "DashedLine" 910 911 912class RoundedLine(Mesh): 913 """ 914 Create a 2D line of specified thickness (in absolute units) passing through 915 a list of input points. Borders of the line are rounded. 916 """ 917 918 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None: 919 """ 920 Arguments: 921 pts : (list) 922 a list of points in 2D or 3D (z will be ignored). 923 lw : (float) 924 thickness of the line. 925 res : (int) 926 resolution of the rounded regions 927 928 Example: 929 ```python 930 from vedo import * 931 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 932 ln = Line(pts).z(0.01) 933 ln.color("red5").linewidth(2) 934 rl = RoundedLine(pts, 0.6) 935 show(Points(pts), ln, rl, axes=1).close() 936 ``` 937 ![](https://vedo.embl.es/images/feats/rounded_line.png) 938 """ 939 pts = utils.make3d(pts) 940 941 def _getpts(pts, revd=False): 942 943 if revd: 944 pts = list(reversed(pts)) 945 946 if len(pts) == 2: 947 p0, p1 = pts 948 v = p1 - p0 949 dv = np.linalg.norm(v) 950 nv = np.cross(v, (0, 0, -1)) 951 nv = nv / np.linalg.norm(nv) * lw 952 return [p0 + nv, p1 + nv] 953 954 ptsnew = [] 955 for k in range(len(pts) - 2): 956 p0 = pts[k] 957 p1 = pts[k + 1] 958 p2 = pts[k + 2] 959 v = p1 - p0 960 u = p2 - p1 961 du = np.linalg.norm(u) 962 dv = np.linalg.norm(v) 963 nv = np.cross(v, (0, 0, -1)) 964 nv = nv / np.linalg.norm(nv) * lw 965 nu = np.cross(u, (0, 0, -1)) 966 nu = nu / np.linalg.norm(nu) * lw 967 uv = np.cross(u, v) 968 if k == 0: 969 ptsnew.append(p0 + nv) 970 if uv[2] <= 0: 971 # the following computation can return a value 972 # ever so slightly > 1.0 causing arccos to fail. 973 uv_arg = np.dot(u, v) / du / dv 974 if uv_arg > 1.0: 975 # since the argument to arcos is 1, simply 976 # assign alpha to 0.0 without calculating the 977 # arccos 978 alpha = 0.0 979 else: 980 alpha = np.arccos(uv_arg) 981 db = lw * np.tan(alpha / 2) 982 p1new = p1 + nv - v / dv * db 983 ptsnew.append(p1new) 984 else: 985 p1a = p1 + nv 986 p1b = p1 + nu 987 for i in range(0, res + 1): 988 pab = p1a * (res - i) / res + p1b * i / res 989 vpab = pab - p1 990 vpab = vpab / np.linalg.norm(vpab) * lw 991 ptsnew.append(p1 + vpab) 992 if k == len(pts) - 3: 993 ptsnew.append(p2 + nu) 994 if revd: 995 ptsnew.append(p2 - nu) 996 return ptsnew 997 998 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 999 1000 ppoints = vtki.vtkPoints() # Generate the polyline 1001 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 1002 lines = vtki.vtkCellArray() 1003 npt = len(ptsnew) 1004 lines.InsertNextCell(npt) 1005 for i in range(npt): 1006 lines.InsertCellPoint(i) 1007 poly = vtki.vtkPolyData() 1008 poly.SetPoints(ppoints) 1009 poly.SetLines(lines) 1010 vct = vtki.new("ContourTriangulator") 1011 vct.SetInputData(poly) 1012 vct.Update() 1013 1014 super().__init__(vct.GetOutput(), c, alpha) 1015 self.flat() 1016 self.properties.LightingOff() 1017 self.name = "RoundedLine" 1018 self.base = ptsnew[0] 1019 self.top = ptsnew[-1] 1020 1021 1022class Lines(Mesh): 1023 """ 1024 Build the line segments between two lists of points `start_pts` and `end_pts`. 1025 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1026 """ 1027 1028 def __init__( 1029 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 1030 ) -> None: 1031 """ 1032 Arguments: 1033 scale : (float) 1034 apply a rescaling factor to the lengths. 1035 c : (color, int, str, list) 1036 color name, number, or list of [R,G,B] colors 1037 alpha : (float) 1038 opacity in range [0,1] 1039 lw : (int) 1040 line width in pixel units 1041 dotted : (bool) 1042 draw a dotted line 1043 res : (int) 1044 resolution, number of points along the line 1045 (only relevant if only 2 points are specified) 1046 1047 Examples: 1048 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 1049 1050 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 1051 """ 1052 1053 if isinstance(start_pts, vtki.vtkPolyData):######## 1054 super().__init__(start_pts, c, alpha) 1055 self.lw(lw).lighting("off") 1056 self.name = "Lines" 1057 return ######################################## 1058 1059 if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line): 1060 # passing a list of Line, see tests/issues/issue_950.py 1061 polylns = vtki.new("AppendPolyData") 1062 for ln in start_pts: 1063 polylns.AddInputData(ln.dataset) 1064 polylns.Update() 1065 1066 super().__init__(polylns.GetOutput(), c, alpha) 1067 self.lw(lw).lighting("off") 1068 if dotted: 1069 self.properties.SetLineStipplePattern(0xF0F0) 1070 self.properties.SetLineStippleRepeatFactor(1) 1071 self.name = "Lines" 1072 return ######################################## 1073 1074 if isinstance(start_pts, Points): 1075 start_pts = start_pts.coordinates 1076 if isinstance(end_pts, Points): 1077 end_pts = end_pts.coordinates 1078 1079 if end_pts is not None: 1080 start_pts = np.stack((start_pts, end_pts), axis=1) 1081 1082 polylns = vtki.new("AppendPolyData") 1083 1084 if not utils.is_ragged(start_pts): 1085 1086 for twopts in start_pts: 1087 line_source = vtki.new("LineSource") 1088 line_source.SetResolution(res) 1089 if len(twopts[0]) == 2: 1090 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 1091 else: 1092 line_source.SetPoint1(twopts[0]) 1093 1094 if scale == 1: 1095 pt2 = twopts[1] 1096 else: 1097 vers = (np.array(twopts[1]) - twopts[0]) * scale 1098 pt2 = np.array(twopts[0]) + vers 1099 1100 if len(pt2) == 2: 1101 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1102 else: 1103 line_source.SetPoint2(pt2) 1104 polylns.AddInputConnection(line_source.GetOutputPort()) 1105 1106 else: 1107 1108 polylns = vtki.new("AppendPolyData") 1109 for t in start_pts: 1110 t = utils.make3d(t) 1111 ppoints = vtki.vtkPoints() # Generate the polyline 1112 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1113 lines = vtki.vtkCellArray() 1114 npt = len(t) 1115 lines.InsertNextCell(npt) 1116 for i in range(npt): 1117 lines.InsertCellPoint(i) 1118 poly = vtki.vtkPolyData() 1119 poly.SetPoints(ppoints) 1120 poly.SetLines(lines) 1121 polylns.AddInputData(poly) 1122 1123 polylns.Update() 1124 1125 super().__init__(polylns.GetOutput(), c, alpha) 1126 self.lw(lw).lighting("off") 1127 if dotted: 1128 self.properties.SetLineStipplePattern(0xF0F0) 1129 self.properties.SetLineStippleRepeatFactor(1) 1130 1131 self.name = "Lines" 1132 1133 1134class Arc(Line): 1135 """ 1136 Build a 2D circular arc between 2 points. 1137 """ 1138 1139 def __init__( 1140 self, 1141 center=None, 1142 point1=None, 1143 point2=None, 1144 normal=None, 1145 angle=None, 1146 invert=False, 1147 res=60, 1148 c="k3", 1149 alpha=1.0, 1150 ) -> None: 1151 """ 1152 Build a 2D circular arc between 2 points. 1153 Two modes are available: 1154 1. [center, point1, point2] are specified 1155 1156 2. [point1, normal, angle] are specified. 1157 1158 In the first case it creates an arc defined by two endpoints and a center. 1159 In the second the arc spans the shortest angular sector defined by 1160 a starting point, a normal and a spanning angle. 1161 if `invert=True`, then the opposite happens. 1162 1163 Example 1: 1164 ```python 1165 from vedo import * 1166 center = [0,1,0] 1167 p1 = [1,2,0.4] 1168 p2 = [0.5,3,-1] 1169 arc = Arc(center, p1, p2).lw(5).c("purple5") 1170 line2 = Line(center, p2) 1171 pts = Points([center, p1,p2], r=9, c='r') 1172 show(pts, line2, arc, f"length={arc.length()}", axes=1).close() 1173 ``` 1174 1175 Example 2: 1176 ```python 1177 from vedo import * 1178 arc = Arc(point1=[0,1,0], normal=[0,0,1], angle=270) 1179 arc.lw(5).c("purple5") 1180 origin = Point([0,0,0], r=9, c='r') 1181 show(origin, arc, arc.labels2d(), axes=1).close() 1182 ``` 1183 """ 1184 ar = vtki.new("ArcSource") 1185 if point2 is not None: 1186 center = utils.make3d(center) 1187 point1 = utils.make3d(point1) 1188 point2 = utils.make3d(point2) 1189 ar.UseNormalAndAngleOff() 1190 ar.SetPoint1(point1-center) 1191 ar.SetPoint2(point2-center) 1192 elif normal is not None and angle and point1 is not None: 1193 normal = utils.make3d(normal) 1194 point1 = utils.make3d(point1) 1195 ar.UseNormalAndAngleOn() 1196 ar.SetAngle(angle) 1197 ar.SetPolarVector(point1) 1198 ar.SetNormal(normal) 1199 self.top = normal 1200 else: 1201 vedo.logger.error("in Arc(), incorrect input combination.") 1202 raise TypeError 1203 ar.SetNegative(invert) 1204 ar.SetResolution(res) 1205 ar.Update() 1206 1207 super().__init__(ar.GetOutput(), c, alpha) 1208 self.lw(2).lighting("off") 1209 if point2 is not None: # nb: not center 1210 self.pos(center) 1211 self.name = "Arc" 1212 1213 1214class Spline(Line): 1215 """ 1216 Find the B-Spline curve through a set of points. This curve does not necessarily 1217 pass exactly through all the input points. Needs to import `scipy`. 1218 """ 1219 1220 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None: 1221 """ 1222 Arguments: 1223 smooth : (float) 1224 smoothing factor. 1225 - 0 = interpolate points exactly [default]. 1226 - 1 = average point positions. 1227 degree : (int) 1228 degree of the spline (between 1 and 5). 1229 easing : (str) 1230 control sensity of points along the spline. 1231 Available options are 1232 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1233 Can be used to create animations (move objects at varying speed). 1234 See e.g.: https://easings.net 1235 res : (int) 1236 number of points on the spline 1237 1238 See also: `CSpline` and `KSpline`. 1239 1240 Examples: 1241 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1242 1243 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1244 """ 1245 from scipy.interpolate import splprep, splev 1246 1247 if isinstance(points, Points): 1248 points = points.coordinates 1249 1250 points = utils.make3d(points) 1251 1252 per = 0 1253 if closed: 1254 points = np.append(points, [points[0]], axis=0) 1255 per = 1 1256 1257 if res is None: 1258 res = len(points) * 10 1259 1260 points = np.array(points, dtype=float) 1261 1262 minx, miny, minz = np.min(points, axis=0) 1263 maxx, maxy, maxz = np.max(points, axis=0) 1264 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1265 smooth *= maxb / 2 # must be in absolute units 1266 1267 x = np.linspace(0.0, 1.0, res) 1268 if easing: 1269 if easing == "InSine": 1270 x = 1.0 - np.cos((x * np.pi) / 2) 1271 elif easing == "OutSine": 1272 x = np.sin((x * np.pi) / 2) 1273 elif easing == "Sine": 1274 x = -(np.cos(np.pi * x) - 1) / 2 1275 elif easing == "InQuad": 1276 x = x * x 1277 elif easing == "OutQuad": 1278 x = 1.0 - (1 - x) * (1 - x) 1279 elif easing == "InCubic": 1280 x = x * x 1281 elif easing == "OutCubic": 1282 x = 1.0 - np.power(1 - x, 3) 1283 elif easing == "InQuart": 1284 x = x * x * x * x 1285 elif easing == "OutQuart": 1286 x = 1.0 - np.power(1 - x, 4) 1287 elif easing == "InCirc": 1288 x = 1.0 - np.sqrt(1 - np.power(x, 2)) 1289 elif easing == "OutCirc": 1290 x = np.sqrt(1.0 - np.power(x - 1, 2)) 1291 else: 1292 vedo.logger.error(f"unknown ease mode {easing}") 1293 1294 # find the knots 1295 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1296 # evaluate spLine, including interpolated points: 1297 xnew, ynew, znew = splev(x, tckp) 1298 1299 super().__init__(np.c_[xnew, ynew, znew], lw=2) 1300 self.name = "Spline" 1301 1302 1303class KSpline(Line): 1304 """ 1305 Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline) 1306 which runs exactly through all the input points. 1307 """ 1308 1309 def __init__(self, points, 1310 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None: 1311 """ 1312 Arguments: 1313 continuity : (float) 1314 changes the sharpness in change between tangents 1315 tension : (float) 1316 changes the length of the tangent vector 1317 bias : (float) 1318 changes the direction of the tangent vector 1319 closed : (bool) 1320 join last to first point to produce a closed curve 1321 res : (int) 1322 approximate resolution of the output line. 1323 Default is 20 times the number of input points. 1324 1325 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1326 1327 Warning: 1328 This class is not necessarily generating the exact number of points 1329 as requested by `res`. Some points may be concident and removed. 1330 1331 See also: `Spline` and `CSpline`. 1332 """ 1333 if isinstance(points, Points): 1334 points = points.coordinates 1335 1336 if not res: 1337 res = len(points) * 20 1338 1339 points = utils.make3d(points).astype(float) 1340 1341 vtkKochanekSpline = vtki.get_class("KochanekSpline") 1342 xspline = vtkKochanekSpline() 1343 yspline = vtkKochanekSpline() 1344 zspline = vtkKochanekSpline() 1345 for s in [xspline, yspline, zspline]: 1346 if bias: 1347 s.SetDefaultBias(bias) 1348 if tension: 1349 s.SetDefaultTension(tension) 1350 if continuity: 1351 s.SetDefaultContinuity(continuity) 1352 s.SetClosed(closed) 1353 1354 lenp = len(points[0]) > 2 1355 1356 for i, p in enumerate(points): 1357 xspline.AddPoint(i, p[0]) 1358 yspline.AddPoint(i, p[1]) 1359 if lenp: 1360 zspline.AddPoint(i, p[2]) 1361 1362 ln = [] 1363 for pos in np.linspace(0, len(points), res): 1364 x = xspline.Evaluate(pos) 1365 y = yspline.Evaluate(pos) 1366 z = 0 1367 if lenp: 1368 z = zspline.Evaluate(pos) 1369 ln.append((x, y, z)) 1370 1371 super().__init__(ln, lw=2) 1372 self.clean() 1373 self.lighting("off") 1374 self.name = "KSpline" 1375 self.base = np.array(points[0], dtype=float) 1376 self.top = np.array(points[-1], dtype=float) 1377 1378 1379class CSpline(Line): 1380 """ 1381 Return a Cardinal spline which runs exactly through all the input points. 1382 """ 1383 1384 def __init__(self, points, closed=False, res=None) -> None: 1385 """ 1386 Arguments: 1387 closed : (bool) 1388 join last to first point to produce a closed curve 1389 res : (int) 1390 approximate resolution of the output line. 1391 Default is 20 times the number of input points. 1392 1393 Warning: 1394 This class is not necessarily generating the exact number of points 1395 as requested by `res`. Some points may be concident and removed. 1396 1397 See also: `Spline` and `KSpline`. 1398 """ 1399 1400 if isinstance(points, Points): 1401 points = points.coordinates 1402 1403 if not res: 1404 res = len(points) * 20 1405 1406 points = utils.make3d(points).astype(float) 1407 1408 vtkCardinalSpline = vtki.get_class("CardinalSpline") 1409 xspline = vtkCardinalSpline() 1410 yspline = vtkCardinalSpline() 1411 zspline = vtkCardinalSpline() 1412 for s in [xspline, yspline, zspline]: 1413 s.SetClosed(closed) 1414 1415 lenp = len(points[0]) > 2 1416 1417 for i, p in enumerate(points): 1418 xspline.AddPoint(i, p[0]) 1419 yspline.AddPoint(i, p[1]) 1420 if lenp: 1421 zspline.AddPoint(i, p[2]) 1422 1423 ln = [] 1424 for pos in np.linspace(0, len(points), res): 1425 x = xspline.Evaluate(pos) 1426 y = yspline.Evaluate(pos) 1427 z = 0 1428 if lenp: 1429 z = zspline.Evaluate(pos) 1430 ln.append((x, y, z)) 1431 1432 super().__init__(ln, lw=2) 1433 self.clean() 1434 self.lighting("off") 1435 self.name = "CSpline" 1436 self.base = points[0] 1437 self.top = points[-1] 1438 1439 1440class Bezier(Line): 1441 """ 1442 Generate the Bezier line that links the first to the last point. 1443 """ 1444 1445 def __init__(self, points, res=None) -> None: 1446 """ 1447 Example: 1448 ```python 1449 from vedo import * 1450 import numpy as np 1451 pts = np.random.randn(25,3) 1452 for i,p in enumerate(pts): 1453 p += [5*i, 15*sin(i/2), i*i*i/200] 1454 show(Points(pts), Bezier(pts), axes=1).close() 1455 ``` 1456 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1457 """ 1458 N = len(points) 1459 if res is None: 1460 res = 10 * N 1461 t = np.linspace(0, 1, num=res) 1462 bcurve = np.zeros((res, len(points[0]))) 1463 1464 def binom(n, k): 1465 b = 1 1466 for t in range(1, min(k, n - k) + 1): 1467 b *= n / t 1468 n -= 1 1469 return b 1470 1471 def bernstein(n, k): 1472 coeff = binom(n, k) 1473 1474 def _bpoly(x): 1475 return coeff * x ** k * (1 - x) ** (n - k) 1476 1477 return _bpoly 1478 1479 for ii in range(N): 1480 b = bernstein(N - 1, ii)(t) 1481 bcurve += np.outer(b, points[ii]) 1482 super().__init__(bcurve, lw=2) 1483 self.name = "BezierLine" 1484 1485 1486class NormalLines(Mesh): 1487 """ 1488 Build an `Glyph` to show the normals at cell centers or at mesh vertices. 1489 1490 Arguments: 1491 ratio : (int) 1492 show 1 normal every `ratio` cells. 1493 on : (str) 1494 either "cells" or "points". 1495 scale : (float) 1496 scale factor to control size. 1497 """ 1498 1499 def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None: 1500 1501 poly = msh.clone().dataset 1502 1503 if "cell" in on: 1504 centers = vtki.new("CellCenters") 1505 centers.SetInputData(poly) 1506 centers.Update() 1507 poly = centers.GetOutput() 1508 1509 mask_pts = vtki.new("MaskPoints") 1510 mask_pts.SetInputData(poly) 1511 mask_pts.SetOnRatio(ratio) 1512 mask_pts.RandomModeOff() 1513 mask_pts.Update() 1514 1515 ln = vtki.new("LineSource") 1516 ln.SetPoint1(0, 0, 0) 1517 ln.SetPoint2(1, 0, 0) 1518 ln.Update() 1519 glyph = vtki.vtkGlyph3D() 1520 glyph.SetSourceData(ln.GetOutput()) 1521 glyph.SetInputData(mask_pts.GetOutput()) 1522 glyph.SetVectorModeToUseNormal() 1523 1524 b = poly.GetBounds() 1525 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1526 glyph.SetScaleFactor(f) 1527 glyph.OrientOn() 1528 glyph.Update() 1529 1530 super().__init__(glyph.GetOutput()) 1531 1532 self.actor.PickableOff() 1533 prop = vtki.vtkProperty() 1534 prop.DeepCopy(msh.properties) 1535 self.actor.SetProperty(prop) 1536 self.properties = prop 1537 self.properties.LightingOff() 1538 self.mapper.ScalarVisibilityOff() 1539 self.name = "NormalLines" 1540 1541 1542class Tube(Mesh): 1543 """ 1544 Build a tube along the line defined by a set of points. 1545 """ 1546 1547 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None: 1548 """ 1549 Arguments: 1550 r : (float, list) 1551 constant radius or list of radii. 1552 res : (int) 1553 resolution, number of the sides of the tube 1554 c : (color) 1555 constant color or list of colors for each point. 1556 1557 Example: 1558 Create a tube along a line, with data associated to each point: 1559 1560 ```python 1561 from vedo import * 1562 line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5) 1563 scalars = np.array([0, 1, 2, 3]) 1564 line.pointdata["myscalars"] = scalars 1565 tube = Tube(line, r=0.1).lw(1) 1566 tube.cmap('viridis', "myscalars").add_scalarbar3d() 1567 show(line, tube, axes=1).close() 1568 ``` 1569 1570 Examples: 1571 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1572 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1573 1574 ![](https://vedo.embl.es/images/basic/tube.png) 1575 """ 1576 if utils.is_sequence(points): 1577 vpoints = vtki.vtkPoints() 1578 idx = len(points) 1579 for p in points: 1580 vpoints.InsertNextPoint(p) 1581 line = vtki.new("PolyLine") 1582 line.GetPointIds().SetNumberOfIds(idx) 1583 for i in range(idx): 1584 line.GetPointIds().SetId(i, i) 1585 lines = vtki.vtkCellArray() 1586 lines.InsertNextCell(line) 1587 polyln = vtki.vtkPolyData() 1588 polyln.SetPoints(vpoints) 1589 polyln.SetLines(lines) 1590 self.base = np.asarray(points[0], dtype=float) 1591 self.top = np.asarray(points[-1], dtype=float) 1592 1593 elif isinstance(points, Mesh): 1594 polyln = points.dataset 1595 n = polyln.GetNumberOfPoints() 1596 self.base = np.array(polyln.GetPoint(0)) 1597 self.top = np.array(polyln.GetPoint(n - 1)) 1598 1599 # from vtkmodules.vtkFiltersCore import vtkTubeBender 1600 # bender = vtkTubeBender() 1601 # bender.SetInputData(polyln) 1602 # bender.SetRadius(r) 1603 # bender.Update() 1604 # polyln = bender.GetOutput() 1605 1606 tuf = vtki.new("TubeFilter") 1607 tuf.SetCapping(cap) 1608 tuf.SetNumberOfSides(res) 1609 tuf.SetInputData(polyln) 1610 if utils.is_sequence(r): 1611 arr = utils.numpy2vtk(r, dtype=float) 1612 arr.SetName("TubeRadius") 1613 polyln.GetPointData().AddArray(arr) 1614 polyln.GetPointData().SetActiveScalars("TubeRadius") 1615 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1616 else: 1617 tuf.SetRadius(r) 1618 1619 usingColScals = False 1620 if utils.is_sequence(c): 1621 usingColScals = True 1622 cc = vtki.vtkUnsignedCharArray() 1623 cc.SetName("TubeColors") 1624 cc.SetNumberOfComponents(3) 1625 cc.SetNumberOfTuples(len(c)) 1626 for i, ic in enumerate(c): 1627 r, g, b = get_color(ic) 1628 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1629 polyln.GetPointData().AddArray(cc) 1630 c = None 1631 tuf.Update() 1632 1633 super().__init__(tuf.GetOutput(), c, alpha) 1634 self.phong() 1635 if usingColScals: 1636 self.mapper.SetScalarModeToUsePointFieldData() 1637 self.mapper.ScalarVisibilityOn() 1638 self.mapper.SelectColorArray("TubeColors") 1639 self.mapper.Modified() 1640 self.name = "Tube" 1641 1642 1643def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0) -> Union["Mesh", None]: 1644 """ 1645 Create a tube with a thickness along a line of points. 1646 1647 Example: 1648 ```python 1649 from vedo import * 1650 pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)] 1651 vline = Line(pts, lw=5, c='red5') 1652 thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1) 1653 show(vline, thick_tube, axes=1).close() 1654 ``` 1655 ![](https://vedo.embl.es/images/feats/thick_tube.png) 1656 """ 1657 1658 def make_cap(t1, t2): 1659 newpoints = t1.coordinates.tolist() + t2.coordinates.tolist() 1660 newfaces = [] 1661 for i in range(n - 1): 1662 newfaces.append([i, i + 1, i + n]) 1663 newfaces.append([i + n, i + 1, i + n + 1]) 1664 newfaces.append([2 * n - 1, 0, n]) 1665 newfaces.append([2 * n - 1, n - 1, 0]) 1666 capm = utils.buildPolyData(newpoints, newfaces) 1667 return capm 1668 1669 assert r1 < r2 1670 1671 t1 = Tube(pts, r=r1, cap=False, res=res) 1672 t2 = Tube(pts, r=r2, cap=False, res=res) 1673 1674 tc1a, tc1b = t1.boundaries().split() 1675 tc2a, tc2b = t2.boundaries().split() 1676 n = tc1b.npoints 1677 1678 tc1b.join(reset=True).clean() # needed because indices are flipped 1679 tc2b.join(reset=True).clean() 1680 1681 capa = make_cap(tc1a, tc2a) 1682 capb = make_cap(tc1b, tc2b) 1683 1684 thick_tube = merge(t1, t2, capa, capb) 1685 if thick_tube: 1686 thick_tube.c(c).alpha(alpha) 1687 thick_tube.base = t1.base 1688 thick_tube.top = t1.top 1689 thick_tube.name = "ThickTube" 1690 return thick_tube 1691 return None 1692 1693 1694class Tubes(Mesh): 1695 """ 1696 Build tubes around a `Lines` object. 1697 """ 1698 def __init__( 1699 self, 1700 lines, 1701 r=1, 1702 vary_radius_by_scalar=False, 1703 vary_radius_by_vector=False, 1704 vary_radius_by_vector_norm=False, 1705 vary_radius_by_absolute_scalar=False, 1706 max_radius_factor=100, 1707 cap=True, 1708 res=12 1709 ) -> None: 1710 """ 1711 Wrap tubes around the input `Lines` object. 1712 1713 Arguments: 1714 lines : (Lines) 1715 input Lines object. 1716 r : (float) 1717 constant radius 1718 vary_radius_by_scalar : (bool) 1719 use scalar array to control radius 1720 vary_radius_by_vector : (bool) 1721 use vector array to control radius 1722 vary_radius_by_vector_norm : (bool) 1723 use vector norm to control radius 1724 vary_radius_by_absolute_scalar : (bool) 1725 use absolute scalar value to control radius 1726 max_radius_factor : (float) 1727 max tube radius as a multiple of the min radius 1728 cap : (bool) 1729 capping of the tube 1730 res : (int) 1731 resolution, number of the sides of the tube 1732 c : (color) 1733 constant color or list of colors for each point. 1734 1735 Examples: 1736 - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py) 1737 """ 1738 plines = lines.dataset 1739 if plines.GetNumberOfLines() == 0: 1740 vedo.logger.warning("Tubes(): input Lines is empty.") 1741 1742 tuf = vtki.new("TubeFilter") 1743 if vary_radius_by_scalar: 1744 tuf.SetVaryRadiusToVaryRadiusByScalar() 1745 elif vary_radius_by_vector: 1746 tuf.SetVaryRadiusToVaryRadiusByVector() 1747 elif vary_radius_by_vector_norm: 1748 tuf.SetVaryRadiusToVaryRadiusByVectorNorm() 1749 elif vary_radius_by_absolute_scalar: 1750 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1751 tuf.SetRadius(r) 1752 tuf.SetCapping(cap) 1753 tuf.SetGenerateTCoords(0) 1754 tuf.SetSidesShareVertices(1) 1755 tuf.SetRadiusFactor(max_radius_factor) 1756 tuf.SetNumberOfSides(res) 1757 tuf.SetInputData(plines) 1758 tuf.Update() 1759 1760 super().__init__(tuf.GetOutput()) 1761 self.name = "Tubes" 1762 1763 1764class Ribbon(Mesh): 1765 """ 1766 Connect two lines to generate the surface inbetween. 1767 Set the mode by which to create the ruled surface. 1768 1769 It also works with a single line in input. In this case the ribbon 1770 is formed by following the local plane of the line in space. 1771 """ 1772 1773 def __init__( 1774 self, 1775 line1, 1776 line2=None, 1777 mode=0, 1778 closed=False, 1779 width=None, 1780 res=(200, 5), 1781 c="indigo3", 1782 alpha=1.0, 1783 ) -> None: 1784 """ 1785 Arguments: 1786 mode : (int) 1787 If mode=0, resample evenly the input lines (based on length) 1788 and generates triangle strips. 1789 1790 If mode=1, use the existing points and walks around the 1791 polyline using existing points. 1792 1793 closed : (bool) 1794 if True, join the last point with the first to form a closed surface 1795 1796 res : (list) 1797 ribbon resolutions along the line and perpendicularly to it. 1798 1799 Examples: 1800 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1801 1802 ![](https://vedo.embl.es/images/basic/ribbon.png) 1803 """ 1804 1805 if isinstance(line1, Points): 1806 line1 = line1.coordinates 1807 1808 if isinstance(line2, Points): 1809 line2 = line2.coordinates 1810 1811 elif line2 is None: 1812 ############################################# 1813 ribbon_filter = vtki.new("RibbonFilter") 1814 aline = Line(line1) 1815 ribbon_filter.SetInputData(aline.dataset) 1816 if width is None: 1817 width = aline.diagonal_size() / 20.0 1818 ribbon_filter.SetWidth(width) 1819 ribbon_filter.Update() 1820 # convert triangle strips to polygons 1821 tris = vtki.new("TriangleFilter") 1822 tris.SetInputData(ribbon_filter.GetOutput()) 1823 tris.Update() 1824 1825 super().__init__(tris.GetOutput(), c, alpha) 1826 self.name = "Ribbon" 1827 ############################################## 1828 return ###################################### 1829 ############################################## 1830 1831 line1 = np.asarray(line1) 1832 line2 = np.asarray(line2) 1833 1834 if closed: 1835 line1 = line1.tolist() 1836 line1 += [line1[0]] 1837 line2 = line2.tolist() 1838 line2 += [line2[0]] 1839 line1 = np.array(line1) 1840 line2 = np.array(line2) 1841 1842 if len(line1[0]) == 2: 1843 line1 = np.c_[line1, np.zeros(len(line1))] 1844 if len(line2[0]) == 2: 1845 line2 = np.c_[line2, np.zeros(len(line2))] 1846 1847 ppoints1 = vtki.vtkPoints() # Generate the polyline1 1848 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1849 lines1 = vtki.vtkCellArray() 1850 lines1.InsertNextCell(len(line1)) 1851 for i in range(len(line1)): 1852 lines1.InsertCellPoint(i) 1853 poly1 = vtki.vtkPolyData() 1854 poly1.SetPoints(ppoints1) 1855 poly1.SetLines(lines1) 1856 1857 ppoints2 = vtki.vtkPoints() # Generate the polyline2 1858 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1859 lines2 = vtki.vtkCellArray() 1860 lines2.InsertNextCell(len(line2)) 1861 for i in range(len(line2)): 1862 lines2.InsertCellPoint(i) 1863 poly2 = vtki.vtkPolyData() 1864 poly2.SetPoints(ppoints2) 1865 poly2.SetLines(lines2) 1866 1867 # build the lines 1868 lines1 = vtki.vtkCellArray() 1869 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1870 for i in range(poly1.GetNumberOfPoints()): 1871 lines1.InsertCellPoint(i) 1872 1873 polygon1 = vtki.vtkPolyData() 1874 polygon1.SetPoints(ppoints1) 1875 polygon1.SetLines(lines1) 1876 1877 lines2 = vtki.vtkCellArray() 1878 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1879 for i in range(poly2.GetNumberOfPoints()): 1880 lines2.InsertCellPoint(i) 1881 1882 polygon2 = vtki.vtkPolyData() 1883 polygon2.SetPoints(ppoints2) 1884 polygon2.SetLines(lines2) 1885 1886 merged_pd = vtki.new("AppendPolyData") 1887 merged_pd.AddInputData(polygon1) 1888 merged_pd.AddInputData(polygon2) 1889 merged_pd.Update() 1890 1891 rsf = vtki.new("RuledSurfaceFilter") 1892 rsf.CloseSurfaceOff() 1893 rsf.SetRuledMode(mode) 1894 rsf.SetResolution(res[0], res[1]) 1895 rsf.SetInputData(merged_pd.GetOutput()) 1896 rsf.Update() 1897 # convert triangle strips to polygons 1898 tris = vtki.new("TriangleFilter") 1899 tris.SetInputData(rsf.GetOutput()) 1900 tris.Update() 1901 out = tris.GetOutput() 1902 1903 super().__init__(out, c, alpha) 1904 1905 self.name = "Ribbon" 1906 1907 1908class Arrow(Mesh): 1909 """ 1910 Build a 3D arrow from `start_pt` to `end_pt` of section size `s`, 1911 expressed as the fraction of the window size. 1912 """ 1913 1914 def __init__( 1915 self, 1916 start_pt=(0, 0, 0), 1917 end_pt=(1, 0, 0), 1918 s=None, 1919 shaft_radius=None, 1920 head_radius=None, 1921 head_length=None, 1922 res=12, 1923 c="r4", 1924 alpha=1.0, 1925 ) -> None: 1926 """ 1927 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1928 from white to red. 1929 1930 .. note:: If `s=None` the arrow is scaled proportionally to its length 1931 1932 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1933 """ 1934 # in case user is passing meshs 1935 if isinstance(start_pt, vtki.vtkActor): 1936 start_pt = start_pt.GetPosition() 1937 if isinstance(end_pt, vtki.vtkActor): 1938 end_pt = end_pt.GetPosition() 1939 1940 axis = np.asarray(end_pt) - np.asarray(start_pt) 1941 length = float(np.linalg.norm(axis)) 1942 if length: 1943 axis = axis / length 1944 if len(axis) < 3: # its 2d 1945 theta = np.pi / 2 1946 start_pt = [start_pt[0], start_pt[1], 0.0] 1947 end_pt = [end_pt[0], end_pt[1], 0.0] 1948 else: 1949 theta = np.arccos(axis[2]) 1950 phi = np.arctan2(axis[1], axis[0]) 1951 self.source = vtki.new("ArrowSource") 1952 self.source.SetShaftResolution(res) 1953 self.source.SetTipResolution(res) 1954 1955 if s: 1956 sz = 0.02 1957 self.source.SetTipRadius(sz) 1958 self.source.SetShaftRadius(sz / 1.75) 1959 self.source.SetTipLength(sz * 15) 1960 1961 if head_length: 1962 self.source.SetTipLength(head_length) 1963 if head_radius: 1964 self.source.SetTipRadius(head_radius) 1965 if shaft_radius: 1966 self.source.SetShaftRadius(shaft_radius) 1967 1968 self.source.Update() 1969 1970 t = vtki.vtkTransform() 1971 t.Translate(start_pt) 1972 t.RotateZ(np.rad2deg(phi)) 1973 t.RotateY(np.rad2deg(theta)) 1974 t.RotateY(-90) # put it along Z 1975 if s: 1976 sz = 800 * s 1977 t.Scale(length, sz, sz) 1978 else: 1979 t.Scale(length, length, length) 1980 1981 tf = vtki.new("TransformPolyDataFilter") 1982 tf.SetInputData(self.source.GetOutput()) 1983 tf.SetTransform(t) 1984 tf.Update() 1985 1986 super().__init__(tf.GetOutput(), c, alpha) 1987 1988 self.transform = LinearTransform().translate(start_pt) 1989 1990 self.phong().lighting("plastic") 1991 self.actor.PickableOff() 1992 self.actor.DragableOff() 1993 self.base = np.array(start_pt, dtype=float) # used by pyplot 1994 self.top = np.array(end_pt, dtype=float) # used by pyplot 1995 self.top_index = self.source.GetTipResolution() * 4 1996 self.fill = True # used by pyplot.__iadd__() 1997 self.s = s if s is not None else 1 # used by pyplot.__iadd__() 1998 self.name = "Arrow" 1999 2000 def top_point(self): 2001 """Return the current coordinates of the tip of the Arrow.""" 2002 return self.transform.transform_point(self.top) 2003 2004 def base_point(self): 2005 """Return the current coordinates of the base of the Arrow.""" 2006 return self.transform.transform_point(self.base) 2007 2008class Arrows(Glyph): 2009 """ 2010 Build arrows between two lists of points. 2011 """ 2012 2013 def __init__( 2014 self, 2015 start_pts, 2016 end_pts=None, 2017 s=None, 2018 shaft_radius=None, 2019 head_radius=None, 2020 head_length=None, 2021 thickness=1.0, 2022 res=6, 2023 c='k3', 2024 alpha=1.0, 2025 ) -> None: 2026 """ 2027 Build arrows between two lists of points `start_pts` and `end_pts`. 2028 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2029 2030 Color can be specified as a colormap which maps the size of the arrows. 2031 2032 Arguments: 2033 s : (float) 2034 fix aspect-ratio of the arrow and scale its cross section 2035 c : (color) 2036 color or color map name 2037 alpha : (float) 2038 set object opacity 2039 res : (int) 2040 set arrow resolution 2041 2042 Examples: 2043 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 2044 2045 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 2046 """ 2047 if isinstance(start_pts, Points): 2048 start_pts = start_pts.coordinates 2049 if isinstance(end_pts, Points): 2050 end_pts = end_pts.coordinates 2051 2052 start_pts = np.asarray(start_pts) 2053 if end_pts is None: 2054 strt = start_pts[:, 0] 2055 end_pts = start_pts[:, 1] 2056 start_pts = strt 2057 else: 2058 end_pts = np.asarray(end_pts) 2059 2060 start_pts = utils.make3d(start_pts) 2061 end_pts = utils.make3d(end_pts) 2062 2063 arr = vtki.new("ArrowSource") 2064 arr.SetShaftResolution(res) 2065 arr.SetTipResolution(res) 2066 2067 if s: 2068 sz = 0.02 * s 2069 arr.SetTipRadius(sz * 2) 2070 arr.SetShaftRadius(sz * thickness) 2071 arr.SetTipLength(sz * 10) 2072 2073 if head_radius: 2074 arr.SetTipRadius(head_radius) 2075 if shaft_radius: 2076 arr.SetShaftRadius(shaft_radius) 2077 if head_length: 2078 arr.SetTipLength(head_length) 2079 2080 arr.Update() 2081 out = arr.GetOutput() 2082 2083 orients = end_pts - start_pts 2084 2085 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2086 2087 super().__init__( 2088 start_pts, 2089 out, 2090 orientation_array=orients, 2091 scale_by_vector_size=True, 2092 color_by_vector_size=color_by_vector_size, 2093 c=c, 2094 alpha=alpha, 2095 ) 2096 self.lighting("off") 2097 if color_by_vector_size: 2098 vals = np.linalg.norm(orients, axis=1) 2099 self.mapper.SetScalarRange(vals.min(), vals.max()) 2100 else: 2101 self.c(c) 2102 self.name = "Arrows" 2103 2104 2105class Arrow2D(Mesh): 2106 """ 2107 Build a 2D arrow. 2108 """ 2109 2110 def __init__( 2111 self, 2112 start_pt=(0, 0, 0), 2113 end_pt=(1, 0, 0), 2114 s=1, 2115 rotation=0.0, 2116 shaft_length=0.85, 2117 shaft_width=0.055, 2118 head_length=0.175, 2119 head_width=0.175, 2120 fill=True, 2121 c="red4", 2122 alpha=1.0, 2123 ) -> None: 2124 """ 2125 Build a 2D arrow from `start_pt` to `end_pt`. 2126 2127 Arguments: 2128 s : (float) 2129 a global multiplicative convenience factor controlling the arrow size 2130 shaft_length : (float) 2131 fractional shaft length 2132 shaft_width : (float) 2133 fractional shaft width 2134 head_length : (float) 2135 fractional head length 2136 head_width : (float) 2137 fractional head width 2138 fill : (bool) 2139 if False only generate the outline 2140 """ 2141 self.fill = fill ## needed by pyplot.__iadd() 2142 self.s = s ## needed by pyplot.__iadd() 2143 2144 if s != 1: 2145 shaft_width *= s 2146 head_width *= np.sqrt(s) 2147 2148 # in case user is passing meshs 2149 if isinstance(start_pt, vtki.vtkActor): 2150 start_pt = start_pt.GetPosition() 2151 if isinstance(end_pt, vtki.vtkActor): 2152 end_pt = end_pt.GetPosition() 2153 if len(start_pt) == 2: 2154 start_pt = [start_pt[0], start_pt[1], 0] 2155 if len(end_pt) == 2: 2156 end_pt = [end_pt[0], end_pt[1], 0] 2157 2158 headBase = 1 - head_length 2159 head_width = max(head_width, shaft_width) 2160 if head_length is None or headBase > shaft_length: 2161 headBase = shaft_length 2162 2163 verts = [] 2164 verts.append([0, -shaft_width / 2, 0]) 2165 verts.append([shaft_length, -shaft_width / 2, 0]) 2166 verts.append([headBase, -head_width / 2, 0]) 2167 verts.append([1, 0, 0]) 2168 verts.append([headBase, head_width / 2, 0]) 2169 verts.append([shaft_length, shaft_width / 2, 0]) 2170 verts.append([0, shaft_width / 2, 0]) 2171 if fill: 2172 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2173 poly = utils.buildPolyData(verts, faces) 2174 else: 2175 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2176 poly = utils.buildPolyData(verts, [], lines=lines) 2177 2178 axis = np.array(end_pt) - np.array(start_pt) 2179 length = float(np.linalg.norm(axis)) 2180 if length: 2181 axis = axis / length 2182 theta = 0 2183 if len(axis) > 2: 2184 theta = np.arccos(axis[2]) 2185 phi = np.arctan2(axis[1], axis[0]) 2186 2187 t = vtki.vtkTransform() 2188 t.Translate(start_pt) 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 if rotation: 2195 t.RotateX(rotation) 2196 t.Scale(length, length, length) 2197 2198 tf = vtki.new("TransformPolyDataFilter") 2199 tf.SetInputData(poly) 2200 tf.SetTransform(t) 2201 tf.Update() 2202 2203 super().__init__(tf.GetOutput(), c, alpha) 2204 2205 self.transform = LinearTransform().translate(start_pt) 2206 2207 self.lighting("off") 2208 self.actor.DragableOff() 2209 self.actor.PickableOff() 2210 self.base = np.array(start_pt, dtype=float) # used by pyplot 2211 self.top = np.array(end_pt, dtype=float) # used by pyplot 2212 self.name = "Arrow2D" 2213 2214 2215class Arrows2D(Glyph): 2216 """ 2217 Build 2D arrows between two lists of points. 2218 """ 2219 2220 def __init__( 2221 self, 2222 start_pts, 2223 end_pts=None, 2224 s=1.0, 2225 rotation=0.0, 2226 shaft_length=0.8, 2227 shaft_width=0.05, 2228 head_length=0.225, 2229 head_width=0.175, 2230 fill=True, 2231 c=None, 2232 alpha=1.0, 2233 ) -> None: 2234 """ 2235 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2236 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2237 2238 Color can be specified as a colormap which maps the size of the arrows. 2239 2240 Arguments: 2241 shaft_length : (float) 2242 fractional shaft length 2243 shaft_width : (float) 2244 fractional shaft width 2245 head_length : (float) 2246 fractional head length 2247 head_width : (float) 2248 fractional head width 2249 fill : (bool) 2250 if False only generate the outline 2251 """ 2252 if isinstance(start_pts, Points): 2253 start_pts = start_pts.coordinates 2254 if isinstance(end_pts, Points): 2255 end_pts = end_pts.coordinates 2256 2257 start_pts = np.asarray(start_pts, dtype=float) 2258 if end_pts is None: 2259 strt = start_pts[:, 0] 2260 end_pts = start_pts[:, 1] 2261 start_pts = strt 2262 else: 2263 end_pts = np.asarray(end_pts, dtype=float) 2264 2265 if head_length is None: 2266 head_length = 1 - shaft_length 2267 2268 arr = Arrow2D( 2269 (0, 0, 0), 2270 (1, 0, 0), 2271 s=s, 2272 rotation=rotation, 2273 shaft_length=shaft_length, 2274 shaft_width=shaft_width, 2275 head_length=head_length, 2276 head_width=head_width, 2277 fill=fill, 2278 ) 2279 2280 orients = end_pts - start_pts 2281 orients = utils.make3d(orients) 2282 2283 pts = Points(start_pts) 2284 super().__init__( 2285 pts, 2286 arr, 2287 orientation_array=orients, 2288 scale_by_vector_size=True, 2289 c=c, 2290 alpha=alpha, 2291 ) 2292 self.flat().lighting("off").pickable(False) 2293 if c is not None: 2294 self.color(c) 2295 self.name = "Arrows2D" 2296 2297 2298class FlatArrow(Ribbon): 2299 """ 2300 Build a 2D arrow in 3D space by joining two close lines. 2301 """ 2302 2303 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2304 """ 2305 Build a 2D arrow in 3D space by joining two close lines. 2306 2307 Examples: 2308 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2309 2310 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2311 """ 2312 if isinstance(line1, Points): 2313 line1 = line1.coordinates 2314 if isinstance(line2, Points): 2315 line2 = line2.coordinates 2316 2317 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2318 2319 v = (sm1 - sm2) / 3 * tip_width 2320 p1 = sm1 + v 2321 p2 = sm2 - v 2322 pm1 = (sm1 + sm2) / 2 2323 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2324 pm12 = pm1 - pm2 2325 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2326 2327 line1.append(p1) 2328 line1.append(tip) 2329 line2.append(p2) 2330 line2.append(tip) 2331 resm = max(100, len(line1)) 2332 2333 super().__init__(line1, line2, res=(resm, 1)) 2334 self.phong().lighting("off") 2335 self.actor.PickableOff() 2336 self.actor.DragableOff() 2337 self.name = "FlatArrow" 2338 2339 2340class Triangle(Mesh): 2341 """Create a triangle from 3 points in space.""" 2342 2343 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2344 """Create a triangle from 3 points in space.""" 2345 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2346 self.properties.LightingOff() 2347 self.name = "Triangle" 2348 2349 2350class Polygon(Mesh): 2351 """ 2352 Build a polygon in the `xy` plane. 2353 """ 2354 2355 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2356 """ 2357 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2358 2359 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2360 """ 2361 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2362 pts = pol2cart(np.ones_like(t) * r, t).T 2363 faces = [list(range(nsides))] 2364 # do not use: vtkRegularPolygonSource 2365 super().__init__([pts, faces], c, alpha) 2366 if len(pos) == 2: 2367 pos = (pos[0], pos[1], 0) 2368 self.pos(pos) 2369 self.properties.LightingOff() 2370 self.name = "Polygon " + str(nsides) 2371 2372 2373class Circle(Polygon): 2374 """ 2375 Build a Circle of radius `r`. 2376 """ 2377 2378 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2379 """ 2380 Build a Circle of radius `r`. 2381 """ 2382 super().__init__(pos, nsides=res, r=r) 2383 2384 self.nr_of_points = 0 2385 self.va = 0 2386 self.vb = 0 2387 self.axis1: List[float] = [] 2388 self.axis2: List[float] = [] 2389 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2390 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2391 self.alpha(alpha).c(c) 2392 self.name = "Circle" 2393 2394 def acircularity(self) -> float: 2395 """ 2396 Return a measure of how different an ellipse is from a circle. 2397 Values close to zero correspond to a circular object. 2398 """ 2399 a, b = self.va, self.vb 2400 value = 0.0 2401 if a+b: 2402 value = ((a-b)/(a+b))**2 2403 return value 2404 2405class GeoCircle(Polygon): 2406 """ 2407 Build a Circle of radius `r`. 2408 """ 2409 2410 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2411 """ 2412 Build a Circle of radius `r` as projected on a geographic map. 2413 Circles near the poles will look very squashed. 2414 2415 See example: 2416 ```bash 2417 vedo -r earthquake 2418 ``` 2419 """ 2420 coords = [] 2421 sinr, cosr = np.sin(r), np.cos(r) 2422 sinlat, coslat = np.sin(lat), np.cos(lat) 2423 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2424 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2425 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2426 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2427 2428 super().__init__(nsides=res, c=c, alpha=alpha) 2429 self.coordinates = coords # warp polygon points to match geo projection 2430 self.name = "Circle" 2431 2432 2433class Star(Mesh): 2434 """ 2435 Build a 2D star shape. 2436 """ 2437 2438 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2439 """ 2440 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2441 2442 If line is True then only build the outer line (no internal surface meshing). 2443 2444 Example: 2445 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2446 2447 ![](https://vedo.embl.es/images/basic/extrude.png) 2448 """ 2449 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2450 x, y = pol2cart(np.ones_like(t) * r2, t) 2451 pts = np.c_[x, y, np.zeros_like(x)] 2452 2453 apts = [] 2454 for i, p in enumerate(pts): 2455 apts.append(p) 2456 if i + 1 < n: 2457 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2458 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2459 2460 if line: 2461 apts.append(pts[0]) 2462 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2463 super().__init__(poly, c, alpha) 2464 self.lw(2) 2465 else: 2466 apts.append((0, 0, 0)) 2467 cells = [] 2468 for i in range(2 * n - 1): 2469 cell = [2 * n, i, i + 1] 2470 cells.append(cell) 2471 cells.append([2 * n, i + 1, 0]) 2472 super().__init__([apts, cells], c, alpha) 2473 2474 if len(pos) == 2: 2475 pos = (pos[0], pos[1], 0) 2476 2477 self.properties.LightingOff() 2478 self.name = "Star" 2479 2480 2481class Disc(Mesh): 2482 """ 2483 Build a 2D disc. 2484 """ 2485 2486 def __init__( 2487 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2488 ) -> None: 2489 """ 2490 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2491 2492 Set `res` as the resolution in R and Phi (can be a list). 2493 2494 Use `angle_range` to create a disc sector between the 2 specified angles. 2495 2496 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2497 """ 2498 if utils.is_sequence(res): 2499 res_r, res_phi = res 2500 else: 2501 res_r, res_phi = res, 12 * res 2502 2503 if len(angle_range) == 0: 2504 ps = vtki.new("DiskSource") 2505 else: 2506 ps = vtki.new("SectorSource") 2507 ps.SetStartAngle(angle_range[0]) 2508 ps.SetEndAngle(angle_range[1]) 2509 2510 ps.SetInnerRadius(r1) 2511 ps.SetOuterRadius(r2) 2512 ps.SetRadialResolution(res_r) 2513 ps.SetCircumferentialResolution(res_phi) 2514 ps.Update() 2515 super().__init__(ps.GetOutput(), c, alpha) 2516 self.flat() 2517 self.pos(utils.make3d(pos)) 2518 self.name = "Disc" 2519 2520class IcoSphere(Mesh): 2521 """ 2522 Create a sphere made of a uniform triangle mesh. 2523 """ 2524 2525 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2526 """ 2527 Create a sphere made of a uniform triangle mesh 2528 (from recursive subdivision of an icosahedron). 2529 2530 Example: 2531 ```python 2532 from vedo import * 2533 icos = IcoSphere(subdivisions=3) 2534 icos.compute_quality().cmap('coolwarm') 2535 icos.show(axes=1).close() 2536 ``` 2537 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2538 """ 2539 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2540 2541 t = (1.0 + np.sqrt(5.0)) / 2.0 2542 points = np.array( 2543 [ 2544 [-1, t, 0], 2545 [1, t, 0], 2546 [-1, -t, 0], 2547 [1, -t, 0], 2548 [0, -1, t], 2549 [0, 1, t], 2550 [0, -1, -t], 2551 [0, 1, -t], 2552 [t, 0, -1], 2553 [t, 0, 1], 2554 [-t, 0, -1], 2555 [-t, 0, 1], 2556 ] 2557 ) 2558 faces = [ 2559 [0, 11, 5], 2560 [0, 5, 1], 2561 [0, 1, 7], 2562 [0, 7, 10], 2563 [0, 10, 11], 2564 [1, 5, 9], 2565 [5, 11, 4], 2566 [11, 10, 2], 2567 [10, 7, 6], 2568 [7, 1, 8], 2569 [3, 9, 4], 2570 [3, 4, 2], 2571 [3, 2, 6], 2572 [3, 6, 8], 2573 [3, 8, 9], 2574 [4, 9, 5], 2575 [2, 4, 11], 2576 [6, 2, 10], 2577 [8, 6, 7], 2578 [9, 8, 1], 2579 ] 2580 super().__init__([points * r, faces], c=c, alpha=alpha) 2581 2582 for _ in range(subdivisions): 2583 self.subdivide(method=1) 2584 pts = utils.versor(self.coordinates) * r 2585 self.coordinates = pts 2586 2587 self.pos(pos) 2588 self.name = "IcoSphere" 2589 2590 2591class Sphere(Mesh): 2592 """ 2593 Build a sphere. 2594 """ 2595 2596 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2597 """ 2598 Build a sphere at position `pos` of radius `r`. 2599 2600 Arguments: 2601 r : (float) 2602 sphere radius 2603 res : (int, list) 2604 resolution in phi, resolution in theta is by default `2*res` 2605 quads : (bool) 2606 sphere mesh will be made of quads instead of triangles 2607 2608 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2609 """ 2610 if len(pos) == 2: 2611 pos = np.asarray([pos[0], pos[1], 0]) 2612 2613 self.radius = r # used by fitSphere 2614 self.center = pos 2615 self.residue = 0 2616 2617 if quads: 2618 res = max(res, 4) 2619 img = vtki.vtkImageData() 2620 img.SetDimensions(res - 1, res - 1, res - 1) 2621 rs = 1.0 / (res - 2) 2622 img.SetSpacing(rs, rs, rs) 2623 gf = vtki.new("GeometryFilter") 2624 gf.SetInputData(img) 2625 gf.Update() 2626 super().__init__(gf.GetOutput(), c, alpha) 2627 self.lw(0.1) 2628 2629 cgpts = self.coordinates - (0.5, 0.5, 0.5) 2630 2631 x, y, z = cgpts.T 2632 x = x * (1 + x * x) / 2 2633 y = y * (1 + y * y) / 2 2634 z = z * (1 + z * z) / 2 2635 _, theta, phi = cart2spher(x, y, z) 2636 2637 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2638 self.coordinates = pts 2639 2640 else: 2641 if utils.is_sequence(res): 2642 res_t, res_phi = res 2643 else: 2644 res_t, res_phi = 2 * res, res 2645 2646 ss = vtki.new("SphereSource") 2647 ss.SetRadius(r) 2648 ss.SetThetaResolution(res_t) 2649 ss.SetPhiResolution(res_phi) 2650 ss.Update() 2651 2652 super().__init__(ss.GetOutput(), c, alpha) 2653 2654 self.phong() 2655 self.pos(pos) 2656 self.name = "Sphere" 2657 2658 2659class Spheres(Mesh): 2660 """ 2661 Build a large set of spheres. 2662 """ 2663 2664 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2665 """ 2666 Build a (possibly large) set of spheres at `centers` of radius `r`. 2667 2668 Either `c` or `r` can be a list of RGB colors or radii. 2669 2670 Examples: 2671 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2672 2673 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2674 """ 2675 2676 if isinstance(centers, Points): 2677 centers = centers.coordinates 2678 centers = np.asarray(centers, dtype=float) 2679 base = centers[0] 2680 2681 cisseq = False 2682 if utils.is_sequence(c): 2683 cisseq = True 2684 2685 if cisseq: 2686 if len(centers) != len(c): 2687 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2688 raise RuntimeError() 2689 2690 risseq = False 2691 if utils.is_sequence(r): 2692 risseq = True 2693 2694 if risseq: 2695 if len(centers) != len(r): 2696 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2697 raise RuntimeError() 2698 if cisseq and risseq: 2699 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2700 raise RuntimeError() 2701 2702 src = vtki.new("SphereSource") 2703 if not risseq: 2704 src.SetRadius(r) 2705 if utils.is_sequence(res): 2706 res_t, res_phi = res 2707 else: 2708 res_t, res_phi = 2 * res, res 2709 2710 src.SetThetaResolution(res_t) 2711 src.SetPhiResolution(res_phi) 2712 src.Update() 2713 2714 psrc = vtki.new("PointSource") 2715 psrc.SetNumberOfPoints(len(centers)) 2716 psrc.Update() 2717 pd = psrc.GetOutput() 2718 vpts = pd.GetPoints() 2719 2720 glyph = vtki.vtkGlyph3D() 2721 glyph.SetSourceConnection(src.GetOutputPort()) 2722 2723 if cisseq: 2724 glyph.SetColorModeToColorByScalar() 2725 ucols = vtki.vtkUnsignedCharArray() 2726 ucols.SetNumberOfComponents(3) 2727 ucols.SetName("Colors") 2728 for acol in c: 2729 cx, cy, cz = get_color(acol) 2730 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2731 pd.GetPointData().AddArray(ucols) 2732 pd.GetPointData().SetActiveScalars("Colors") 2733 glyph.ScalingOff() 2734 elif risseq: 2735 glyph.SetScaleModeToScaleByScalar() 2736 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2737 urads.SetName("Radii") 2738 pd.GetPointData().AddArray(urads) 2739 pd.GetPointData().SetActiveScalars("Radii") 2740 2741 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2742 2743 glyph.SetInputData(pd) 2744 glyph.Update() 2745 2746 super().__init__(glyph.GetOutput(), alpha=alpha) 2747 self.pos(base) 2748 self.phong() 2749 if cisseq: 2750 self.mapper.ScalarVisibilityOn() 2751 else: 2752 self.mapper.ScalarVisibilityOff() 2753 self.c(c) 2754 self.name = "Spheres" 2755 2756 2757class Earth(Mesh): 2758 """ 2759 Build a textured mesh representing the Earth. 2760 """ 2761 2762 def __init__(self, style=1, r=1.0) -> None: 2763 """ 2764 Build a textured mesh representing the Earth. 2765 2766 Example: 2767 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2768 2769 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2770 """ 2771 tss = vtki.new("TexturedSphereSource") 2772 tss.SetRadius(r) 2773 tss.SetThetaResolution(72) 2774 tss.SetPhiResolution(36) 2775 tss.Update() 2776 super().__init__(tss.GetOutput(), c="w") 2777 atext = vtki.vtkTexture() 2778 pnm_reader = vtki.new("JPEGReader") 2779 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2780 pnm_reader.SetFileName(fn) 2781 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2782 atext.InterpolateOn() 2783 self.texture(atext) 2784 self.name = "Earth" 2785 2786 2787class Ellipsoid(Mesh): 2788 """Build a 3D ellipsoid.""" 2789 def __init__( 2790 self, 2791 pos=(0, 0, 0), 2792 axis1=(0.5, 0, 0), 2793 axis2=(0, 1, 0), 2794 axis3=(0, 0, 1.5), 2795 res=24, 2796 c="cyan4", 2797 alpha=1.0, 2798 ) -> None: 2799 """ 2800 Build a 3D ellipsoid centered at position `pos`. 2801 2802 Arguments: 2803 axis1 : (list) 2804 First axis. Length corresponds to semi-axis. 2805 axis2 : (list) 2806 Second axis. Length corresponds to semi-axis. 2807 axis3 : (list) 2808 Third axis. Length corresponds to semi-axis. 2809 """ 2810 self.center = utils.make3d(pos) 2811 2812 self.axis1 = utils.make3d(axis1) 2813 self.axis2 = utils.make3d(axis2) 2814 self.axis3 = utils.make3d(axis3) 2815 2816 self.va = np.linalg.norm(self.axis1) 2817 self.vb = np.linalg.norm(self.axis2) 2818 self.vc = np.linalg.norm(self.axis3) 2819 2820 self.va_error = 0 2821 self.vb_error = 0 2822 self.vc_error = 0 2823 2824 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2825 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2826 2827 if utils.is_sequence(res): 2828 res_t, res_phi = res 2829 else: 2830 res_t, res_phi = 2 * res, res 2831 2832 elli_source = vtki.new("SphereSource") 2833 elli_source.SetRadius(1) 2834 elli_source.SetThetaResolution(res_t) 2835 elli_source.SetPhiResolution(res_phi) 2836 elli_source.Update() 2837 2838 super().__init__(elli_source.GetOutput(), c, alpha) 2839 2840 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2841 lt = LinearTransform(matrix).translate(pos) 2842 self.apply_transform(lt) 2843 self.name = "Ellipsoid" 2844 2845 def asphericity(self) -> float: 2846 """ 2847 Return a measure of how different an ellipsoid is from a sphere. 2848 Values close to zero correspond to a spheric object. 2849 """ 2850 a, b, c = self.va, self.vb, self.vc 2851 asp = ( ((a-b)/(a+b))**2 2852 + ((a-c)/(a+c))**2 2853 + ((b-c)/(b+c))**2 ) / 3. * 4. 2854 return float(asp) 2855 2856 def asphericity_error(self) -> float: 2857 """ 2858 Calculate statistical error on the asphericity value. 2859 2860 Errors on the main axes are stored in 2861 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2862 """ 2863 a, b, c = self.va, self.vb, self.vc 2864 sqrtn = np.sqrt(self.nr_of_points) 2865 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2866 2867 # from sympy import * 2868 # init_printing(use_unicode=True) 2869 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2870 # L = ( 2871 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2872 # / 3 * 4) 2873 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2874 # print(dl2) 2875 # exit() 2876 2877 dL2 = ( 2878 ea ** 2 2879 * ( 2880 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2881 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2882 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2883 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2884 ) ** 2 2885 + eb ** 2 2886 * ( 2887 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2888 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2889 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2890 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2891 ) ** 2 2892 + ec ** 2 2893 * ( 2894 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2895 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2896 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2897 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2898 ) ** 2 2899 ) 2900 err = np.sqrt(dL2) 2901 self.va_error = ea 2902 self.vb_error = eb 2903 self.vc_error = ec 2904 return err 2905 2906 2907class Grid(Mesh): 2908 """ 2909 An even or uneven 2D grid. 2910 """ 2911 2912 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2913 """ 2914 Create an even or uneven 2D grid. 2915 Can also be created from a `np.mgrid` object (see example). 2916 2917 Arguments: 2918 pos : (list, Points, Mesh) 2919 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2920 s : (float, list) 2921 if a float is provided it is interpreted as the total size along x and y, 2922 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2923 In this case keyword `res` is ignored (see example below). 2924 res : (list) 2925 resolutions along x and y, e.i. the number of subdivisions 2926 lw : (int) 2927 line width 2928 2929 Example: 2930 ```python 2931 from vedo import * 2932 xcoords = np.arange(0, 2, 0.2) 2933 ycoords = np.arange(0, 1, 0.2) 2934 sqrtx = sqrt(xcoords) 2935 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2936 grid.show(axes=8).close() 2937 2938 # Can also create a grid from a np.mgrid: 2939 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2940 vgrid = Grid(s=(X[:,0], Y[0])) 2941 vgrid.show(axes=8).close() 2942 ``` 2943 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2944 """ 2945 resx, resy = res 2946 sx, sy = s 2947 2948 try: 2949 bb = pos.bounds() 2950 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2951 sx = bb[1] - bb[0] 2952 sy = bb[3] - bb[2] 2953 except AttributeError: 2954 pass 2955 2956 if len(pos) == 2: 2957 pos = (pos[0], pos[1], 0) 2958 elif len(pos) in [4,6]: # passing a bounding box 2959 bb = pos 2960 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2961 sx = bb[1] - bb[0] 2962 sy = bb[3] - bb[2] 2963 if len(pos)==6: 2964 pos[2] = bb[4] - bb[5] 2965 2966 if utils.is_sequence(sx) and utils.is_sequence(sy): 2967 verts = [] 2968 for y in sy: 2969 for x in sx: 2970 verts.append([x, y, 0]) 2971 faces = [] 2972 n = len(sx) 2973 m = len(sy) 2974 for j in range(m - 1): 2975 j1n = (j + 1) * n 2976 for i in range(n - 1): 2977 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2978 2979 super().__init__([verts, faces], c, alpha) 2980 2981 else: 2982 ps = vtki.new("PlaneSource") 2983 ps.SetResolution(resx, resy) 2984 ps.Update() 2985 2986 t = vtki.vtkTransform() 2987 t.Translate(pos) 2988 t.Scale(sx, sy, 1) 2989 2990 tf = vtki.new("TransformPolyDataFilter") 2991 tf.SetInputData(ps.GetOutput()) 2992 tf.SetTransform(t) 2993 tf.Update() 2994 2995 super().__init__(tf.GetOutput(), c, alpha) 2996 2997 self.wireframe().lw(lw) 2998 self.properties.LightingOff() 2999 self.name = "Grid" 3000 3001 3002class Plane(Mesh): 3003 """Create a plane in space.""" 3004 def __init__( 3005 self, 3006 pos=(0, 0, 0), 3007 normal=(0, 0, 1), 3008 s=(1, 1), 3009 res=(1, 1), 3010 edge_direction=(), 3011 c="gray5", 3012 alpha=1.0, 3013 ) -> None: 3014 """ 3015 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 3016 to vector `normal` so that it passes through point `pos`, optionally 3017 aligning an edge with `direction`. 3018 3019 Arguments: 3020 pos : (list) 3021 position of the plane center 3022 normal : (list) 3023 normal vector to the plane 3024 s : (list) 3025 size of the plane along x and y 3026 res : (list) 3027 resolution of the plane along x and y 3028 edge_direction : (list) 3029 direction vector to align one edge of the plane 3030 """ 3031 if isinstance(pos, vtki.vtkPolyData): 3032 super().__init__(pos, c, alpha) 3033 3034 else: 3035 ps = vtki.new("PlaneSource") 3036 ps.SetResolution(res[0], res[1]) 3037 tri = vtki.new("TriangleFilter") 3038 tri.SetInputConnection(ps.GetOutputPort()) 3039 tri.Update() 3040 super().__init__(tri.GetOutput(), c, alpha) 3041 3042 pos = utils.make3d(pos) 3043 normal = np.asarray(normal, dtype=float) 3044 axis = normal / np.linalg.norm(normal) 3045 3046 # Calculate orientation using normal 3047 theta = np.arccos(axis[2]) 3048 phi = np.arctan2(axis[1], axis[0]) 3049 3050 t = LinearTransform() 3051 t.scale([s[0], s[1], 1]) 3052 3053 # Rotate to align normal 3054 t.rotate_y(np.rad2deg(theta)) 3055 t.rotate_z(np.rad2deg(phi)) 3056 3057 # Additional direction alignment 3058 if len(edge_direction) >= 2: 3059 direction = utils.make3d(edge_direction).astype(float) 3060 direction /= np.linalg.norm(direction) 3061 3062 if s[0] <= s[1]: 3063 current_direction = np.asarray([0,1,0]) 3064 else: 3065 current_direction = np.asarray([1,0,0]) 3066 3067 transformed_current_direction = t.transform_point(current_direction) 3068 n = transformed_current_direction / np.linalg.norm(transformed_current_direction) 3069 3070 if np.linalg.norm(transformed_current_direction) >= 1e-6: 3071 angle = np.arccos(np.dot(n, direction)) 3072 t.rotate(axis=axis, angle=np.rad2deg(angle)) 3073 3074 t.translate(pos) 3075 self.apply_transform(t) 3076 3077 self.lighting("off") 3078 self.name = "Plane" 3079 self.variance = 0 # used by pointcloud.fit_plane() 3080 3081 def clone(self, deep=True) -> "Plane": 3082 newplane = Plane() 3083 if deep: 3084 newplane.dataset.DeepCopy(self.dataset) 3085 else: 3086 newplane.dataset.ShallowCopy(self.dataset) 3087 newplane.copy_properties_from(self) 3088 newplane.transform = self.transform.clone() 3089 newplane.variance = 0 3090 return newplane 3091 3092 @property 3093 def normal(self) -> np.ndarray: 3094 pts = self.coordinates 3095 # this is necessary because plane can have high resolution 3096 # p0, p1 = pts[0], pts[1] 3097 # AB = p1 - p0 3098 # AB /= np.linalg.norm(AB) 3099 # for pt in pts[2:]: 3100 # AC = pt - p0 3101 # AC /= np.linalg.norm(AC) 3102 # cosine_angle = np.dot(AB, AC) 3103 # if abs(cosine_angle) < 0.99: 3104 # normal = np.cross(AB, AC) 3105 # return normal / np.linalg.norm(normal) 3106 p0, p1, p2 = pts[0], pts[1], pts[int(len(pts)/2 +0.5)] 3107 AB = p1 - p0 3108 AB /= np.linalg.norm(AB) 3109 AC = p2 - p0 3110 AC /= np.linalg.norm(AC) 3111 normal = np.cross(AB, AC) 3112 return normal / np.linalg.norm(normal) 3113 3114 @property 3115 def center(self) -> np.ndarray: 3116 pts = self.coordinates 3117 return np.mean(pts, axis=0) 3118 3119 def contains(self, points, tol=0) -> np.ndarray: 3120 """ 3121 Check if each of the provided point lies on this plane. 3122 `points` is an array of shape (n, 3). 3123 """ 3124 points = np.array(points, dtype=float) 3125 bounds = self.coordinates 3126 3127 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3128 3129 for i in [1, 3]: 3130 AB = bounds[i] - bounds[0] 3131 AP = points - bounds[0] 3132 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3133 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3134 mask = np.logical_and(mask, mask_l) 3135 mask = np.logical_and(mask, mask_g) 3136 return mask 3137 3138 3139class Rectangle(Mesh): 3140 """ 3141 Build a rectangle in the xy plane. 3142 """ 3143 3144 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3145 """ 3146 Build a rectangle in the xy plane identified by any two corner points. 3147 3148 Arguments: 3149 p1 : (list) 3150 bottom-left position of the corner 3151 p2 : (list) 3152 top-right position of the corner 3153 radius : (float, list) 3154 smoothing radius of the corner in world units. 3155 A list can be passed with 4 individual values. 3156 """ 3157 if len(p1) == 2: 3158 p1 = np.array([p1[0], p1[1], 0.0]) 3159 else: 3160 p1 = np.array(p1, dtype=float) 3161 if len(p2) == 2: 3162 p2 = np.array([p2[0], p2[1], 0.0]) 3163 else: 3164 p2 = np.array(p2, dtype=float) 3165 3166 self.corner1 = p1 3167 self.corner2 = p2 3168 3169 color = c 3170 smoothr = False 3171 risseq = False 3172 if utils.is_sequence(radius): 3173 risseq = True 3174 smoothr = True 3175 if max(radius) == 0: 3176 smoothr = False 3177 elif radius: 3178 smoothr = True 3179 3180 if not smoothr: 3181 radius = None 3182 self.radius = radius 3183 3184 if smoothr: 3185 r = radius 3186 if not risseq: 3187 r = [r, r, r, r] 3188 rd, ra, rb, rc = r 3189 3190 if p1[0] > p2[0]: # flip p1 - p2 3191 p1, p2 = p2, p1 3192 if p1[1] > p2[1]: # flip p1y - p2y 3193 p1[1], p2[1] = p2[1], p1[1] 3194 3195 px, py, _ = p2 - p1 3196 k = min(px / 2, py / 2) 3197 ra = min(abs(ra), k) 3198 rb = min(abs(rb), k) 3199 rc = min(abs(rc), k) 3200 rd = min(abs(rd), k) 3201 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3202 betas = np.split(beta, 4) 3203 rrx = np.cos(betas) 3204 rry = np.sin(betas) 3205 3206 q1 = (rd, 0) 3207 # q2 = (px-ra, 0) 3208 q3 = (px, ra) 3209 # q4 = (px, py-rb) 3210 q5 = (px - rb, py) 3211 # q6 = (rc, py) 3212 q7 = (0, py - rc) 3213 # q8 = (0, rd) 3214 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3215 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3216 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3217 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3218 3219 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3220 faces = [list(range(len(pts)))] 3221 else: 3222 p1r = np.array([p2[0], p1[1], 0.0]) 3223 p2l = np.array([p1[0], p2[1], 0.0]) 3224 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3225 faces = [(0, 1, 2, 3)] 3226 3227 super().__init__([pts, faces], color, alpha) 3228 self.pos(p1) 3229 self.properties.LightingOff() 3230 self.name = "Rectangle" 3231 3232 3233class Box(Mesh): 3234 """ 3235 Build a box of specified dimensions. 3236 """ 3237 3238 def __init__( 3239 self, pos=(0, 0, 0), 3240 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3241 """ 3242 Build a box of dimensions `x=length, y=width and z=height`. 3243 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3244 3245 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3246 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3247 3248 Note that the shape polygonal data contains duplicated vertices. This is to allow 3249 each face to have its own normal, which is essential for some operations. 3250 Use the `clean()` method to remove duplicate points. 3251 3252 Examples: 3253 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3254 3255 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3256 """ 3257 src = vtki.new("CubeSource") 3258 3259 if len(pos) == 2: 3260 pos = (pos[0], pos[1], 0) 3261 3262 if len(pos) == 6: 3263 src.SetBounds(pos) 3264 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3265 elif len(size) == 3: 3266 length, width, height = size 3267 src.SetXLength(length) 3268 src.SetYLength(width) 3269 src.SetZLength(height) 3270 src.SetCenter(pos) 3271 else: 3272 src.SetXLength(length) 3273 src.SetYLength(width) 3274 src.SetZLength(height) 3275 src.SetCenter(pos) 3276 3277 src.Update() 3278 pd = src.GetOutput() 3279 3280 tc = [ 3281 [0.0, 0.0], 3282 [1.0, 0.0], 3283 [0.0, 1.0], 3284 [1.0, 1.0], 3285 [1.0, 0.0], 3286 [0.0, 0.0], 3287 [1.0, 1.0], 3288 [0.0, 1.0], 3289 [1.0, 1.0], 3290 [1.0, 0.0], 3291 [0.0, 1.0], 3292 [0.0, 0.0], 3293 [0.0, 1.0], 3294 [0.0, 0.0], 3295 [1.0, 1.0], 3296 [1.0, 0.0], 3297 [1.0, 0.0], 3298 [0.0, 0.0], 3299 [1.0, 1.0], 3300 [0.0, 1.0], 3301 [0.0, 0.0], 3302 [1.0, 0.0], 3303 [0.0, 1.0], 3304 [1.0, 1.0], 3305 ] 3306 vtc = utils.numpy2vtk(tc) 3307 pd.GetPointData().SetTCoords(vtc) 3308 super().__init__(pd, c, alpha) 3309 self.transform = LinearTransform().translate(pos) 3310 self.name = "Box" 3311 3312 3313class Cube(Box): 3314 """ 3315 Build a cube shape. 3316 3317 Note that the shape polygonal data contains duplicated vertices. This is to allow 3318 each face to have its own normal, which is essential for some operations. 3319 Use the `clean()` method to remove duplicate points. 3320 """ 3321 3322 def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None: 3323 """Build a cube of size `side`.""" 3324 super().__init__(pos, side, side, side, (), c, alpha) 3325 self.name = "Cube" 3326 3327 3328class TessellatedBox(Mesh): 3329 """ 3330 Build a cubic `Mesh` made of quads. 3331 """ 3332 3333 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3334 """ 3335 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3336 3337 Arguments: 3338 pos : (list) 3339 position of the left bottom corner 3340 n : (int, list) 3341 number of subdivisions along each side 3342 spacing : (float) 3343 size of the side of the single quad in the 3 directions 3344 """ 3345 if utils.is_sequence(n): # slow 3346 img = vtki.vtkImageData() 3347 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3348 img.SetSpacing(spacing) 3349 gf = vtki.new("GeometryFilter") 3350 gf.SetInputData(img) 3351 gf.Update() 3352 poly = gf.GetOutput() 3353 else: # fast 3354 n -= 1 3355 tbs = vtki.new("TessellatedBoxSource") 3356 tbs.SetLevel(n) 3357 if len(bounds): 3358 tbs.SetBounds(bounds) 3359 else: 3360 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3361 tbs.QuadsOn() 3362 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3363 tbs.Update() 3364 poly = tbs.GetOutput() 3365 super().__init__(poly, c=c, alpha=alpha) 3366 self.pos(pos) 3367 self.lw(1).lighting("off") 3368 self.name = "TessellatedBox" 3369 3370 3371class Spring(Mesh): 3372 """ 3373 Build a spring model. 3374 """ 3375 3376 def __init__( 3377 self, 3378 start_pt=(0, 0, 0), 3379 end_pt=(1, 0, 0), 3380 coils=20, 3381 r1=0.1, 3382 r2=None, 3383 thickness=None, 3384 c="gray5", 3385 alpha=1.0, 3386 ) -> None: 3387 """ 3388 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3389 3390 Arguments: 3391 coils : (int) 3392 number of coils 3393 r1 : (float) 3394 radius at start point 3395 r2 : (float) 3396 radius at end point 3397 thickness : (float) 3398 thickness of the coil section 3399 """ 3400 start_pt = utils.make3d(start_pt) 3401 end_pt = utils.make3d(end_pt) 3402 3403 diff = end_pt - start_pt 3404 length = np.linalg.norm(diff) 3405 if not length: 3406 return 3407 if not r1: 3408 r1 = length / 20 3409 trange = np.linspace(0, length, num=50 * coils) 3410 om = 6.283 * (coils - 0.5) / length 3411 if not r2: 3412 r2 = r1 3413 pts = [] 3414 for t in trange: 3415 f = (length - t) / length 3416 rd = r1 * f + r2 * (1 - f) 3417 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3418 3419 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3420 diff = diff / length 3421 theta = np.arccos(diff[2]) 3422 phi = np.arctan2(diff[1], diff[0]) 3423 sp = Line(pts) 3424 3425 t = vtki.vtkTransform() 3426 t.Translate(start_pt) 3427 t.RotateZ(np.rad2deg(phi)) 3428 t.RotateY(np.rad2deg(theta)) 3429 3430 tf = vtki.new("TransformPolyDataFilter") 3431 tf.SetInputData(sp.dataset) 3432 tf.SetTransform(t) 3433 tf.Update() 3434 3435 tuf = vtki.new("TubeFilter") 3436 tuf.SetNumberOfSides(12) 3437 tuf.CappingOn() 3438 tuf.SetInputData(tf.GetOutput()) 3439 if not thickness: 3440 thickness = r1 / 10 3441 tuf.SetRadius(thickness) 3442 tuf.Update() 3443 3444 super().__init__(tuf.GetOutput(), c, alpha) 3445 3446 self.phong().lighting("metallic") 3447 self.base = np.array(start_pt, dtype=float) 3448 self.top = np.array(end_pt, dtype=float) 3449 self.name = "Spring" 3450 3451 3452class Cylinder(Mesh): 3453 """ 3454 Build a cylinder of specified height and radius. 3455 """ 3456 3457 def __init__( 3458 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3459 cap=True, res=24, c="teal3", alpha=1.0 3460 ) -> None: 3461 """ 3462 Build a cylinder of specified height and radius `r`, centered at `pos`. 3463 3464 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3465 centered at `v1` and top at `v2`. 3466 3467 Arguments: 3468 cap : (bool) 3469 enable/disable the caps of the cylinder 3470 res : (int) 3471 resolution of the cylinder sides 3472 3473 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3474 """ 3475 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3476 base = np.array(pos[0], dtype=float) 3477 top = np.array(pos[1], dtype=float) 3478 pos = (base + top) / 2 3479 height = np.linalg.norm(top - base) 3480 axis = top - base 3481 axis = utils.versor(axis) 3482 else: 3483 axis = utils.versor(axis) 3484 base = pos - axis * height / 2 3485 top = pos + axis * height / 2 3486 3487 cyl = vtki.new("CylinderSource") 3488 cyl.SetResolution(res) 3489 cyl.SetRadius(r) 3490 cyl.SetHeight(height) 3491 cyl.SetCapping(cap) 3492 cyl.Update() 3493 3494 theta = np.arccos(axis[2]) 3495 phi = np.arctan2(axis[1], axis[0]) 3496 t = vtki.vtkTransform() 3497 t.PostMultiply() 3498 t.RotateX(90) # put it along Z 3499 t.RotateY(np.rad2deg(theta)) 3500 t.RotateZ(np.rad2deg(phi)) 3501 t.Translate(pos) 3502 3503 tf = vtki.new("TransformPolyDataFilter") 3504 tf.SetInputData(cyl.GetOutput()) 3505 tf.SetTransform(t) 3506 tf.Update() 3507 3508 super().__init__(tf.GetOutput(), c, alpha) 3509 3510 self.phong() 3511 self.base = base 3512 self.top = top 3513 self.transform = LinearTransform().translate(pos) 3514 self.name = "Cylinder" 3515 3516 3517class Cone(Mesh): 3518 """Build a cone of specified radius and height.""" 3519 3520 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3521 res=48, c="green3", alpha=1.0) -> None: 3522 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3523 con = vtki.new("ConeSource") 3524 con.SetResolution(res) 3525 con.SetRadius(r) 3526 con.SetHeight(height) 3527 con.SetDirection(axis) 3528 con.Update() 3529 super().__init__(con.GetOutput(), c, alpha) 3530 self.phong() 3531 if len(pos) == 2: 3532 pos = (pos[0], pos[1], 0) 3533 self.pos(pos) 3534 v = utils.versor(axis) * height / 2 3535 self.base = pos - v 3536 self.top = pos + v 3537 self.name = "Cone" 3538 3539 3540class Pyramid(Cone): 3541 """Build a pyramidal shape.""" 3542 3543 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3544 c="green3", alpha=1) -> None: 3545 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3546 super().__init__(pos, s, height, axis, 4, c, alpha) 3547 self.name = "Pyramid" 3548 3549 3550class Torus(Mesh): 3551 """ 3552 Build a toroidal shape. 3553 """ 3554 3555 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3556 """ 3557 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3558 If `quad=True` a quad-mesh is generated. 3559 """ 3560 if utils.is_sequence(res): 3561 res_u, res_v = res 3562 else: 3563 res_u, res_v = 3 * res, res 3564 3565 if quads: 3566 # https://github.com/marcomusy/vedo/issues/710 3567 3568 n = res_v 3569 m = res_u 3570 3571 theta = np.linspace(0, 2.0 * np.pi, n) 3572 phi = np.linspace(0, 2.0 * np.pi, m) 3573 theta, phi = np.meshgrid(theta, phi) 3574 t = r1 + r2 * np.cos(theta) 3575 x = t * np.cos(phi) 3576 y = t * np.sin(phi) 3577 z = r2 * np.sin(theta) 3578 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3579 3580 faces = [] 3581 for j in range(m - 1): 3582 j1n = (j + 1) * n 3583 for i in range(n - 1): 3584 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3585 3586 super().__init__([pts, faces], c, alpha) 3587 3588 else: 3589 rs = vtki.new("ParametricTorus") 3590 rs.SetRingRadius(r1) 3591 rs.SetCrossSectionRadius(r2) 3592 pfs = vtki.new("ParametricFunctionSource") 3593 pfs.SetParametricFunction(rs) 3594 pfs.SetUResolution(res_u) 3595 pfs.SetVResolution(res_v) 3596 pfs.Update() 3597 3598 super().__init__(pfs.GetOutput(), c, alpha) 3599 3600 self.phong() 3601 if len(pos) == 2: 3602 pos = (pos[0], pos[1], 0) 3603 self.pos(pos) 3604 self.name = "Torus" 3605 3606 3607class Paraboloid(Mesh): 3608 """ 3609 Build a paraboloid. 3610 """ 3611 3612 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3613 """ 3614 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3615 3616 Full volumetric expression is: 3617 `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` 3618 3619 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3620 """ 3621 quadric = vtki.new("Quadric") 3622 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3623 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3624 # + a3*x*y + a4*y*z + a5*x*z 3625 # + a6*x + a7*y + a8*z +a9 3626 sample = vtki.new("SampleFunction") 3627 sample.SetSampleDimensions(res, res, res) 3628 sample.SetImplicitFunction(quadric) 3629 3630 contours = vtki.new("ContourFilter") 3631 contours.SetInputConnection(sample.GetOutputPort()) 3632 contours.GenerateValues(1, 0.01, 0.01) 3633 contours.Update() 3634 3635 super().__init__(contours.GetOutput(), c, alpha) 3636 self.compute_normals().phong() 3637 self.mapper.ScalarVisibilityOff() 3638 self.pos(pos) 3639 self.name = "Paraboloid" 3640 3641 3642class Hyperboloid(Mesh): 3643 """ 3644 Build a hyperboloid. 3645 """ 3646 3647 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3648 """ 3649 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3650 3651 Full volumetric expression is: 3652 `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` 3653 """ 3654 q = vtki.new("Quadric") 3655 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3656 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3657 # + a3*x*y + a4*y*z + a5*x*z 3658 # + a6*x + a7*y + a8*z +a9 3659 sample = vtki.new("SampleFunction") 3660 sample.SetSampleDimensions(res, res, res) 3661 sample.SetImplicitFunction(q) 3662 3663 contours = vtki.new("ContourFilter") 3664 contours.SetInputConnection(sample.GetOutputPort()) 3665 contours.GenerateValues(1, value, value) 3666 contours.Update() 3667 3668 super().__init__(contours.GetOutput(), c, alpha) 3669 self.compute_normals().phong() 3670 self.mapper.ScalarVisibilityOff() 3671 self.pos(pos) 3672 self.name = "Hyperboloid" 3673 3674 3675def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True) -> Any: 3676 """ 3677 Generate a marker shape. Typically used in association with `Glyph`. 3678 """ 3679 if isinstance(symbol, Mesh): 3680 return symbol.c(c).alpha(alpha).lighting("off") 3681 3682 if isinstance(symbol, int): 3683 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 3684 symbol = symbol % len(symbs) 3685 symbol = symbs[symbol] 3686 3687 if symbol == ".": 3688 mesh = Polygon(nsides=24, r=s * 0.6) 3689 elif symbol == "o": 3690 mesh = Polygon(nsides=24, r=s * 0.75) 3691 elif symbol == "O": 3692 mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3693 elif symbol == "0": 3694 m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3695 m2 = Circle(r=s * 0.36).reverse() 3696 mesh = merge(m1, m2) 3697 elif symbol == "p": 3698 mesh = Polygon(nsides=5, r=s) 3699 elif symbol == "*": 3700 mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled) 3701 elif symbol == "h": 3702 mesh = Polygon(nsides=6, r=s) 3703 elif symbol == "D": 3704 mesh = Polygon(nsides=4, r=s) 3705 elif symbol == "d": 3706 mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1]) 3707 elif symbol == "v": 3708 mesh = Polygon(nsides=3, r=s).rotate_z(180) 3709 elif symbol == "^": 3710 mesh = Polygon(nsides=3, r=s) 3711 elif symbol == ">": 3712 mesh = Polygon(nsides=3, r=s).rotate_z(-90) 3713 elif symbol == "<": 3714 mesh = Polygon(nsides=3, r=s).rotate_z(90) 3715 elif symbol == "s": 3716 mesh = Mesh( 3717 [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]] 3718 ).scale(s / 1.4) 3719 elif symbol == "x": 3720 mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3721 # mesh.rotate_z(45) 3722 elif symbol == "a": 3723 mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3724 else: 3725 mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0) 3726 mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha) 3727 if len(pos) == 2: 3728 pos = (pos[0], pos[1], 0) 3729 mesh.pos(pos) 3730 mesh.name = "Marker" 3731 return mesh 3732 3733 3734class Brace(Mesh): 3735 """ 3736 Create a brace (bracket) shape. 3737 """ 3738 3739 def __init__( 3740 self, 3741 q1, 3742 q2, 3743 style="}", 3744 padding1=0.0, 3745 font="Theemim", 3746 comment="", 3747 justify=None, 3748 angle=0.0, 3749 padding2=0.2, 3750 s=1.0, 3751 italic=0, 3752 c="k1", 3753 alpha=1.0, 3754 ) -> None: 3755 """ 3756 Create a brace (bracket) shape which spans from point q1 to point q2. 3757 3758 Arguments: 3759 q1 : (list) 3760 point 1. 3761 q2 : (list) 3762 point 2. 3763 style : (str) 3764 style of the bracket, eg. `{}, [], (), <>`. 3765 padding1 : (float) 3766 padding space in percent form the input points. 3767 font : (str) 3768 font type 3769 comment : (str) 3770 additional text to appear next to the brace symbol. 3771 justify : (str) 3772 specify the anchor point to justify text comment, e.g. "top-left". 3773 italic : float 3774 italicness of the text comment (can be a positive or negative number) 3775 angle : (float) 3776 rotation angle of text. Use `None` to keep it horizontal. 3777 padding2 : (float) 3778 padding space in percent form brace to text comment. 3779 s : (float) 3780 scale factor for the comment 3781 3782 Examples: 3783 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3784 3785 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3786 """ 3787 if isinstance(q1, vtki.vtkActor): 3788 q1 = q1.GetPosition() 3789 if isinstance(q2, vtki.vtkActor): 3790 q2 = q2.GetPosition() 3791 if len(q1) == 2: 3792 q1 = [q1[0], q1[1], 0.0] 3793 if len(q2) == 2: 3794 q2 = [q2[0], q2[1], 0.0] 3795 q1 = np.array(q1, dtype=float) 3796 q2 = np.array(q2, dtype=float) 3797 mq = (q1 + q2) / 2 3798 q1 = q1 - mq 3799 q2 = q2 - mq 3800 d = np.linalg.norm(q2 - q1) 3801 q2[2] = q1[2] 3802 3803 if style not in "{}[]()<>|I": 3804 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3805 style = "}" 3806 3807 flip = False 3808 if style in ["{", "[", "(", "<"]: 3809 flip = True 3810 i = ["{", "[", "(", "<"].index(style) 3811 style = ["}", "]", ")", ">"][i] 3812 3813 br = Text3D(style, font="Theemim", justify="center-left") 3814 br.scale([0.4, 1, 1]) 3815 3816 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3817 if flip: 3818 angler += 180 3819 3820 _, x1, y0, y1, _, _ = br.bounds() 3821 if comment: 3822 just = "center-top" 3823 if angle is None: 3824 angle = -angler + 90 3825 if not flip: 3826 angle += 180 3827 3828 if flip: 3829 angle += 180 3830 just = "center-bottom" 3831 if justify is not None: 3832 just = justify 3833 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3834 cx0, cx1 = cmt.xbounds() 3835 cmt.rotate_z(90 + angle) 3836 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3837 cmt.shift(x1 * (1 + padding2), 0, 0) 3838 poly = merge(br, cmt).dataset 3839 3840 else: 3841 poly = br.dataset 3842 3843 tr = vtki.vtkTransform() 3844 tr.Translate(mq) 3845 tr.RotateZ(angler) 3846 tr.Translate(padding1 * d, 0, 0) 3847 pscale = 1 3848 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3849 3850 tf = vtki.new("TransformPolyDataFilter") 3851 tf.SetInputData(poly) 3852 tf.SetTransform(tr) 3853 tf.Update() 3854 poly = tf.GetOutput() 3855 3856 super().__init__(poly, c, alpha) 3857 3858 self.base = q1 3859 self.top = q2 3860 self.name = "Brace" 3861 3862 3863class Star3D(Mesh): 3864 """ 3865 Build a 3D starred shape. 3866 """ 3867 3868 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3869 """ 3870 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3871 """ 3872 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3873 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3874 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3875 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3876 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3877 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3878 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3879 [10,1, 0],[10,11, 9]] 3880 3881 super().__init__([pts, fcs], c, alpha) 3882 self.rotate_x(90) 3883 self.scale(r).lighting("shiny") 3884 3885 if len(pos) == 2: 3886 pos = (pos[0], pos[1], 0) 3887 self.pos(pos) 3888 self.name = "Star3D" 3889 3890 3891class Cross3D(Mesh): 3892 """ 3893 Build a 3D cross shape. 3894 """ 3895 3896 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3897 """ 3898 Build a 3D cross shape, mainly useful as a 3D marker. 3899 """ 3900 if len(pos) == 2: 3901 pos = (pos[0], pos[1], 0) 3902 3903 c1 = Cylinder(r=thickness * s, height=2 * s) 3904 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3905 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3906 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3907 super().__init__(poly, c, alpha) 3908 self.name = "Cross3D" 3909 3910 3911class ParametricShape(Mesh): 3912 """ 3913 A set of built-in shapes mainly for illustration purposes. 3914 """ 3915 3916 def __init__(self, name, res=51, n=25, seed=1): 3917 """ 3918 A set of built-in shapes mainly for illustration purposes. 3919 3920 Name can be an integer or a string in this list: 3921 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3922 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3923 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3924 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3925 3926 Example: 3927 ```python 3928 from vedo import * 3929 settings.immediate_rendering = False 3930 plt = Plotter(N=18) 3931 for i in range(18): 3932 ps = ParametricShape(i).color(i) 3933 plt.at(i).show(ps, ps.name) 3934 plt.interactive().close() 3935 ``` 3936 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3937 """ 3938 3939 shapes = [ 3940 "Boy", 3941 "ConicSpiral", 3942 "CrossCap", 3943 "Enneper", 3944 "Figure8Klein", 3945 "Klein", 3946 "Dini", 3947 "Mobius", 3948 "RandomHills", 3949 "Roman", 3950 "SuperEllipsoid", 3951 "BohemianDome", 3952 "Bour", 3953 "CatalanMinimal", 3954 "Henneberg", 3955 "Kuen", 3956 "PluckerConoid", 3957 "Pseudosphere", 3958 ] 3959 3960 if isinstance(name, int): 3961 name = name % len(shapes) 3962 name = shapes[name] 3963 3964 if name == "Boy": 3965 ps = vtki.new("ParametricBoy") 3966 elif name == "ConicSpiral": 3967 ps = vtki.new("ParametricConicSpiral") 3968 elif name == "CrossCap": 3969 ps = vtki.new("ParametricCrossCap") 3970 elif name == "Dini": 3971 ps = vtki.new("ParametricDini") 3972 elif name == "Enneper": 3973 ps = vtki.new("ParametricEnneper") 3974 elif name == "Figure8Klein": 3975 ps = vtki.new("ParametricFigure8Klein") 3976 elif name == "Klein": 3977 ps = vtki.new("ParametricKlein") 3978 elif name == "Mobius": 3979 ps = vtki.new("ParametricMobius") 3980 ps.SetRadius(2.0) 3981 ps.SetMinimumV(-0.5) 3982 ps.SetMaximumV(0.5) 3983 elif name == "RandomHills": 3984 ps = vtki.new("ParametricRandomHills") 3985 ps.AllowRandomGenerationOn() 3986 ps.SetRandomSeed(seed) 3987 ps.SetNumberOfHills(n) 3988 elif name == "Roman": 3989 ps = vtki.new("ParametricRoman") 3990 elif name == "SuperEllipsoid": 3991 ps = vtki.new("ParametricSuperEllipsoid") 3992 ps.SetN1(0.5) 3993 ps.SetN2(0.4) 3994 elif name == "BohemianDome": 3995 ps = vtki.new("ParametricBohemianDome") 3996 ps.SetA(5.0) 3997 ps.SetB(1.0) 3998 ps.SetC(2.0) 3999 elif name == "Bour": 4000 ps = vtki.new("ParametricBour") 4001 elif name == "CatalanMinimal": 4002 ps = vtki.new("ParametricCatalanMinimal") 4003 elif name == "Henneberg": 4004 ps = vtki.new("ParametricHenneberg") 4005 elif name == "Kuen": 4006 ps = vtki.new("ParametricKuen") 4007 ps.SetDeltaV0(0.001) 4008 elif name == "PluckerConoid": 4009 ps = vtki.new("ParametricPluckerConoid") 4010 elif name == "Pseudosphere": 4011 ps = vtki.new("ParametricPseudosphere") 4012 else: 4013 vedo.logger.error(f"unknown ParametricShape {name}") 4014 return 4015 4016 pfs = vtki.new("ParametricFunctionSource") 4017 pfs.SetParametricFunction(ps) 4018 pfs.SetUResolution(res) 4019 pfs.SetVResolution(res) 4020 pfs.SetWResolution(res) 4021 pfs.SetScalarModeToZ() 4022 pfs.Update() 4023 4024 super().__init__(pfs.GetOutput()) 4025 4026 if name == "RandomHills": self.shift([0,-10,-2.25]) 4027 if name != 'Kuen': self.normalize() 4028 if name == 'Dini': self.scale(0.4) 4029 if name == 'Enneper': self.scale(0.4) 4030 if name == 'ConicSpiral': self.bc('tomato') 4031 self.name = name 4032 4033 4034@lru_cache(None) 4035def _load_font(font) -> np.ndarray: 4036 # print('_load_font()', font) 4037 4038 if utils.is_number(font): 4039 font = list(settings.font_parameters.keys())[int(font)] 4040 4041 if font.endswith(".npz"): # user passed font as a local path 4042 fontfile = font 4043 font = os.path.basename(font).split(".")[0] 4044 4045 elif font.startswith("https"): # user passed URL link, make it a path 4046 try: 4047 fontfile = vedo.file_io.download(font, verbose=False, force=False) 4048 font = os.path.basename(font).split(".")[0] 4049 except: 4050 vedo.logger.warning(f"font {font} not found") 4051 font = settings.default_font 4052 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4053 4054 else: # user passed font by its standard name 4055 font = font[:1].upper() + font[1:] # capitalize first letter only 4056 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4057 4058 if font not in settings.font_parameters.keys(): 4059 font = "Normografo" 4060 vedo.logger.warning( 4061 f"Unknown font: {font}\n" 4062 f"Available 3D fonts are: " 4063 f"{list(settings.font_parameters.keys())}\n" 4064 f"Using font {font} instead." 4065 ) 4066 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4067 4068 if not settings.font_parameters[font]["islocal"]: 4069 font = "https://vedo.embl.es/fonts/" + font + ".npz" 4070 try: 4071 fontfile = vedo.file_io.download(font, verbose=False, force=False) 4072 font = os.path.basename(font).split(".")[0] 4073 except: 4074 vedo.logger.warning(f"font {font} not found") 4075 font = settings.default_font 4076 fontfile = os.path.join(vedo.fonts_path, font + ".npz") 4077 4078 ##### 4079 try: 4080 font_meshes = np.load(fontfile, allow_pickle=True)["font"][0] 4081 except: 4082 vedo.logger.warning(f"font name {font} not found.") 4083 raise RuntimeError 4084 return font_meshes 4085 4086 4087@lru_cache(None) 4088def _get_font_letter(font, letter): 4089 # print("_get_font_letter", font, letter) 4090 font_meshes = _load_font(font) 4091 try: 4092 pts, faces = font_meshes[letter] 4093 return utils.buildPolyData(pts.astype(float), faces) 4094 except KeyError: 4095 return None 4096 4097 4098class Text3D(Mesh): 4099 """ 4100 Generate a 3D polygonal Mesh to represent a text string. 4101 """ 4102 4103 def __init__( 4104 self, 4105 txt, 4106 pos=(0, 0, 0), 4107 s=1.0, 4108 font="", 4109 hspacing=1.15, 4110 vspacing=2.15, 4111 depth=0.0, 4112 italic=False, 4113 justify="bottom-left", 4114 literal=False, 4115 c=None, 4116 alpha=1.0, 4117 ) -> None: 4118 """ 4119 Generate a 3D polygonal `Mesh` representing a text string. 4120 4121 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4122 Most Latex symbols are also supported. 4123 4124 Symbols `~ ^ _` are reserved modifiers: 4125 - use ~ to add a short space, 1/4 of the default empty space, 4126 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4127 4128 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4129 4130 More fonts at: https://vedo.embl.es/fonts/ 4131 4132 Arguments: 4133 pos : (list) 4134 position coordinates in 3D space 4135 s : (float) 4136 vertical size of the text (as scaling factor) 4137 depth : (float) 4138 text thickness (along z) 4139 italic : (bool), float 4140 italic font type (can be a signed float too) 4141 justify : (str) 4142 text justification as centering of the bounding box 4143 (bottom-left, bottom-right, top-left, top-right, centered) 4144 font : (str, int) 4145 some of the available 3D-polygonized fonts are: 4146 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4147 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4148 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4149 4150 Check for more at https://vedo.embl.es/fonts/ 4151 4152 Or type in your terminal `vedo --run fonts`. 4153 4154 Default is Normografo, which can be changed using `settings.default_font`. 4155 4156 hspacing : (float) 4157 horizontal spacing of the font 4158 vspacing : (float) 4159 vertical spacing of the font for multiple lines text 4160 literal : (bool) 4161 if set to True will ignore modifiers like _ or ^ 4162 4163 Examples: 4164 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4165 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4166 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4167 4168 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4169 4170 .. note:: Type `vedo -r fonts` for a demo. 4171 """ 4172 if len(pos) == 2: 4173 pos = (pos[0], pos[1], 0) 4174 4175 if c is None: # automatic black or white 4176 pli = vedo.plotter_instance 4177 if pli and pli.renderer: 4178 c = (0.9, 0.9, 0.9) 4179 if pli.renderer.GetGradientBackground(): 4180 bgcol = pli.renderer.GetBackground2() 4181 else: 4182 bgcol = pli.renderer.GetBackground() 4183 if np.sum(bgcol) > 1.5: 4184 c = (0.1, 0.1, 0.1) 4185 else: 4186 c = (0.6, 0.6, 0.6) 4187 4188 tpoly = self._get_text3d_poly( 4189 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4190 ) 4191 4192 super().__init__(tpoly, c, alpha) 4193 4194 self.pos(pos) 4195 self.lighting("off") 4196 4197 self.actor.PickableOff() 4198 self.actor.DragableOff() 4199 self.init_scale = s 4200 self.name = "Text3D" 4201 self.txt = txt 4202 self.justify = justify 4203 4204 def text( 4205 self, 4206 txt=None, 4207 s=1, 4208 font="", 4209 hspacing=1.15, 4210 vspacing=2.15, 4211 depth=0, 4212 italic=False, 4213 justify="", 4214 literal=False, 4215 ) -> "Text3D": 4216 """ 4217 Update the text and some of its properties. 4218 4219 Check [available fonts here](https://vedo.embl.es/fonts). 4220 """ 4221 if txt is None: 4222 return self.txt 4223 if not justify: 4224 justify = self.justify 4225 4226 poly = self._get_text3d_poly( 4227 txt, self.init_scale * s, font, hspacing, vspacing, 4228 depth, italic, justify, literal 4229 ) 4230 4231 # apply the current transformation to the new polydata 4232 tf = vtki.new("TransformPolyDataFilter") 4233 tf.SetInputData(poly) 4234 tf.SetTransform(self.transform.T) 4235 tf.Update() 4236 tpoly = tf.GetOutput() 4237 4238 self._update(tpoly) 4239 self.txt = txt 4240 return self 4241 4242 def _get_text3d_poly( 4243 self, 4244 txt, 4245 s=1, 4246 font="", 4247 hspacing=1.15, 4248 vspacing=2.15, 4249 depth=0, 4250 italic=False, 4251 justify="bottom-left", 4252 literal=False, 4253 ) -> vtki.vtkPolyData: 4254 if not font: 4255 font = settings.default_font 4256 4257 txt = str(txt) 4258 4259 if font == "VTK": ####################################### 4260 vtt = vtki.new("VectorText") 4261 vtt.SetText(txt) 4262 vtt.Update() 4263 tpoly = vtt.GetOutput() 4264 4265 else: ################################################### 4266 4267 stxt = set(txt) # check here if null or only spaces 4268 if not txt or (len(stxt) == 1 and " " in stxt): 4269 return vtki.vtkPolyData() 4270 4271 if italic is True: 4272 italic = 1 4273 4274 if isinstance(font, int): 4275 lfonts = list(settings.font_parameters.keys()) 4276 font = font % len(lfonts) 4277 font = lfonts[font] 4278 4279 if font not in settings.font_parameters.keys(): 4280 fpars = settings.font_parameters["Normografo"] 4281 else: 4282 fpars = settings.font_parameters[font] 4283 4284 # ad hoc adjustments 4285 mono = fpars["mono"] 4286 lspacing = fpars["lspacing"] 4287 hspacing *= fpars["hspacing"] 4288 fscale = fpars["fscale"] 4289 dotsep = fpars["dotsep"] 4290 4291 # replacements 4292 if ":" in txt: 4293 for r in _reps: 4294 txt = txt.replace(r[0], r[1]) 4295 4296 if not literal: 4297 reps2 = [ 4298 (r"\_", "┭"), # trick to protect ~ _ and ^ chars 4299 (r"\^", "┮"), # 4300 (r"\~", "┯"), # 4301 ("**", "^"), # order matters 4302 ("e+0", dotsep + "10^"), 4303 ("e-0", dotsep + "10^-"), 4304 ("E+0", dotsep + "10^"), 4305 ("E-0", dotsep + "10^-"), 4306 ("e+", dotsep + "10^"), 4307 ("e-", dotsep + "10^-"), 4308 ("E+", dotsep + "10^"), 4309 ("E-", dotsep + "10^-"), 4310 ] 4311 for r in reps2: 4312 txt = txt.replace(r[0], r[1]) 4313 4314 xmax, ymax, yshift, scale = 0.0, 0.0, 0.0, 1.0 4315 save_xmax = 0.0 4316 4317 notfounds = set() 4318 polyletters = [] 4319 ntxt = len(txt) 4320 for i, t in enumerate(txt): 4321 ########## 4322 if t == "┭": 4323 t = "_" 4324 elif t == "┮": 4325 t = "^" 4326 elif t == "┯": 4327 t = "~" 4328 elif t == "^" and not literal: 4329 if yshift < 0: 4330 xmax = save_xmax 4331 yshift = 0.9 * fscale 4332 scale = 0.5 4333 continue 4334 elif t == "_" and not literal: 4335 if yshift > 0: 4336 xmax = save_xmax 4337 yshift = -0.3 * fscale 4338 scale = 0.5 4339 continue 4340 elif (t in (" ", "\\n")) and yshift: 4341 yshift = 0.0 4342 scale = 1.0 4343 save_xmax = xmax 4344 if t == " ": 4345 continue 4346 elif t == "~": 4347 if i < ntxt - 1 and txt[i + 1] == "_": 4348 continue 4349 xmax += hspacing * scale * fscale / 4 4350 continue 4351 4352 ############ 4353 if t == " ": 4354 xmax += hspacing * scale * fscale 4355 4356 elif t == "\n": 4357 xmax = 0.0 4358 save_xmax = 0.0 4359 ymax -= vspacing 4360 4361 else: 4362 poly = _get_font_letter(font, t) 4363 if not poly: 4364 notfounds.add(t) 4365 xmax += hspacing * scale * fscale 4366 continue 4367 4368 if poly.GetNumberOfPoints() == 0: 4369 continue 4370 4371 tr = vtki.vtkTransform() 4372 tr.Translate(xmax, ymax + yshift, 0) 4373 pscale = scale * fscale / 1000 4374 tr.Scale(pscale, pscale, pscale) 4375 if italic: 4376 tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 4377 tf = vtki.new("TransformPolyDataFilter") 4378 tf.SetInputData(poly) 4379 tf.SetTransform(tr) 4380 tf.Update() 4381 poly = tf.GetOutput() 4382 polyletters.append(poly) 4383 4384 bx = poly.GetBounds() 4385 if mono: 4386 xmax += hspacing * scale * fscale 4387 else: 4388 xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing 4389 if yshift == 0: 4390 save_xmax = xmax 4391 4392 if len(polyletters) == 1: 4393 tpoly = polyletters[0] 4394 else: 4395 polyapp = vtki.new("AppendPolyData") 4396 for polyd in polyletters: 4397 polyapp.AddInputData(polyd) 4398 polyapp.Update() 4399 tpoly = polyapp.GetOutput() 4400 4401 if notfounds: 4402 wmsg = f"unavailable characters in font name '{font}': {notfounds}." 4403 wmsg += '\nType "vedo -r fonts" for a demo.' 4404 vedo.logger.warning(wmsg) 4405 4406 bb = tpoly.GetBounds() 4407 dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s 4408 shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2 4409 if "bottom" in justify: shift += np.array([ 0, dy, 0.]) 4410 if "top" in justify: shift += np.array([ 0,-dy, 0.]) 4411 if "left" in justify: shift += np.array([ dx, 0, 0.]) 4412 if "right" in justify: shift += np.array([-dx, 0, 0.]) 4413 4414 if tpoly.GetNumberOfPoints(): 4415 t = vtki.vtkTransform() 4416 t.PostMultiply() 4417 t.Scale(s, s, s) 4418 t.Translate(shift) 4419 tf = vtki.new("TransformPolyDataFilter") 4420 tf.SetInputData(tpoly) 4421 tf.SetTransform(t) 4422 tf.Update() 4423 tpoly = tf.GetOutput() 4424 4425 if depth: 4426 extrude = vtki.new("LinearExtrusionFilter") 4427 extrude.SetInputData(tpoly) 4428 extrude.SetExtrusionTypeToVectorExtrusion() 4429 extrude.SetVector(0, 0, 1) 4430 extrude.SetScaleFactor(depth * dy) 4431 extrude.Update() 4432 tpoly = extrude.GetOutput() 4433 4434 return tpoly 4435 4436 4437class TextBase: 4438 "Base class." 4439 4440 def __init__(self): 4441 "Do not instantiate this base class." 4442 4443 self.rendered_at = set() 4444 self.properties = None 4445 4446 self.name = "Text" 4447 self.filename = "" 4448 self.time = 0 4449 self.info = {} 4450 4451 if isinstance(settings.default_font, int): 4452 lfonts = list(settings.font_parameters.keys()) 4453 font = settings.default_font % len(lfonts) 4454 self.fontname = lfonts[font] 4455 else: 4456 self.fontname = settings.default_font 4457 4458 def angle(self, value: float): 4459 """Orientation angle in degrees""" 4460 self.properties.SetOrientation(value) 4461 return self 4462 4463 def line_spacing(self, value: float): 4464 """Set the extra spacing between lines 4465 expressed as a text height multiplicative factor.""" 4466 self.properties.SetLineSpacing(value) 4467 return self 4468 4469 def line_offset(self, value: float): 4470 """Set/Get the vertical offset (measured in pixels).""" 4471 self.properties.SetLineOffset(value) 4472 return self 4473 4474 def bold(self, value=True): 4475 """Set bold face""" 4476 self.properties.SetBold(value) 4477 return self 4478 4479 def italic(self, value=True): 4480 """Set italic face""" 4481 self.properties.SetItalic(value) 4482 return self 4483 4484 def shadow(self, offset=(1, -1)): 4485 """Text shadowing. Set to `None` to disable it.""" 4486 if offset is None: 4487 self.properties.ShadowOff() 4488 else: 4489 self.properties.ShadowOn() 4490 self.properties.SetShadowOffset(offset) 4491 return self 4492 4493 def color(self, c=None): 4494 """Set the text color""" 4495 if c is None: 4496 return get_color(self.properties.GetColor()) 4497 self.properties.SetColor(get_color(c)) 4498 return self 4499 4500 def c(self, color=None): 4501 """Set the text color""" 4502 if color is None: 4503 return get_color(self.properties.GetColor()) 4504 return self.color(color) 4505 4506 def alpha(self, value: float): 4507 """Set the text opacity""" 4508 self.properties.SetBackgroundOpacity(value) 4509 return self 4510 4511 def background(self, color="k9", alpha=1.0): 4512 """Text background. Set to `None` to disable it.""" 4513 bg = get_color(color) 4514 if color is None: 4515 self.properties.SetBackgroundOpacity(0) 4516 else: 4517 self.properties.SetBackgroundColor(bg) 4518 if alpha: 4519 self.properties.SetBackgroundOpacity(alpha) 4520 return self 4521 4522 def frame(self, color="k1", lw=2): 4523 """Border color and width""" 4524 if color is None: 4525 self.properties.FrameOff() 4526 else: 4527 c = get_color(color) 4528 self.properties.FrameOn() 4529 self.properties.SetFrameColor(c) 4530 self.properties.SetFrameWidth(lw) 4531 return self 4532 4533 def font(self, font: str): 4534 """Text font face""" 4535 if isinstance(font, int): 4536 lfonts = list(settings.font_parameters.keys()) 4537 n = font % len(lfonts) 4538 font = lfonts[n] 4539 self.fontname = font 4540 4541 if not font: # use default font 4542 font = self.fontname 4543 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4544 elif font.startswith("https"): # user passed URL link, make it a path 4545 fpath = vedo.file_io.download(font, verbose=False, force=False) 4546 elif font.endswith(".ttf"): # user passing a local path to font file 4547 fpath = font 4548 else: # user passing name of preset font 4549 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4550 4551 if font == "Courier": self.properties.SetFontFamilyToCourier() 4552 elif font == "Times": self.properties.SetFontFamilyToTimes() 4553 elif font == "Arial": self.properties.SetFontFamilyToArial() 4554 else: 4555 fpath = utils.get_font_path(font) 4556 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4557 self.properties.SetFontFile(fpath) 4558 self.fontname = font # io.tonumpy() uses it 4559 4560 return self 4561 4562 def on(self): 4563 """Make text visible""" 4564 self.actor.SetVisibility(True) 4565 return self 4566 4567 def off(self): 4568 """Make text invisible""" 4569 self.actor.SetVisibility(False) 4570 return self 4571 4572class Text2D(TextBase, vedo.visual.Actor2D): 4573 """ 4574 Create a 2D text object. 4575 """ 4576 def __init__( 4577 self, 4578 txt="", 4579 pos="top-left", 4580 s=1.0, 4581 bg=None, 4582 font="", 4583 justify="", 4584 bold=False, 4585 italic=False, 4586 c=None, 4587 alpha=0.5, 4588 ) -> None: 4589 """ 4590 Create a 2D text object. 4591 4592 All properties of the text, and the text itself, can be changed after creation 4593 (which is especially useful in loops). 4594 4595 Arguments: 4596 pos : (str) 4597 text is placed in one of the 8 positions: 4598 - bottom-left 4599 - bottom-right 4600 - top-left 4601 - top-right 4602 - bottom-middle 4603 - middle-right 4604 - middle-left 4605 - top-middle 4606 4607 If a pair (x,y) is passed as input the 2D text is place at that 4608 position in the coordinate system of the 2D screen (with the 4609 origin sitting at the bottom left). 4610 4611 s : (float) 4612 size of text 4613 bg : (color) 4614 background color 4615 alpha : (float) 4616 background opacity 4617 justify : (str) 4618 text justification 4619 4620 font : (str) 4621 built-in available fonts are: 4622 - Antares 4623 - Arial 4624 - Bongas 4625 - Calco 4626 - Comae 4627 - ComicMono 4628 - Courier 4629 - Glasgo 4630 - Kanopus 4631 - LogoType 4632 - Normografo 4633 - Quikhand 4634 - SmartCouric 4635 - Theemim 4636 - Times 4637 - VictorMono 4638 - More fonts at: https://vedo.embl.es/fonts/ 4639 4640 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4641 4642 Examples: 4643 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4644 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4645 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4646 4647 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4648 """ 4649 super().__init__() 4650 self.name = "Text2D" 4651 4652 self.mapper = vtki.new("TextMapper") 4653 self.SetMapper(self.mapper) 4654 4655 self.properties = self.mapper.GetTextProperty() 4656 self.actor = self 4657 self.actor.retrieve_object = weak_ref_to(self) 4658 4659 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4660 4661 # automatic black or white 4662 if c is None: 4663 c = (0.1, 0.1, 0.1) 4664 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4665 if vedo.plotter_instance.renderer.GetGradientBackground(): 4666 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4667 else: 4668 bgcol = vedo.plotter_instance.renderer.GetBackground() 4669 c = (0.9, 0.9, 0.9) 4670 if np.sum(bgcol) > 1.5: 4671 c = (0.1, 0.1, 0.1) 4672 4673 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4674 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4675 self.PickableOff() 4676 4677 def pos(self, pos="top-left", justify=""): 4678 """ 4679 Set position of the text to draw. Keyword `pos` can be a string 4680 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4681 """ 4682 ajustify = "top-left" # autojustify 4683 if isinstance(pos, str): # corners 4684 ajustify = pos 4685 if "top" in pos: 4686 if "left" in pos: 4687 pos = (0.008, 0.994) 4688 elif "right" in pos: 4689 pos = (0.994, 0.994) 4690 elif "mid" in pos or "cent" in pos: 4691 pos = (0.5, 0.994) 4692 elif "bottom" in pos: 4693 if "left" in pos: 4694 pos = (0.008, 0.008) 4695 elif "right" in pos: 4696 pos = (0.994, 0.008) 4697 elif "mid" in pos or "cent" in pos: 4698 pos = (0.5, 0.008) 4699 elif "mid" in pos or "cent" in pos: 4700 if "left" in pos: 4701 pos = (0.008, 0.5) 4702 elif "right" in pos: 4703 pos = (0.994, 0.5) 4704 else: 4705 pos = (0.5, 0.5) 4706 4707 else: 4708 vedo.logger.warning(f"cannot understand text position {pos}") 4709 pos = (0.008, 0.994) 4710 ajustify = "top-left" 4711 4712 elif len(pos) != 2: 4713 vedo.logger.error("pos must be of length 2 or integer value or string") 4714 raise RuntimeError() 4715 4716 if not justify: 4717 justify = ajustify 4718 4719 self.properties.SetJustificationToLeft() 4720 if "top" in justify: 4721 self.properties.SetVerticalJustificationToTop() 4722 if "bottom" in justify: 4723 self.properties.SetVerticalJustificationToBottom() 4724 if "cent" in justify or "mid" in justify: 4725 self.properties.SetJustificationToCentered() 4726 if "left" in justify: 4727 self.properties.SetJustificationToLeft() 4728 if "right" in justify: 4729 self.properties.SetJustificationToRight() 4730 4731 self.SetPosition(pos) 4732 return self 4733 4734 def text(self, txt=None): 4735 """Set/get the input text string.""" 4736 if txt is None: 4737 return self.mapper.GetInput() 4738 4739 if ":" in txt: 4740 for r in _reps: 4741 txt = txt.replace(r[0], r[1]) 4742 else: 4743 txt = str(txt) 4744 4745 self.mapper.SetInput(txt) 4746 return self 4747 4748 def size(self, s): 4749 """Set the font size.""" 4750 self.properties.SetFontSize(int(s * 22.5)) 4751 return self 4752 4753 4754class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation): 4755 # PROBABLY USELESS given that Text2D does pretty much the same ... 4756 """ 4757 Annotate the window corner with 2D text. 4758 4759 See `Text2D` description as the basic functionality is very similar. 4760 4761 The added value of this class is the possibility to manage with one single 4762 object the all corner annotations (instead of creating 4 `Text2D` instances). 4763 4764 Examples: 4765 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 4766 """ 4767 4768 def __init__(self, c=None) -> None: 4769 4770 super().__init__() 4771 4772 self.properties = self.GetTextProperty() 4773 4774 # automatic black or white 4775 if c is None: 4776 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4777 c = (0.9, 0.9, 0.9) 4778 if vedo.plotter_instance.renderer.GetGradientBackground(): 4779 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4780 else: 4781 bgcol = vedo.plotter_instance.renderer.GetBackground() 4782 if np.sum(bgcol) > 1.5: 4783 c = (0.1, 0.1, 0.1) 4784 else: 4785 c = (0.5, 0.5, 0.5) 4786 4787 self.SetNonlinearFontScaleFactor(1 / 2.75) 4788 self.PickableOff() 4789 self.properties.SetColor(get_color(c)) 4790 self.properties.SetBold(False) 4791 self.properties.SetItalic(False) 4792 4793 def size(self, s:float, linear=False) -> "CornerAnnotation": 4794 """ 4795 The font size is calculated as the largest possible value such that the annotations 4796 for the given viewport do not overlap. 4797 4798 This font size can be scaled non-linearly with the viewport size, to maintain an 4799 acceptable readable size at larger viewport sizes, without being too big. 4800 `f' = linearScale * pow(f,nonlinearScale)` 4801 """ 4802 if linear: 4803 self.SetLinearFontScaleFactor(s * 5.5) 4804 else: 4805 self.SetNonlinearFontScaleFactor(s / 2.75) 4806 return self 4807 4808 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4809 """Set text at the assigned position""" 4810 4811 if isinstance(pos, str): # corners 4812 if "top" in pos: 4813 if "left" in pos: pos = 2 4814 elif "right" in pos: pos = 3 4815 elif "mid" in pos or "cent" in pos: pos = 7 4816 elif "bottom" in pos: 4817 if "left" in pos: pos = 0 4818 elif "right" in pos: pos = 1 4819 elif "mid" in pos or "cent" in pos: pos = 4 4820 else: 4821 if "left" in pos: pos = 6 4822 elif "right" in pos: pos = 5 4823 else: pos = 2 4824 4825 if "\\" in repr(txt): 4826 for r in _reps: 4827 txt = txt.replace(r[0], r[1]) 4828 else: 4829 txt = str(txt) 4830 4831 self.SetText(pos, txt) 4832 return self 4833 4834 def clear(self) -> "CornerAnnotation": 4835 """Remove all text from all corners""" 4836 self.ClearAllTexts() 4837 return self 4838 4839 4840class Latex(Image): 4841 """ 4842 Render Latex text and formulas. 4843 """ 4844 4845 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4846 """ 4847 Render Latex text and formulas. 4848 4849 Arguments: 4850 formula : (str) 4851 latex text string 4852 pos : (list) 4853 position coordinates in space 4854 bg : (color) 4855 background color box 4856 res : (int) 4857 dpi resolution 4858 usetex : (bool) 4859 use latex compiler of matplotlib if available 4860 4861 You can access the latex formula in `Latex.formula`. 4862 4863 Examples: 4864 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4865 4866 ![](https://vedo.embl.es/images/pyplot/latex.png) 4867 """ 4868 from tempfile import NamedTemporaryFile 4869 import matplotlib.pyplot as mpltib 4870 4871 def build_img_plt(formula, tfile): 4872 4873 mpltib.rc("text", usetex=usetex) 4874 4875 formula1 = "$" + formula + "$" 4876 mpltib.axis("off") 4877 col = get_color(c) 4878 if bg: 4879 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4880 else: 4881 bx = None 4882 mpltib.text( 4883 0.5, 4884 0.5, 4885 formula1, 4886 size=res, 4887 color=col, 4888 alpha=alpha, 4889 ha="center", 4890 va="center", 4891 bbox=bx, 4892 ) 4893 mpltib.savefig( 4894 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4895 ) 4896 mpltib.close() 4897 4898 if len(pos) == 2: 4899 pos = (pos[0], pos[1], 0) 4900 4901 tmp_file = NamedTemporaryFile(delete=True) 4902 tmp_file.name = tmp_file.name + ".png" 4903 4904 build_img_plt(formula, tmp_file.name) 4905 4906 super().__init__(tmp_file.name, channels=4) 4907 self.alpha(alpha) 4908 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4909 self.pos(pos) 4910 self.name = "Latex" 4911 self.formula = formula 4912 4913 # except: 4914 # printc("Error in Latex()\n", formula, c="r") 4915 # printc(" latex or dvipng not installed?", c="r") 4916 # printc(" Try: usetex=False", c="r") 4917 # printc(" Try: sudo apt install dvipng", c="r") 4918 4919 4920class ConvexHull(Mesh): 4921 """ 4922 Create the 2D/3D convex hull from a set of points. 4923 """ 4924 4925 def __init__(self, pts) -> None: 4926 """ 4927 Create the 2D/3D convex hull from a set of input points or input Mesh. 4928 4929 Examples: 4930 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4931 4932 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4933 """ 4934 if utils.is_sequence(pts): 4935 pts = utils.make3d(pts).astype(float) 4936 mesh = Points(pts) 4937 else: 4938 mesh = pts 4939 apoly = mesh.clean().dataset 4940 4941 # Create the convex hull of the pointcloud 4942 z0, z1 = mesh.zbounds() 4943 d = mesh.diagonal_size() 4944 if (z1 - z0) / d > 0.0001: 4945 delaunay = vtki.new("Delaunay3D") 4946 delaunay.SetInputData(apoly) 4947 delaunay.Update() 4948 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4949 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4950 surfaceFilter.Update() 4951 out = surfaceFilter.GetOutput() 4952 else: 4953 delaunay = vtki.new("Delaunay2D") 4954 delaunay.SetInputData(apoly) 4955 delaunay.Update() 4956 fe = vtki.new("FeatureEdges") 4957 fe.SetInputConnection(delaunay.GetOutputPort()) 4958 fe.BoundaryEdgesOn() 4959 fe.Update() 4960 out = fe.GetOutput() 4961 4962 super().__init__(out, c=mesh.color(), alpha=0.75) 4963 self.flat() 4964 self.name = "ConvexHull" 4965 4966 4967def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True) -> "vedo.Assembly": 4968 """ 4969 Create the 3D vedo logo. 4970 4971 Arguments: 4972 distance : (float) 4973 send back logo by this distance from camera 4974 version : (bool) 4975 add version text to the right end of the logo 4976 bc : (color) 4977 text back face color 4978 """ 4979 if c is None: 4980 c = (0, 0, 0) 4981 if vedo.plotter_instance: 4982 if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5: 4983 c = [0, 0, 0] 4984 else: 4985 c = "linen" 4986 4987 font = "Comae" 4988 vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8) 4989 vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc) 4990 vlogo.properties.LightingOn() 4991 4992 vr, rul = None, None 4993 if version: 4994 vr = Text3D( 4995 vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1 4996 ).scale([1, 0.7, 1]) 4997 vr.rotate_z(90).pos(2450, 50, 80) 4998 vr.bc(bc).pickable(False) 4999 elif frame: 5000 rul = vedo.RulerAxes( 5001 (-2600, 2110, 0, 1650, 0, 0), 5002 xlabel="European Molecular Biology Laboratory", 5003 ylabel=vedo.__version__, 5004 font=font, 5005 xpadding=0.09, 5006 ypadding=0.04, 5007 ) 5008 fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0) 5009 return vedo.Assembly([vlogo, vr, fakept, rul]).scale(1 / 1725)
3676def Marker(symbol, pos=(0, 0, 0), c="k", alpha=1.0, s=0.1, filled=True) -> Any: 3677 """ 3678 Generate a marker shape. Typically used in association with `Glyph`. 3679 """ 3680 if isinstance(symbol, Mesh): 3681 return symbol.c(c).alpha(alpha).lighting("off") 3682 3683 if isinstance(symbol, int): 3684 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 3685 symbol = symbol % len(symbs) 3686 symbol = symbs[symbol] 3687 3688 if symbol == ".": 3689 mesh = Polygon(nsides=24, r=s * 0.6) 3690 elif symbol == "o": 3691 mesh = Polygon(nsides=24, r=s * 0.75) 3692 elif symbol == "O": 3693 mesh = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3694 elif symbol == "0": 3695 m1 = Disc(r1=s * 0.6, r2=s * 0.75, res=(1, 24)) 3696 m2 = Circle(r=s * 0.36).reverse() 3697 mesh = merge(m1, m2) 3698 elif symbol == "p": 3699 mesh = Polygon(nsides=5, r=s) 3700 elif symbol == "*": 3701 mesh = Star(r1=0.65 * s * 1.1, r2=s * 1.1, line=not filled) 3702 elif symbol == "h": 3703 mesh = Polygon(nsides=6, r=s) 3704 elif symbol == "D": 3705 mesh = Polygon(nsides=4, r=s) 3706 elif symbol == "d": 3707 mesh = Polygon(nsides=4, r=s * 1.1).scale([0.5, 1, 1]) 3708 elif symbol == "v": 3709 mesh = Polygon(nsides=3, r=s).rotate_z(180) 3710 elif symbol == "^": 3711 mesh = Polygon(nsides=3, r=s) 3712 elif symbol == ">": 3713 mesh = Polygon(nsides=3, r=s).rotate_z(-90) 3714 elif symbol == "<": 3715 mesh = Polygon(nsides=3, r=s).rotate_z(90) 3716 elif symbol == "s": 3717 mesh = Mesh( 3718 [[[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], [[0, 1, 2, 3]]] 3719 ).scale(s / 1.4) 3720 elif symbol == "x": 3721 mesh = Text3D("+", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3722 # mesh.rotate_z(45) 3723 elif symbol == "a": 3724 mesh = Text3D("*", pos=(0, 0, 0), s=s * 2.6, justify="center", depth=0) 3725 else: 3726 mesh = Text3D(symbol, pos=(0, 0, 0), s=s * 2, justify="center", depth=0) 3727 mesh.flat().lighting("off").wireframe(not filled).c(c).alpha(alpha) 3728 if len(pos) == 2: 3729 pos = (pos[0], pos[1], 0) 3730 mesh.pos(pos) 3731 mesh.name = "Marker" 3732 return mesh
Generate a marker shape. Typically used in association with Glyph
.
395class Line(Mesh): 396 """ 397 Build the line segment between point `p0` and point `p1`. 398 399 If `p0` is already a list of points, return the line connecting them. 400 401 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 402 """ 403 404 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None: 405 """ 406 Arguments: 407 closed : (bool) 408 join last to first point 409 res : (int) 410 resolution, number of points along the line 411 (only relevant if only 2 points are specified) 412 lw : (int) 413 line width in pixel units 414 """ 415 416 if isinstance(p1, Points): 417 p1 = p1.pos() 418 if isinstance(p0, Points): 419 p0 = p0.pos() 420 try: 421 p0 = p0.dataset 422 except AttributeError: 423 pass 424 425 if isinstance(p0, vtki.vtkPolyData): 426 poly = p0 427 top = np.array([0,0,1]) 428 base = np.array([0,0,0]) 429 430 elif utils.is_sequence(p0[0]): # detect if user is passing a list of points 431 432 p0 = utils.make3d(p0) 433 ppoints = vtki.vtkPoints() # Generate the polyline 434 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 435 lines = vtki.vtkCellArray() 436 npt = len(p0) 437 if closed: 438 lines.InsertNextCell(npt + 1) 439 else: 440 lines.InsertNextCell(npt) 441 for i in range(npt): 442 lines.InsertCellPoint(i) 443 if closed: 444 lines.InsertCellPoint(0) 445 poly = vtki.vtkPolyData() 446 poly.SetPoints(ppoints) 447 poly.SetLines(lines) 448 top = p0[-1] 449 base = p0[0] 450 if res != 2: 451 printc(f"Warning: calling Line(res={res}), try remove []?", c='y') 452 res = 2 453 454 else: # or just 2 points to link 455 456 line_source = vtki.new("LineSource") 457 p0 = utils.make3d(p0) 458 p1 = utils.make3d(p1) 459 line_source.SetPoint1(p0) 460 line_source.SetPoint2(p1) 461 line_source.SetResolution(res - 1) 462 line_source.Update() 463 poly = line_source.GetOutput() 464 top = np.asarray(p1, dtype=float) 465 base = np.asarray(p0, dtype=float) 466 467 super().__init__(poly, c, alpha) 468 469 self.slope: List[float] = [] # populated by analysis.fit_line 470 self.center: List[float] = [] 471 self.variances: List[float] = [] 472 473 self.coefficients: List[float] = [] # populated by pyplot.fit() 474 self.covariance_matrix: List[float] = [] 475 self.coefficient_errors: List[float] = [] 476 self.monte_carlo_coefficients: List[float] = [] 477 self.reduced_chi2 = -1 478 self.ndof = 0 479 self.data_sigma = 0 480 self.error_lines: List[Any] = [] 481 self.error_band = None 482 self.res = res 483 484 self.lw(lw) 485 self.properties.LightingOff() 486 self.actor.PickableOff() 487 self.actor.DragableOff() 488 self.base = base 489 self.top = top 490 self.name = "Line" 491 492 def clone(self, deep=True) -> "Line": 493 """ 494 Return a copy of the ``Line`` object. 495 496 Example: 497 ```python 498 from vedo import * 499 ln1 = Line([1,1,1], [2,2,2], lw=3).print() 500 ln2 = ln1.clone().shift(0,0,1).c('red').print() 501 show(ln1, ln2, axes=1, viewup='z').close() 502 ``` 503 ![](https://vedo.embl.es/images/feats/line_clone.png) 504 """ 505 poly = vtki.vtkPolyData() 506 if deep: 507 poly.DeepCopy(self.dataset) 508 else: 509 poly.ShallowCopy(self.dataset) 510 ln = Line(poly) 511 ln.copy_properties_from(self) 512 ln.transform = self.transform.clone() 513 ln.name = self.name 514 ln.base = self.base 515 ln.top = self.top 516 ln.pipeline = utils.OperationNode( 517 "clone", parents=[self], shape="diamond", c="#edede9") 518 return ln 519 520 def linecolor(self, lc=None) -> "Line": 521 """Assign a color to the line""" 522 # overrides mesh.linecolor which would have no effect here 523 return self.color(lc) 524 525 def eval(self, x: float) -> np.ndarray: 526 """ 527 Calculate the position of an intermediate point 528 as a fraction of the length of the line, 529 being x=0 the first point and x=1 the last point. 530 This corresponds to an imaginary point that travels along the line 531 at constant speed. 532 533 Can be used in conjunction with `lin_interpolate()` 534 to map any range to the [0,1] range. 535 """ 536 distance1 = 0.0 537 length = self.length() 538 pts = self.coordinates 539 for i in range(1, len(pts)): 540 p0 = pts[i - 1] 541 p1 = pts[i] 542 seg = p1 - p0 543 distance0 = distance1 544 distance1 += np.linalg.norm(seg) 545 w1 = distance1 / length 546 if w1 >= x: 547 break 548 w0 = distance0 / length 549 v = p0 + seg * (x - w0) / (w1 - w0) 550 return v 551 552 def find_index_at_position(self, p) -> float: 553 """ 554 Find the index of the line vertex that is closest to the point `p`. 555 Note that the returned index can be fractional if `p` is not exactly 556 one of the vertices of the line. 557 """ 558 q = self.closest_point(p) 559 a, b = sorted(self.closest_point(q, n=2, return_point_id=True)) 560 pts = self.coordinates 561 d = np.linalg.norm(pts[a] - pts[b]) 562 t = a + np.linalg.norm(pts[a] - q) / d 563 return t 564 565 def pattern(self, stipple, repeats=10) -> "Line": 566 """ 567 Define a stipple pattern for dashing the line. 568 Pass the stipple pattern as a string like `'- - -'`. 569 Repeats controls the number of times the pattern repeats in a single segment. 570 571 Examples are: `'- -', '-- - --'`, etc. 572 573 The resolution of the line (nr of points) can affect how pattern will show up. 574 575 Example: 576 ```python 577 from vedo import Line 578 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 579 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 580 ln.show(axes=1).close() 581 ``` 582 ![](https://vedo.embl.es/images/feats/line_pattern.png) 583 """ 584 stipple = str(stipple) * int(2 * repeats) 585 dimension = len(stipple) 586 587 image = vtki.vtkImageData() 588 image.SetDimensions(dimension, 1, 1) 589 image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4) 590 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 591 i_dim = 0 592 while i_dim < dimension: 593 for i in range(dimension): 594 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 595 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 596 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 597 if stipple[i] == " ": 598 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 599 else: 600 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 601 i_dim += 1 602 603 poly = self.dataset 604 605 # Create texture coordinates 606 tcoords = vtki.vtkDoubleArray() 607 tcoords.SetName("TCoordsStippledLine") 608 tcoords.SetNumberOfComponents(1) 609 tcoords.SetNumberOfTuples(poly.GetNumberOfPoints()) 610 for i in range(poly.GetNumberOfPoints()): 611 tcoords.SetTypedTuple(i, [i / 2]) 612 poly.GetPointData().SetTCoords(tcoords) 613 poly.GetPointData().Modified() 614 texture = vtki.vtkTexture() 615 texture.SetInputData(image) 616 texture.InterpolateOff() 617 texture.RepeatOn() 618 self.actor.SetTexture(texture) 619 return self 620 621 def length(self) -> float: 622 """Calculate length of the line.""" 623 distance = 0.0 624 pts = self.coordinates 625 for i in range(1, len(pts)): 626 distance += np.linalg.norm(pts[i] - pts[i - 1]) 627 return distance 628 629 def tangents(self) -> np.ndarray: 630 """ 631 Compute the tangents of a line in space. 632 633 Example: 634 ```python 635 from vedo import * 636 shape = Assembly(dataurl+"timecourse1d.npy")[58] 637 pts = shape.rotate_x(30).coordinates 638 tangents = Line(pts).tangents() 639 arrs = Arrows(pts, pts+tangents, c='blue9') 640 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 641 ``` 642 ![](https://vedo.embl.es/images/feats/line_tangents.png) 643 """ 644 v = np.gradient(self.coordinates)[0] 645 ds_dt = np.linalg.norm(v, axis=1) 646 tangent = np.array([1 / ds_dt] * 3).transpose() * v 647 return tangent 648 649 def curvature(self) -> np.ndarray: 650 """ 651 Compute the signed curvature of a line in space. 652 The signed is computed assuming the line is about coplanar to the xy plane. 653 654 Example: 655 ```python 656 from vedo import * 657 from vedo.pyplot import plot 658 shape = Assembly(dataurl+"timecourse1d.npy")[55] 659 curvs = Line(shape.coordinates).curvature() 660 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 661 shape.render_lines_as_tubes().lw(12) 662 pp = plot(curvs, ac='white', lc='yellow5') 663 show(shape, pp, N=2, bg='bb', sharecam=False).close() 664 ``` 665 ![](https://vedo.embl.es/images/feats/line_curvature.png) 666 """ 667 v = np.gradient(self.coordinates)[0] 668 a = np.gradient(v)[0] 669 av = np.cross(a, v) 670 mav = np.linalg.norm(av, axis=1) 671 mv = utils.mag2(v) 672 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 673 val[0] = val[1] 674 val[-1] = val[-2] 675 return val 676 677 def compute_curvature(self, method=0) -> "Line": 678 """ 679 Add a pointdata array named 'Curvatures' which contains 680 the curvature value at each point. 681 682 NB: keyword `method` is overridden in Mesh and has no effect here. 683 """ 684 # overrides mesh.compute_curvature 685 curvs = self.curvature() 686 vmin, vmax = np.min(curvs), np.max(curvs) 687 if vmin < 0 and vmax > 0: 688 v = max(-vmin, vmax) 689 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 690 else: 691 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 692 return self 693 694 def plot_scalar( 695 self, 696 radius=0.0, 697 height=1.1, 698 normal=(), 699 camera=None, 700 ) -> "Line": 701 """ 702 Generate a new `Line` which plots the active scalar along the line. 703 704 Arguments: 705 radius : (float) 706 distance radius to the line 707 height: (float) 708 height of the plot 709 normal: (list) 710 normal vector to the plane of the plot 711 camera: (vtkCamera) 712 camera object to use for the plot orientation 713 714 Example: 715 ```python 716 from vedo import * 717 circle = Circle(res=360).rotate_y(20) 718 pts = circle.coordinates 719 bore = Line(pts).lw(5) 720 values = np.arctan2(pts[:,1], pts[:,0]) 721 bore.pointdata["scalars"] = values + np.random.randn(360)/5 722 vap = bore.plot_scalar(radius=0, height=1) 723 show(bore, vap, axes=1, viewup='z').close() 724 ``` 725 ![](https://vedo.embl.es/images/feats/line_plot_scalar.png) 726 """ 727 ap = vtki.new("ArcPlotter") 728 ap.SetInputData(self.dataset) 729 ap.SetCamera(camera) 730 ap.SetRadius(radius) 731 ap.SetHeight(height) 732 if len(normal)>0: 733 ap.UseDefaultNormalOn() 734 ap.SetDefaultNormal(normal) 735 ap.Update() 736 vap = Line(ap.GetOutput()) 737 vap.linewidth(3).lighting('off') 738 vap.name = "ArcPlot" 739 return vap 740 741 def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh": 742 """ 743 Sweep the `Line` along the specified vector direction. 744 745 Returns a `Mesh` surface. 746 Line position is updated to allow for additional sweepings. 747 748 Example: 749 ```python 750 from vedo import Line, show 751 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 752 surf1 = aline.sweep((1,0.2,0), res=3) 753 surf2 = aline.sweep((0.2,0,1)).alpha(0.5) 754 aline.color('r').linewidth(4) 755 show(surf1, surf2, aline, axes=1).close() 756 ``` 757 ![](https://vedo.embl.es/images/feats/sweepline.png) 758 """ 759 line = self.dataset 760 rows = line.GetNumberOfPoints() 761 762 spacing = 1 / res 763 surface = vtki.vtkPolyData() 764 765 res += 1 766 npts = rows * res 767 npolys = (rows - 1) * (res - 1) 768 points = vtki.vtkPoints() 769 points.Allocate(npts) 770 771 cnt = 0 772 x = [0.0, 0.0, 0.0] 773 for row in range(rows): 774 for col in range(res): 775 p = [0.0, 0.0, 0.0] 776 line.GetPoint(row, p) 777 x[0] = p[0] + direction[0] * col * spacing 778 x[1] = p[1] + direction[1] * col * spacing 779 x[2] = p[2] + direction[2] * col * spacing 780 points.InsertPoint(cnt, x) 781 cnt += 1 782 783 # Generate the quads 784 polys = vtki.vtkCellArray() 785 polys.Allocate(npolys * 4) 786 pts = [0, 0, 0, 0] 787 for row in range(rows - 1): 788 for col in range(res - 1): 789 pts[0] = col + row * res 790 pts[1] = pts[0] + 1 791 pts[2] = pts[0] + res + 1 792 pts[3] = pts[0] + res 793 polys.InsertNextCell(4, pts) 794 surface.SetPoints(points) 795 surface.SetPolys(polys) 796 asurface = Mesh(surface) 797 asurface.copy_properties_from(self) 798 asurface.lighting("default") 799 self.coordinates = self.coordinates + direction 800 return asurface 801 802 def reverse(self): 803 """Reverse the points sequence order.""" 804 pts = np.flip(self.coordinates, axis=0) 805 self.coordinates = pts 806 return self
Build the line segment between point p0
and point p1
.
If p0
is already a list of points, return the line connecting them.
A 2D set of coords can also be passed as p0=[x..], p1=[y..]
.
404 def __init__(self, p0, p1=None, closed=False, res=2, lw=1, c="k1", alpha=1.0) -> None: 405 """ 406 Arguments: 407 closed : (bool) 408 join last to first point 409 res : (int) 410 resolution, number of points along the line 411 (only relevant if only 2 points are specified) 412 lw : (int) 413 line width in pixel units 414 """ 415 416 if isinstance(p1, Points): 417 p1 = p1.pos() 418 if isinstance(p0, Points): 419 p0 = p0.pos() 420 try: 421 p0 = p0.dataset 422 except AttributeError: 423 pass 424 425 if isinstance(p0, vtki.vtkPolyData): 426 poly = p0 427 top = np.array([0,0,1]) 428 base = np.array([0,0,0]) 429 430 elif utils.is_sequence(p0[0]): # detect if user is passing a list of points 431 432 p0 = utils.make3d(p0) 433 ppoints = vtki.vtkPoints() # Generate the polyline 434 ppoints.SetData(utils.numpy2vtk(np.asarray(p0), dtype=np.float32)) 435 lines = vtki.vtkCellArray() 436 npt = len(p0) 437 if closed: 438 lines.InsertNextCell(npt + 1) 439 else: 440 lines.InsertNextCell(npt) 441 for i in range(npt): 442 lines.InsertCellPoint(i) 443 if closed: 444 lines.InsertCellPoint(0) 445 poly = vtki.vtkPolyData() 446 poly.SetPoints(ppoints) 447 poly.SetLines(lines) 448 top = p0[-1] 449 base = p0[0] 450 if res != 2: 451 printc(f"Warning: calling Line(res={res}), try remove []?", c='y') 452 res = 2 453 454 else: # or just 2 points to link 455 456 line_source = vtki.new("LineSource") 457 p0 = utils.make3d(p0) 458 p1 = utils.make3d(p1) 459 line_source.SetPoint1(p0) 460 line_source.SetPoint2(p1) 461 line_source.SetResolution(res - 1) 462 line_source.Update() 463 poly = line_source.GetOutput() 464 top = np.asarray(p1, dtype=float) 465 base = np.asarray(p0, dtype=float) 466 467 super().__init__(poly, c, alpha) 468 469 self.slope: List[float] = [] # populated by analysis.fit_line 470 self.center: List[float] = [] 471 self.variances: List[float] = [] 472 473 self.coefficients: List[float] = [] # populated by pyplot.fit() 474 self.covariance_matrix: List[float] = [] 475 self.coefficient_errors: List[float] = [] 476 self.monte_carlo_coefficients: List[float] = [] 477 self.reduced_chi2 = -1 478 self.ndof = 0 479 self.data_sigma = 0 480 self.error_lines: List[Any] = [] 481 self.error_band = None 482 self.res = res 483 484 self.lw(lw) 485 self.properties.LightingOff() 486 self.actor.PickableOff() 487 self.actor.DragableOff() 488 self.base = base 489 self.top = top 490 self.name = "Line"
Arguments:
- closed : (bool) join last to first point
- res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
- lw : (int) line width in pixel units
492 def clone(self, deep=True) -> "Line": 493 """ 494 Return a copy of the ``Line`` object. 495 496 Example: 497 ```python 498 from vedo import * 499 ln1 = Line([1,1,1], [2,2,2], lw=3).print() 500 ln2 = ln1.clone().shift(0,0,1).c('red').print() 501 show(ln1, ln2, axes=1, viewup='z').close() 502 ``` 503 ![](https://vedo.embl.es/images/feats/line_clone.png) 504 """ 505 poly = vtki.vtkPolyData() 506 if deep: 507 poly.DeepCopy(self.dataset) 508 else: 509 poly.ShallowCopy(self.dataset) 510 ln = Line(poly) 511 ln.copy_properties_from(self) 512 ln.transform = self.transform.clone() 513 ln.name = self.name 514 ln.base = self.base 515 ln.top = self.top 516 ln.pipeline = utils.OperationNode( 517 "clone", parents=[self], shape="diamond", c="#edede9") 518 return ln
Return a copy of the Line
object.
Example:
from vedo import * ln1 = Line([1,1,1], [2,2,2], lw=3).print() ln2 = ln1.clone().shift(0,0,1).c('red').print() show(ln1, ln2, axes=1, viewup='z').close()
520 def linecolor(self, lc=None) -> "Line": 521 """Assign a color to the line""" 522 # overrides mesh.linecolor which would have no effect here 523 return self.color(lc)
Assign a color to the line
525 def eval(self, x: float) -> np.ndarray: 526 """ 527 Calculate the position of an intermediate point 528 as a fraction of the length of the line, 529 being x=0 the first point and x=1 the last point. 530 This corresponds to an imaginary point that travels along the line 531 at constant speed. 532 533 Can be used in conjunction with `lin_interpolate()` 534 to map any range to the [0,1] range. 535 """ 536 distance1 = 0.0 537 length = self.length() 538 pts = self.coordinates 539 for i in range(1, len(pts)): 540 p0 = pts[i - 1] 541 p1 = pts[i] 542 seg = p1 - p0 543 distance0 = distance1 544 distance1 += np.linalg.norm(seg) 545 w1 = distance1 / length 546 if w1 >= x: 547 break 548 w0 = distance0 / length 549 v = p0 + seg * (x - w0) / (w1 - w0) 550 return v
Calculate the position of an intermediate point as a fraction of the length of the line, being x=0 the first point and x=1 the last point. This corresponds to an imaginary point that travels along the line at constant speed.
Can be used in conjunction with lin_interpolate()
to map any range to the [0,1] range.
552 def find_index_at_position(self, p) -> float: 553 """ 554 Find the index of the line vertex that is closest to the point `p`. 555 Note that the returned index can be fractional if `p` is not exactly 556 one of the vertices of the line. 557 """ 558 q = self.closest_point(p) 559 a, b = sorted(self.closest_point(q, n=2, return_point_id=True)) 560 pts = self.coordinates 561 d = np.linalg.norm(pts[a] - pts[b]) 562 t = a + np.linalg.norm(pts[a] - q) / d 563 return t
Find the index of the line vertex that is closest to the point p
.
Note that the returned index can be fractional if p
is not exactly
one of the vertices of the line.
565 def pattern(self, stipple, repeats=10) -> "Line": 566 """ 567 Define a stipple pattern for dashing the line. 568 Pass the stipple pattern as a string like `'- - -'`. 569 Repeats controls the number of times the pattern repeats in a single segment. 570 571 Examples are: `'- -', '-- - --'`, etc. 572 573 The resolution of the line (nr of points) can affect how pattern will show up. 574 575 Example: 576 ```python 577 from vedo import Line 578 pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] 579 ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) 580 ln.show(axes=1).close() 581 ``` 582 ![](https://vedo.embl.es/images/feats/line_pattern.png) 583 """ 584 stipple = str(stipple) * int(2 * repeats) 585 dimension = len(stipple) 586 587 image = vtki.vtkImageData() 588 image.SetDimensions(dimension, 1, 1) 589 image.AllocateScalars(vtki.VTK_UNSIGNED_CHAR, 4) 590 image.SetExtent(0, dimension - 1, 0, 0, 0, 0) 591 i_dim = 0 592 while i_dim < dimension: 593 for i in range(dimension): 594 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, 255) 595 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, 255) 596 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, 255) 597 if stipple[i] == " ": 598 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 0) 599 else: 600 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, 255) 601 i_dim += 1 602 603 poly = self.dataset 604 605 # Create texture coordinates 606 tcoords = vtki.vtkDoubleArray() 607 tcoords.SetName("TCoordsStippledLine") 608 tcoords.SetNumberOfComponents(1) 609 tcoords.SetNumberOfTuples(poly.GetNumberOfPoints()) 610 for i in range(poly.GetNumberOfPoints()): 611 tcoords.SetTypedTuple(i, [i / 2]) 612 poly.GetPointData().SetTCoords(tcoords) 613 poly.GetPointData().Modified() 614 texture = vtki.vtkTexture() 615 texture.SetInputData(image) 616 texture.InterpolateOff() 617 texture.RepeatOn() 618 self.actor.SetTexture(texture) 619 return self
Define a stipple pattern for dashing the line.
Pass the stipple pattern as a string like '- - -'
.
Repeats controls the number of times the pattern repeats in a single segment.
Examples are: '- -', '-- - --'
, etc.
The resolution of the line (nr of points) can affect how pattern will show up.
Example:
from vedo import Line pts = [[1, 0, 0], [5, 2, 0], [3, 3, 1]] ln = Line(pts, c='r', lw=5).pattern('- -', repeats=10) ln.show(axes=1).close()
621 def length(self) -> float: 622 """Calculate length of the line.""" 623 distance = 0.0 624 pts = self.coordinates 625 for i in range(1, len(pts)): 626 distance += np.linalg.norm(pts[i] - pts[i - 1]) 627 return distance
Calculate length of the line.
629 def tangents(self) -> np.ndarray: 630 """ 631 Compute the tangents of a line in space. 632 633 Example: 634 ```python 635 from vedo import * 636 shape = Assembly(dataurl+"timecourse1d.npy")[58] 637 pts = shape.rotate_x(30).coordinates 638 tangents = Line(pts).tangents() 639 arrs = Arrows(pts, pts+tangents, c='blue9') 640 show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close() 641 ``` 642 ![](https://vedo.embl.es/images/feats/line_tangents.png) 643 """ 644 v = np.gradient(self.coordinates)[0] 645 ds_dt = np.linalg.norm(v, axis=1) 646 tangent = np.array([1 / ds_dt] * 3).transpose() * v 647 return tangent
Compute the tangents of a line in space.
Example:
from vedo import * shape = Assembly(dataurl+"timecourse1d.npy")[58] pts = shape.rotate_x(30).coordinates tangents = Line(pts).tangents() arrs = Arrows(pts, pts+tangents, c='blue9') show(shape.c('red5').lw(5), arrs, bg='bb', axes=1).close()
649 def curvature(self) -> np.ndarray: 650 """ 651 Compute the signed curvature of a line in space. 652 The signed is computed assuming the line is about coplanar to the xy plane. 653 654 Example: 655 ```python 656 from vedo import * 657 from vedo.pyplot import plot 658 shape = Assembly(dataurl+"timecourse1d.npy")[55] 659 curvs = Line(shape.coordinates).curvature() 660 shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') 661 shape.render_lines_as_tubes().lw(12) 662 pp = plot(curvs, ac='white', lc='yellow5') 663 show(shape, pp, N=2, bg='bb', sharecam=False).close() 664 ``` 665 ![](https://vedo.embl.es/images/feats/line_curvature.png) 666 """ 667 v = np.gradient(self.coordinates)[0] 668 a = np.gradient(v)[0] 669 av = np.cross(a, v) 670 mav = np.linalg.norm(av, axis=1) 671 mv = utils.mag2(v) 672 val = mav * np.sign(av[:, 2]) / np.power(mv, 1.5) 673 val[0] = val[1] 674 val[-1] = val[-2] 675 return val
Compute the signed curvature of a line in space. The signed is computed assuming the line is about coplanar to the xy plane.
Example:
from vedo import * from vedo.pyplot import plot shape = Assembly(dataurl+"timecourse1d.npy")[55] curvs = Line(shape.coordinates).curvature() shape.cmap('coolwarm', curvs, vmin=-2,vmax=2).add_scalarbar3d(c='w') shape.render_lines_as_tubes().lw(12) pp = plot(curvs, ac='white', lc='yellow5') show(shape, pp, N=2, bg='bb', sharecam=False).close()
677 def compute_curvature(self, method=0) -> "Line": 678 """ 679 Add a pointdata array named 'Curvatures' which contains 680 the curvature value at each point. 681 682 NB: keyword `method` is overridden in Mesh and has no effect here. 683 """ 684 # overrides mesh.compute_curvature 685 curvs = self.curvature() 686 vmin, vmax = np.min(curvs), np.max(curvs) 687 if vmin < 0 and vmax > 0: 688 v = max(-vmin, vmax) 689 self.cmap("coolwarm", curvs, vmin=-v, vmax=v, name="Curvature") 690 else: 691 self.cmap("coolwarm", curvs, vmin=vmin, vmax=vmax, name="Curvature") 692 return self
Add a pointdata array named 'Curvatures' which contains the curvature value at each point.
NB: keyword method
is overridden in Mesh and has no effect here.
694 def plot_scalar( 695 self, 696 radius=0.0, 697 height=1.1, 698 normal=(), 699 camera=None, 700 ) -> "Line": 701 """ 702 Generate a new `Line` which plots the active scalar along the line. 703 704 Arguments: 705 radius : (float) 706 distance radius to the line 707 height: (float) 708 height of the plot 709 normal: (list) 710 normal vector to the plane of the plot 711 camera: (vtkCamera) 712 camera object to use for the plot orientation 713 714 Example: 715 ```python 716 from vedo import * 717 circle = Circle(res=360).rotate_y(20) 718 pts = circle.coordinates 719 bore = Line(pts).lw(5) 720 values = np.arctan2(pts[:,1], pts[:,0]) 721 bore.pointdata["scalars"] = values + np.random.randn(360)/5 722 vap = bore.plot_scalar(radius=0, height=1) 723 show(bore, vap, axes=1, viewup='z').close() 724 ``` 725 ![](https://vedo.embl.es/images/feats/line_plot_scalar.png) 726 """ 727 ap = vtki.new("ArcPlotter") 728 ap.SetInputData(self.dataset) 729 ap.SetCamera(camera) 730 ap.SetRadius(radius) 731 ap.SetHeight(height) 732 if len(normal)>0: 733 ap.UseDefaultNormalOn() 734 ap.SetDefaultNormal(normal) 735 ap.Update() 736 vap = Line(ap.GetOutput()) 737 vap.linewidth(3).lighting('off') 738 vap.name = "ArcPlot" 739 return vap
Generate a new Line
which plots the active scalar along the line.
Arguments:
- radius : (float) distance radius to the line
- height: (float) height of the plot
- normal: (list) normal vector to the plane of the plot
- camera: (vtkCamera) camera object to use for the plot orientation
Example:
from vedo import * circle = Circle(res=360).rotate_y(20) pts = circle.coordinates bore = Line(pts).lw(5) values = np.arctan2(pts[:,1], pts[:,0]) bore.pointdata["scalars"] = values + np.random.randn(360)/5 vap = bore.plot_scalar(radius=0, height=1) show(bore, vap, axes=1, viewup='z').close()
741 def sweep(self, direction=(1, 0, 0), res=1) -> "Mesh": 742 """ 743 Sweep the `Line` along the specified vector direction. 744 745 Returns a `Mesh` surface. 746 Line position is updated to allow for additional sweepings. 747 748 Example: 749 ```python 750 from vedo import Line, show 751 aline = Line([(0,0,0),(1,3,0),(2,4,0)]) 752 surf1 = aline.sweep((1,0.2,0), res=3) 753 surf2 = aline.sweep((0.2,0,1)).alpha(0.5) 754 aline.color('r').linewidth(4) 755 show(surf1, surf2, aline, axes=1).close() 756 ``` 757 ![](https://vedo.embl.es/images/feats/sweepline.png) 758 """ 759 line = self.dataset 760 rows = line.GetNumberOfPoints() 761 762 spacing = 1 / res 763 surface = vtki.vtkPolyData() 764 765 res += 1 766 npts = rows * res 767 npolys = (rows - 1) * (res - 1) 768 points = vtki.vtkPoints() 769 points.Allocate(npts) 770 771 cnt = 0 772 x = [0.0, 0.0, 0.0] 773 for row in range(rows): 774 for col in range(res): 775 p = [0.0, 0.0, 0.0] 776 line.GetPoint(row, p) 777 x[0] = p[0] + direction[0] * col * spacing 778 x[1] = p[1] + direction[1] * col * spacing 779 x[2] = p[2] + direction[2] * col * spacing 780 points.InsertPoint(cnt, x) 781 cnt += 1 782 783 # Generate the quads 784 polys = vtki.vtkCellArray() 785 polys.Allocate(npolys * 4) 786 pts = [0, 0, 0, 0] 787 for row in range(rows - 1): 788 for col in range(res - 1): 789 pts[0] = col + row * res 790 pts[1] = pts[0] + 1 791 pts[2] = pts[0] + res + 1 792 pts[3] = pts[0] + res 793 polys.InsertNextCell(4, pts) 794 surface.SetPoints(points) 795 surface.SetPolys(polys) 796 asurface = Mesh(surface) 797 asurface.copy_properties_from(self) 798 asurface.lighting("default") 799 self.coordinates = self.coordinates + direction 800 return asurface
Sweep the Line
along the specified vector direction.
Returns a Mesh
surface.
Line position is updated to allow for additional sweepings.
Example:
from vedo import Line, show aline = Line([(0,0,0),(1,3,0),(2,4,0)]) surf1 = aline.sweep((1,0.2,0), res=3) surf2 = aline.sweep((0.2,0,1)).alpha(0.5) aline.color('r').linewidth(4) show(surf1, surf2, aline, axes=1).close()
809class DashedLine(Mesh): 810 """ 811 Consider using `Line.pattern()` instead. 812 813 Build a dashed line segment between points `p0` and `p1`. 814 If `p0` is a list of points returns the line connecting them. 815 A 2D set of coords can also be passed as `p0=[x..], p1=[y..]`. 816 """ 817 818 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None: 819 """ 820 Arguments: 821 closed : (bool) 822 join last to first point 823 spacing : (float) 824 relative size of the dash 825 lw : (int) 826 line width in pixels 827 """ 828 if isinstance(p1, vtki.vtkActor): 829 p1 = p1.GetPosition() 830 if isinstance(p0, vtki.vtkActor): 831 p0 = p0.GetPosition() 832 if isinstance(p0, Points): 833 p0 = p0.coordinates 834 835 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 836 if len(p0) > 3: 837 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 838 # assume input is 2D xlist, ylist 839 p0 = np.stack((p0, p1), axis=1) 840 p1 = None 841 p0 = utils.make3d(p0) 842 if closed: 843 p0 = np.append(p0, [p0[0]], axis=0) 844 845 if p1 is not None: # assume passing p0=[x,y] 846 if len(p0) == 2 and not utils.is_sequence(p0[0]): 847 p0 = (p0[0], p0[1], 0) 848 if len(p1) == 2 and not utils.is_sequence(p1[0]): 849 p1 = (p1[0], p1[1], 0) 850 851 # detect if user is passing a list of points: 852 if utils.is_sequence(p0[0]): 853 listp = p0 854 else: # or just 2 points to link 855 listp = [p0, p1] 856 857 listp = np.array(listp) 858 if listp.shape[1] == 2: 859 listp = np.c_[listp, np.zeros(listp.shape[0])] 860 861 xmn = np.min(listp, axis=0) 862 xmx = np.max(listp, axis=0) 863 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 864 if not dlen: 865 super().__init__(vtki.vtkPolyData(), c, alpha) 866 self.name = "DashedLine (void)" 867 return 868 869 qs = [] 870 for ipt in range(len(listp) - 1): 871 p0 = listp[ipt] 872 p1 = listp[ipt + 1] 873 v = p1 - p0 874 vdist = np.linalg.norm(v) 875 n1 = int(vdist / dlen) 876 if not n1: 877 continue 878 879 res = 0.0 880 for i in range(n1 + 2): 881 ist = (i - 0.5) / n1 882 ist = max(ist, 0) 883 qi = p0 + v * (ist - res / vdist) 884 if ist > 1: 885 qi = p1 886 res = np.linalg.norm(qi - p1) 887 qs.append(qi) 888 break 889 qs.append(qi) 890 891 polylns = vtki.new("AppendPolyData") 892 for i, q1 in enumerate(qs): 893 if not i % 2: 894 continue 895 q0 = qs[i - 1] 896 line_source = vtki.new("LineSource") 897 line_source.SetPoint1(q0) 898 line_source.SetPoint2(q1) 899 line_source.Update() 900 polylns.AddInputData(line_source.GetOutput()) 901 polylns.Update() 902 903 super().__init__(polylns.GetOutput(), c, alpha) 904 self.lw(lw).lighting("off") 905 self.base = listp[0] 906 if closed: 907 self.top = listp[-2] 908 else: 909 self.top = listp[-1] 910 self.name = "DashedLine"
Consider using Line.pattern()
instead.
Build a dashed line segment between points p0
and p1
.
If p0
is a list of points returns the line connecting them.
A 2D set of coords can also be passed as p0=[x..], p1=[y..]
.
818 def __init__(self, p0, p1=None, spacing=0.1, closed=False, lw=2, c="k5", alpha=1.0) -> None: 819 """ 820 Arguments: 821 closed : (bool) 822 join last to first point 823 spacing : (float) 824 relative size of the dash 825 lw : (int) 826 line width in pixels 827 """ 828 if isinstance(p1, vtki.vtkActor): 829 p1 = p1.GetPosition() 830 if isinstance(p0, vtki.vtkActor): 831 p0 = p0.GetPosition() 832 if isinstance(p0, Points): 833 p0 = p0.coordinates 834 835 # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: 836 if len(p0) > 3: 837 if not utils.is_sequence(p0[0]) and not utils.is_sequence(p1[0]) and len(p0) == len(p1): 838 # assume input is 2D xlist, ylist 839 p0 = np.stack((p0, p1), axis=1) 840 p1 = None 841 p0 = utils.make3d(p0) 842 if closed: 843 p0 = np.append(p0, [p0[0]], axis=0) 844 845 if p1 is not None: # assume passing p0=[x,y] 846 if len(p0) == 2 and not utils.is_sequence(p0[0]): 847 p0 = (p0[0], p0[1], 0) 848 if len(p1) == 2 and not utils.is_sequence(p1[0]): 849 p1 = (p1[0], p1[1], 0) 850 851 # detect if user is passing a list of points: 852 if utils.is_sequence(p0[0]): 853 listp = p0 854 else: # or just 2 points to link 855 listp = [p0, p1] 856 857 listp = np.array(listp) 858 if listp.shape[1] == 2: 859 listp = np.c_[listp, np.zeros(listp.shape[0])] 860 861 xmn = np.min(listp, axis=0) 862 xmx = np.max(listp, axis=0) 863 dlen = np.linalg.norm(xmx - xmn) * np.clip(spacing, 0.01, 1.0) / 10 864 if not dlen: 865 super().__init__(vtki.vtkPolyData(), c, alpha) 866 self.name = "DashedLine (void)" 867 return 868 869 qs = [] 870 for ipt in range(len(listp) - 1): 871 p0 = listp[ipt] 872 p1 = listp[ipt + 1] 873 v = p1 - p0 874 vdist = np.linalg.norm(v) 875 n1 = int(vdist / dlen) 876 if not n1: 877 continue 878 879 res = 0.0 880 for i in range(n1 + 2): 881 ist = (i - 0.5) / n1 882 ist = max(ist, 0) 883 qi = p0 + v * (ist - res / vdist) 884 if ist > 1: 885 qi = p1 886 res = np.linalg.norm(qi - p1) 887 qs.append(qi) 888 break 889 qs.append(qi) 890 891 polylns = vtki.new("AppendPolyData") 892 for i, q1 in enumerate(qs): 893 if not i % 2: 894 continue 895 q0 = qs[i - 1] 896 line_source = vtki.new("LineSource") 897 line_source.SetPoint1(q0) 898 line_source.SetPoint2(q1) 899 line_source.Update() 900 polylns.AddInputData(line_source.GetOutput()) 901 polylns.Update() 902 903 super().__init__(polylns.GetOutput(), c, alpha) 904 self.lw(lw).lighting("off") 905 self.base = listp[0] 906 if closed: 907 self.top = listp[-2] 908 else: 909 self.top = listp[-1] 910 self.name = "DashedLine"
Arguments:
- closed : (bool) join last to first point
- spacing : (float) relative size of the dash
- lw : (int) line width in pixels
913class RoundedLine(Mesh): 914 """ 915 Create a 2D line of specified thickness (in absolute units) passing through 916 a list of input points. Borders of the line are rounded. 917 """ 918 919 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None: 920 """ 921 Arguments: 922 pts : (list) 923 a list of points in 2D or 3D (z will be ignored). 924 lw : (float) 925 thickness of the line. 926 res : (int) 927 resolution of the rounded regions 928 929 Example: 930 ```python 931 from vedo import * 932 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 933 ln = Line(pts).z(0.01) 934 ln.color("red5").linewidth(2) 935 rl = RoundedLine(pts, 0.6) 936 show(Points(pts), ln, rl, axes=1).close() 937 ``` 938 ![](https://vedo.embl.es/images/feats/rounded_line.png) 939 """ 940 pts = utils.make3d(pts) 941 942 def _getpts(pts, revd=False): 943 944 if revd: 945 pts = list(reversed(pts)) 946 947 if len(pts) == 2: 948 p0, p1 = pts 949 v = p1 - p0 950 dv = np.linalg.norm(v) 951 nv = np.cross(v, (0, 0, -1)) 952 nv = nv / np.linalg.norm(nv) * lw 953 return [p0 + nv, p1 + nv] 954 955 ptsnew = [] 956 for k in range(len(pts) - 2): 957 p0 = pts[k] 958 p1 = pts[k + 1] 959 p2 = pts[k + 2] 960 v = p1 - p0 961 u = p2 - p1 962 du = np.linalg.norm(u) 963 dv = np.linalg.norm(v) 964 nv = np.cross(v, (0, 0, -1)) 965 nv = nv / np.linalg.norm(nv) * lw 966 nu = np.cross(u, (0, 0, -1)) 967 nu = nu / np.linalg.norm(nu) * lw 968 uv = np.cross(u, v) 969 if k == 0: 970 ptsnew.append(p0 + nv) 971 if uv[2] <= 0: 972 # the following computation can return a value 973 # ever so slightly > 1.0 causing arccos to fail. 974 uv_arg = np.dot(u, v) / du / dv 975 if uv_arg > 1.0: 976 # since the argument to arcos is 1, simply 977 # assign alpha to 0.0 without calculating the 978 # arccos 979 alpha = 0.0 980 else: 981 alpha = np.arccos(uv_arg) 982 db = lw * np.tan(alpha / 2) 983 p1new = p1 + nv - v / dv * db 984 ptsnew.append(p1new) 985 else: 986 p1a = p1 + nv 987 p1b = p1 + nu 988 for i in range(0, res + 1): 989 pab = p1a * (res - i) / res + p1b * i / res 990 vpab = pab - p1 991 vpab = vpab / np.linalg.norm(vpab) * lw 992 ptsnew.append(p1 + vpab) 993 if k == len(pts) - 3: 994 ptsnew.append(p2 + nu) 995 if revd: 996 ptsnew.append(p2 - nu) 997 return ptsnew 998 999 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 1000 1001 ppoints = vtki.vtkPoints() # Generate the polyline 1002 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 1003 lines = vtki.vtkCellArray() 1004 npt = len(ptsnew) 1005 lines.InsertNextCell(npt) 1006 for i in range(npt): 1007 lines.InsertCellPoint(i) 1008 poly = vtki.vtkPolyData() 1009 poly.SetPoints(ppoints) 1010 poly.SetLines(lines) 1011 vct = vtki.new("ContourTriangulator") 1012 vct.SetInputData(poly) 1013 vct.Update() 1014 1015 super().__init__(vct.GetOutput(), c, alpha) 1016 self.flat() 1017 self.properties.LightingOff() 1018 self.name = "RoundedLine" 1019 self.base = ptsnew[0] 1020 self.top = ptsnew[-1]
Create a 2D line of specified thickness (in absolute units) passing through a list of input points. Borders of the line are rounded.
919 def __init__(self, pts, lw, res=10, c="gray4", alpha=1.0) -> None: 920 """ 921 Arguments: 922 pts : (list) 923 a list of points in 2D or 3D (z will be ignored). 924 lw : (float) 925 thickness of the line. 926 res : (int) 927 resolution of the rounded regions 928 929 Example: 930 ```python 931 from vedo import * 932 pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] 933 ln = Line(pts).z(0.01) 934 ln.color("red5").linewidth(2) 935 rl = RoundedLine(pts, 0.6) 936 show(Points(pts), ln, rl, axes=1).close() 937 ``` 938 ![](https://vedo.embl.es/images/feats/rounded_line.png) 939 """ 940 pts = utils.make3d(pts) 941 942 def _getpts(pts, revd=False): 943 944 if revd: 945 pts = list(reversed(pts)) 946 947 if len(pts) == 2: 948 p0, p1 = pts 949 v = p1 - p0 950 dv = np.linalg.norm(v) 951 nv = np.cross(v, (0, 0, -1)) 952 nv = nv / np.linalg.norm(nv) * lw 953 return [p0 + nv, p1 + nv] 954 955 ptsnew = [] 956 for k in range(len(pts) - 2): 957 p0 = pts[k] 958 p1 = pts[k + 1] 959 p2 = pts[k + 2] 960 v = p1 - p0 961 u = p2 - p1 962 du = np.linalg.norm(u) 963 dv = np.linalg.norm(v) 964 nv = np.cross(v, (0, 0, -1)) 965 nv = nv / np.linalg.norm(nv) * lw 966 nu = np.cross(u, (0, 0, -1)) 967 nu = nu / np.linalg.norm(nu) * lw 968 uv = np.cross(u, v) 969 if k == 0: 970 ptsnew.append(p0 + nv) 971 if uv[2] <= 0: 972 # the following computation can return a value 973 # ever so slightly > 1.0 causing arccos to fail. 974 uv_arg = np.dot(u, v) / du / dv 975 if uv_arg > 1.0: 976 # since the argument to arcos is 1, simply 977 # assign alpha to 0.0 without calculating the 978 # arccos 979 alpha = 0.0 980 else: 981 alpha = np.arccos(uv_arg) 982 db = lw * np.tan(alpha / 2) 983 p1new = p1 + nv - v / dv * db 984 ptsnew.append(p1new) 985 else: 986 p1a = p1 + nv 987 p1b = p1 + nu 988 for i in range(0, res + 1): 989 pab = p1a * (res - i) / res + p1b * i / res 990 vpab = pab - p1 991 vpab = vpab / np.linalg.norm(vpab) * lw 992 ptsnew.append(p1 + vpab) 993 if k == len(pts) - 3: 994 ptsnew.append(p2 + nu) 995 if revd: 996 ptsnew.append(p2 - nu) 997 return ptsnew 998 999 ptsnew = _getpts(pts) + _getpts(pts, revd=True) 1000 1001 ppoints = vtki.vtkPoints() # Generate the polyline 1002 ppoints.SetData(utils.numpy2vtk(np.asarray(ptsnew), dtype=np.float32)) 1003 lines = vtki.vtkCellArray() 1004 npt = len(ptsnew) 1005 lines.InsertNextCell(npt) 1006 for i in range(npt): 1007 lines.InsertCellPoint(i) 1008 poly = vtki.vtkPolyData() 1009 poly.SetPoints(ppoints) 1010 poly.SetLines(lines) 1011 vct = vtki.new("ContourTriangulator") 1012 vct.SetInputData(poly) 1013 vct.Update() 1014 1015 super().__init__(vct.GetOutput(), c, alpha) 1016 self.flat() 1017 self.properties.LightingOff() 1018 self.name = "RoundedLine" 1019 self.base = ptsnew[0] 1020 self.top = ptsnew[-1]
Arguments:
- pts : (list) a list of points in 2D or 3D (z will be ignored).
- lw : (float) thickness of the line.
- res : (int) resolution of the rounded regions
Example:
from vedo import * pts = [(-4,-3),(1,1),(2,4),(4,1),(3,-1),(2,-5),(9,-3)] ln = Line(pts).z(0.01) ln.color("red5").linewidth(2) rl = RoundedLine(pts, 0.6) show(Points(pts), ln, rl, axes=1).close()
1543class Tube(Mesh): 1544 """ 1545 Build a tube along the line defined by a set of points. 1546 """ 1547 1548 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None: 1549 """ 1550 Arguments: 1551 r : (float, list) 1552 constant radius or list of radii. 1553 res : (int) 1554 resolution, number of the sides of the tube 1555 c : (color) 1556 constant color or list of colors for each point. 1557 1558 Example: 1559 Create a tube along a line, with data associated to each point: 1560 1561 ```python 1562 from vedo import * 1563 line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5) 1564 scalars = np.array([0, 1, 2, 3]) 1565 line.pointdata["myscalars"] = scalars 1566 tube = Tube(line, r=0.1).lw(1) 1567 tube.cmap('viridis', "myscalars").add_scalarbar3d() 1568 show(line, tube, axes=1).close() 1569 ``` 1570 1571 Examples: 1572 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1573 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1574 1575 ![](https://vedo.embl.es/images/basic/tube.png) 1576 """ 1577 if utils.is_sequence(points): 1578 vpoints = vtki.vtkPoints() 1579 idx = len(points) 1580 for p in points: 1581 vpoints.InsertNextPoint(p) 1582 line = vtki.new("PolyLine") 1583 line.GetPointIds().SetNumberOfIds(idx) 1584 for i in range(idx): 1585 line.GetPointIds().SetId(i, i) 1586 lines = vtki.vtkCellArray() 1587 lines.InsertNextCell(line) 1588 polyln = vtki.vtkPolyData() 1589 polyln.SetPoints(vpoints) 1590 polyln.SetLines(lines) 1591 self.base = np.asarray(points[0], dtype=float) 1592 self.top = np.asarray(points[-1], dtype=float) 1593 1594 elif isinstance(points, Mesh): 1595 polyln = points.dataset 1596 n = polyln.GetNumberOfPoints() 1597 self.base = np.array(polyln.GetPoint(0)) 1598 self.top = np.array(polyln.GetPoint(n - 1)) 1599 1600 # from vtkmodules.vtkFiltersCore import vtkTubeBender 1601 # bender = vtkTubeBender() 1602 # bender.SetInputData(polyln) 1603 # bender.SetRadius(r) 1604 # bender.Update() 1605 # polyln = bender.GetOutput() 1606 1607 tuf = vtki.new("TubeFilter") 1608 tuf.SetCapping(cap) 1609 tuf.SetNumberOfSides(res) 1610 tuf.SetInputData(polyln) 1611 if utils.is_sequence(r): 1612 arr = utils.numpy2vtk(r, dtype=float) 1613 arr.SetName("TubeRadius") 1614 polyln.GetPointData().AddArray(arr) 1615 polyln.GetPointData().SetActiveScalars("TubeRadius") 1616 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1617 else: 1618 tuf.SetRadius(r) 1619 1620 usingColScals = False 1621 if utils.is_sequence(c): 1622 usingColScals = True 1623 cc = vtki.vtkUnsignedCharArray() 1624 cc.SetName("TubeColors") 1625 cc.SetNumberOfComponents(3) 1626 cc.SetNumberOfTuples(len(c)) 1627 for i, ic in enumerate(c): 1628 r, g, b = get_color(ic) 1629 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1630 polyln.GetPointData().AddArray(cc) 1631 c = None 1632 tuf.Update() 1633 1634 super().__init__(tuf.GetOutput(), c, alpha) 1635 self.phong() 1636 if usingColScals: 1637 self.mapper.SetScalarModeToUsePointFieldData() 1638 self.mapper.ScalarVisibilityOn() 1639 self.mapper.SelectColorArray("TubeColors") 1640 self.mapper.Modified() 1641 self.name = "Tube"
Build a tube along the line defined by a set of points.
1548 def __init__(self, points, r=1.0, cap=True, res=12, c=None, alpha=1.0) -> None: 1549 """ 1550 Arguments: 1551 r : (float, list) 1552 constant radius or list of radii. 1553 res : (int) 1554 resolution, number of the sides of the tube 1555 c : (color) 1556 constant color or list of colors for each point. 1557 1558 Example: 1559 Create a tube along a line, with data associated to each point: 1560 1561 ```python 1562 from vedo import * 1563 line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5) 1564 scalars = np.array([0, 1, 2, 3]) 1565 line.pointdata["myscalars"] = scalars 1566 tube = Tube(line, r=0.1).lw(1) 1567 tube.cmap('viridis', "myscalars").add_scalarbar3d() 1568 show(line, tube, axes=1).close() 1569 ``` 1570 1571 Examples: 1572 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1573 - [tube_radii.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/tube_radii.py) 1574 1575 ![](https://vedo.embl.es/images/basic/tube.png) 1576 """ 1577 if utils.is_sequence(points): 1578 vpoints = vtki.vtkPoints() 1579 idx = len(points) 1580 for p in points: 1581 vpoints.InsertNextPoint(p) 1582 line = vtki.new("PolyLine") 1583 line.GetPointIds().SetNumberOfIds(idx) 1584 for i in range(idx): 1585 line.GetPointIds().SetId(i, i) 1586 lines = vtki.vtkCellArray() 1587 lines.InsertNextCell(line) 1588 polyln = vtki.vtkPolyData() 1589 polyln.SetPoints(vpoints) 1590 polyln.SetLines(lines) 1591 self.base = np.asarray(points[0], dtype=float) 1592 self.top = np.asarray(points[-1], dtype=float) 1593 1594 elif isinstance(points, Mesh): 1595 polyln = points.dataset 1596 n = polyln.GetNumberOfPoints() 1597 self.base = np.array(polyln.GetPoint(0)) 1598 self.top = np.array(polyln.GetPoint(n - 1)) 1599 1600 # from vtkmodules.vtkFiltersCore import vtkTubeBender 1601 # bender = vtkTubeBender() 1602 # bender.SetInputData(polyln) 1603 # bender.SetRadius(r) 1604 # bender.Update() 1605 # polyln = bender.GetOutput() 1606 1607 tuf = vtki.new("TubeFilter") 1608 tuf.SetCapping(cap) 1609 tuf.SetNumberOfSides(res) 1610 tuf.SetInputData(polyln) 1611 if utils.is_sequence(r): 1612 arr = utils.numpy2vtk(r, dtype=float) 1613 arr.SetName("TubeRadius") 1614 polyln.GetPointData().AddArray(arr) 1615 polyln.GetPointData().SetActiveScalars("TubeRadius") 1616 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1617 else: 1618 tuf.SetRadius(r) 1619 1620 usingColScals = False 1621 if utils.is_sequence(c): 1622 usingColScals = True 1623 cc = vtki.vtkUnsignedCharArray() 1624 cc.SetName("TubeColors") 1625 cc.SetNumberOfComponents(3) 1626 cc.SetNumberOfTuples(len(c)) 1627 for i, ic in enumerate(c): 1628 r, g, b = get_color(ic) 1629 cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) 1630 polyln.GetPointData().AddArray(cc) 1631 c = None 1632 tuf.Update() 1633 1634 super().__init__(tuf.GetOutput(), c, alpha) 1635 self.phong() 1636 if usingColScals: 1637 self.mapper.SetScalarModeToUsePointFieldData() 1638 self.mapper.ScalarVisibilityOn() 1639 self.mapper.SelectColorArray("TubeColors") 1640 self.mapper.Modified() 1641 self.name = "Tube"
Arguments:
- r : (float, list) constant radius or list of radii.
- res : (int) resolution, number of the sides of the tube
- c : (color) constant color or list of colors for each point.
Example:
Create a tube along a line, with data associated to each point:
from vedo import * line = Line([(0,0,0), (1,1,1), (2,0,1), (3,1,0)]).lw(5) scalars = np.array([0, 1, 2, 3]) line.pointdata["myscalars"] = scalars tube = Tube(line, r=0.1).lw(1) tube.cmap('viridis', "myscalars").add_scalarbar3d() show(line, tube, axes=1).close()
Examples:
1695class Tubes(Mesh): 1696 """ 1697 Build tubes around a `Lines` object. 1698 """ 1699 def __init__( 1700 self, 1701 lines, 1702 r=1, 1703 vary_radius_by_scalar=False, 1704 vary_radius_by_vector=False, 1705 vary_radius_by_vector_norm=False, 1706 vary_radius_by_absolute_scalar=False, 1707 max_radius_factor=100, 1708 cap=True, 1709 res=12 1710 ) -> None: 1711 """ 1712 Wrap tubes around the input `Lines` object. 1713 1714 Arguments: 1715 lines : (Lines) 1716 input Lines object. 1717 r : (float) 1718 constant radius 1719 vary_radius_by_scalar : (bool) 1720 use scalar array to control radius 1721 vary_radius_by_vector : (bool) 1722 use vector array to control radius 1723 vary_radius_by_vector_norm : (bool) 1724 use vector norm to control radius 1725 vary_radius_by_absolute_scalar : (bool) 1726 use absolute scalar value to control radius 1727 max_radius_factor : (float) 1728 max tube radius as a multiple of the min radius 1729 cap : (bool) 1730 capping of the tube 1731 res : (int) 1732 resolution, number of the sides of the tube 1733 c : (color) 1734 constant color or list of colors for each point. 1735 1736 Examples: 1737 - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py) 1738 """ 1739 plines = lines.dataset 1740 if plines.GetNumberOfLines() == 0: 1741 vedo.logger.warning("Tubes(): input Lines is empty.") 1742 1743 tuf = vtki.new("TubeFilter") 1744 if vary_radius_by_scalar: 1745 tuf.SetVaryRadiusToVaryRadiusByScalar() 1746 elif vary_radius_by_vector: 1747 tuf.SetVaryRadiusToVaryRadiusByVector() 1748 elif vary_radius_by_vector_norm: 1749 tuf.SetVaryRadiusToVaryRadiusByVectorNorm() 1750 elif vary_radius_by_absolute_scalar: 1751 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1752 tuf.SetRadius(r) 1753 tuf.SetCapping(cap) 1754 tuf.SetGenerateTCoords(0) 1755 tuf.SetSidesShareVertices(1) 1756 tuf.SetRadiusFactor(max_radius_factor) 1757 tuf.SetNumberOfSides(res) 1758 tuf.SetInputData(plines) 1759 tuf.Update() 1760 1761 super().__init__(tuf.GetOutput()) 1762 self.name = "Tubes"
Build tubes around a Lines
object.
1699 def __init__( 1700 self, 1701 lines, 1702 r=1, 1703 vary_radius_by_scalar=False, 1704 vary_radius_by_vector=False, 1705 vary_radius_by_vector_norm=False, 1706 vary_radius_by_absolute_scalar=False, 1707 max_radius_factor=100, 1708 cap=True, 1709 res=12 1710 ) -> None: 1711 """ 1712 Wrap tubes around the input `Lines` object. 1713 1714 Arguments: 1715 lines : (Lines) 1716 input Lines object. 1717 r : (float) 1718 constant radius 1719 vary_radius_by_scalar : (bool) 1720 use scalar array to control radius 1721 vary_radius_by_vector : (bool) 1722 use vector array to control radius 1723 vary_radius_by_vector_norm : (bool) 1724 use vector norm to control radius 1725 vary_radius_by_absolute_scalar : (bool) 1726 use absolute scalar value to control radius 1727 max_radius_factor : (float) 1728 max tube radius as a multiple of the min radius 1729 cap : (bool) 1730 capping of the tube 1731 res : (int) 1732 resolution, number of the sides of the tube 1733 c : (color) 1734 constant color or list of colors for each point. 1735 1736 Examples: 1737 - [streamlines1.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/streamlines1.py) 1738 """ 1739 plines = lines.dataset 1740 if plines.GetNumberOfLines() == 0: 1741 vedo.logger.warning("Tubes(): input Lines is empty.") 1742 1743 tuf = vtki.new("TubeFilter") 1744 if vary_radius_by_scalar: 1745 tuf.SetVaryRadiusToVaryRadiusByScalar() 1746 elif vary_radius_by_vector: 1747 tuf.SetVaryRadiusToVaryRadiusByVector() 1748 elif vary_radius_by_vector_norm: 1749 tuf.SetVaryRadiusToVaryRadiusByVectorNorm() 1750 elif vary_radius_by_absolute_scalar: 1751 tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() 1752 tuf.SetRadius(r) 1753 tuf.SetCapping(cap) 1754 tuf.SetGenerateTCoords(0) 1755 tuf.SetSidesShareVertices(1) 1756 tuf.SetRadiusFactor(max_radius_factor) 1757 tuf.SetNumberOfSides(res) 1758 tuf.SetInputData(plines) 1759 tuf.Update() 1760 1761 super().__init__(tuf.GetOutput()) 1762 self.name = "Tubes"
Wrap tubes around the input Lines
object.
Arguments:
- lines : (Lines) input Lines object.
- r : (float) constant radius
- vary_radius_by_scalar : (bool) use scalar array to control radius
- vary_radius_by_vector : (bool) use vector array to control radius
- vary_radius_by_vector_norm : (bool) use vector norm to control radius
- vary_radius_by_absolute_scalar : (bool) use absolute scalar value to control radius
- max_radius_factor : (float) max tube radius as a multiple of the min radius
- cap : (bool) capping of the tube
- res : (int) resolution, number of the sides of the tube
- c : (color) constant color or list of colors for each point.
Examples:
1644def ThickTube(pts, r1, r2, res=12, c=None, alpha=1.0) -> Union["Mesh", None]: 1645 """ 1646 Create a tube with a thickness along a line of points. 1647 1648 Example: 1649 ```python 1650 from vedo import * 1651 pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)] 1652 vline = Line(pts, lw=5, c='red5') 1653 thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1) 1654 show(vline, thick_tube, axes=1).close() 1655 ``` 1656 ![](https://vedo.embl.es/images/feats/thick_tube.png) 1657 """ 1658 1659 def make_cap(t1, t2): 1660 newpoints = t1.coordinates.tolist() + t2.coordinates.tolist() 1661 newfaces = [] 1662 for i in range(n - 1): 1663 newfaces.append([i, i + 1, i + n]) 1664 newfaces.append([i + n, i + 1, i + n + 1]) 1665 newfaces.append([2 * n - 1, 0, n]) 1666 newfaces.append([2 * n - 1, n - 1, 0]) 1667 capm = utils.buildPolyData(newpoints, newfaces) 1668 return capm 1669 1670 assert r1 < r2 1671 1672 t1 = Tube(pts, r=r1, cap=False, res=res) 1673 t2 = Tube(pts, r=r2, cap=False, res=res) 1674 1675 tc1a, tc1b = t1.boundaries().split() 1676 tc2a, tc2b = t2.boundaries().split() 1677 n = tc1b.npoints 1678 1679 tc1b.join(reset=True).clean() # needed because indices are flipped 1680 tc2b.join(reset=True).clean() 1681 1682 capa = make_cap(tc1a, tc2a) 1683 capb = make_cap(tc1b, tc2b) 1684 1685 thick_tube = merge(t1, t2, capa, capb) 1686 if thick_tube: 1687 thick_tube.c(c).alpha(alpha) 1688 thick_tube.base = t1.base 1689 thick_tube.top = t1.top 1690 thick_tube.name = "ThickTube" 1691 return thick_tube 1692 return None
Create a tube with a thickness along a line of points.
Example:
from vedo import *
pts = [[sin(x), cos(x), x/3] for x in np.arange(0.1, 3, 0.3)]
vline = Line(pts, lw=5, c='red5')
thick_tube = ThickTube(vline, r1=0.2, r2=0.3).lw(1)
show(vline, thick_tube, axes=1).close()
1023class Lines(Mesh): 1024 """ 1025 Build the line segments between two lists of points `start_pts` and `end_pts`. 1026 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 1027 """ 1028 1029 def __init__( 1030 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 1031 ) -> None: 1032 """ 1033 Arguments: 1034 scale : (float) 1035 apply a rescaling factor to the lengths. 1036 c : (color, int, str, list) 1037 color name, number, or list of [R,G,B] colors 1038 alpha : (float) 1039 opacity in range [0,1] 1040 lw : (int) 1041 line width in pixel units 1042 dotted : (bool) 1043 draw a dotted line 1044 res : (int) 1045 resolution, number of points along the line 1046 (only relevant if only 2 points are specified) 1047 1048 Examples: 1049 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 1050 1051 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 1052 """ 1053 1054 if isinstance(start_pts, vtki.vtkPolyData):######## 1055 super().__init__(start_pts, c, alpha) 1056 self.lw(lw).lighting("off") 1057 self.name = "Lines" 1058 return ######################################## 1059 1060 if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line): 1061 # passing a list of Line, see tests/issues/issue_950.py 1062 polylns = vtki.new("AppendPolyData") 1063 for ln in start_pts: 1064 polylns.AddInputData(ln.dataset) 1065 polylns.Update() 1066 1067 super().__init__(polylns.GetOutput(), c, alpha) 1068 self.lw(lw).lighting("off") 1069 if dotted: 1070 self.properties.SetLineStipplePattern(0xF0F0) 1071 self.properties.SetLineStippleRepeatFactor(1) 1072 self.name = "Lines" 1073 return ######################################## 1074 1075 if isinstance(start_pts, Points): 1076 start_pts = start_pts.coordinates 1077 if isinstance(end_pts, Points): 1078 end_pts = end_pts.coordinates 1079 1080 if end_pts is not None: 1081 start_pts = np.stack((start_pts, end_pts), axis=1) 1082 1083 polylns = vtki.new("AppendPolyData") 1084 1085 if not utils.is_ragged(start_pts): 1086 1087 for twopts in start_pts: 1088 line_source = vtki.new("LineSource") 1089 line_source.SetResolution(res) 1090 if len(twopts[0]) == 2: 1091 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 1092 else: 1093 line_source.SetPoint1(twopts[0]) 1094 1095 if scale == 1: 1096 pt2 = twopts[1] 1097 else: 1098 vers = (np.array(twopts[1]) - twopts[0]) * scale 1099 pt2 = np.array(twopts[0]) + vers 1100 1101 if len(pt2) == 2: 1102 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1103 else: 1104 line_source.SetPoint2(pt2) 1105 polylns.AddInputConnection(line_source.GetOutputPort()) 1106 1107 else: 1108 1109 polylns = vtki.new("AppendPolyData") 1110 for t in start_pts: 1111 t = utils.make3d(t) 1112 ppoints = vtki.vtkPoints() # Generate the polyline 1113 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1114 lines = vtki.vtkCellArray() 1115 npt = len(t) 1116 lines.InsertNextCell(npt) 1117 for i in range(npt): 1118 lines.InsertCellPoint(i) 1119 poly = vtki.vtkPolyData() 1120 poly.SetPoints(ppoints) 1121 poly.SetLines(lines) 1122 polylns.AddInputData(poly) 1123 1124 polylns.Update() 1125 1126 super().__init__(polylns.GetOutput(), c, alpha) 1127 self.lw(lw).lighting("off") 1128 if dotted: 1129 self.properties.SetLineStipplePattern(0xF0F0) 1130 self.properties.SetLineStippleRepeatFactor(1) 1131 1132 self.name = "Lines"
Build the line segments between two lists of points start_pts
and end_pts
.
start_pts
can be also passed in the form [[point1, point2], ...]
.
1029 def __init__( 1030 self, start_pts, end_pts=None, dotted=False, res=1, scale=1.0, lw=1, c="k4", alpha=1.0 1031 ) -> None: 1032 """ 1033 Arguments: 1034 scale : (float) 1035 apply a rescaling factor to the lengths. 1036 c : (color, int, str, list) 1037 color name, number, or list of [R,G,B] colors 1038 alpha : (float) 1039 opacity in range [0,1] 1040 lw : (int) 1041 line width in pixel units 1042 dotted : (bool) 1043 draw a dotted line 1044 res : (int) 1045 resolution, number of points along the line 1046 (only relevant if only 2 points are specified) 1047 1048 Examples: 1049 - [fitspheres2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitspheres2.py) 1050 1051 ![](https://user-images.githubusercontent.com/32848391/52503049-ac9cb600-2be4-11e9-86af-72a538af14ef.png) 1052 """ 1053 1054 if isinstance(start_pts, vtki.vtkPolyData):######## 1055 super().__init__(start_pts, c, alpha) 1056 self.lw(lw).lighting("off") 1057 self.name = "Lines" 1058 return ######################################## 1059 1060 if utils.is_sequence(start_pts) and len(start_pts)>1 and isinstance(start_pts[0], Line): 1061 # passing a list of Line, see tests/issues/issue_950.py 1062 polylns = vtki.new("AppendPolyData") 1063 for ln in start_pts: 1064 polylns.AddInputData(ln.dataset) 1065 polylns.Update() 1066 1067 super().__init__(polylns.GetOutput(), c, alpha) 1068 self.lw(lw).lighting("off") 1069 if dotted: 1070 self.properties.SetLineStipplePattern(0xF0F0) 1071 self.properties.SetLineStippleRepeatFactor(1) 1072 self.name = "Lines" 1073 return ######################################## 1074 1075 if isinstance(start_pts, Points): 1076 start_pts = start_pts.coordinates 1077 if isinstance(end_pts, Points): 1078 end_pts = end_pts.coordinates 1079 1080 if end_pts is not None: 1081 start_pts = np.stack((start_pts, end_pts), axis=1) 1082 1083 polylns = vtki.new("AppendPolyData") 1084 1085 if not utils.is_ragged(start_pts): 1086 1087 for twopts in start_pts: 1088 line_source = vtki.new("LineSource") 1089 line_source.SetResolution(res) 1090 if len(twopts[0]) == 2: 1091 line_source.SetPoint1(twopts[0][0], twopts[0][1], 0.0) 1092 else: 1093 line_source.SetPoint1(twopts[0]) 1094 1095 if scale == 1: 1096 pt2 = twopts[1] 1097 else: 1098 vers = (np.array(twopts[1]) - twopts[0]) * scale 1099 pt2 = np.array(twopts[0]) + vers 1100 1101 if len(pt2) == 2: 1102 line_source.SetPoint2(pt2[0], pt2[1], 0.0) 1103 else: 1104 line_source.SetPoint2(pt2) 1105 polylns.AddInputConnection(line_source.GetOutputPort()) 1106 1107 else: 1108 1109 polylns = vtki.new("AppendPolyData") 1110 for t in start_pts: 1111 t = utils.make3d(t) 1112 ppoints = vtki.vtkPoints() # Generate the polyline 1113 ppoints.SetData(utils.numpy2vtk(t, dtype=np.float32)) 1114 lines = vtki.vtkCellArray() 1115 npt = len(t) 1116 lines.InsertNextCell(npt) 1117 for i in range(npt): 1118 lines.InsertCellPoint(i) 1119 poly = vtki.vtkPolyData() 1120 poly.SetPoints(ppoints) 1121 poly.SetLines(lines) 1122 polylns.AddInputData(poly) 1123 1124 polylns.Update() 1125 1126 super().__init__(polylns.GetOutput(), c, alpha) 1127 self.lw(lw).lighting("off") 1128 if dotted: 1129 self.properties.SetLineStipplePattern(0xF0F0) 1130 self.properties.SetLineStippleRepeatFactor(1) 1131 1132 self.name = "Lines"
Arguments:
- scale : (float) apply a rescaling factor to the lengths.
- c : (color, int, str, list) color name, number, or list of [R,G,B] colors
- alpha : (float) opacity in range [0,1]
- lw : (int) line width in pixel units
- dotted : (bool) draw a dotted line
- res : (int) resolution, number of points along the line (only relevant if only 2 points are specified)
Examples:
1215class Spline(Line): 1216 """ 1217 Find the B-Spline curve through a set of points. This curve does not necessarily 1218 pass exactly through all the input points. Needs to import `scipy`. 1219 """ 1220 1221 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None: 1222 """ 1223 Arguments: 1224 smooth : (float) 1225 smoothing factor. 1226 - 0 = interpolate points exactly [default]. 1227 - 1 = average point positions. 1228 degree : (int) 1229 degree of the spline (between 1 and 5). 1230 easing : (str) 1231 control sensity of points along the spline. 1232 Available options are 1233 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1234 Can be used to create animations (move objects at varying speed). 1235 See e.g.: https://easings.net 1236 res : (int) 1237 number of points on the spline 1238 1239 See also: `CSpline` and `KSpline`. 1240 1241 Examples: 1242 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1243 1244 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1245 """ 1246 from scipy.interpolate import splprep, splev 1247 1248 if isinstance(points, Points): 1249 points = points.coordinates 1250 1251 points = utils.make3d(points) 1252 1253 per = 0 1254 if closed: 1255 points = np.append(points, [points[0]], axis=0) 1256 per = 1 1257 1258 if res is None: 1259 res = len(points) * 10 1260 1261 points = np.array(points, dtype=float) 1262 1263 minx, miny, minz = np.min(points, axis=0) 1264 maxx, maxy, maxz = np.max(points, axis=0) 1265 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1266 smooth *= maxb / 2 # must be in absolute units 1267 1268 x = np.linspace(0.0, 1.0, res) 1269 if easing: 1270 if easing == "InSine": 1271 x = 1.0 - np.cos((x * np.pi) / 2) 1272 elif easing == "OutSine": 1273 x = np.sin((x * np.pi) / 2) 1274 elif easing == "Sine": 1275 x = -(np.cos(np.pi * x) - 1) / 2 1276 elif easing == "InQuad": 1277 x = x * x 1278 elif easing == "OutQuad": 1279 x = 1.0 - (1 - x) * (1 - x) 1280 elif easing == "InCubic": 1281 x = x * x 1282 elif easing == "OutCubic": 1283 x = 1.0 - np.power(1 - x, 3) 1284 elif easing == "InQuart": 1285 x = x * x * x * x 1286 elif easing == "OutQuart": 1287 x = 1.0 - np.power(1 - x, 4) 1288 elif easing == "InCirc": 1289 x = 1.0 - np.sqrt(1 - np.power(x, 2)) 1290 elif easing == "OutCirc": 1291 x = np.sqrt(1.0 - np.power(x - 1, 2)) 1292 else: 1293 vedo.logger.error(f"unknown ease mode {easing}") 1294 1295 # find the knots 1296 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1297 # evaluate spLine, including interpolated points: 1298 xnew, ynew, znew = splev(x, tckp) 1299 1300 super().__init__(np.c_[xnew, ynew, znew], lw=2) 1301 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
.
1221 def __init__(self, points, smooth=0.0, degree=2, closed=False, res=None, easing="") -> None: 1222 """ 1223 Arguments: 1224 smooth : (float) 1225 smoothing factor. 1226 - 0 = interpolate points exactly [default]. 1227 - 1 = average point positions. 1228 degree : (int) 1229 degree of the spline (between 1 and 5). 1230 easing : (str) 1231 control sensity of points along the spline. 1232 Available options are 1233 `[InSine, OutSine, Sine, InQuad, OutQuad, InCubic, OutCubic, InQuart, OutQuart, InCirc, OutCirc].` 1234 Can be used to create animations (move objects at varying speed). 1235 See e.g.: https://easings.net 1236 res : (int) 1237 number of points on the spline 1238 1239 See also: `CSpline` and `KSpline`. 1240 1241 Examples: 1242 - [spline_ease.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/spline_ease.py) 1243 1244 ![](https://vedo.embl.es/images/simulations/spline_ease.gif) 1245 """ 1246 from scipy.interpolate import splprep, splev 1247 1248 if isinstance(points, Points): 1249 points = points.coordinates 1250 1251 points = utils.make3d(points) 1252 1253 per = 0 1254 if closed: 1255 points = np.append(points, [points[0]], axis=0) 1256 per = 1 1257 1258 if res is None: 1259 res = len(points) * 10 1260 1261 points = np.array(points, dtype=float) 1262 1263 minx, miny, minz = np.min(points, axis=0) 1264 maxx, maxy, maxz = np.max(points, axis=0) 1265 maxb = max(maxx - minx, maxy - miny, maxz - minz) 1266 smooth *= maxb / 2 # must be in absolute units 1267 1268 x = np.linspace(0.0, 1.0, res) 1269 if easing: 1270 if easing == "InSine": 1271 x = 1.0 - np.cos((x * np.pi) / 2) 1272 elif easing == "OutSine": 1273 x = np.sin((x * np.pi) / 2) 1274 elif easing == "Sine": 1275 x = -(np.cos(np.pi * x) - 1) / 2 1276 elif easing == "InQuad": 1277 x = x * x 1278 elif easing == "OutQuad": 1279 x = 1.0 - (1 - x) * (1 - x) 1280 elif easing == "InCubic": 1281 x = x * x 1282 elif easing == "OutCubic": 1283 x = 1.0 - np.power(1 - x, 3) 1284 elif easing == "InQuart": 1285 x = x * x * x * x 1286 elif easing == "OutQuart": 1287 x = 1.0 - np.power(1 - x, 4) 1288 elif easing == "InCirc": 1289 x = 1.0 - np.sqrt(1 - np.power(x, 2)) 1290 elif easing == "OutCirc": 1291 x = np.sqrt(1.0 - np.power(x - 1, 2)) 1292 else: 1293 vedo.logger.error(f"unknown ease mode {easing}") 1294 1295 # find the knots 1296 tckp, _ = splprep(points.T, task=0, s=smooth, k=degree, per=per) 1297 # evaluate spLine, including interpolated points: 1298 xnew, ynew, znew = splev(x, tckp) 1299 1300 super().__init__(np.c_[xnew, ynew, znew], lw=2) 1301 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:
1304class KSpline(Line): 1305 """ 1306 Return a [Kochanek spline](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline) 1307 which runs exactly through all the input points. 1308 """ 1309 1310 def __init__(self, points, 1311 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None: 1312 """ 1313 Arguments: 1314 continuity : (float) 1315 changes the sharpness in change between tangents 1316 tension : (float) 1317 changes the length of the tangent vector 1318 bias : (float) 1319 changes the direction of the tangent vector 1320 closed : (bool) 1321 join last to first point to produce a closed curve 1322 res : (int) 1323 approximate resolution of the output line. 1324 Default is 20 times the number of input points. 1325 1326 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1327 1328 Warning: 1329 This class is not necessarily generating the exact number of points 1330 as requested by `res`. Some points may be concident and removed. 1331 1332 See also: `Spline` and `CSpline`. 1333 """ 1334 if isinstance(points, Points): 1335 points = points.coordinates 1336 1337 if not res: 1338 res = len(points) * 20 1339 1340 points = utils.make3d(points).astype(float) 1341 1342 vtkKochanekSpline = vtki.get_class("KochanekSpline") 1343 xspline = vtkKochanekSpline() 1344 yspline = vtkKochanekSpline() 1345 zspline = vtkKochanekSpline() 1346 for s in [xspline, yspline, zspline]: 1347 if bias: 1348 s.SetDefaultBias(bias) 1349 if tension: 1350 s.SetDefaultTension(tension) 1351 if continuity: 1352 s.SetDefaultContinuity(continuity) 1353 s.SetClosed(closed) 1354 1355 lenp = len(points[0]) > 2 1356 1357 for i, p in enumerate(points): 1358 xspline.AddPoint(i, p[0]) 1359 yspline.AddPoint(i, p[1]) 1360 if lenp: 1361 zspline.AddPoint(i, p[2]) 1362 1363 ln = [] 1364 for pos in np.linspace(0, len(points), res): 1365 x = xspline.Evaluate(pos) 1366 y = yspline.Evaluate(pos) 1367 z = 0 1368 if lenp: 1369 z = zspline.Evaluate(pos) 1370 ln.append((x, y, z)) 1371 1372 super().__init__(ln, lw=2) 1373 self.clean() 1374 self.lighting("off") 1375 self.name = "KSpline" 1376 self.base = np.array(points[0], dtype=float) 1377 self.top = np.array(points[-1], dtype=float)
Return a Kochanek spline which runs exactly through all the input points.
1310 def __init__(self, points, 1311 continuity=0.0, tension=0.0, bias=0.0, closed=False, res=None) -> None: 1312 """ 1313 Arguments: 1314 continuity : (float) 1315 changes the sharpness in change between tangents 1316 tension : (float) 1317 changes the length of the tangent vector 1318 bias : (float) 1319 changes the direction of the tangent vector 1320 closed : (bool) 1321 join last to first point to produce a closed curve 1322 res : (int) 1323 approximate resolution of the output line. 1324 Default is 20 times the number of input points. 1325 1326 ![](https://user-images.githubusercontent.com/32848391/65975805-73fd6580-e46f-11e9-8957-75eddb28fa72.png) 1327 1328 Warning: 1329 This class is not necessarily generating the exact number of points 1330 as requested by `res`. Some points may be concident and removed. 1331 1332 See also: `Spline` and `CSpline`. 1333 """ 1334 if isinstance(points, Points): 1335 points = points.coordinates 1336 1337 if not res: 1338 res = len(points) * 20 1339 1340 points = utils.make3d(points).astype(float) 1341 1342 vtkKochanekSpline = vtki.get_class("KochanekSpline") 1343 xspline = vtkKochanekSpline() 1344 yspline = vtkKochanekSpline() 1345 zspline = vtkKochanekSpline() 1346 for s in [xspline, yspline, zspline]: 1347 if bias: 1348 s.SetDefaultBias(bias) 1349 if tension: 1350 s.SetDefaultTension(tension) 1351 if continuity: 1352 s.SetDefaultContinuity(continuity) 1353 s.SetClosed(closed) 1354 1355 lenp = len(points[0]) > 2 1356 1357 for i, p in enumerate(points): 1358 xspline.AddPoint(i, p[0]) 1359 yspline.AddPoint(i, p[1]) 1360 if lenp: 1361 zspline.AddPoint(i, p[2]) 1362 1363 ln = [] 1364 for pos in np.linspace(0, len(points), res): 1365 x = xspline.Evaluate(pos) 1366 y = yspline.Evaluate(pos) 1367 z = 0 1368 if lenp: 1369 z = zspline.Evaluate(pos) 1370 ln.append((x, y, z)) 1371 1372 super().__init__(ln, lw=2) 1373 self.clean() 1374 self.lighting("off") 1375 self.name = "KSpline" 1376 self.base = np.array(points[0], dtype=float) 1377 self.top = np.array(points[-1], dtype=float)
Arguments:
- continuity : (float) changes the sharpness in change between tangents
- tension : (float) changes the length of the tangent vector
- bias : (float) changes the direction of the tangent vector
- closed : (bool) join last to first point to produce a closed curve
- res : (int) approximate resolution of the output line. Default is 20 times the number of input points.
Warning:
This class is not necessarily generating the exact number of points as requested by
res
. Some points may be concident and removed.
1380class CSpline(Line): 1381 """ 1382 Return a Cardinal spline which runs exactly through all the input points. 1383 """ 1384 1385 def __init__(self, points, closed=False, res=None) -> None: 1386 """ 1387 Arguments: 1388 closed : (bool) 1389 join last to first point to produce a closed curve 1390 res : (int) 1391 approximate resolution of the output line. 1392 Default is 20 times the number of input points. 1393 1394 Warning: 1395 This class is not necessarily generating the exact number of points 1396 as requested by `res`. Some points may be concident and removed. 1397 1398 See also: `Spline` and `KSpline`. 1399 """ 1400 1401 if isinstance(points, Points): 1402 points = points.coordinates 1403 1404 if not res: 1405 res = len(points) * 20 1406 1407 points = utils.make3d(points).astype(float) 1408 1409 vtkCardinalSpline = vtki.get_class("CardinalSpline") 1410 xspline = vtkCardinalSpline() 1411 yspline = vtkCardinalSpline() 1412 zspline = vtkCardinalSpline() 1413 for s in [xspline, yspline, zspline]: 1414 s.SetClosed(closed) 1415 1416 lenp = len(points[0]) > 2 1417 1418 for i, p in enumerate(points): 1419 xspline.AddPoint(i, p[0]) 1420 yspline.AddPoint(i, p[1]) 1421 if lenp: 1422 zspline.AddPoint(i, p[2]) 1423 1424 ln = [] 1425 for pos in np.linspace(0, len(points), res): 1426 x = xspline.Evaluate(pos) 1427 y = yspline.Evaluate(pos) 1428 z = 0 1429 if lenp: 1430 z = zspline.Evaluate(pos) 1431 ln.append((x, y, z)) 1432 1433 super().__init__(ln, lw=2) 1434 self.clean() 1435 self.lighting("off") 1436 self.name = "CSpline" 1437 self.base = points[0] 1438 self.top = points[-1]
Return a Cardinal spline which runs exactly through all the input points.
1385 def __init__(self, points, closed=False, res=None) -> None: 1386 """ 1387 Arguments: 1388 closed : (bool) 1389 join last to first point to produce a closed curve 1390 res : (int) 1391 approximate resolution of the output line. 1392 Default is 20 times the number of input points. 1393 1394 Warning: 1395 This class is not necessarily generating the exact number of points 1396 as requested by `res`. Some points may be concident and removed. 1397 1398 See also: `Spline` and `KSpline`. 1399 """ 1400 1401 if isinstance(points, Points): 1402 points = points.coordinates 1403 1404 if not res: 1405 res = len(points) * 20 1406 1407 points = utils.make3d(points).astype(float) 1408 1409 vtkCardinalSpline = vtki.get_class("CardinalSpline") 1410 xspline = vtkCardinalSpline() 1411 yspline = vtkCardinalSpline() 1412 zspline = vtkCardinalSpline() 1413 for s in [xspline, yspline, zspline]: 1414 s.SetClosed(closed) 1415 1416 lenp = len(points[0]) > 2 1417 1418 for i, p in enumerate(points): 1419 xspline.AddPoint(i, p[0]) 1420 yspline.AddPoint(i, p[1]) 1421 if lenp: 1422 zspline.AddPoint(i, p[2]) 1423 1424 ln = [] 1425 for pos in np.linspace(0, len(points), res): 1426 x = xspline.Evaluate(pos) 1427 y = yspline.Evaluate(pos) 1428 z = 0 1429 if lenp: 1430 z = zspline.Evaluate(pos) 1431 ln.append((x, y, z)) 1432 1433 super().__init__(ln, lw=2) 1434 self.clean() 1435 self.lighting("off") 1436 self.name = "CSpline" 1437 self.base = points[0] 1438 self.top = points[-1]
Arguments:
- closed : (bool) join last to first point to produce a closed curve
- res : (int) approximate resolution of the output line. Default is 20 times the number of input points.
Warning:
This class is not necessarily generating the exact number of points as requested by
res
. Some points may be concident and removed.
1441class Bezier(Line): 1442 """ 1443 Generate the Bezier line that links the first to the last point. 1444 """ 1445 1446 def __init__(self, points, res=None) -> None: 1447 """ 1448 Example: 1449 ```python 1450 from vedo import * 1451 import numpy as np 1452 pts = np.random.randn(25,3) 1453 for i,p in enumerate(pts): 1454 p += [5*i, 15*sin(i/2), i*i*i/200] 1455 show(Points(pts), Bezier(pts), axes=1).close() 1456 ``` 1457 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1458 """ 1459 N = len(points) 1460 if res is None: 1461 res = 10 * N 1462 t = np.linspace(0, 1, num=res) 1463 bcurve = np.zeros((res, len(points[0]))) 1464 1465 def binom(n, k): 1466 b = 1 1467 for t in range(1, min(k, n - k) + 1): 1468 b *= n / t 1469 n -= 1 1470 return b 1471 1472 def bernstein(n, k): 1473 coeff = binom(n, k) 1474 1475 def _bpoly(x): 1476 return coeff * x ** k * (1 - x) ** (n - k) 1477 1478 return _bpoly 1479 1480 for ii in range(N): 1481 b = bernstein(N - 1, ii)(t) 1482 bcurve += np.outer(b, points[ii]) 1483 super().__init__(bcurve, lw=2) 1484 self.name = "BezierLine"
Generate the Bezier line that links the first to the last point.
1446 def __init__(self, points, res=None) -> None: 1447 """ 1448 Example: 1449 ```python 1450 from vedo import * 1451 import numpy as np 1452 pts = np.random.randn(25,3) 1453 for i,p in enumerate(pts): 1454 p += [5*i, 15*sin(i/2), i*i*i/200] 1455 show(Points(pts), Bezier(pts), axes=1).close() 1456 ``` 1457 ![](https://user-images.githubusercontent.com/32848391/90437534-dafd2a80-e0d2-11ea-9b93-9ecb3f48a3ff.png) 1458 """ 1459 N = len(points) 1460 if res is None: 1461 res = 10 * N 1462 t = np.linspace(0, 1, num=res) 1463 bcurve = np.zeros((res, len(points[0]))) 1464 1465 def binom(n, k): 1466 b = 1 1467 for t in range(1, min(k, n - k) + 1): 1468 b *= n / t 1469 n -= 1 1470 return b 1471 1472 def bernstein(n, k): 1473 coeff = binom(n, k) 1474 1475 def _bpoly(x): 1476 return coeff * x ** k * (1 - x) ** (n - k) 1477 1478 return _bpoly 1479 1480 for ii in range(N): 1481 b = bernstein(N - 1, ii)(t) 1482 bcurve += np.outer(b, points[ii]) 1483 super().__init__(bcurve, lw=2) 1484 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()
3735class Brace(Mesh): 3736 """ 3737 Create a brace (bracket) shape. 3738 """ 3739 3740 def __init__( 3741 self, 3742 q1, 3743 q2, 3744 style="}", 3745 padding1=0.0, 3746 font="Theemim", 3747 comment="", 3748 justify=None, 3749 angle=0.0, 3750 padding2=0.2, 3751 s=1.0, 3752 italic=0, 3753 c="k1", 3754 alpha=1.0, 3755 ) -> None: 3756 """ 3757 Create a brace (bracket) shape which spans from point q1 to point q2. 3758 3759 Arguments: 3760 q1 : (list) 3761 point 1. 3762 q2 : (list) 3763 point 2. 3764 style : (str) 3765 style of the bracket, eg. `{}, [], (), <>`. 3766 padding1 : (float) 3767 padding space in percent form the input points. 3768 font : (str) 3769 font type 3770 comment : (str) 3771 additional text to appear next to the brace symbol. 3772 justify : (str) 3773 specify the anchor point to justify text comment, e.g. "top-left". 3774 italic : float 3775 italicness of the text comment (can be a positive or negative number) 3776 angle : (float) 3777 rotation angle of text. Use `None` to keep it horizontal. 3778 padding2 : (float) 3779 padding space in percent form brace to text comment. 3780 s : (float) 3781 scale factor for the comment 3782 3783 Examples: 3784 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3785 3786 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3787 """ 3788 if isinstance(q1, vtki.vtkActor): 3789 q1 = q1.GetPosition() 3790 if isinstance(q2, vtki.vtkActor): 3791 q2 = q2.GetPosition() 3792 if len(q1) == 2: 3793 q1 = [q1[0], q1[1], 0.0] 3794 if len(q2) == 2: 3795 q2 = [q2[0], q2[1], 0.0] 3796 q1 = np.array(q1, dtype=float) 3797 q2 = np.array(q2, dtype=float) 3798 mq = (q1 + q2) / 2 3799 q1 = q1 - mq 3800 q2 = q2 - mq 3801 d = np.linalg.norm(q2 - q1) 3802 q2[2] = q1[2] 3803 3804 if style not in "{}[]()<>|I": 3805 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3806 style = "}" 3807 3808 flip = False 3809 if style in ["{", "[", "(", "<"]: 3810 flip = True 3811 i = ["{", "[", "(", "<"].index(style) 3812 style = ["}", "]", ")", ">"][i] 3813 3814 br = Text3D(style, font="Theemim", justify="center-left") 3815 br.scale([0.4, 1, 1]) 3816 3817 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3818 if flip: 3819 angler += 180 3820 3821 _, x1, y0, y1, _, _ = br.bounds() 3822 if comment: 3823 just = "center-top" 3824 if angle is None: 3825 angle = -angler + 90 3826 if not flip: 3827 angle += 180 3828 3829 if flip: 3830 angle += 180 3831 just = "center-bottom" 3832 if justify is not None: 3833 just = justify 3834 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3835 cx0, cx1 = cmt.xbounds() 3836 cmt.rotate_z(90 + angle) 3837 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3838 cmt.shift(x1 * (1 + padding2), 0, 0) 3839 poly = merge(br, cmt).dataset 3840 3841 else: 3842 poly = br.dataset 3843 3844 tr = vtki.vtkTransform() 3845 tr.Translate(mq) 3846 tr.RotateZ(angler) 3847 tr.Translate(padding1 * d, 0, 0) 3848 pscale = 1 3849 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3850 3851 tf = vtki.new("TransformPolyDataFilter") 3852 tf.SetInputData(poly) 3853 tf.SetTransform(tr) 3854 tf.Update() 3855 poly = tf.GetOutput() 3856 3857 super().__init__(poly, c, alpha) 3858 3859 self.base = q1 3860 self.top = q2 3861 self.name = "Brace"
Create a brace (bracket) shape.
3740 def __init__( 3741 self, 3742 q1, 3743 q2, 3744 style="}", 3745 padding1=0.0, 3746 font="Theemim", 3747 comment="", 3748 justify=None, 3749 angle=0.0, 3750 padding2=0.2, 3751 s=1.0, 3752 italic=0, 3753 c="k1", 3754 alpha=1.0, 3755 ) -> None: 3756 """ 3757 Create a brace (bracket) shape which spans from point q1 to point q2. 3758 3759 Arguments: 3760 q1 : (list) 3761 point 1. 3762 q2 : (list) 3763 point 2. 3764 style : (str) 3765 style of the bracket, eg. `{}, [], (), <>`. 3766 padding1 : (float) 3767 padding space in percent form the input points. 3768 font : (str) 3769 font type 3770 comment : (str) 3771 additional text to appear next to the brace symbol. 3772 justify : (str) 3773 specify the anchor point to justify text comment, e.g. "top-left". 3774 italic : float 3775 italicness of the text comment (can be a positive or negative number) 3776 angle : (float) 3777 rotation angle of text. Use `None` to keep it horizontal. 3778 padding2 : (float) 3779 padding space in percent form brace to text comment. 3780 s : (float) 3781 scale factor for the comment 3782 3783 Examples: 3784 - [scatter3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter3.py) 3785 3786 ![](https://vedo.embl.es/images/pyplot/scatter3.png) 3787 """ 3788 if isinstance(q1, vtki.vtkActor): 3789 q1 = q1.GetPosition() 3790 if isinstance(q2, vtki.vtkActor): 3791 q2 = q2.GetPosition() 3792 if len(q1) == 2: 3793 q1 = [q1[0], q1[1], 0.0] 3794 if len(q2) == 2: 3795 q2 = [q2[0], q2[1], 0.0] 3796 q1 = np.array(q1, dtype=float) 3797 q2 = np.array(q2, dtype=float) 3798 mq = (q1 + q2) / 2 3799 q1 = q1 - mq 3800 q2 = q2 - mq 3801 d = np.linalg.norm(q2 - q1) 3802 q2[2] = q1[2] 3803 3804 if style not in "{}[]()<>|I": 3805 vedo.logger.error(f"unknown style {style}." + "Use {}[]()<>|I") 3806 style = "}" 3807 3808 flip = False 3809 if style in ["{", "[", "(", "<"]: 3810 flip = True 3811 i = ["{", "[", "(", "<"].index(style) 3812 style = ["}", "]", ")", ">"][i] 3813 3814 br = Text3D(style, font="Theemim", justify="center-left") 3815 br.scale([0.4, 1, 1]) 3816 3817 angler = np.arctan2(q2[1], q2[0]) * 180 / np.pi - 90 3818 if flip: 3819 angler += 180 3820 3821 _, x1, y0, y1, _, _ = br.bounds() 3822 if comment: 3823 just = "center-top" 3824 if angle is None: 3825 angle = -angler + 90 3826 if not flip: 3827 angle += 180 3828 3829 if flip: 3830 angle += 180 3831 just = "center-bottom" 3832 if justify is not None: 3833 just = justify 3834 cmt = Text3D(comment, font=font, justify=just, italic=italic) 3835 cx0, cx1 = cmt.xbounds() 3836 cmt.rotate_z(90 + angle) 3837 cmt.scale(1 / (cx1 - cx0) * s * len(comment) / 5) 3838 cmt.shift(x1 * (1 + padding2), 0, 0) 3839 poly = merge(br, cmt).dataset 3840 3841 else: 3842 poly = br.dataset 3843 3844 tr = vtki.vtkTransform() 3845 tr.Translate(mq) 3846 tr.RotateZ(angler) 3847 tr.Translate(padding1 * d, 0, 0) 3848 pscale = 1 3849 tr.Scale(pscale / (y1 - y0) * d, pscale / (y1 - y0) * d, 1) 3850 3851 tf = vtki.new("TransformPolyDataFilter") 3852 tf.SetInputData(poly) 3853 tf.SetTransform(tr) 3854 tf.Update() 3855 poly = tf.GetOutput() 3856 3857 super().__init__(poly, c, alpha) 3858 3859 self.base = q1 3860 self.top = q2 3861 self.name = "Brace"
Create a brace (bracket) shape which spans from point q1 to point q2.
Arguments:
- q1 : (list) point 1.
- q2 : (list) point 2.
- style : (str)
style of the bracket, eg.
{}, [], (), <>
. - padding1 : (float) padding space in percent form the input points.
- font : (str) font type
- comment : (str) additional text to appear next to the brace symbol.
- justify : (str) specify the anchor point to justify text comment, e.g. "top-left".
- italic : float italicness of the text comment (can be a positive or negative number)
- angle : (float)
rotation angle of text. Use
None
to keep it horizontal. - padding2 : (float) padding space in percent form brace to text comment.
- s : (float) scale factor for the comment
Examples:
1487class NormalLines(Mesh): 1488 """ 1489 Build an `Glyph` to show the normals at cell centers or at mesh vertices. 1490 1491 Arguments: 1492 ratio : (int) 1493 show 1 normal every `ratio` cells. 1494 on : (str) 1495 either "cells" or "points". 1496 scale : (float) 1497 scale factor to control size. 1498 """ 1499 1500 def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None: 1501 1502 poly = msh.clone().dataset 1503 1504 if "cell" in on: 1505 centers = vtki.new("CellCenters") 1506 centers.SetInputData(poly) 1507 centers.Update() 1508 poly = centers.GetOutput() 1509 1510 mask_pts = vtki.new("MaskPoints") 1511 mask_pts.SetInputData(poly) 1512 mask_pts.SetOnRatio(ratio) 1513 mask_pts.RandomModeOff() 1514 mask_pts.Update() 1515 1516 ln = vtki.new("LineSource") 1517 ln.SetPoint1(0, 0, 0) 1518 ln.SetPoint2(1, 0, 0) 1519 ln.Update() 1520 glyph = vtki.vtkGlyph3D() 1521 glyph.SetSourceData(ln.GetOutput()) 1522 glyph.SetInputData(mask_pts.GetOutput()) 1523 glyph.SetVectorModeToUseNormal() 1524 1525 b = poly.GetBounds() 1526 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1527 glyph.SetScaleFactor(f) 1528 glyph.OrientOn() 1529 glyph.Update() 1530 1531 super().__init__(glyph.GetOutput()) 1532 1533 self.actor.PickableOff() 1534 prop = vtki.vtkProperty() 1535 prop.DeepCopy(msh.properties) 1536 self.actor.SetProperty(prop) 1537 self.properties = prop 1538 self.properties.LightingOff() 1539 self.mapper.ScalarVisibilityOff() 1540 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.
1500 def __init__(self, msh, ratio=1, on="cells", scale=1.0) -> None: 1501 1502 poly = msh.clone().dataset 1503 1504 if "cell" in on: 1505 centers = vtki.new("CellCenters") 1506 centers.SetInputData(poly) 1507 centers.Update() 1508 poly = centers.GetOutput() 1509 1510 mask_pts = vtki.new("MaskPoints") 1511 mask_pts.SetInputData(poly) 1512 mask_pts.SetOnRatio(ratio) 1513 mask_pts.RandomModeOff() 1514 mask_pts.Update() 1515 1516 ln = vtki.new("LineSource") 1517 ln.SetPoint1(0, 0, 0) 1518 ln.SetPoint2(1, 0, 0) 1519 ln.Update() 1520 glyph = vtki.vtkGlyph3D() 1521 glyph.SetSourceData(ln.GetOutput()) 1522 glyph.SetInputData(mask_pts.GetOutput()) 1523 glyph.SetVectorModeToUseNormal() 1524 1525 b = poly.GetBounds() 1526 f = max([b[1] - b[0], b[3] - b[2], b[5] - b[4]]) / 50 * scale 1527 glyph.SetScaleFactor(f) 1528 glyph.OrientOn() 1529 glyph.Update() 1530 1531 super().__init__(glyph.GetOutput()) 1532 1533 self.actor.PickableOff() 1534 prop = vtki.vtkProperty() 1535 prop.DeepCopy(msh.properties) 1536 self.actor.SetProperty(prop) 1537 self.properties = prop 1538 self.properties.LightingOff() 1539 self.mapper.ScalarVisibilityOff() 1540 self.name = "NormalLines"
Initialize a Mesh
object.
Arguments:
- inputobj : (str, vtkPolyData, vtkActor, vedo.Mesh)
If inputobj is
None
an empty mesh is created. If inputobj is astr
then it is interpreted as the name of a file to load as mesh. If inputobj is anvtkPolyData
orvtkActor
orvedo.Mesh
then a shallow copy of it is created. If inputobj is avedo.Mesh
then a shallow copy of it is created.
Examples:
- buildmesh.py (and many others!)
1765class Ribbon(Mesh): 1766 """ 1767 Connect two lines to generate the surface inbetween. 1768 Set the mode by which to create the ruled surface. 1769 1770 It also works with a single line in input. In this case the ribbon 1771 is formed by following the local plane of the line in space. 1772 """ 1773 1774 def __init__( 1775 self, 1776 line1, 1777 line2=None, 1778 mode=0, 1779 closed=False, 1780 width=None, 1781 res=(200, 5), 1782 c="indigo3", 1783 alpha=1.0, 1784 ) -> None: 1785 """ 1786 Arguments: 1787 mode : (int) 1788 If mode=0, resample evenly the input lines (based on length) 1789 and generates triangle strips. 1790 1791 If mode=1, use the existing points and walks around the 1792 polyline using existing points. 1793 1794 closed : (bool) 1795 if True, join the last point with the first to form a closed surface 1796 1797 res : (list) 1798 ribbon resolutions along the line and perpendicularly to it. 1799 1800 Examples: 1801 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1802 1803 ![](https://vedo.embl.es/images/basic/ribbon.png) 1804 """ 1805 1806 if isinstance(line1, Points): 1807 line1 = line1.coordinates 1808 1809 if isinstance(line2, Points): 1810 line2 = line2.coordinates 1811 1812 elif line2 is None: 1813 ############################################# 1814 ribbon_filter = vtki.new("RibbonFilter") 1815 aline = Line(line1) 1816 ribbon_filter.SetInputData(aline.dataset) 1817 if width is None: 1818 width = aline.diagonal_size() / 20.0 1819 ribbon_filter.SetWidth(width) 1820 ribbon_filter.Update() 1821 # convert triangle strips to polygons 1822 tris = vtki.new("TriangleFilter") 1823 tris.SetInputData(ribbon_filter.GetOutput()) 1824 tris.Update() 1825 1826 super().__init__(tris.GetOutput(), c, alpha) 1827 self.name = "Ribbon" 1828 ############################################## 1829 return ###################################### 1830 ############################################## 1831 1832 line1 = np.asarray(line1) 1833 line2 = np.asarray(line2) 1834 1835 if closed: 1836 line1 = line1.tolist() 1837 line1 += [line1[0]] 1838 line2 = line2.tolist() 1839 line2 += [line2[0]] 1840 line1 = np.array(line1) 1841 line2 = np.array(line2) 1842 1843 if len(line1[0]) == 2: 1844 line1 = np.c_[line1, np.zeros(len(line1))] 1845 if len(line2[0]) == 2: 1846 line2 = np.c_[line2, np.zeros(len(line2))] 1847 1848 ppoints1 = vtki.vtkPoints() # Generate the polyline1 1849 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1850 lines1 = vtki.vtkCellArray() 1851 lines1.InsertNextCell(len(line1)) 1852 for i in range(len(line1)): 1853 lines1.InsertCellPoint(i) 1854 poly1 = vtki.vtkPolyData() 1855 poly1.SetPoints(ppoints1) 1856 poly1.SetLines(lines1) 1857 1858 ppoints2 = vtki.vtkPoints() # Generate the polyline2 1859 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1860 lines2 = vtki.vtkCellArray() 1861 lines2.InsertNextCell(len(line2)) 1862 for i in range(len(line2)): 1863 lines2.InsertCellPoint(i) 1864 poly2 = vtki.vtkPolyData() 1865 poly2.SetPoints(ppoints2) 1866 poly2.SetLines(lines2) 1867 1868 # build the lines 1869 lines1 = vtki.vtkCellArray() 1870 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1871 for i in range(poly1.GetNumberOfPoints()): 1872 lines1.InsertCellPoint(i) 1873 1874 polygon1 = vtki.vtkPolyData() 1875 polygon1.SetPoints(ppoints1) 1876 polygon1.SetLines(lines1) 1877 1878 lines2 = vtki.vtkCellArray() 1879 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1880 for i in range(poly2.GetNumberOfPoints()): 1881 lines2.InsertCellPoint(i) 1882 1883 polygon2 = vtki.vtkPolyData() 1884 polygon2.SetPoints(ppoints2) 1885 polygon2.SetLines(lines2) 1886 1887 merged_pd = vtki.new("AppendPolyData") 1888 merged_pd.AddInputData(polygon1) 1889 merged_pd.AddInputData(polygon2) 1890 merged_pd.Update() 1891 1892 rsf = vtki.new("RuledSurfaceFilter") 1893 rsf.CloseSurfaceOff() 1894 rsf.SetRuledMode(mode) 1895 rsf.SetResolution(res[0], res[1]) 1896 rsf.SetInputData(merged_pd.GetOutput()) 1897 rsf.Update() 1898 # convert triangle strips to polygons 1899 tris = vtki.new("TriangleFilter") 1900 tris.SetInputData(rsf.GetOutput()) 1901 tris.Update() 1902 out = tris.GetOutput() 1903 1904 super().__init__(out, c, alpha) 1905 1906 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.
1774 def __init__( 1775 self, 1776 line1, 1777 line2=None, 1778 mode=0, 1779 closed=False, 1780 width=None, 1781 res=(200, 5), 1782 c="indigo3", 1783 alpha=1.0, 1784 ) -> None: 1785 """ 1786 Arguments: 1787 mode : (int) 1788 If mode=0, resample evenly the input lines (based on length) 1789 and generates triangle strips. 1790 1791 If mode=1, use the existing points and walks around the 1792 polyline using existing points. 1793 1794 closed : (bool) 1795 if True, join the last point with the first to form a closed surface 1796 1797 res : (list) 1798 ribbon resolutions along the line and perpendicularly to it. 1799 1800 Examples: 1801 - [ribbon.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/ribbon.py) 1802 1803 ![](https://vedo.embl.es/images/basic/ribbon.png) 1804 """ 1805 1806 if isinstance(line1, Points): 1807 line1 = line1.coordinates 1808 1809 if isinstance(line2, Points): 1810 line2 = line2.coordinates 1811 1812 elif line2 is None: 1813 ############################################# 1814 ribbon_filter = vtki.new("RibbonFilter") 1815 aline = Line(line1) 1816 ribbon_filter.SetInputData(aline.dataset) 1817 if width is None: 1818 width = aline.diagonal_size() / 20.0 1819 ribbon_filter.SetWidth(width) 1820 ribbon_filter.Update() 1821 # convert triangle strips to polygons 1822 tris = vtki.new("TriangleFilter") 1823 tris.SetInputData(ribbon_filter.GetOutput()) 1824 tris.Update() 1825 1826 super().__init__(tris.GetOutput(), c, alpha) 1827 self.name = "Ribbon" 1828 ############################################## 1829 return ###################################### 1830 ############################################## 1831 1832 line1 = np.asarray(line1) 1833 line2 = np.asarray(line2) 1834 1835 if closed: 1836 line1 = line1.tolist() 1837 line1 += [line1[0]] 1838 line2 = line2.tolist() 1839 line2 += [line2[0]] 1840 line1 = np.array(line1) 1841 line2 = np.array(line2) 1842 1843 if len(line1[0]) == 2: 1844 line1 = np.c_[line1, np.zeros(len(line1))] 1845 if len(line2[0]) == 2: 1846 line2 = np.c_[line2, np.zeros(len(line2))] 1847 1848 ppoints1 = vtki.vtkPoints() # Generate the polyline1 1849 ppoints1.SetData(utils.numpy2vtk(line1, dtype=np.float32)) 1850 lines1 = vtki.vtkCellArray() 1851 lines1.InsertNextCell(len(line1)) 1852 for i in range(len(line1)): 1853 lines1.InsertCellPoint(i) 1854 poly1 = vtki.vtkPolyData() 1855 poly1.SetPoints(ppoints1) 1856 poly1.SetLines(lines1) 1857 1858 ppoints2 = vtki.vtkPoints() # Generate the polyline2 1859 ppoints2.SetData(utils.numpy2vtk(line2, dtype=np.float32)) 1860 lines2 = vtki.vtkCellArray() 1861 lines2.InsertNextCell(len(line2)) 1862 for i in range(len(line2)): 1863 lines2.InsertCellPoint(i) 1864 poly2 = vtki.vtkPolyData() 1865 poly2.SetPoints(ppoints2) 1866 poly2.SetLines(lines2) 1867 1868 # build the lines 1869 lines1 = vtki.vtkCellArray() 1870 lines1.InsertNextCell(poly1.GetNumberOfPoints()) 1871 for i in range(poly1.GetNumberOfPoints()): 1872 lines1.InsertCellPoint(i) 1873 1874 polygon1 = vtki.vtkPolyData() 1875 polygon1.SetPoints(ppoints1) 1876 polygon1.SetLines(lines1) 1877 1878 lines2 = vtki.vtkCellArray() 1879 lines2.InsertNextCell(poly2.GetNumberOfPoints()) 1880 for i in range(poly2.GetNumberOfPoints()): 1881 lines2.InsertCellPoint(i) 1882 1883 polygon2 = vtki.vtkPolyData() 1884 polygon2.SetPoints(ppoints2) 1885 polygon2.SetLines(lines2) 1886 1887 merged_pd = vtki.new("AppendPolyData") 1888 merged_pd.AddInputData(polygon1) 1889 merged_pd.AddInputData(polygon2) 1890 merged_pd.Update() 1891 1892 rsf = vtki.new("RuledSurfaceFilter") 1893 rsf.CloseSurfaceOff() 1894 rsf.SetRuledMode(mode) 1895 rsf.SetResolution(res[0], res[1]) 1896 rsf.SetInputData(merged_pd.GetOutput()) 1897 rsf.Update() 1898 # convert triangle strips to polygons 1899 tris = vtki.new("TriangleFilter") 1900 tris.SetInputData(rsf.GetOutput()) 1901 tris.Update() 1902 out = tris.GetOutput() 1903 1904 super().__init__(out, c, alpha) 1905 1906 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:
1909class Arrow(Mesh): 1910 """ 1911 Build a 3D arrow from `start_pt` to `end_pt` of section size `s`, 1912 expressed as the fraction of the window size. 1913 """ 1914 1915 def __init__( 1916 self, 1917 start_pt=(0, 0, 0), 1918 end_pt=(1, 0, 0), 1919 s=None, 1920 shaft_radius=None, 1921 head_radius=None, 1922 head_length=None, 1923 res=12, 1924 c="r4", 1925 alpha=1.0, 1926 ) -> None: 1927 """ 1928 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1929 from white to red. 1930 1931 .. note:: If `s=None` the arrow is scaled proportionally to its length 1932 1933 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1934 """ 1935 # in case user is passing meshs 1936 if isinstance(start_pt, vtki.vtkActor): 1937 start_pt = start_pt.GetPosition() 1938 if isinstance(end_pt, vtki.vtkActor): 1939 end_pt = end_pt.GetPosition() 1940 1941 axis = np.asarray(end_pt) - np.asarray(start_pt) 1942 length = float(np.linalg.norm(axis)) 1943 if length: 1944 axis = axis / length 1945 if len(axis) < 3: # its 2d 1946 theta = np.pi / 2 1947 start_pt = [start_pt[0], start_pt[1], 0.0] 1948 end_pt = [end_pt[0], end_pt[1], 0.0] 1949 else: 1950 theta = np.arccos(axis[2]) 1951 phi = np.arctan2(axis[1], axis[0]) 1952 self.source = vtki.new("ArrowSource") 1953 self.source.SetShaftResolution(res) 1954 self.source.SetTipResolution(res) 1955 1956 if s: 1957 sz = 0.02 1958 self.source.SetTipRadius(sz) 1959 self.source.SetShaftRadius(sz / 1.75) 1960 self.source.SetTipLength(sz * 15) 1961 1962 if head_length: 1963 self.source.SetTipLength(head_length) 1964 if head_radius: 1965 self.source.SetTipRadius(head_radius) 1966 if shaft_radius: 1967 self.source.SetShaftRadius(shaft_radius) 1968 1969 self.source.Update() 1970 1971 t = vtki.vtkTransform() 1972 t.Translate(start_pt) 1973 t.RotateZ(np.rad2deg(phi)) 1974 t.RotateY(np.rad2deg(theta)) 1975 t.RotateY(-90) # put it along Z 1976 if s: 1977 sz = 800 * s 1978 t.Scale(length, sz, sz) 1979 else: 1980 t.Scale(length, length, length) 1981 1982 tf = vtki.new("TransformPolyDataFilter") 1983 tf.SetInputData(self.source.GetOutput()) 1984 tf.SetTransform(t) 1985 tf.Update() 1986 1987 super().__init__(tf.GetOutput(), c, alpha) 1988 1989 self.transform = LinearTransform().translate(start_pt) 1990 1991 self.phong().lighting("plastic") 1992 self.actor.PickableOff() 1993 self.actor.DragableOff() 1994 self.base = np.array(start_pt, dtype=float) # used by pyplot 1995 self.top = np.array(end_pt, dtype=float) # used by pyplot 1996 self.top_index = self.source.GetTipResolution() * 4 1997 self.fill = True # used by pyplot.__iadd__() 1998 self.s = s if s is not None else 1 # used by pyplot.__iadd__() 1999 self.name = "Arrow" 2000 2001 def top_point(self): 2002 """Return the current coordinates of the tip of the Arrow.""" 2003 return self.transform.transform_point(self.top) 2004 2005 def base_point(self): 2006 """Return the current coordinates of the base of the Arrow.""" 2007 return self.transform.transform_point(self.base)
Build a 3D arrow from start_pt
to end_pt
of section size s
,
expressed as the fraction of the window size.
1915 def __init__( 1916 self, 1917 start_pt=(0, 0, 0), 1918 end_pt=(1, 0, 0), 1919 s=None, 1920 shaft_radius=None, 1921 head_radius=None, 1922 head_length=None, 1923 res=12, 1924 c="r4", 1925 alpha=1.0, 1926 ) -> None: 1927 """ 1928 If `c` is a `float` less than 1, the arrow is rendered as a in a color scale 1929 from white to red. 1930 1931 .. note:: If `s=None` the arrow is scaled proportionally to its length 1932 1933 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png) 1934 """ 1935 # in case user is passing meshs 1936 if isinstance(start_pt, vtki.vtkActor): 1937 start_pt = start_pt.GetPosition() 1938 if isinstance(end_pt, vtki.vtkActor): 1939 end_pt = end_pt.GetPosition() 1940 1941 axis = np.asarray(end_pt) - np.asarray(start_pt) 1942 length = float(np.linalg.norm(axis)) 1943 if length: 1944 axis = axis / length 1945 if len(axis) < 3: # its 2d 1946 theta = np.pi / 2 1947 start_pt = [start_pt[0], start_pt[1], 0.0] 1948 end_pt = [end_pt[0], end_pt[1], 0.0] 1949 else: 1950 theta = np.arccos(axis[2]) 1951 phi = np.arctan2(axis[1], axis[0]) 1952 self.source = vtki.new("ArrowSource") 1953 self.source.SetShaftResolution(res) 1954 self.source.SetTipResolution(res) 1955 1956 if s: 1957 sz = 0.02 1958 self.source.SetTipRadius(sz) 1959 self.source.SetShaftRadius(sz / 1.75) 1960 self.source.SetTipLength(sz * 15) 1961 1962 if head_length: 1963 self.source.SetTipLength(head_length) 1964 if head_radius: 1965 self.source.SetTipRadius(head_radius) 1966 if shaft_radius: 1967 self.source.SetShaftRadius(shaft_radius) 1968 1969 self.source.Update() 1970 1971 t = vtki.vtkTransform() 1972 t.Translate(start_pt) 1973 t.RotateZ(np.rad2deg(phi)) 1974 t.RotateY(np.rad2deg(theta)) 1975 t.RotateY(-90) # put it along Z 1976 if s: 1977 sz = 800 * s 1978 t.Scale(length, sz, sz) 1979 else: 1980 t.Scale(length, length, length) 1981 1982 tf = vtki.new("TransformPolyDataFilter") 1983 tf.SetInputData(self.source.GetOutput()) 1984 tf.SetTransform(t) 1985 tf.Update() 1986 1987 super().__init__(tf.GetOutput(), c, alpha) 1988 1989 self.transform = LinearTransform().translate(start_pt) 1990 1991 self.phong().lighting("plastic") 1992 self.actor.PickableOff() 1993 self.actor.DragableOff() 1994 self.base = np.array(start_pt, dtype=float) # used by pyplot 1995 self.top = np.array(end_pt, dtype=float) # used by pyplot 1996 self.top_index = self.source.GetTipResolution() * 4 1997 self.fill = True # used by pyplot.__iadd__() 1998 self.s = s if s is not None else 1 # used by pyplot.__iadd__() 1999 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
2009class Arrows(Glyph): 2010 """ 2011 Build arrows between two lists of points. 2012 """ 2013 2014 def __init__( 2015 self, 2016 start_pts, 2017 end_pts=None, 2018 s=None, 2019 shaft_radius=None, 2020 head_radius=None, 2021 head_length=None, 2022 thickness=1.0, 2023 res=6, 2024 c='k3', 2025 alpha=1.0, 2026 ) -> None: 2027 """ 2028 Build arrows between two lists of points `start_pts` and `end_pts`. 2029 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2030 2031 Color can be specified as a colormap which maps the size of the arrows. 2032 2033 Arguments: 2034 s : (float) 2035 fix aspect-ratio of the arrow and scale its cross section 2036 c : (color) 2037 color or color map name 2038 alpha : (float) 2039 set object opacity 2040 res : (int) 2041 set arrow resolution 2042 2043 Examples: 2044 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 2045 2046 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 2047 """ 2048 if isinstance(start_pts, Points): 2049 start_pts = start_pts.coordinates 2050 if isinstance(end_pts, Points): 2051 end_pts = end_pts.coordinates 2052 2053 start_pts = np.asarray(start_pts) 2054 if end_pts is None: 2055 strt = start_pts[:, 0] 2056 end_pts = start_pts[:, 1] 2057 start_pts = strt 2058 else: 2059 end_pts = np.asarray(end_pts) 2060 2061 start_pts = utils.make3d(start_pts) 2062 end_pts = utils.make3d(end_pts) 2063 2064 arr = vtki.new("ArrowSource") 2065 arr.SetShaftResolution(res) 2066 arr.SetTipResolution(res) 2067 2068 if s: 2069 sz = 0.02 * s 2070 arr.SetTipRadius(sz * 2) 2071 arr.SetShaftRadius(sz * thickness) 2072 arr.SetTipLength(sz * 10) 2073 2074 if head_radius: 2075 arr.SetTipRadius(head_radius) 2076 if shaft_radius: 2077 arr.SetShaftRadius(shaft_radius) 2078 if head_length: 2079 arr.SetTipLength(head_length) 2080 2081 arr.Update() 2082 out = arr.GetOutput() 2083 2084 orients = end_pts - start_pts 2085 2086 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2087 2088 super().__init__( 2089 start_pts, 2090 out, 2091 orientation_array=orients, 2092 scale_by_vector_size=True, 2093 color_by_vector_size=color_by_vector_size, 2094 c=c, 2095 alpha=alpha, 2096 ) 2097 self.lighting("off") 2098 if color_by_vector_size: 2099 vals = np.linalg.norm(orients, axis=1) 2100 self.mapper.SetScalarRange(vals.min(), vals.max()) 2101 else: 2102 self.c(c) 2103 self.name = "Arrows"
Build arrows between two lists of points.
2014 def __init__( 2015 self, 2016 start_pts, 2017 end_pts=None, 2018 s=None, 2019 shaft_radius=None, 2020 head_radius=None, 2021 head_length=None, 2022 thickness=1.0, 2023 res=6, 2024 c='k3', 2025 alpha=1.0, 2026 ) -> None: 2027 """ 2028 Build arrows between two lists of points `start_pts` and `end_pts`. 2029 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2030 2031 Color can be specified as a colormap which maps the size of the arrows. 2032 2033 Arguments: 2034 s : (float) 2035 fix aspect-ratio of the arrow and scale its cross section 2036 c : (color) 2037 color or color map name 2038 alpha : (float) 2039 set object opacity 2040 res : (int) 2041 set arrow resolution 2042 2043 Examples: 2044 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 2045 2046 ![](https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg) 2047 """ 2048 if isinstance(start_pts, Points): 2049 start_pts = start_pts.coordinates 2050 if isinstance(end_pts, Points): 2051 end_pts = end_pts.coordinates 2052 2053 start_pts = np.asarray(start_pts) 2054 if end_pts is None: 2055 strt = start_pts[:, 0] 2056 end_pts = start_pts[:, 1] 2057 start_pts = strt 2058 else: 2059 end_pts = np.asarray(end_pts) 2060 2061 start_pts = utils.make3d(start_pts) 2062 end_pts = utils.make3d(end_pts) 2063 2064 arr = vtki.new("ArrowSource") 2065 arr.SetShaftResolution(res) 2066 arr.SetTipResolution(res) 2067 2068 if s: 2069 sz = 0.02 * s 2070 arr.SetTipRadius(sz * 2) 2071 arr.SetShaftRadius(sz * thickness) 2072 arr.SetTipLength(sz * 10) 2073 2074 if head_radius: 2075 arr.SetTipRadius(head_radius) 2076 if shaft_radius: 2077 arr.SetShaftRadius(shaft_radius) 2078 if head_length: 2079 arr.SetTipLength(head_length) 2080 2081 arr.Update() 2082 out = arr.GetOutput() 2083 2084 orients = end_pts - start_pts 2085 2086 color_by_vector_size = utils.is_sequence(c) or c in cmaps_names 2087 2088 super().__init__( 2089 start_pts, 2090 out, 2091 orientation_array=orients, 2092 scale_by_vector_size=True, 2093 color_by_vector_size=color_by_vector_size, 2094 c=c, 2095 alpha=alpha, 2096 ) 2097 self.lighting("off") 2098 if color_by_vector_size: 2099 vals = np.linalg.norm(orients, axis=1) 2100 self.mapper.SetScalarRange(vals.min(), vals.max()) 2101 else: 2102 self.c(c) 2103 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:
2106class Arrow2D(Mesh): 2107 """ 2108 Build a 2D arrow. 2109 """ 2110 2111 def __init__( 2112 self, 2113 start_pt=(0, 0, 0), 2114 end_pt=(1, 0, 0), 2115 s=1, 2116 rotation=0.0, 2117 shaft_length=0.85, 2118 shaft_width=0.055, 2119 head_length=0.175, 2120 head_width=0.175, 2121 fill=True, 2122 c="red4", 2123 alpha=1.0, 2124 ) -> None: 2125 """ 2126 Build a 2D arrow from `start_pt` to `end_pt`. 2127 2128 Arguments: 2129 s : (float) 2130 a global multiplicative convenience factor controlling the arrow size 2131 shaft_length : (float) 2132 fractional shaft length 2133 shaft_width : (float) 2134 fractional shaft width 2135 head_length : (float) 2136 fractional head length 2137 head_width : (float) 2138 fractional head width 2139 fill : (bool) 2140 if False only generate the outline 2141 """ 2142 self.fill = fill ## needed by pyplot.__iadd() 2143 self.s = s ## needed by pyplot.__iadd() 2144 2145 if s != 1: 2146 shaft_width *= s 2147 head_width *= np.sqrt(s) 2148 2149 # in case user is passing meshs 2150 if isinstance(start_pt, vtki.vtkActor): 2151 start_pt = start_pt.GetPosition() 2152 if isinstance(end_pt, vtki.vtkActor): 2153 end_pt = end_pt.GetPosition() 2154 if len(start_pt) == 2: 2155 start_pt = [start_pt[0], start_pt[1], 0] 2156 if len(end_pt) == 2: 2157 end_pt = [end_pt[0], end_pt[1], 0] 2158 2159 headBase = 1 - head_length 2160 head_width = max(head_width, shaft_width) 2161 if head_length is None or headBase > shaft_length: 2162 headBase = shaft_length 2163 2164 verts = [] 2165 verts.append([0, -shaft_width / 2, 0]) 2166 verts.append([shaft_length, -shaft_width / 2, 0]) 2167 verts.append([headBase, -head_width / 2, 0]) 2168 verts.append([1, 0, 0]) 2169 verts.append([headBase, head_width / 2, 0]) 2170 verts.append([shaft_length, shaft_width / 2, 0]) 2171 verts.append([0, shaft_width / 2, 0]) 2172 if fill: 2173 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2174 poly = utils.buildPolyData(verts, faces) 2175 else: 2176 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2177 poly = utils.buildPolyData(verts, [], lines=lines) 2178 2179 axis = np.array(end_pt) - np.array(start_pt) 2180 length = float(np.linalg.norm(axis)) 2181 if length: 2182 axis = axis / length 2183 theta = 0 2184 if len(axis) > 2: 2185 theta = np.arccos(axis[2]) 2186 phi = np.arctan2(axis[1], axis[0]) 2187 2188 t = vtki.vtkTransform() 2189 t.Translate(start_pt) 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 if rotation: 2196 t.RotateX(rotation) 2197 t.Scale(length, length, length) 2198 2199 tf = vtki.new("TransformPolyDataFilter") 2200 tf.SetInputData(poly) 2201 tf.SetTransform(t) 2202 tf.Update() 2203 2204 super().__init__(tf.GetOutput(), c, alpha) 2205 2206 self.transform = LinearTransform().translate(start_pt) 2207 2208 self.lighting("off") 2209 self.actor.DragableOff() 2210 self.actor.PickableOff() 2211 self.base = np.array(start_pt, dtype=float) # used by pyplot 2212 self.top = np.array(end_pt, dtype=float) # used by pyplot 2213 self.name = "Arrow2D"
Build a 2D arrow.
2111 def __init__( 2112 self, 2113 start_pt=(0, 0, 0), 2114 end_pt=(1, 0, 0), 2115 s=1, 2116 rotation=0.0, 2117 shaft_length=0.85, 2118 shaft_width=0.055, 2119 head_length=0.175, 2120 head_width=0.175, 2121 fill=True, 2122 c="red4", 2123 alpha=1.0, 2124 ) -> None: 2125 """ 2126 Build a 2D arrow from `start_pt` to `end_pt`. 2127 2128 Arguments: 2129 s : (float) 2130 a global multiplicative convenience factor controlling the arrow size 2131 shaft_length : (float) 2132 fractional shaft length 2133 shaft_width : (float) 2134 fractional shaft width 2135 head_length : (float) 2136 fractional head length 2137 head_width : (float) 2138 fractional head width 2139 fill : (bool) 2140 if False only generate the outline 2141 """ 2142 self.fill = fill ## needed by pyplot.__iadd() 2143 self.s = s ## needed by pyplot.__iadd() 2144 2145 if s != 1: 2146 shaft_width *= s 2147 head_width *= np.sqrt(s) 2148 2149 # in case user is passing meshs 2150 if isinstance(start_pt, vtki.vtkActor): 2151 start_pt = start_pt.GetPosition() 2152 if isinstance(end_pt, vtki.vtkActor): 2153 end_pt = end_pt.GetPosition() 2154 if len(start_pt) == 2: 2155 start_pt = [start_pt[0], start_pt[1], 0] 2156 if len(end_pt) == 2: 2157 end_pt = [end_pt[0], end_pt[1], 0] 2158 2159 headBase = 1 - head_length 2160 head_width = max(head_width, shaft_width) 2161 if head_length is None or headBase > shaft_length: 2162 headBase = shaft_length 2163 2164 verts = [] 2165 verts.append([0, -shaft_width / 2, 0]) 2166 verts.append([shaft_length, -shaft_width / 2, 0]) 2167 verts.append([headBase, -head_width / 2, 0]) 2168 verts.append([1, 0, 0]) 2169 verts.append([headBase, head_width / 2, 0]) 2170 verts.append([shaft_length, shaft_width / 2, 0]) 2171 verts.append([0, shaft_width / 2, 0]) 2172 if fill: 2173 faces = ((0, 1, 3, 5, 6), (5, 3, 4), (1, 2, 3)) 2174 poly = utils.buildPolyData(verts, faces) 2175 else: 2176 lines = (0, 1, 2, 3, 4, 5, 6, 0) 2177 poly = utils.buildPolyData(verts, [], lines=lines) 2178 2179 axis = np.array(end_pt) - np.array(start_pt) 2180 length = float(np.linalg.norm(axis)) 2181 if length: 2182 axis = axis / length 2183 theta = 0 2184 if len(axis) > 2: 2185 theta = np.arccos(axis[2]) 2186 phi = np.arctan2(axis[1], axis[0]) 2187 2188 t = vtki.vtkTransform() 2189 t.Translate(start_pt) 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 if rotation: 2196 t.RotateX(rotation) 2197 t.Scale(length, length, length) 2198 2199 tf = vtki.new("TransformPolyDataFilter") 2200 tf.SetInputData(poly) 2201 tf.SetTransform(t) 2202 tf.Update() 2203 2204 super().__init__(tf.GetOutput(), c, alpha) 2205 2206 self.transform = LinearTransform().translate(start_pt) 2207 2208 self.lighting("off") 2209 self.actor.DragableOff() 2210 self.actor.PickableOff() 2211 self.base = np.array(start_pt, dtype=float) # used by pyplot 2212 self.top = np.array(end_pt, dtype=float) # used by pyplot 2213 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
2216class Arrows2D(Glyph): 2217 """ 2218 Build 2D arrows between two lists of points. 2219 """ 2220 2221 def __init__( 2222 self, 2223 start_pts, 2224 end_pts=None, 2225 s=1.0, 2226 rotation=0.0, 2227 shaft_length=0.8, 2228 shaft_width=0.05, 2229 head_length=0.225, 2230 head_width=0.175, 2231 fill=True, 2232 c=None, 2233 alpha=1.0, 2234 ) -> None: 2235 """ 2236 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2237 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2238 2239 Color can be specified as a colormap which maps the size of the arrows. 2240 2241 Arguments: 2242 shaft_length : (float) 2243 fractional shaft length 2244 shaft_width : (float) 2245 fractional shaft width 2246 head_length : (float) 2247 fractional head length 2248 head_width : (float) 2249 fractional head width 2250 fill : (bool) 2251 if False only generate the outline 2252 """ 2253 if isinstance(start_pts, Points): 2254 start_pts = start_pts.coordinates 2255 if isinstance(end_pts, Points): 2256 end_pts = end_pts.coordinates 2257 2258 start_pts = np.asarray(start_pts, dtype=float) 2259 if end_pts is None: 2260 strt = start_pts[:, 0] 2261 end_pts = start_pts[:, 1] 2262 start_pts = strt 2263 else: 2264 end_pts = np.asarray(end_pts, dtype=float) 2265 2266 if head_length is None: 2267 head_length = 1 - shaft_length 2268 2269 arr = Arrow2D( 2270 (0, 0, 0), 2271 (1, 0, 0), 2272 s=s, 2273 rotation=rotation, 2274 shaft_length=shaft_length, 2275 shaft_width=shaft_width, 2276 head_length=head_length, 2277 head_width=head_width, 2278 fill=fill, 2279 ) 2280 2281 orients = end_pts - start_pts 2282 orients = utils.make3d(orients) 2283 2284 pts = Points(start_pts) 2285 super().__init__( 2286 pts, 2287 arr, 2288 orientation_array=orients, 2289 scale_by_vector_size=True, 2290 c=c, 2291 alpha=alpha, 2292 ) 2293 self.flat().lighting("off").pickable(False) 2294 if c is not None: 2295 self.color(c) 2296 self.name = "Arrows2D"
Build 2D arrows between two lists of points.
2221 def __init__( 2222 self, 2223 start_pts, 2224 end_pts=None, 2225 s=1.0, 2226 rotation=0.0, 2227 shaft_length=0.8, 2228 shaft_width=0.05, 2229 head_length=0.225, 2230 head_width=0.175, 2231 fill=True, 2232 c=None, 2233 alpha=1.0, 2234 ) -> None: 2235 """ 2236 Build 2D arrows between two lists of points `start_pts` and `end_pts`. 2237 `start_pts` can be also passed in the form `[[point1, point2], ...]`. 2238 2239 Color can be specified as a colormap which maps the size of the arrows. 2240 2241 Arguments: 2242 shaft_length : (float) 2243 fractional shaft length 2244 shaft_width : (float) 2245 fractional shaft width 2246 head_length : (float) 2247 fractional head length 2248 head_width : (float) 2249 fractional head width 2250 fill : (bool) 2251 if False only generate the outline 2252 """ 2253 if isinstance(start_pts, Points): 2254 start_pts = start_pts.coordinates 2255 if isinstance(end_pts, Points): 2256 end_pts = end_pts.coordinates 2257 2258 start_pts = np.asarray(start_pts, dtype=float) 2259 if end_pts is None: 2260 strt = start_pts[:, 0] 2261 end_pts = start_pts[:, 1] 2262 start_pts = strt 2263 else: 2264 end_pts = np.asarray(end_pts, dtype=float) 2265 2266 if head_length is None: 2267 head_length = 1 - shaft_length 2268 2269 arr = Arrow2D( 2270 (0, 0, 0), 2271 (1, 0, 0), 2272 s=s, 2273 rotation=rotation, 2274 shaft_length=shaft_length, 2275 shaft_width=shaft_width, 2276 head_length=head_length, 2277 head_width=head_width, 2278 fill=fill, 2279 ) 2280 2281 orients = end_pts - start_pts 2282 orients = utils.make3d(orients) 2283 2284 pts = Points(start_pts) 2285 super().__init__( 2286 pts, 2287 arr, 2288 orientation_array=orients, 2289 scale_by_vector_size=True, 2290 c=c, 2291 alpha=alpha, 2292 ) 2293 self.flat().lighting("off").pickable(False) 2294 if c is not None: 2295 self.color(c) 2296 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
2299class FlatArrow(Ribbon): 2300 """ 2301 Build a 2D arrow in 3D space by joining two close lines. 2302 """ 2303 2304 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2305 """ 2306 Build a 2D arrow in 3D space by joining two close lines. 2307 2308 Examples: 2309 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2310 2311 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2312 """ 2313 if isinstance(line1, Points): 2314 line1 = line1.coordinates 2315 if isinstance(line2, Points): 2316 line2 = line2.coordinates 2317 2318 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2319 2320 v = (sm1 - sm2) / 3 * tip_width 2321 p1 = sm1 + v 2322 p2 = sm2 - v 2323 pm1 = (sm1 + sm2) / 2 2324 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2325 pm12 = pm1 - pm2 2326 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2327 2328 line1.append(p1) 2329 line1.append(tip) 2330 line2.append(p2) 2331 line2.append(tip) 2332 resm = max(100, len(line1)) 2333 2334 super().__init__(line1, line2, res=(resm, 1)) 2335 self.phong().lighting("off") 2336 self.actor.PickableOff() 2337 self.actor.DragableOff() 2338 self.name = "FlatArrow"
Build a 2D arrow in 3D space by joining two close lines.
2304 def __init__(self, line1, line2, tip_size=1.0, tip_width=1.0) -> None: 2305 """ 2306 Build a 2D arrow in 3D space by joining two close lines. 2307 2308 Examples: 2309 - [flatarrow.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/flatarrow.py) 2310 2311 ![](https://vedo.embl.es/images/basic/flatarrow.png) 2312 """ 2313 if isinstance(line1, Points): 2314 line1 = line1.coordinates 2315 if isinstance(line2, Points): 2316 line2 = line2.coordinates 2317 2318 sm1, sm2 = np.array(line1[-1], dtype=float), np.array(line2[-1], dtype=float) 2319 2320 v = (sm1 - sm2) / 3 * tip_width 2321 p1 = sm1 + v 2322 p2 = sm2 - v 2323 pm1 = (sm1 + sm2) / 2 2324 pm2 = (np.array(line1[-2]) + np.array(line2[-2])) / 2 2325 pm12 = pm1 - pm2 2326 tip = pm12 / np.linalg.norm(pm12) * np.linalg.norm(v) * 3 * tip_size / tip_width + pm1 2327 2328 line1.append(p1) 2329 line1.append(tip) 2330 line2.append(p2) 2331 line2.append(tip) 2332 resm = max(100, len(line1)) 2333 2334 super().__init__(line1, line2, res=(resm, 1)) 2335 self.phong().lighting("off") 2336 self.actor.PickableOff() 2337 self.actor.DragableOff() 2338 self.name = "FlatArrow"
2351class Polygon(Mesh): 2352 """ 2353 Build a polygon in the `xy` plane. 2354 """ 2355 2356 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2357 """ 2358 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2359 2360 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2361 """ 2362 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2363 pts = pol2cart(np.ones_like(t) * r, t).T 2364 faces = [list(range(nsides))] 2365 # do not use: vtkRegularPolygonSource 2366 super().__init__([pts, faces], c, alpha) 2367 if len(pos) == 2: 2368 pos = (pos[0], pos[1], 0) 2369 self.pos(pos) 2370 self.properties.LightingOff() 2371 self.name = "Polygon " + str(nsides)
Build a polygon in the xy
plane.
2356 def __init__(self, pos=(0, 0, 0), nsides=6, r=1.0, c="coral", alpha=1.0) -> None: 2357 """ 2358 Build a polygon in the `xy` plane of `nsides` of radius `r`. 2359 2360 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestRegularPolygonSource.png) 2361 """ 2362 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=nsides, endpoint=False) 2363 pts = pol2cart(np.ones_like(t) * r, t).T 2364 faces = [list(range(nsides))] 2365 # do not use: vtkRegularPolygonSource 2366 super().__init__([pts, faces], c, alpha) 2367 if len(pos) == 2: 2368 pos = (pos[0], pos[1], 0) 2369 self.pos(pos) 2370 self.properties.LightingOff() 2371 self.name = "Polygon " + str(nsides)
Build a polygon in the xy
plane of nsides
of radius r
.
2341class Triangle(Mesh): 2342 """Create a triangle from 3 points in space.""" 2343 2344 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2345 """Create a triangle from 3 points in space.""" 2346 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2347 self.properties.LightingOff() 2348 self.name = "Triangle"
Create a triangle from 3 points in space.
2344 def __init__(self, p1, p2, p3, c="green7", alpha=1.0) -> None: 2345 """Create a triangle from 3 points in space.""" 2346 super().__init__([[p1, p2, p3], [[0, 1, 2]]], c, alpha) 2347 self.properties.LightingOff() 2348 self.name = "Triangle"
Create a triangle from 3 points in space.
3140class Rectangle(Mesh): 3141 """ 3142 Build a rectangle in the xy plane. 3143 """ 3144 3145 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3146 """ 3147 Build a rectangle in the xy plane identified by any two corner points. 3148 3149 Arguments: 3150 p1 : (list) 3151 bottom-left position of the corner 3152 p2 : (list) 3153 top-right position of the corner 3154 radius : (float, list) 3155 smoothing radius of the corner in world units. 3156 A list can be passed with 4 individual values. 3157 """ 3158 if len(p1) == 2: 3159 p1 = np.array([p1[0], p1[1], 0.0]) 3160 else: 3161 p1 = np.array(p1, dtype=float) 3162 if len(p2) == 2: 3163 p2 = np.array([p2[0], p2[1], 0.0]) 3164 else: 3165 p2 = np.array(p2, dtype=float) 3166 3167 self.corner1 = p1 3168 self.corner2 = p2 3169 3170 color = c 3171 smoothr = False 3172 risseq = False 3173 if utils.is_sequence(radius): 3174 risseq = True 3175 smoothr = True 3176 if max(radius) == 0: 3177 smoothr = False 3178 elif radius: 3179 smoothr = True 3180 3181 if not smoothr: 3182 radius = None 3183 self.radius = radius 3184 3185 if smoothr: 3186 r = radius 3187 if not risseq: 3188 r = [r, r, r, r] 3189 rd, ra, rb, rc = r 3190 3191 if p1[0] > p2[0]: # flip p1 - p2 3192 p1, p2 = p2, p1 3193 if p1[1] > p2[1]: # flip p1y - p2y 3194 p1[1], p2[1] = p2[1], p1[1] 3195 3196 px, py, _ = p2 - p1 3197 k = min(px / 2, py / 2) 3198 ra = min(abs(ra), k) 3199 rb = min(abs(rb), k) 3200 rc = min(abs(rc), k) 3201 rd = min(abs(rd), k) 3202 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3203 betas = np.split(beta, 4) 3204 rrx = np.cos(betas) 3205 rry = np.sin(betas) 3206 3207 q1 = (rd, 0) 3208 # q2 = (px-ra, 0) 3209 q3 = (px, ra) 3210 # q4 = (px, py-rb) 3211 q5 = (px - rb, py) 3212 # q6 = (rc, py) 3213 q7 = (0, py - rc) 3214 # q8 = (0, rd) 3215 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3216 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3217 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3218 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3219 3220 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3221 faces = [list(range(len(pts)))] 3222 else: 3223 p1r = np.array([p2[0], p1[1], 0.0]) 3224 p2l = np.array([p1[0], p2[1], 0.0]) 3225 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3226 faces = [(0, 1, 2, 3)] 3227 3228 super().__init__([pts, faces], color, alpha) 3229 self.pos(p1) 3230 self.properties.LightingOff() 3231 self.name = "Rectangle"
Build a rectangle in the xy plane.
3145 def __init__(self, p1=(0, 0), p2=(1, 1), radius=None, res=12, c="gray5", alpha=1.0) -> None: 3146 """ 3147 Build a rectangle in the xy plane identified by any two corner points. 3148 3149 Arguments: 3150 p1 : (list) 3151 bottom-left position of the corner 3152 p2 : (list) 3153 top-right position of the corner 3154 radius : (float, list) 3155 smoothing radius of the corner in world units. 3156 A list can be passed with 4 individual values. 3157 """ 3158 if len(p1) == 2: 3159 p1 = np.array([p1[0], p1[1], 0.0]) 3160 else: 3161 p1 = np.array(p1, dtype=float) 3162 if len(p2) == 2: 3163 p2 = np.array([p2[0], p2[1], 0.0]) 3164 else: 3165 p2 = np.array(p2, dtype=float) 3166 3167 self.corner1 = p1 3168 self.corner2 = p2 3169 3170 color = c 3171 smoothr = False 3172 risseq = False 3173 if utils.is_sequence(radius): 3174 risseq = True 3175 smoothr = True 3176 if max(radius) == 0: 3177 smoothr = False 3178 elif radius: 3179 smoothr = True 3180 3181 if not smoothr: 3182 radius = None 3183 self.radius = radius 3184 3185 if smoothr: 3186 r = radius 3187 if not risseq: 3188 r = [r, r, r, r] 3189 rd, ra, rb, rc = r 3190 3191 if p1[0] > p2[0]: # flip p1 - p2 3192 p1, p2 = p2, p1 3193 if p1[1] > p2[1]: # flip p1y - p2y 3194 p1[1], p2[1] = p2[1], p1[1] 3195 3196 px, py, _ = p2 - p1 3197 k = min(px / 2, py / 2) 3198 ra = min(abs(ra), k) 3199 rb = min(abs(rb), k) 3200 rc = min(abs(rc), k) 3201 rd = min(abs(rd), k) 3202 beta = np.linspace(0, 2 * np.pi, num=res * 4, endpoint=False) 3203 betas = np.split(beta, 4) 3204 rrx = np.cos(betas) 3205 rry = np.sin(betas) 3206 3207 q1 = (rd, 0) 3208 # q2 = (px-ra, 0) 3209 q3 = (px, ra) 3210 # q4 = (px, py-rb) 3211 q5 = (px - rb, py) 3212 # q6 = (rc, py) 3213 q7 = (0, py - rc) 3214 # q8 = (0, rd) 3215 a = np.c_[rrx[3], rry[3]]*ra + [px-ra, ra] if ra else np.array([]) 3216 b = np.c_[rrx[0], rry[0]]*rb + [px-rb, py-rb] if rb else np.array([]) 3217 c = np.c_[rrx[1], rry[1]]*rc + [rc, py-rc] if rc else np.array([]) 3218 d = np.c_[rrx[2], rry[2]]*rd + [rd, rd] if rd else np.array([]) 3219 3220 pts = [q1, *a.tolist(), q3, *b.tolist(), q5, *c.tolist(), q7, *d.tolist()] 3221 faces = [list(range(len(pts)))] 3222 else: 3223 p1r = np.array([p2[0], p1[1], 0.0]) 3224 p2l = np.array([p1[0], p2[1], 0.0]) 3225 pts = ([0.0, 0.0, 0.0], p1r - p1, p2 - p1, p2l - p1) 3226 faces = [(0, 1, 2, 3)] 3227 3228 super().__init__([pts, faces], color, alpha) 3229 self.pos(p1) 3230 self.properties.LightingOff() 3231 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.
2482class Disc(Mesh): 2483 """ 2484 Build a 2D disc. 2485 """ 2486 2487 def __init__( 2488 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2489 ) -> None: 2490 """ 2491 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2492 2493 Set `res` as the resolution in R and Phi (can be a list). 2494 2495 Use `angle_range` to create a disc sector between the 2 specified angles. 2496 2497 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2498 """ 2499 if utils.is_sequence(res): 2500 res_r, res_phi = res 2501 else: 2502 res_r, res_phi = res, 12 * res 2503 2504 if len(angle_range) == 0: 2505 ps = vtki.new("DiskSource") 2506 else: 2507 ps = vtki.new("SectorSource") 2508 ps.SetStartAngle(angle_range[0]) 2509 ps.SetEndAngle(angle_range[1]) 2510 2511 ps.SetInnerRadius(r1) 2512 ps.SetOuterRadius(r2) 2513 ps.SetRadialResolution(res_r) 2514 ps.SetCircumferentialResolution(res_phi) 2515 ps.Update() 2516 super().__init__(ps.GetOutput(), c, alpha) 2517 self.flat() 2518 self.pos(utils.make3d(pos)) 2519 self.name = "Disc"
Build a 2D disc.
2487 def __init__( 2488 self, pos=(0, 0, 0), r1=0.5, r2=1.0, res=(1, 120), angle_range=(), c="gray4", alpha=1.0 2489 ) -> None: 2490 """ 2491 Build a 2D disc of inner radius `r1` and outer radius `r2`. 2492 2493 Set `res` as the resolution in R and Phi (can be a list). 2494 2495 Use `angle_range` to create a disc sector between the 2 specified angles. 2496 2497 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestDisk.png) 2498 """ 2499 if utils.is_sequence(res): 2500 res_r, res_phi = res 2501 else: 2502 res_r, res_phi = res, 12 * res 2503 2504 if len(angle_range) == 0: 2505 ps = vtki.new("DiskSource") 2506 else: 2507 ps = vtki.new("SectorSource") 2508 ps.SetStartAngle(angle_range[0]) 2509 ps.SetEndAngle(angle_range[1]) 2510 2511 ps.SetInnerRadius(r1) 2512 ps.SetOuterRadius(r2) 2513 ps.SetRadialResolution(res_r) 2514 ps.SetCircumferentialResolution(res_phi) 2515 ps.Update() 2516 super().__init__(ps.GetOutput(), c, alpha) 2517 self.flat() 2518 self.pos(utils.make3d(pos)) 2519 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.
2374class Circle(Polygon): 2375 """ 2376 Build a Circle of radius `r`. 2377 """ 2378 2379 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2380 """ 2381 Build a Circle of radius `r`. 2382 """ 2383 super().__init__(pos, nsides=res, r=r) 2384 2385 self.nr_of_points = 0 2386 self.va = 0 2387 self.vb = 0 2388 self.axis1: List[float] = [] 2389 self.axis2: List[float] = [] 2390 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2391 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2392 self.alpha(alpha).c(c) 2393 self.name = "Circle" 2394 2395 def acircularity(self) -> float: 2396 """ 2397 Return a measure of how different an ellipse is from a circle. 2398 Values close to zero correspond to a circular object. 2399 """ 2400 a, b = self.va, self.vb 2401 value = 0.0 2402 if a+b: 2403 value = ((a-b)/(a+b))**2 2404 return value
Build a Circle of radius r
.
2379 def __init__(self, pos=(0, 0, 0), r=1.0, res=120, c="gray5", alpha=1.0) -> None: 2380 """ 2381 Build a Circle of radius `r`. 2382 """ 2383 super().__init__(pos, nsides=res, r=r) 2384 2385 self.nr_of_points = 0 2386 self.va = 0 2387 self.vb = 0 2388 self.axis1: List[float] = [] 2389 self.axis2: List[float] = [] 2390 self.center: List[float] = [] # filled by pointcloud.pca_ellipse() 2391 self.pvalue = 0.0 # filled by pointcloud.pca_ellipse() 2392 self.alpha(alpha).c(c) 2393 self.name = "Circle"
Build a Circle of radius r
.
2395 def acircularity(self) -> float: 2396 """ 2397 Return a measure of how different an ellipse is from a circle. 2398 Values close to zero correspond to a circular object. 2399 """ 2400 a, b = self.va, self.vb 2401 value = 0.0 2402 if a+b: 2403 value = ((a-b)/(a+b))**2 2404 return value
Return a measure of how different an ellipse is from a circle. Values close to zero correspond to a circular object.
2406class GeoCircle(Polygon): 2407 """ 2408 Build a Circle of radius `r`. 2409 """ 2410 2411 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2412 """ 2413 Build a Circle of radius `r` as projected on a geographic map. 2414 Circles near the poles will look very squashed. 2415 2416 See example: 2417 ```bash 2418 vedo -r earthquake 2419 ``` 2420 """ 2421 coords = [] 2422 sinr, cosr = np.sin(r), np.cos(r) 2423 sinlat, coslat = np.sin(lat), np.cos(lat) 2424 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2425 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2426 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2427 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2428 2429 super().__init__(nsides=res, c=c, alpha=alpha) 2430 self.coordinates = coords # warp polygon points to match geo projection 2431 self.name = "Circle"
Build a Circle of radius r
.
2411 def __init__(self, lat, lon, r=1.0, res=60, c="red4", alpha=1.0) -> None: 2412 """ 2413 Build a Circle of radius `r` as projected on a geographic map. 2414 Circles near the poles will look very squashed. 2415 2416 See example: 2417 ```bash 2418 vedo -r earthquake 2419 ``` 2420 """ 2421 coords = [] 2422 sinr, cosr = np.sin(r), np.cos(r) 2423 sinlat, coslat = np.sin(lat), np.cos(lat) 2424 for phi in np.linspace(0, 2 * np.pi, num=res, endpoint=False): 2425 clat = np.arcsin(sinlat * cosr + coslat * sinr * np.cos(phi)) 2426 clng = lon + np.arctan2(np.sin(phi) * sinr * coslat, cosr - sinlat * np.sin(clat)) 2427 coords.append([clng / np.pi + 1, clat * 2 / np.pi + 1, 0]) 2428 2429 super().__init__(nsides=res, c=c, alpha=alpha) 2430 self.coordinates = coords # warp polygon points to match geo projection 2431 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
1135class Arc(Line): 1136 """ 1137 Build a 2D circular arc between 2 points. 1138 """ 1139 1140 def __init__( 1141 self, 1142 center=None, 1143 point1=None, 1144 point2=None, 1145 normal=None, 1146 angle=None, 1147 invert=False, 1148 res=60, 1149 c="k3", 1150 alpha=1.0, 1151 ) -> None: 1152 """ 1153 Build a 2D circular arc between 2 points. 1154 Two modes are available: 1155 1. [center, point1, point2] are specified 1156 1157 2. [point1, normal, angle] are specified. 1158 1159 In the first case it creates an arc defined by two endpoints and a center. 1160 In the second the arc spans the shortest angular sector defined by 1161 a starting point, a normal and a spanning angle. 1162 if `invert=True`, then the opposite happens. 1163 1164 Example 1: 1165 ```python 1166 from vedo import * 1167 center = [0,1,0] 1168 p1 = [1,2,0.4] 1169 p2 = [0.5,3,-1] 1170 arc = Arc(center, p1, p2).lw(5).c("purple5") 1171 line2 = Line(center, p2) 1172 pts = Points([center, p1,p2], r=9, c='r') 1173 show(pts, line2, arc, f"length={arc.length()}", axes=1).close() 1174 ``` 1175 1176 Example 2: 1177 ```python 1178 from vedo import * 1179 arc = Arc(point1=[0,1,0], normal=[0,0,1], angle=270) 1180 arc.lw(5).c("purple5") 1181 origin = Point([0,0,0], r=9, c='r') 1182 show(origin, arc, arc.labels2d(), axes=1).close() 1183 ``` 1184 """ 1185 ar = vtki.new("ArcSource") 1186 if point2 is not None: 1187 center = utils.make3d(center) 1188 point1 = utils.make3d(point1) 1189 point2 = utils.make3d(point2) 1190 ar.UseNormalAndAngleOff() 1191 ar.SetPoint1(point1-center) 1192 ar.SetPoint2(point2-center) 1193 elif normal is not None and angle and point1 is not None: 1194 normal = utils.make3d(normal) 1195 point1 = utils.make3d(point1) 1196 ar.UseNormalAndAngleOn() 1197 ar.SetAngle(angle) 1198 ar.SetPolarVector(point1) 1199 ar.SetNormal(normal) 1200 self.top = normal 1201 else: 1202 vedo.logger.error("in Arc(), incorrect input combination.") 1203 raise TypeError 1204 ar.SetNegative(invert) 1205 ar.SetResolution(res) 1206 ar.Update() 1207 1208 super().__init__(ar.GetOutput(), c, alpha) 1209 self.lw(2).lighting("off") 1210 if point2 is not None: # nb: not center 1211 self.pos(center) 1212 self.name = "Arc"
Build a 2D circular arc between 2 points.
1140 def __init__( 1141 self, 1142 center=None, 1143 point1=None, 1144 point2=None, 1145 normal=None, 1146 angle=None, 1147 invert=False, 1148 res=60, 1149 c="k3", 1150 alpha=1.0, 1151 ) -> None: 1152 """ 1153 Build a 2D circular arc between 2 points. 1154 Two modes are available: 1155 1. [center, point1, point2] are specified 1156 1157 2. [point1, normal, angle] are specified. 1158 1159 In the first case it creates an arc defined by two endpoints and a center. 1160 In the second the arc spans the shortest angular sector defined by 1161 a starting point, a normal and a spanning angle. 1162 if `invert=True`, then the opposite happens. 1163 1164 Example 1: 1165 ```python 1166 from vedo import * 1167 center = [0,1,0] 1168 p1 = [1,2,0.4] 1169 p2 = [0.5,3,-1] 1170 arc = Arc(center, p1, p2).lw(5).c("purple5") 1171 line2 = Line(center, p2) 1172 pts = Points([center, p1,p2], r=9, c='r') 1173 show(pts, line2, arc, f"length={arc.length()}", axes=1).close() 1174 ``` 1175 1176 Example 2: 1177 ```python 1178 from vedo import * 1179 arc = Arc(point1=[0,1,0], normal=[0,0,1], angle=270) 1180 arc.lw(5).c("purple5") 1181 origin = Point([0,0,0], r=9, c='r') 1182 show(origin, arc, arc.labels2d(), axes=1).close() 1183 ``` 1184 """ 1185 ar = vtki.new("ArcSource") 1186 if point2 is not None: 1187 center = utils.make3d(center) 1188 point1 = utils.make3d(point1) 1189 point2 = utils.make3d(point2) 1190 ar.UseNormalAndAngleOff() 1191 ar.SetPoint1(point1-center) 1192 ar.SetPoint2(point2-center) 1193 elif normal is not None and angle and point1 is not None: 1194 normal = utils.make3d(normal) 1195 point1 = utils.make3d(point1) 1196 ar.UseNormalAndAngleOn() 1197 ar.SetAngle(angle) 1198 ar.SetPolarVector(point1) 1199 ar.SetNormal(normal) 1200 self.top = normal 1201 else: 1202 vedo.logger.error("in Arc(), incorrect input combination.") 1203 raise TypeError 1204 ar.SetNegative(invert) 1205 ar.SetResolution(res) 1206 ar.Update() 1207 1208 super().__init__(ar.GetOutput(), c, alpha) 1209 self.lw(2).lighting("off") 1210 if point2 is not None: # nb: not center 1211 self.pos(center) 1212 self.name = "Arc"
Build a 2D circular arc between 2 points.
Two modes are available:
[center, point1, point2] are specified
[point1, normal, angle] are specified.
In the first case it creates an arc defined by two endpoints and a center.
In the second the arc spans the shortest angular sector defined by
a starting point, a normal and a spanning angle.
if invert=True
, then the opposite happens.
Example 1:
from vedo import *
center = [0,1,0]
p1 = [1,2,0.4]
p2 = [0.5,3,-1]
arc = Arc(center, p1, p2).lw(5).c("purple5")
line2 = Line(center, p2)
pts = Points([center, p1,p2], r=9, c='r')
show(pts, line2, arc, f"length={arc.length()}", axes=1).close()
Example 2:
from vedo import *
arc = Arc(point1=[0,1,0], normal=[0,0,1], angle=270)
arc.lw(5).c("purple5")
origin = Point([0,0,0], r=9, c='r')
show(origin, arc, arc.labels2d(), axes=1).close()
2434class Star(Mesh): 2435 """ 2436 Build a 2D star shape. 2437 """ 2438 2439 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2440 """ 2441 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2442 2443 If line is True then only build the outer line (no internal surface meshing). 2444 2445 Example: 2446 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2447 2448 ![](https://vedo.embl.es/images/basic/extrude.png) 2449 """ 2450 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2451 x, y = pol2cart(np.ones_like(t) * r2, t) 2452 pts = np.c_[x, y, np.zeros_like(x)] 2453 2454 apts = [] 2455 for i, p in enumerate(pts): 2456 apts.append(p) 2457 if i + 1 < n: 2458 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2459 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2460 2461 if line: 2462 apts.append(pts[0]) 2463 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2464 super().__init__(poly, c, alpha) 2465 self.lw(2) 2466 else: 2467 apts.append((0, 0, 0)) 2468 cells = [] 2469 for i in range(2 * n - 1): 2470 cell = [2 * n, i, i + 1] 2471 cells.append(cell) 2472 cells.append([2 * n, i + 1, 0]) 2473 super().__init__([apts, cells], c, alpha) 2474 2475 if len(pos) == 2: 2476 pos = (pos[0], pos[1], 0) 2477 2478 self.properties.LightingOff() 2479 self.name = "Star"
Build a 2D star shape.
2439 def __init__(self, pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="blue6", alpha=1.0) -> None: 2440 """ 2441 Build a 2D star shape of `n` cusps of inner radius `r1` and outer radius `r2`. 2442 2443 If line is True then only build the outer line (no internal surface meshing). 2444 2445 Example: 2446 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2447 2448 ![](https://vedo.embl.es/images/basic/extrude.png) 2449 """ 2450 t = np.linspace(np.pi / 2, 5 / 2 * np.pi, num=n, endpoint=False) 2451 x, y = pol2cart(np.ones_like(t) * r2, t) 2452 pts = np.c_[x, y, np.zeros_like(x)] 2453 2454 apts = [] 2455 for i, p in enumerate(pts): 2456 apts.append(p) 2457 if i + 1 < n: 2458 apts.append((p + pts[i + 1]) / 2 * r1 / r2) 2459 apts.append((pts[-1] + pts[0]) / 2 * r1 / r2) 2460 2461 if line: 2462 apts.append(pts[0]) 2463 poly = utils.buildPolyData(apts, lines=list(range(len(apts)))) 2464 super().__init__(poly, c, alpha) 2465 self.lw(2) 2466 else: 2467 apts.append((0, 0, 0)) 2468 cells = [] 2469 for i in range(2 * n - 1): 2470 cell = [2 * n, i, i + 1] 2471 cells.append(cell) 2472 cells.append([2 * n, i + 1, 0]) 2473 super().__init__([apts, cells], c, alpha) 2474 2475 if len(pos) == 2: 2476 pos = (pos[0], pos[1], 0) 2477 2478 self.properties.LightingOff() 2479 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:
3864class Star3D(Mesh): 3865 """ 3866 Build a 3D starred shape. 3867 """ 3868 3869 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3870 """ 3871 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3872 """ 3873 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3874 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3875 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3876 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3877 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3878 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3879 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3880 [10,1, 0],[10,11, 9]] 3881 3882 super().__init__([pts, fcs], c, alpha) 3883 self.rotate_x(90) 3884 self.scale(r).lighting("shiny") 3885 3886 if len(pos) == 2: 3887 pos = (pos[0], pos[1], 0) 3888 self.pos(pos) 3889 self.name = "Star3D"
Build a 3D starred shape.
3869 def __init__(self, pos=(0, 0, 0), r=1.0, thickness=0.1, c="blue4", alpha=1.0) -> None: 3870 """ 3871 Build a 3D star shape of 5 cusps, mainly useful as a 3D marker. 3872 """ 3873 pts = ((1.34, 0., -0.37), (5.75e-3, -0.588, thickness/10), (0.377, 0.,-0.38), 3874 (0.0116, 0., -1.35), (-0.366, 0., -0.384), (-1.33, 0., -0.385), 3875 (-0.600, 0., 0.321), (-0.829, 0., 1.19), (-1.17e-3, 0., 0.761), 3876 (0.824, 0., 1.20), (0.602, 0., 0.328), (6.07e-3, 0.588, thickness/10)) 3877 fcs = [[0, 1, 2], [0, 11,10], [2, 1, 3], [2, 11, 0], [3, 1, 4], [3, 11, 2], 3878 [4, 1, 5], [4, 11, 3], [5, 1, 6], [5, 11, 4], [6, 1, 7], [6, 11, 5], 3879 [7, 1, 8], [7, 11, 6], [8, 1, 9], [8, 11, 7], [9, 1,10], [9, 11, 8], 3880 [10,1, 0],[10,11, 9]] 3881 3882 super().__init__([pts, fcs], c, alpha) 3883 self.rotate_x(90) 3884 self.scale(r).lighting("shiny") 3885 3886 if len(pos) == 2: 3887 pos = (pos[0], pos[1], 0) 3888 self.pos(pos) 3889 self.name = "Star3D"
Build a 3D star shape of 5 cusps, mainly useful as a 3D marker.
3892class Cross3D(Mesh): 3893 """ 3894 Build a 3D cross shape. 3895 """ 3896 3897 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3898 """ 3899 Build a 3D cross shape, mainly useful as a 3D marker. 3900 """ 3901 if len(pos) == 2: 3902 pos = (pos[0], pos[1], 0) 3903 3904 c1 = Cylinder(r=thickness * s, height=2 * s) 3905 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3906 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3907 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3908 super().__init__(poly, c, alpha) 3909 self.name = "Cross3D"
Build a 3D cross shape.
3897 def __init__(self, pos=(0, 0, 0), s=1.0, thickness=0.3, c="b", alpha=1.0) -> None: 3898 """ 3899 Build a 3D cross shape, mainly useful as a 3D marker. 3900 """ 3901 if len(pos) == 2: 3902 pos = (pos[0], pos[1], 0) 3903 3904 c1 = Cylinder(r=thickness * s, height=2 * s) 3905 c2 = Cylinder(r=thickness * s, height=2 * s).rotate_x(90) 3906 c3 = Cylinder(r=thickness * s, height=2 * s).rotate_y(90) 3907 poly = merge(c1, c2, c3).color(c).alpha(alpha).pos(pos).dataset 3908 super().__init__(poly, c, alpha) 3909 self.name = "Cross3D"
Build a 3D cross shape, mainly useful as a 3D marker.
2521class IcoSphere(Mesh): 2522 """ 2523 Create a sphere made of a uniform triangle mesh. 2524 """ 2525 2526 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2527 """ 2528 Create a sphere made of a uniform triangle mesh 2529 (from recursive subdivision of an icosahedron). 2530 2531 Example: 2532 ```python 2533 from vedo import * 2534 icos = IcoSphere(subdivisions=3) 2535 icos.compute_quality().cmap('coolwarm') 2536 icos.show(axes=1).close() 2537 ``` 2538 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2539 """ 2540 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2541 2542 t = (1.0 + np.sqrt(5.0)) / 2.0 2543 points = np.array( 2544 [ 2545 [-1, t, 0], 2546 [1, t, 0], 2547 [-1, -t, 0], 2548 [1, -t, 0], 2549 [0, -1, t], 2550 [0, 1, t], 2551 [0, -1, -t], 2552 [0, 1, -t], 2553 [t, 0, -1], 2554 [t, 0, 1], 2555 [-t, 0, -1], 2556 [-t, 0, 1], 2557 ] 2558 ) 2559 faces = [ 2560 [0, 11, 5], 2561 [0, 5, 1], 2562 [0, 1, 7], 2563 [0, 7, 10], 2564 [0, 10, 11], 2565 [1, 5, 9], 2566 [5, 11, 4], 2567 [11, 10, 2], 2568 [10, 7, 6], 2569 [7, 1, 8], 2570 [3, 9, 4], 2571 [3, 4, 2], 2572 [3, 2, 6], 2573 [3, 6, 8], 2574 [3, 8, 9], 2575 [4, 9, 5], 2576 [2, 4, 11], 2577 [6, 2, 10], 2578 [8, 6, 7], 2579 [9, 8, 1], 2580 ] 2581 super().__init__([points * r, faces], c=c, alpha=alpha) 2582 2583 for _ in range(subdivisions): 2584 self.subdivide(method=1) 2585 pts = utils.versor(self.coordinates) * r 2586 self.coordinates = pts 2587 2588 self.pos(pos) 2589 self.name = "IcoSphere"
Create a sphere made of a uniform triangle mesh.
2526 def __init__(self, pos=(0, 0, 0), r=1.0, subdivisions=4, c="r5", alpha=1.0) -> None: 2527 """ 2528 Create a sphere made of a uniform triangle mesh 2529 (from recursive subdivision of an icosahedron). 2530 2531 Example: 2532 ```python 2533 from vedo import * 2534 icos = IcoSphere(subdivisions=3) 2535 icos.compute_quality().cmap('coolwarm') 2536 icos.show(axes=1).close() 2537 ``` 2538 ![](https://vedo.embl.es/images/basic/icosphere.jpg) 2539 """ 2540 subdivisions = int(min(subdivisions, 9)) # to avoid disasters 2541 2542 t = (1.0 + np.sqrt(5.0)) / 2.0 2543 points = np.array( 2544 [ 2545 [-1, t, 0], 2546 [1, t, 0], 2547 [-1, -t, 0], 2548 [1, -t, 0], 2549 [0, -1, t], 2550 [0, 1, t], 2551 [0, -1, -t], 2552 [0, 1, -t], 2553 [t, 0, -1], 2554 [t, 0, 1], 2555 [-t, 0, -1], 2556 [-t, 0, 1], 2557 ] 2558 ) 2559 faces = [ 2560 [0, 11, 5], 2561 [0, 5, 1], 2562 [0, 1, 7], 2563 [0, 7, 10], 2564 [0, 10, 11], 2565 [1, 5, 9], 2566 [5, 11, 4], 2567 [11, 10, 2], 2568 [10, 7, 6], 2569 [7, 1, 8], 2570 [3, 9, 4], 2571 [3, 4, 2], 2572 [3, 2, 6], 2573 [3, 6, 8], 2574 [3, 8, 9], 2575 [4, 9, 5], 2576 [2, 4, 11], 2577 [6, 2, 10], 2578 [8, 6, 7], 2579 [9, 8, 1], 2580 ] 2581 super().__init__([points * r, faces], c=c, alpha=alpha) 2582 2583 for _ in range(subdivisions): 2584 self.subdivide(method=1) 2585 pts = utils.versor(self.coordinates) * r 2586 self.coordinates = pts 2587 2588 self.pos(pos) 2589 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()
2592class Sphere(Mesh): 2593 """ 2594 Build a sphere. 2595 """ 2596 2597 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2598 """ 2599 Build a sphere at position `pos` of radius `r`. 2600 2601 Arguments: 2602 r : (float) 2603 sphere radius 2604 res : (int, list) 2605 resolution in phi, resolution in theta is by default `2*res` 2606 quads : (bool) 2607 sphere mesh will be made of quads instead of triangles 2608 2609 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2610 """ 2611 if len(pos) == 2: 2612 pos = np.asarray([pos[0], pos[1], 0]) 2613 2614 self.radius = r # used by fitSphere 2615 self.center = pos 2616 self.residue = 0 2617 2618 if quads: 2619 res = max(res, 4) 2620 img = vtki.vtkImageData() 2621 img.SetDimensions(res - 1, res - 1, res - 1) 2622 rs = 1.0 / (res - 2) 2623 img.SetSpacing(rs, rs, rs) 2624 gf = vtki.new("GeometryFilter") 2625 gf.SetInputData(img) 2626 gf.Update() 2627 super().__init__(gf.GetOutput(), c, alpha) 2628 self.lw(0.1) 2629 2630 cgpts = self.coordinates - (0.5, 0.5, 0.5) 2631 2632 x, y, z = cgpts.T 2633 x = x * (1 + x * x) / 2 2634 y = y * (1 + y * y) / 2 2635 z = z * (1 + z * z) / 2 2636 _, theta, phi = cart2spher(x, y, z) 2637 2638 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2639 self.coordinates = pts 2640 2641 else: 2642 if utils.is_sequence(res): 2643 res_t, res_phi = res 2644 else: 2645 res_t, res_phi = 2 * res, res 2646 2647 ss = vtki.new("SphereSource") 2648 ss.SetRadius(r) 2649 ss.SetThetaResolution(res_t) 2650 ss.SetPhiResolution(res_phi) 2651 ss.Update() 2652 2653 super().__init__(ss.GetOutput(), c, alpha) 2654 2655 self.phong() 2656 self.pos(pos) 2657 self.name = "Sphere"
Build a sphere.
2597 def __init__(self, pos=(0, 0, 0), r=1.0, res=24, quads=False, c="r5", alpha=1.0) -> None: 2598 """ 2599 Build a sphere at position `pos` of radius `r`. 2600 2601 Arguments: 2602 r : (float) 2603 sphere radius 2604 res : (int, list) 2605 resolution in phi, resolution in theta is by default `2*res` 2606 quads : (bool) 2607 sphere mesh will be made of quads instead of triangles 2608 2609 [](https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png) 2610 """ 2611 if len(pos) == 2: 2612 pos = np.asarray([pos[0], pos[1], 0]) 2613 2614 self.radius = r # used by fitSphere 2615 self.center = pos 2616 self.residue = 0 2617 2618 if quads: 2619 res = max(res, 4) 2620 img = vtki.vtkImageData() 2621 img.SetDimensions(res - 1, res - 1, res - 1) 2622 rs = 1.0 / (res - 2) 2623 img.SetSpacing(rs, rs, rs) 2624 gf = vtki.new("GeometryFilter") 2625 gf.SetInputData(img) 2626 gf.Update() 2627 super().__init__(gf.GetOutput(), c, alpha) 2628 self.lw(0.1) 2629 2630 cgpts = self.coordinates - (0.5, 0.5, 0.5) 2631 2632 x, y, z = cgpts.T 2633 x = x * (1 + x * x) / 2 2634 y = y * (1 + y * y) / 2 2635 z = z * (1 + z * z) / 2 2636 _, theta, phi = cart2spher(x, y, z) 2637 2638 pts = spher2cart(np.ones_like(phi) * r, theta, phi).T 2639 self.coordinates = pts 2640 2641 else: 2642 if utils.is_sequence(res): 2643 res_t, res_phi = res 2644 else: 2645 res_t, res_phi = 2 * res, res 2646 2647 ss = vtki.new("SphereSource") 2648 ss.SetRadius(r) 2649 ss.SetThetaResolution(res_t) 2650 ss.SetPhiResolution(res_phi) 2651 ss.Update() 2652 2653 super().__init__(ss.GetOutput(), c, alpha) 2654 2655 self.phong() 2656 self.pos(pos) 2657 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
2660class Spheres(Mesh): 2661 """ 2662 Build a large set of spheres. 2663 """ 2664 2665 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2666 """ 2667 Build a (possibly large) set of spheres at `centers` of radius `r`. 2668 2669 Either `c` or `r` can be a list of RGB colors or radii. 2670 2671 Examples: 2672 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2673 2674 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2675 """ 2676 2677 if isinstance(centers, Points): 2678 centers = centers.coordinates 2679 centers = np.asarray(centers, dtype=float) 2680 base = centers[0] 2681 2682 cisseq = False 2683 if utils.is_sequence(c): 2684 cisseq = True 2685 2686 if cisseq: 2687 if len(centers) != len(c): 2688 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2689 raise RuntimeError() 2690 2691 risseq = False 2692 if utils.is_sequence(r): 2693 risseq = True 2694 2695 if risseq: 2696 if len(centers) != len(r): 2697 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2698 raise RuntimeError() 2699 if cisseq and risseq: 2700 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2701 raise RuntimeError() 2702 2703 src = vtki.new("SphereSource") 2704 if not risseq: 2705 src.SetRadius(r) 2706 if utils.is_sequence(res): 2707 res_t, res_phi = res 2708 else: 2709 res_t, res_phi = 2 * res, res 2710 2711 src.SetThetaResolution(res_t) 2712 src.SetPhiResolution(res_phi) 2713 src.Update() 2714 2715 psrc = vtki.new("PointSource") 2716 psrc.SetNumberOfPoints(len(centers)) 2717 psrc.Update() 2718 pd = psrc.GetOutput() 2719 vpts = pd.GetPoints() 2720 2721 glyph = vtki.vtkGlyph3D() 2722 glyph.SetSourceConnection(src.GetOutputPort()) 2723 2724 if cisseq: 2725 glyph.SetColorModeToColorByScalar() 2726 ucols = vtki.vtkUnsignedCharArray() 2727 ucols.SetNumberOfComponents(3) 2728 ucols.SetName("Colors") 2729 for acol in c: 2730 cx, cy, cz = get_color(acol) 2731 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2732 pd.GetPointData().AddArray(ucols) 2733 pd.GetPointData().SetActiveScalars("Colors") 2734 glyph.ScalingOff() 2735 elif risseq: 2736 glyph.SetScaleModeToScaleByScalar() 2737 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2738 urads.SetName("Radii") 2739 pd.GetPointData().AddArray(urads) 2740 pd.GetPointData().SetActiveScalars("Radii") 2741 2742 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2743 2744 glyph.SetInputData(pd) 2745 glyph.Update() 2746 2747 super().__init__(glyph.GetOutput(), alpha=alpha) 2748 self.pos(base) 2749 self.phong() 2750 if cisseq: 2751 self.mapper.ScalarVisibilityOn() 2752 else: 2753 self.mapper.ScalarVisibilityOff() 2754 self.c(c) 2755 self.name = "Spheres"
Build a large set of spheres.
2665 def __init__(self, centers, r=1.0, res=8, c="red5", alpha=1) -> None: 2666 """ 2667 Build a (possibly large) set of spheres at `centers` of radius `r`. 2668 2669 Either `c` or `r` can be a list of RGB colors or radii. 2670 2671 Examples: 2672 - [manyspheres.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/manyspheres.py) 2673 2674 ![](https://vedo.embl.es/images/basic/manyspheres.jpg) 2675 """ 2676 2677 if isinstance(centers, Points): 2678 centers = centers.coordinates 2679 centers = np.asarray(centers, dtype=float) 2680 base = centers[0] 2681 2682 cisseq = False 2683 if utils.is_sequence(c): 2684 cisseq = True 2685 2686 if cisseq: 2687 if len(centers) != len(c): 2688 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(c)} #colors") 2689 raise RuntimeError() 2690 2691 risseq = False 2692 if utils.is_sequence(r): 2693 risseq = True 2694 2695 if risseq: 2696 if len(centers) != len(r): 2697 vedo.logger.error(f"mismatch #centers {len(centers)} != {len(r)} #radii") 2698 raise RuntimeError() 2699 if cisseq and risseq: 2700 vedo.logger.error("Limitation: c and r cannot be both sequences.") 2701 raise RuntimeError() 2702 2703 src = vtki.new("SphereSource") 2704 if not risseq: 2705 src.SetRadius(r) 2706 if utils.is_sequence(res): 2707 res_t, res_phi = res 2708 else: 2709 res_t, res_phi = 2 * res, res 2710 2711 src.SetThetaResolution(res_t) 2712 src.SetPhiResolution(res_phi) 2713 src.Update() 2714 2715 psrc = vtki.new("PointSource") 2716 psrc.SetNumberOfPoints(len(centers)) 2717 psrc.Update() 2718 pd = psrc.GetOutput() 2719 vpts = pd.GetPoints() 2720 2721 glyph = vtki.vtkGlyph3D() 2722 glyph.SetSourceConnection(src.GetOutputPort()) 2723 2724 if cisseq: 2725 glyph.SetColorModeToColorByScalar() 2726 ucols = vtki.vtkUnsignedCharArray() 2727 ucols.SetNumberOfComponents(3) 2728 ucols.SetName("Colors") 2729 for acol in c: 2730 cx, cy, cz = get_color(acol) 2731 ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) 2732 pd.GetPointData().AddArray(ucols) 2733 pd.GetPointData().SetActiveScalars("Colors") 2734 glyph.ScalingOff() 2735 elif risseq: 2736 glyph.SetScaleModeToScaleByScalar() 2737 urads = utils.numpy2vtk(2 * np.ascontiguousarray(r), dtype=np.float32) 2738 urads.SetName("Radii") 2739 pd.GetPointData().AddArray(urads) 2740 pd.GetPointData().SetActiveScalars("Radii") 2741 2742 vpts.SetData(utils.numpy2vtk(centers - base, dtype=np.float32)) 2743 2744 glyph.SetInputData(pd) 2745 glyph.Update() 2746 2747 super().__init__(glyph.GetOutput(), alpha=alpha) 2748 self.pos(base) 2749 self.phong() 2750 if cisseq: 2751 self.mapper.ScalarVisibilityOn() 2752 else: 2753 self.mapper.ScalarVisibilityOff() 2754 self.c(c) 2755 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:
2758class Earth(Mesh): 2759 """ 2760 Build a textured mesh representing the Earth. 2761 """ 2762 2763 def __init__(self, style=1, r=1.0) -> None: 2764 """ 2765 Build a textured mesh representing the Earth. 2766 2767 Example: 2768 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2769 2770 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2771 """ 2772 tss = vtki.new("TexturedSphereSource") 2773 tss.SetRadius(r) 2774 tss.SetThetaResolution(72) 2775 tss.SetPhiResolution(36) 2776 tss.Update() 2777 super().__init__(tss.GetOutput(), c="w") 2778 atext = vtki.vtkTexture() 2779 pnm_reader = vtki.new("JPEGReader") 2780 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2781 pnm_reader.SetFileName(fn) 2782 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2783 atext.InterpolateOn() 2784 self.texture(atext) 2785 self.name = "Earth"
Build a textured mesh representing the Earth.
2763 def __init__(self, style=1, r=1.0) -> None: 2764 """ 2765 Build a textured mesh representing the Earth. 2766 2767 Example: 2768 - [geodesic_curve.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic_curve.py) 2769 2770 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2771 """ 2772 tss = vtki.new("TexturedSphereSource") 2773 tss.SetRadius(r) 2774 tss.SetThetaResolution(72) 2775 tss.SetPhiResolution(36) 2776 tss.Update() 2777 super().__init__(tss.GetOutput(), c="w") 2778 atext = vtki.vtkTexture() 2779 pnm_reader = vtki.new("JPEGReader") 2780 fn = vedo.file_io.download(vedo.dataurl + f"textures/earth{style}.jpg", verbose=False) 2781 pnm_reader.SetFileName(fn) 2782 atext.SetInputConnection(pnm_reader.GetOutputPort()) 2783 atext.InterpolateOn() 2784 self.texture(atext) 2785 self.name = "Earth"
2788class Ellipsoid(Mesh): 2789 """Build a 3D ellipsoid.""" 2790 def __init__( 2791 self, 2792 pos=(0, 0, 0), 2793 axis1=(0.5, 0, 0), 2794 axis2=(0, 1, 0), 2795 axis3=(0, 0, 1.5), 2796 res=24, 2797 c="cyan4", 2798 alpha=1.0, 2799 ) -> None: 2800 """ 2801 Build a 3D ellipsoid centered at position `pos`. 2802 2803 Arguments: 2804 axis1 : (list) 2805 First axis. Length corresponds to semi-axis. 2806 axis2 : (list) 2807 Second axis. Length corresponds to semi-axis. 2808 axis3 : (list) 2809 Third axis. Length corresponds to semi-axis. 2810 """ 2811 self.center = utils.make3d(pos) 2812 2813 self.axis1 = utils.make3d(axis1) 2814 self.axis2 = utils.make3d(axis2) 2815 self.axis3 = utils.make3d(axis3) 2816 2817 self.va = np.linalg.norm(self.axis1) 2818 self.vb = np.linalg.norm(self.axis2) 2819 self.vc = np.linalg.norm(self.axis3) 2820 2821 self.va_error = 0 2822 self.vb_error = 0 2823 self.vc_error = 0 2824 2825 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2826 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2827 2828 if utils.is_sequence(res): 2829 res_t, res_phi = res 2830 else: 2831 res_t, res_phi = 2 * res, res 2832 2833 elli_source = vtki.new("SphereSource") 2834 elli_source.SetRadius(1) 2835 elli_source.SetThetaResolution(res_t) 2836 elli_source.SetPhiResolution(res_phi) 2837 elli_source.Update() 2838 2839 super().__init__(elli_source.GetOutput(), c, alpha) 2840 2841 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2842 lt = LinearTransform(matrix).translate(pos) 2843 self.apply_transform(lt) 2844 self.name = "Ellipsoid" 2845 2846 def asphericity(self) -> float: 2847 """ 2848 Return a measure of how different an ellipsoid is from a sphere. 2849 Values close to zero correspond to a spheric object. 2850 """ 2851 a, b, c = self.va, self.vb, self.vc 2852 asp = ( ((a-b)/(a+b))**2 2853 + ((a-c)/(a+c))**2 2854 + ((b-c)/(b+c))**2 ) / 3. * 4. 2855 return float(asp) 2856 2857 def asphericity_error(self) -> float: 2858 """ 2859 Calculate statistical error on the asphericity value. 2860 2861 Errors on the main axes are stored in 2862 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2863 """ 2864 a, b, c = self.va, self.vb, self.vc 2865 sqrtn = np.sqrt(self.nr_of_points) 2866 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2867 2868 # from sympy import * 2869 # init_printing(use_unicode=True) 2870 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2871 # L = ( 2872 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2873 # / 3 * 4) 2874 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2875 # print(dl2) 2876 # exit() 2877 2878 dL2 = ( 2879 ea ** 2 2880 * ( 2881 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2882 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2883 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2884 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2885 ) ** 2 2886 + eb ** 2 2887 * ( 2888 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2889 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2890 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2891 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2892 ) ** 2 2893 + ec ** 2 2894 * ( 2895 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2896 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2897 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2898 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2899 ) ** 2 2900 ) 2901 err = np.sqrt(dL2) 2902 self.va_error = ea 2903 self.vb_error = eb 2904 self.vc_error = ec 2905 return err
Build a 3D ellipsoid.
2790 def __init__( 2791 self, 2792 pos=(0, 0, 0), 2793 axis1=(0.5, 0, 0), 2794 axis2=(0, 1, 0), 2795 axis3=(0, 0, 1.5), 2796 res=24, 2797 c="cyan4", 2798 alpha=1.0, 2799 ) -> None: 2800 """ 2801 Build a 3D ellipsoid centered at position `pos`. 2802 2803 Arguments: 2804 axis1 : (list) 2805 First axis. Length corresponds to semi-axis. 2806 axis2 : (list) 2807 Second axis. Length corresponds to semi-axis. 2808 axis3 : (list) 2809 Third axis. Length corresponds to semi-axis. 2810 """ 2811 self.center = utils.make3d(pos) 2812 2813 self.axis1 = utils.make3d(axis1) 2814 self.axis2 = utils.make3d(axis2) 2815 self.axis3 = utils.make3d(axis3) 2816 2817 self.va = np.linalg.norm(self.axis1) 2818 self.vb = np.linalg.norm(self.axis2) 2819 self.vc = np.linalg.norm(self.axis3) 2820 2821 self.va_error = 0 2822 self.vb_error = 0 2823 self.vc_error = 0 2824 2825 self.nr_of_points = 1 # used by pointcloud.pca_ellipsoid() 2826 self.pvalue = 0 # used by pointcloud.pca_ellipsoid() 2827 2828 if utils.is_sequence(res): 2829 res_t, res_phi = res 2830 else: 2831 res_t, res_phi = 2 * res, res 2832 2833 elli_source = vtki.new("SphereSource") 2834 elli_source.SetRadius(1) 2835 elli_source.SetThetaResolution(res_t) 2836 elli_source.SetPhiResolution(res_phi) 2837 elli_source.Update() 2838 2839 super().__init__(elli_source.GetOutput(), c, alpha) 2840 2841 matrix = np.c_[self.axis1, self.axis2, self.axis3] 2842 lt = LinearTransform(matrix).translate(pos) 2843 self.apply_transform(lt) 2844 self.name = "Ellipsoid"
Build a 3D ellipsoid centered at position pos
.
Arguments:
- axis1 : (list) First axis. Length corresponds to semi-axis.
- axis2 : (list) Second axis. Length corresponds to semi-axis.
- axis3 : (list) Third axis. Length corresponds to semi-axis.
2846 def asphericity(self) -> float: 2847 """ 2848 Return a measure of how different an ellipsoid is from a sphere. 2849 Values close to zero correspond to a spheric object. 2850 """ 2851 a, b, c = self.va, self.vb, self.vc 2852 asp = ( ((a-b)/(a+b))**2 2853 + ((a-c)/(a+c))**2 2854 + ((b-c)/(b+c))**2 ) / 3. * 4. 2855 return float(asp)
Return a measure of how different an ellipsoid is from a sphere. Values close to zero correspond to a spheric object.
2857 def asphericity_error(self) -> float: 2858 """ 2859 Calculate statistical error on the asphericity value. 2860 2861 Errors on the main axes are stored in 2862 `Ellipsoid.va_error`, Ellipsoid.vb_error` and `Ellipsoid.vc_error`. 2863 """ 2864 a, b, c = self.va, self.vb, self.vc 2865 sqrtn = np.sqrt(self.nr_of_points) 2866 ea, eb, ec = a / 2 / sqrtn, b / 2 / sqrtn, b / 2 / sqrtn 2867 2868 # from sympy import * 2869 # init_printing(use_unicode=True) 2870 # a, b, c, ea, eb, ec = symbols("a b c, ea, eb,ec") 2871 # L = ( 2872 # (((a - b) / (a + b)) ** 2 + ((c - b) / (c + b)) ** 2 + ((a - c) / (a + c)) ** 2) 2873 # / 3 * 4) 2874 # dl2 = (diff(L, a) * ea) ** 2 + (diff(L, b) * eb) ** 2 + (diff(L, c) * ec) ** 2 2875 # print(dl2) 2876 # exit() 2877 2878 dL2 = ( 2879 ea ** 2 2880 * ( 2881 -8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2882 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2883 + 4 * (2 * a - 2 * c) / (3 * (a + c) ** 2) 2884 + 4 * (2 * a - 2 * b) / (3 * (a + b) ** 2) 2885 ) ** 2 2886 + eb ** 2 2887 * ( 2888 4 * (-2 * a + 2 * b) / (3 * (a + b) ** 2) 2889 - 8 * (a - b) ** 2 / (3 * (a + b) ** 3) 2890 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2891 + 4 * (2 * b - 2 * c) / (3 * (b + c) ** 2) 2892 ) ** 2 2893 + ec ** 2 2894 * ( 2895 4 * (-2 * a + 2 * c) / (3 * (a + c) ** 2) 2896 - 8 * (a - c) ** 2 / (3 * (a + c) ** 3) 2897 + 4 * (-2 * b + 2 * c) / (3 * (b + c) ** 2) 2898 - 8 * (-b + c) ** 2 / (3 * (b + c) ** 3) 2899 ) ** 2 2900 ) 2901 err = np.sqrt(dL2) 2902 self.va_error = ea 2903 self.vb_error = eb 2904 self.vc_error = ec 2905 return err
Calculate statistical error on the asphericity value.
Errors on the main axes are stored in
Ellipsoid.va_error
, Ellipsoid.vb_errorand
Ellipsoid.vc_error`.
2908class Grid(Mesh): 2909 """ 2910 An even or uneven 2D grid. 2911 """ 2912 2913 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2914 """ 2915 Create an even or uneven 2D grid. 2916 Can also be created from a `np.mgrid` object (see example). 2917 2918 Arguments: 2919 pos : (list, Points, Mesh) 2920 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2921 s : (float, list) 2922 if a float is provided it is interpreted as the total size along x and y, 2923 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2924 In this case keyword `res` is ignored (see example below). 2925 res : (list) 2926 resolutions along x and y, e.i. the number of subdivisions 2927 lw : (int) 2928 line width 2929 2930 Example: 2931 ```python 2932 from vedo import * 2933 xcoords = np.arange(0, 2, 0.2) 2934 ycoords = np.arange(0, 1, 0.2) 2935 sqrtx = sqrt(xcoords) 2936 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2937 grid.show(axes=8).close() 2938 2939 # Can also create a grid from a np.mgrid: 2940 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2941 vgrid = Grid(s=(X[:,0], Y[0])) 2942 vgrid.show(axes=8).close() 2943 ``` 2944 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2945 """ 2946 resx, resy = res 2947 sx, sy = s 2948 2949 try: 2950 bb = pos.bounds() 2951 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2952 sx = bb[1] - bb[0] 2953 sy = bb[3] - bb[2] 2954 except AttributeError: 2955 pass 2956 2957 if len(pos) == 2: 2958 pos = (pos[0], pos[1], 0) 2959 elif len(pos) in [4,6]: # passing a bounding box 2960 bb = pos 2961 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2962 sx = bb[1] - bb[0] 2963 sy = bb[3] - bb[2] 2964 if len(pos)==6: 2965 pos[2] = bb[4] - bb[5] 2966 2967 if utils.is_sequence(sx) and utils.is_sequence(sy): 2968 verts = [] 2969 for y in sy: 2970 for x in sx: 2971 verts.append([x, y, 0]) 2972 faces = [] 2973 n = len(sx) 2974 m = len(sy) 2975 for j in range(m - 1): 2976 j1n = (j + 1) * n 2977 for i in range(n - 1): 2978 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2979 2980 super().__init__([verts, faces], c, alpha) 2981 2982 else: 2983 ps = vtki.new("PlaneSource") 2984 ps.SetResolution(resx, resy) 2985 ps.Update() 2986 2987 t = vtki.vtkTransform() 2988 t.Translate(pos) 2989 t.Scale(sx, sy, 1) 2990 2991 tf = vtki.new("TransformPolyDataFilter") 2992 tf.SetInputData(ps.GetOutput()) 2993 tf.SetTransform(t) 2994 tf.Update() 2995 2996 super().__init__(tf.GetOutput(), c, alpha) 2997 2998 self.wireframe().lw(lw) 2999 self.properties.LightingOff() 3000 self.name = "Grid"
An even or uneven 2D grid.
2913 def __init__(self, pos=(0, 0, 0), s=(1, 1), res=(10, 10), lw=1, c="k3", alpha=1.0) -> None: 2914 """ 2915 Create an even or uneven 2D grid. 2916 Can also be created from a `np.mgrid` object (see example). 2917 2918 Arguments: 2919 pos : (list, Points, Mesh) 2920 position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax]. 2921 s : (float, list) 2922 if a float is provided it is interpreted as the total size along x and y, 2923 if a list of coords is provided they are interpreted as the vertices of the grid along x and y. 2924 In this case keyword `res` is ignored (see example below). 2925 res : (list) 2926 resolutions along x and y, e.i. the number of subdivisions 2927 lw : (int) 2928 line width 2929 2930 Example: 2931 ```python 2932 from vedo import * 2933 xcoords = np.arange(0, 2, 0.2) 2934 ycoords = np.arange(0, 1, 0.2) 2935 sqrtx = sqrt(xcoords) 2936 grid = Grid(s=(sqrtx, ycoords)).lw(2) 2937 grid.show(axes=8).close() 2938 2939 # Can also create a grid from a np.mgrid: 2940 X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] 2941 vgrid = Grid(s=(X[:,0], Y[0])) 2942 vgrid.show(axes=8).close() 2943 ``` 2944 ![](https://vedo.embl.es/images/feats/uneven_grid.png) 2945 """ 2946 resx, resy = res 2947 sx, sy = s 2948 2949 try: 2950 bb = pos.bounds() 2951 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, (bb[4] + bb[5])/2] 2952 sx = bb[1] - bb[0] 2953 sy = bb[3] - bb[2] 2954 except AttributeError: 2955 pass 2956 2957 if len(pos) == 2: 2958 pos = (pos[0], pos[1], 0) 2959 elif len(pos) in [4,6]: # passing a bounding box 2960 bb = pos 2961 pos = [(bb[0] + bb[1])/2, (bb[2] + bb[3])/2, 0] 2962 sx = bb[1] - bb[0] 2963 sy = bb[3] - bb[2] 2964 if len(pos)==6: 2965 pos[2] = bb[4] - bb[5] 2966 2967 if utils.is_sequence(sx) and utils.is_sequence(sy): 2968 verts = [] 2969 for y in sy: 2970 for x in sx: 2971 verts.append([x, y, 0]) 2972 faces = [] 2973 n = len(sx) 2974 m = len(sy) 2975 for j in range(m - 1): 2976 j1n = (j + 1) * n 2977 for i in range(n - 1): 2978 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 2979 2980 super().__init__([verts, faces], c, alpha) 2981 2982 else: 2983 ps = vtki.new("PlaneSource") 2984 ps.SetResolution(resx, resy) 2985 ps.Update() 2986 2987 t = vtki.vtkTransform() 2988 t.Translate(pos) 2989 t.Scale(sx, sy, 1) 2990 2991 tf = vtki.new("TransformPolyDataFilter") 2992 tf.SetInputData(ps.GetOutput()) 2993 tf.SetTransform(t) 2994 tf.Update() 2995 2996 super().__init__(tf.GetOutput(), c, alpha) 2997 2998 self.wireframe().lw(lw) 2999 self.properties.LightingOff() 3000 self.name = "Grid"
Create an even or uneven 2D grid.
Can also be created from a np.mgrid
object (see example).
Arguments:
- pos : (list, Points, Mesh) position in space, can also be passed as a bounding box [xmin,xmax, ymin,ymax].
- s : (float, list)
if a float is provided it is interpreted as the total size along x and y,
if a list of coords is provided they are interpreted as the vertices of the grid along x and y.
In this case keyword
res
is ignored (see example below). - res : (list) resolutions along x and y, e.i. the number of subdivisions
- lw : (int) line width
Example:
from vedo import * xcoords = np.arange(0, 2, 0.2) ycoords = np.arange(0, 1, 0.2) sqrtx = sqrt(xcoords) grid = Grid(s=(sqrtx, ycoords)).lw(2) grid.show(axes=8).close() # Can also create a grid from a np.mgrid: X, Y = np.mgrid[-12:12:10*1j, 200:215:10*1j] vgrid = Grid(s=(X[:,0], Y[0])) vgrid.show(axes=8).close()
3329class TessellatedBox(Mesh): 3330 """ 3331 Build a cubic `Mesh` made of quads. 3332 """ 3333 3334 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3335 """ 3336 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3337 3338 Arguments: 3339 pos : (list) 3340 position of the left bottom corner 3341 n : (int, list) 3342 number of subdivisions along each side 3343 spacing : (float) 3344 size of the side of the single quad in the 3 directions 3345 """ 3346 if utils.is_sequence(n): # slow 3347 img = vtki.vtkImageData() 3348 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3349 img.SetSpacing(spacing) 3350 gf = vtki.new("GeometryFilter") 3351 gf.SetInputData(img) 3352 gf.Update() 3353 poly = gf.GetOutput() 3354 else: # fast 3355 n -= 1 3356 tbs = vtki.new("TessellatedBoxSource") 3357 tbs.SetLevel(n) 3358 if len(bounds): 3359 tbs.SetBounds(bounds) 3360 else: 3361 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3362 tbs.QuadsOn() 3363 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3364 tbs.Update() 3365 poly = tbs.GetOutput() 3366 super().__init__(poly, c=c, alpha=alpha) 3367 self.pos(pos) 3368 self.lw(1).lighting("off") 3369 self.name = "TessellatedBox"
Build a cubic Mesh
made of quads.
3334 def __init__(self, pos=(0, 0, 0), n=10, spacing=(1, 1, 1), bounds=(), c="k5", alpha=0.5) -> None: 3335 """ 3336 Build a cubic `Mesh` made of `n` small quads in the 3 axis directions. 3337 3338 Arguments: 3339 pos : (list) 3340 position of the left bottom corner 3341 n : (int, list) 3342 number of subdivisions along each side 3343 spacing : (float) 3344 size of the side of the single quad in the 3 directions 3345 """ 3346 if utils.is_sequence(n): # slow 3347 img = vtki.vtkImageData() 3348 img.SetDimensions(n[0] + 1, n[1] + 1, n[2] + 1) 3349 img.SetSpacing(spacing) 3350 gf = vtki.new("GeometryFilter") 3351 gf.SetInputData(img) 3352 gf.Update() 3353 poly = gf.GetOutput() 3354 else: # fast 3355 n -= 1 3356 tbs = vtki.new("TessellatedBoxSource") 3357 tbs.SetLevel(n) 3358 if len(bounds): 3359 tbs.SetBounds(bounds) 3360 else: 3361 tbs.SetBounds(0, n * spacing[0], 0, n * spacing[1], 0, n * spacing[2]) 3362 tbs.QuadsOn() 3363 #tbs.SetOutputPointsPrecision(vtki.vtkAlgorithm.SINGLE_PRECISION) 3364 tbs.Update() 3365 poly = tbs.GetOutput() 3366 super().__init__(poly, c=c, alpha=alpha) 3367 self.pos(pos) 3368 self.lw(1).lighting("off") 3369 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
3003class Plane(Mesh): 3004 """Create a plane in space.""" 3005 def __init__( 3006 self, 3007 pos=(0, 0, 0), 3008 normal=(0, 0, 1), 3009 s=(1, 1), 3010 res=(1, 1), 3011 edge_direction=(), 3012 c="gray5", 3013 alpha=1.0, 3014 ) -> None: 3015 """ 3016 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 3017 to vector `normal` so that it passes through point `pos`, optionally 3018 aligning an edge with `direction`. 3019 3020 Arguments: 3021 pos : (list) 3022 position of the plane center 3023 normal : (list) 3024 normal vector to the plane 3025 s : (list) 3026 size of the plane along x and y 3027 res : (list) 3028 resolution of the plane along x and y 3029 edge_direction : (list) 3030 direction vector to align one edge of the plane 3031 """ 3032 if isinstance(pos, vtki.vtkPolyData): 3033 super().__init__(pos, c, alpha) 3034 3035 else: 3036 ps = vtki.new("PlaneSource") 3037 ps.SetResolution(res[0], res[1]) 3038 tri = vtki.new("TriangleFilter") 3039 tri.SetInputConnection(ps.GetOutputPort()) 3040 tri.Update() 3041 super().__init__(tri.GetOutput(), c, alpha) 3042 3043 pos = utils.make3d(pos) 3044 normal = np.asarray(normal, dtype=float) 3045 axis = normal / np.linalg.norm(normal) 3046 3047 # Calculate orientation using normal 3048 theta = np.arccos(axis[2]) 3049 phi = np.arctan2(axis[1], axis[0]) 3050 3051 t = LinearTransform() 3052 t.scale([s[0], s[1], 1]) 3053 3054 # Rotate to align normal 3055 t.rotate_y(np.rad2deg(theta)) 3056 t.rotate_z(np.rad2deg(phi)) 3057 3058 # Additional direction alignment 3059 if len(edge_direction) >= 2: 3060 direction = utils.make3d(edge_direction).astype(float) 3061 direction /= np.linalg.norm(direction) 3062 3063 if s[0] <= s[1]: 3064 current_direction = np.asarray([0,1,0]) 3065 else: 3066 current_direction = np.asarray([1,0,0]) 3067 3068 transformed_current_direction = t.transform_point(current_direction) 3069 n = transformed_current_direction / np.linalg.norm(transformed_current_direction) 3070 3071 if np.linalg.norm(transformed_current_direction) >= 1e-6: 3072 angle = np.arccos(np.dot(n, direction)) 3073 t.rotate(axis=axis, angle=np.rad2deg(angle)) 3074 3075 t.translate(pos) 3076 self.apply_transform(t) 3077 3078 self.lighting("off") 3079 self.name = "Plane" 3080 self.variance = 0 # used by pointcloud.fit_plane() 3081 3082 def clone(self, deep=True) -> "Plane": 3083 newplane = Plane() 3084 if deep: 3085 newplane.dataset.DeepCopy(self.dataset) 3086 else: 3087 newplane.dataset.ShallowCopy(self.dataset) 3088 newplane.copy_properties_from(self) 3089 newplane.transform = self.transform.clone() 3090 newplane.variance = 0 3091 return newplane 3092 3093 @property 3094 def normal(self) -> np.ndarray: 3095 pts = self.coordinates 3096 # this is necessary because plane can have high resolution 3097 # p0, p1 = pts[0], pts[1] 3098 # AB = p1 - p0 3099 # AB /= np.linalg.norm(AB) 3100 # for pt in pts[2:]: 3101 # AC = pt - p0 3102 # AC /= np.linalg.norm(AC) 3103 # cosine_angle = np.dot(AB, AC) 3104 # if abs(cosine_angle) < 0.99: 3105 # normal = np.cross(AB, AC) 3106 # return normal / np.linalg.norm(normal) 3107 p0, p1, p2 = pts[0], pts[1], pts[int(len(pts)/2 +0.5)] 3108 AB = p1 - p0 3109 AB /= np.linalg.norm(AB) 3110 AC = p2 - p0 3111 AC /= np.linalg.norm(AC) 3112 normal = np.cross(AB, AC) 3113 return normal / np.linalg.norm(normal) 3114 3115 @property 3116 def center(self) -> np.ndarray: 3117 pts = self.coordinates 3118 return np.mean(pts, axis=0) 3119 3120 def contains(self, points, tol=0) -> np.ndarray: 3121 """ 3122 Check if each of the provided point lies on this plane. 3123 `points` is an array of shape (n, 3). 3124 """ 3125 points = np.array(points, dtype=float) 3126 bounds = self.coordinates 3127 3128 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3129 3130 for i in [1, 3]: 3131 AB = bounds[i] - bounds[0] 3132 AP = points - bounds[0] 3133 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3134 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3135 mask = np.logical_and(mask, mask_l) 3136 mask = np.logical_and(mask, mask_g) 3137 return mask
Create a plane in space.
3005 def __init__( 3006 self, 3007 pos=(0, 0, 0), 3008 normal=(0, 0, 1), 3009 s=(1, 1), 3010 res=(1, 1), 3011 edge_direction=(), 3012 c="gray5", 3013 alpha=1.0, 3014 ) -> None: 3015 """ 3016 Create a plane of size `s=(xsize, ysize)` oriented perpendicular 3017 to vector `normal` so that it passes through point `pos`, optionally 3018 aligning an edge with `direction`. 3019 3020 Arguments: 3021 pos : (list) 3022 position of the plane center 3023 normal : (list) 3024 normal vector to the plane 3025 s : (list) 3026 size of the plane along x and y 3027 res : (list) 3028 resolution of the plane along x and y 3029 edge_direction : (list) 3030 direction vector to align one edge of the plane 3031 """ 3032 if isinstance(pos, vtki.vtkPolyData): 3033 super().__init__(pos, c, alpha) 3034 3035 else: 3036 ps = vtki.new("PlaneSource") 3037 ps.SetResolution(res[0], res[1]) 3038 tri = vtki.new("TriangleFilter") 3039 tri.SetInputConnection(ps.GetOutputPort()) 3040 tri.Update() 3041 super().__init__(tri.GetOutput(), c, alpha) 3042 3043 pos = utils.make3d(pos) 3044 normal = np.asarray(normal, dtype=float) 3045 axis = normal / np.linalg.norm(normal) 3046 3047 # Calculate orientation using normal 3048 theta = np.arccos(axis[2]) 3049 phi = np.arctan2(axis[1], axis[0]) 3050 3051 t = LinearTransform() 3052 t.scale([s[0], s[1], 1]) 3053 3054 # Rotate to align normal 3055 t.rotate_y(np.rad2deg(theta)) 3056 t.rotate_z(np.rad2deg(phi)) 3057 3058 # Additional direction alignment 3059 if len(edge_direction) >= 2: 3060 direction = utils.make3d(edge_direction).astype(float) 3061 direction /= np.linalg.norm(direction) 3062 3063 if s[0] <= s[1]: 3064 current_direction = np.asarray([0,1,0]) 3065 else: 3066 current_direction = np.asarray([1,0,0]) 3067 3068 transformed_current_direction = t.transform_point(current_direction) 3069 n = transformed_current_direction / np.linalg.norm(transformed_current_direction) 3070 3071 if np.linalg.norm(transformed_current_direction) >= 1e-6: 3072 angle = np.arccos(np.dot(n, direction)) 3073 t.rotate(axis=axis, angle=np.rad2deg(angle)) 3074 3075 t.translate(pos) 3076 self.apply_transform(t) 3077 3078 self.lighting("off") 3079 self.name = "Plane" 3080 self.variance = 0 # used by pointcloud.fit_plane()
Create a plane of size s=(xsize, ysize)
oriented perpendicular
to vector normal
so that it passes through point pos
, optionally
aligning an edge with direction
.
Arguments:
- pos : (list) position of the plane center
- normal : (list) normal vector to the plane
- s : (list) size of the plane along x and y
- res : (list) resolution of the plane along x and y
- edge_direction : (list) direction vector to align one edge of the plane
3082 def clone(self, deep=True) -> "Plane": 3083 newplane = Plane() 3084 if deep: 3085 newplane.dataset.DeepCopy(self.dataset) 3086 else: 3087 newplane.dataset.ShallowCopy(self.dataset) 3088 newplane.copy_properties_from(self) 3089 newplane.transform = self.transform.clone() 3090 newplane.variance = 0 3091 return newplane
3120 def contains(self, points, tol=0) -> np.ndarray: 3121 """ 3122 Check if each of the provided point lies on this plane. 3123 `points` is an array of shape (n, 3). 3124 """ 3125 points = np.array(points, dtype=float) 3126 bounds = self.coordinates 3127 3128 mask = np.isclose(np.dot(points - self.center, self.normal), 0, atol=tol) 3129 3130 for i in [1, 3]: 3131 AB = bounds[i] - bounds[0] 3132 AP = points - bounds[0] 3133 mask_l = np.less_equal(np.dot(AP, AB), np.linalg.norm(AB)) 3134 mask_g = np.greater_equal(np.dot(AP, AB), 0) 3135 mask = np.logical_and(mask, mask_l) 3136 mask = np.logical_and(mask, mask_g) 3137 return mask
Check if each of the provided point lies on this plane.
points
is an array of shape (n, 3).
3234class Box(Mesh): 3235 """ 3236 Build a box of specified dimensions. 3237 """ 3238 3239 def __init__( 3240 self, pos=(0, 0, 0), 3241 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3242 """ 3243 Build a box of dimensions `x=length, y=width and z=height`. 3244 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3245 3246 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3247 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3248 3249 Note that the shape polygonal data contains duplicated vertices. This is to allow 3250 each face to have its own normal, which is essential for some operations. 3251 Use the `clean()` method to remove duplicate points. 3252 3253 Examples: 3254 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3255 3256 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3257 """ 3258 src = vtki.new("CubeSource") 3259 3260 if len(pos) == 2: 3261 pos = (pos[0], pos[1], 0) 3262 3263 if len(pos) == 6: 3264 src.SetBounds(pos) 3265 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3266 elif len(size) == 3: 3267 length, width, height = size 3268 src.SetXLength(length) 3269 src.SetYLength(width) 3270 src.SetZLength(height) 3271 src.SetCenter(pos) 3272 else: 3273 src.SetXLength(length) 3274 src.SetYLength(width) 3275 src.SetZLength(height) 3276 src.SetCenter(pos) 3277 3278 src.Update() 3279 pd = src.GetOutput() 3280 3281 tc = [ 3282 [0.0, 0.0], 3283 [1.0, 0.0], 3284 [0.0, 1.0], 3285 [1.0, 1.0], 3286 [1.0, 0.0], 3287 [0.0, 0.0], 3288 [1.0, 1.0], 3289 [0.0, 1.0], 3290 [1.0, 1.0], 3291 [1.0, 0.0], 3292 [0.0, 1.0], 3293 [0.0, 0.0], 3294 [0.0, 1.0], 3295 [0.0, 0.0], 3296 [1.0, 1.0], 3297 [1.0, 0.0], 3298 [1.0, 0.0], 3299 [0.0, 0.0], 3300 [1.0, 1.0], 3301 [0.0, 1.0], 3302 [0.0, 0.0], 3303 [1.0, 0.0], 3304 [0.0, 1.0], 3305 [1.0, 1.0], 3306 ] 3307 vtc = utils.numpy2vtk(tc) 3308 pd.GetPointData().SetTCoords(vtc) 3309 super().__init__(pd, c, alpha) 3310 self.transform = LinearTransform().translate(pos) 3311 self.name = "Box"
Build a box of specified dimensions.
3239 def __init__( 3240 self, pos=(0, 0, 0), 3241 length=1.0, width=2.0, height=3.0, size=(), c="g4", alpha=1.0) -> None: 3242 """ 3243 Build a box of dimensions `x=length, y=width and z=height`. 3244 Alternatively dimensions can be defined by setting `size` keyword with a tuple. 3245 3246 If `pos` is a list of 6 numbers, this will be interpreted as the bounding box: 3247 `[xmin,xmax, ymin,ymax, zmin,zmax]` 3248 3249 Note that the shape polygonal data contains duplicated vertices. This is to allow 3250 each face to have its own normal, which is essential for some operations. 3251 Use the `clean()` method to remove duplicate points. 3252 3253 Examples: 3254 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 3255 3256 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 3257 """ 3258 src = vtki.new("CubeSource") 3259 3260 if len(pos) == 2: 3261 pos = (pos[0], pos[1], 0) 3262 3263 if len(pos) == 6: 3264 src.SetBounds(pos) 3265 pos = [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2, (pos[4] + pos[5]) / 2] 3266 elif len(size) == 3: 3267 length, width, height = size 3268 src.SetXLength(length) 3269 src.SetYLength(width) 3270 src.SetZLength(height) 3271 src.SetCenter(pos) 3272 else: 3273 src.SetXLength(length) 3274 src.SetYLength(width) 3275 src.SetZLength(height) 3276 src.SetCenter(pos) 3277 3278 src.Update() 3279 pd = src.GetOutput() 3280 3281 tc = [ 3282 [0.0, 0.0], 3283 [1.0, 0.0], 3284 [0.0, 1.0], 3285 [1.0, 1.0], 3286 [1.0, 0.0], 3287 [0.0, 0.0], 3288 [1.0, 1.0], 3289 [0.0, 1.0], 3290 [1.0, 1.0], 3291 [1.0, 0.0], 3292 [0.0, 1.0], 3293 [0.0, 0.0], 3294 [0.0, 1.0], 3295 [0.0, 0.0], 3296 [1.0, 1.0], 3297 [1.0, 0.0], 3298 [1.0, 0.0], 3299 [0.0, 0.0], 3300 [1.0, 1.0], 3301 [0.0, 1.0], 3302 [0.0, 0.0], 3303 [1.0, 0.0], 3304 [0.0, 1.0], 3305 [1.0, 1.0], 3306 ] 3307 vtc = utils.numpy2vtk(tc) 3308 pd.GetPointData().SetTCoords(vtc) 3309 super().__init__(pd, c, alpha) 3310 self.transform = LinearTransform().translate(pos) 3311 self.name = "Box"
Build a box of dimensions x=length, y=width and z=height
.
Alternatively dimensions can be defined by setting size
keyword with a tuple.
If pos
is a list of 6 numbers, this will be interpreted as the bounding box:
[xmin,xmax, ymin,ymax, zmin,zmax]
Note that the shape polygonal data contains duplicated vertices. This is to allow
each face to have its own normal, which is essential for some operations.
Use the clean()
method to remove duplicate points.
Examples:
3314class Cube(Box): 3315 """ 3316 Build a cube shape. 3317 3318 Note that the shape polygonal data contains duplicated vertices. This is to allow 3319 each face to have its own normal, which is essential for some operations. 3320 Use the `clean()` method to remove duplicate points. 3321 """ 3322 3323 def __init__(self, pos=(0, 0, 0), side=1.0, c="g4", alpha=1.0) -> None: 3324 """Build a cube of size `side`.""" 3325 super().__init__(pos, side, side, side, (), c, alpha) 3326 self.name = "Cube"
Build a cube shape.
Note that the shape polygonal data contains duplicated vertices. This is to allow
each face to have its own normal, which is essential for some operations.
Use the clean()
method to remove duplicate points.
3372class Spring(Mesh): 3373 """ 3374 Build a spring model. 3375 """ 3376 3377 def __init__( 3378 self, 3379 start_pt=(0, 0, 0), 3380 end_pt=(1, 0, 0), 3381 coils=20, 3382 r1=0.1, 3383 r2=None, 3384 thickness=None, 3385 c="gray5", 3386 alpha=1.0, 3387 ) -> None: 3388 """ 3389 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3390 3391 Arguments: 3392 coils : (int) 3393 number of coils 3394 r1 : (float) 3395 radius at start point 3396 r2 : (float) 3397 radius at end point 3398 thickness : (float) 3399 thickness of the coil section 3400 """ 3401 start_pt = utils.make3d(start_pt) 3402 end_pt = utils.make3d(end_pt) 3403 3404 diff = end_pt - start_pt 3405 length = np.linalg.norm(diff) 3406 if not length: 3407 return 3408 if not r1: 3409 r1 = length / 20 3410 trange = np.linspace(0, length, num=50 * coils) 3411 om = 6.283 * (coils - 0.5) / length 3412 if not r2: 3413 r2 = r1 3414 pts = [] 3415 for t in trange: 3416 f = (length - t) / length 3417 rd = r1 * f + r2 * (1 - f) 3418 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3419 3420 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3421 diff = diff / length 3422 theta = np.arccos(diff[2]) 3423 phi = np.arctan2(diff[1], diff[0]) 3424 sp = Line(pts) 3425 3426 t = vtki.vtkTransform() 3427 t.Translate(start_pt) 3428 t.RotateZ(np.rad2deg(phi)) 3429 t.RotateY(np.rad2deg(theta)) 3430 3431 tf = vtki.new("TransformPolyDataFilter") 3432 tf.SetInputData(sp.dataset) 3433 tf.SetTransform(t) 3434 tf.Update() 3435 3436 tuf = vtki.new("TubeFilter") 3437 tuf.SetNumberOfSides(12) 3438 tuf.CappingOn() 3439 tuf.SetInputData(tf.GetOutput()) 3440 if not thickness: 3441 thickness = r1 / 10 3442 tuf.SetRadius(thickness) 3443 tuf.Update() 3444 3445 super().__init__(tuf.GetOutput(), c, alpha) 3446 3447 self.phong().lighting("metallic") 3448 self.base = np.array(start_pt, dtype=float) 3449 self.top = np.array(end_pt, dtype=float) 3450 self.name = "Spring"
Build a spring model.
3377 def __init__( 3378 self, 3379 start_pt=(0, 0, 0), 3380 end_pt=(1, 0, 0), 3381 coils=20, 3382 r1=0.1, 3383 r2=None, 3384 thickness=None, 3385 c="gray5", 3386 alpha=1.0, 3387 ) -> None: 3388 """ 3389 Build a spring of specified nr of `coils` between `start_pt` and `end_pt`. 3390 3391 Arguments: 3392 coils : (int) 3393 number of coils 3394 r1 : (float) 3395 radius at start point 3396 r2 : (float) 3397 radius at end point 3398 thickness : (float) 3399 thickness of the coil section 3400 """ 3401 start_pt = utils.make3d(start_pt) 3402 end_pt = utils.make3d(end_pt) 3403 3404 diff = end_pt - start_pt 3405 length = np.linalg.norm(diff) 3406 if not length: 3407 return 3408 if not r1: 3409 r1 = length / 20 3410 trange = np.linspace(0, length, num=50 * coils) 3411 om = 6.283 * (coils - 0.5) / length 3412 if not r2: 3413 r2 = r1 3414 pts = [] 3415 for t in trange: 3416 f = (length - t) / length 3417 rd = r1 * f + r2 * (1 - f) 3418 pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) 3419 3420 pts = [[0, 0, 0]] + pts + [[0, 0, length]] 3421 diff = diff / length 3422 theta = np.arccos(diff[2]) 3423 phi = np.arctan2(diff[1], diff[0]) 3424 sp = Line(pts) 3425 3426 t = vtki.vtkTransform() 3427 t.Translate(start_pt) 3428 t.RotateZ(np.rad2deg(phi)) 3429 t.RotateY(np.rad2deg(theta)) 3430 3431 tf = vtki.new("TransformPolyDataFilter") 3432 tf.SetInputData(sp.dataset) 3433 tf.SetTransform(t) 3434 tf.Update() 3435 3436 tuf = vtki.new("TubeFilter") 3437 tuf.SetNumberOfSides(12) 3438 tuf.CappingOn() 3439 tuf.SetInputData(tf.GetOutput()) 3440 if not thickness: 3441 thickness = r1 / 10 3442 tuf.SetRadius(thickness) 3443 tuf.Update() 3444 3445 super().__init__(tuf.GetOutput(), c, alpha) 3446 3447 self.phong().lighting("metallic") 3448 self.base = np.array(start_pt, dtype=float) 3449 self.top = np.array(end_pt, dtype=float) 3450 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
3453class Cylinder(Mesh): 3454 """ 3455 Build a cylinder of specified height and radius. 3456 """ 3457 3458 def __init__( 3459 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3460 cap=True, res=24, c="teal3", alpha=1.0 3461 ) -> None: 3462 """ 3463 Build a cylinder of specified height and radius `r`, centered at `pos`. 3464 3465 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3466 centered at `v1` and top at `v2`. 3467 3468 Arguments: 3469 cap : (bool) 3470 enable/disable the caps of the cylinder 3471 res : (int) 3472 resolution of the cylinder sides 3473 3474 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3475 """ 3476 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3477 base = np.array(pos[0], dtype=float) 3478 top = np.array(pos[1], dtype=float) 3479 pos = (base + top) / 2 3480 height = np.linalg.norm(top - base) 3481 axis = top - base 3482 axis = utils.versor(axis) 3483 else: 3484 axis = utils.versor(axis) 3485 base = pos - axis * height / 2 3486 top = pos + axis * height / 2 3487 3488 cyl = vtki.new("CylinderSource") 3489 cyl.SetResolution(res) 3490 cyl.SetRadius(r) 3491 cyl.SetHeight(height) 3492 cyl.SetCapping(cap) 3493 cyl.Update() 3494 3495 theta = np.arccos(axis[2]) 3496 phi = np.arctan2(axis[1], axis[0]) 3497 t = vtki.vtkTransform() 3498 t.PostMultiply() 3499 t.RotateX(90) # put it along Z 3500 t.RotateY(np.rad2deg(theta)) 3501 t.RotateZ(np.rad2deg(phi)) 3502 t.Translate(pos) 3503 3504 tf = vtki.new("TransformPolyDataFilter") 3505 tf.SetInputData(cyl.GetOutput()) 3506 tf.SetTransform(t) 3507 tf.Update() 3508 3509 super().__init__(tf.GetOutput(), c, alpha) 3510 3511 self.phong() 3512 self.base = base 3513 self.top = top 3514 self.transform = LinearTransform().translate(pos) 3515 self.name = "Cylinder"
Build a cylinder of specified height and radius.
3458 def __init__( 3459 self, pos=(0, 0, 0), r=1.0, height=2.0, axis=(0, 0, 1), 3460 cap=True, res=24, c="teal3", alpha=1.0 3461 ) -> None: 3462 """ 3463 Build a cylinder of specified height and radius `r`, centered at `pos`. 3464 3465 If `pos` is a list of 2 points, e.g. `pos=[v1, v2]`, build a cylinder with base 3466 centered at `v1` and top at `v2`. 3467 3468 Arguments: 3469 cap : (bool) 3470 enable/disable the caps of the cylinder 3471 res : (int) 3472 resolution of the cylinder sides 3473 3474 ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestCylinder.png) 3475 """ 3476 if utils.is_sequence(pos[0]): # assume user is passing pos=[base, top] 3477 base = np.array(pos[0], dtype=float) 3478 top = np.array(pos[1], dtype=float) 3479 pos = (base + top) / 2 3480 height = np.linalg.norm(top - base) 3481 axis = top - base 3482 axis = utils.versor(axis) 3483 else: 3484 axis = utils.versor(axis) 3485 base = pos - axis * height / 2 3486 top = pos + axis * height / 2 3487 3488 cyl = vtki.new("CylinderSource") 3489 cyl.SetResolution(res) 3490 cyl.SetRadius(r) 3491 cyl.SetHeight(height) 3492 cyl.SetCapping(cap) 3493 cyl.Update() 3494 3495 theta = np.arccos(axis[2]) 3496 phi = np.arctan2(axis[1], axis[0]) 3497 t = vtki.vtkTransform() 3498 t.PostMultiply() 3499 t.RotateX(90) # put it along Z 3500 t.RotateY(np.rad2deg(theta)) 3501 t.RotateZ(np.rad2deg(phi)) 3502 t.Translate(pos) 3503 3504 tf = vtki.new("TransformPolyDataFilter") 3505 tf.SetInputData(cyl.GetOutput()) 3506 tf.SetTransform(t) 3507 tf.Update() 3508 3509 super().__init__(tf.GetOutput(), c, alpha) 3510 3511 self.phong() 3512 self.base = base 3513 self.top = top 3514 self.transform = LinearTransform().translate(pos) 3515 self.name = "Cylinder"
3518class Cone(Mesh): 3519 """Build a cone of specified radius and height.""" 3520 3521 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3522 res=48, c="green3", alpha=1.0) -> None: 3523 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3524 con = vtki.new("ConeSource") 3525 con.SetResolution(res) 3526 con.SetRadius(r) 3527 con.SetHeight(height) 3528 con.SetDirection(axis) 3529 con.Update() 3530 super().__init__(con.GetOutput(), c, alpha) 3531 self.phong() 3532 if len(pos) == 2: 3533 pos = (pos[0], pos[1], 0) 3534 self.pos(pos) 3535 v = utils.versor(axis) * height / 2 3536 self.base = pos - v 3537 self.top = pos + v 3538 self.name = "Cone"
Build a cone of specified radius and height.
3521 def __init__(self, pos=(0, 0, 0), r=1.0, height=3.0, axis=(0, 0, 1), 3522 res=48, c="green3", alpha=1.0) -> None: 3523 """Build a cone of specified radius `r` and `height`, centered at `pos`.""" 3524 con = vtki.new("ConeSource") 3525 con.SetResolution(res) 3526 con.SetRadius(r) 3527 con.SetHeight(height) 3528 con.SetDirection(axis) 3529 con.Update() 3530 super().__init__(con.GetOutput(), c, alpha) 3531 self.phong() 3532 if len(pos) == 2: 3533 pos = (pos[0], pos[1], 0) 3534 self.pos(pos) 3535 v = utils.versor(axis) * height / 2 3536 self.base = pos - v 3537 self.top = pos + v 3538 self.name = "Cone"
Build a cone of specified radius r
and height
, centered at pos
.
3541class Pyramid(Cone): 3542 """Build a pyramidal shape.""" 3543 3544 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3545 c="green3", alpha=1) -> None: 3546 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3547 super().__init__(pos, s, height, axis, 4, c, alpha) 3548 self.name = "Pyramid"
Build a pyramidal shape.
3544 def __init__(self, pos=(0, 0, 0), s=1.0, height=1.0, axis=(0, 0, 1), 3545 c="green3", alpha=1) -> None: 3546 """Build a pyramid of specified base size `s` and `height`, centered at `pos`.""" 3547 super().__init__(pos, s, height, axis, 4, c, alpha) 3548 self.name = "Pyramid"
Build a pyramid of specified base size s
and height
, centered at pos
.
3551class Torus(Mesh): 3552 """ 3553 Build a toroidal shape. 3554 """ 3555 3556 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3557 """ 3558 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3559 If `quad=True` a quad-mesh is generated. 3560 """ 3561 if utils.is_sequence(res): 3562 res_u, res_v = res 3563 else: 3564 res_u, res_v = 3 * res, res 3565 3566 if quads: 3567 # https://github.com/marcomusy/vedo/issues/710 3568 3569 n = res_v 3570 m = res_u 3571 3572 theta = np.linspace(0, 2.0 * np.pi, n) 3573 phi = np.linspace(0, 2.0 * np.pi, m) 3574 theta, phi = np.meshgrid(theta, phi) 3575 t = r1 + r2 * np.cos(theta) 3576 x = t * np.cos(phi) 3577 y = t * np.sin(phi) 3578 z = r2 * np.sin(theta) 3579 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3580 3581 faces = [] 3582 for j in range(m - 1): 3583 j1n = (j + 1) * n 3584 for i in range(n - 1): 3585 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3586 3587 super().__init__([pts, faces], c, alpha) 3588 3589 else: 3590 rs = vtki.new("ParametricTorus") 3591 rs.SetRingRadius(r1) 3592 rs.SetCrossSectionRadius(r2) 3593 pfs = vtki.new("ParametricFunctionSource") 3594 pfs.SetParametricFunction(rs) 3595 pfs.SetUResolution(res_u) 3596 pfs.SetVResolution(res_v) 3597 pfs.Update() 3598 3599 super().__init__(pfs.GetOutput(), c, alpha) 3600 3601 self.phong() 3602 if len(pos) == 2: 3603 pos = (pos[0], pos[1], 0) 3604 self.pos(pos) 3605 self.name = "Torus"
Build a toroidal shape.
3556 def __init__(self, pos=(0, 0, 0), r1=1.0, r2=0.2, res=36, quads=False, c="yellow3", alpha=1.0) -> None: 3557 """ 3558 Build a torus of specified outer radius `r1` internal radius `r2`, centered at `pos`. 3559 If `quad=True` a quad-mesh is generated. 3560 """ 3561 if utils.is_sequence(res): 3562 res_u, res_v = res 3563 else: 3564 res_u, res_v = 3 * res, res 3565 3566 if quads: 3567 # https://github.com/marcomusy/vedo/issues/710 3568 3569 n = res_v 3570 m = res_u 3571 3572 theta = np.linspace(0, 2.0 * np.pi, n) 3573 phi = np.linspace(0, 2.0 * np.pi, m) 3574 theta, phi = np.meshgrid(theta, phi) 3575 t = r1 + r2 * np.cos(theta) 3576 x = t * np.cos(phi) 3577 y = t * np.sin(phi) 3578 z = r2 * np.sin(theta) 3579 pts = np.column_stack((x.ravel(), y.ravel(), z.ravel())) 3580 3581 faces = [] 3582 for j in range(m - 1): 3583 j1n = (j + 1) * n 3584 for i in range(n - 1): 3585 faces.append([i + j * n, i + 1 + j * n, i + 1 + j1n, i + j1n]) 3586 3587 super().__init__([pts, faces], c, alpha) 3588 3589 else: 3590 rs = vtki.new("ParametricTorus") 3591 rs.SetRingRadius(r1) 3592 rs.SetCrossSectionRadius(r2) 3593 pfs = vtki.new("ParametricFunctionSource") 3594 pfs.SetParametricFunction(rs) 3595 pfs.SetUResolution(res_u) 3596 pfs.SetVResolution(res_v) 3597 pfs.Update() 3598 3599 super().__init__(pfs.GetOutput(), c, alpha) 3600 3601 self.phong() 3602 if len(pos) == 2: 3603 pos = (pos[0], pos[1], 0) 3604 self.pos(pos) 3605 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.
3608class Paraboloid(Mesh): 3609 """ 3610 Build a paraboloid. 3611 """ 3612 3613 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3614 """ 3615 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3616 3617 Full volumetric expression is: 3618 `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` 3619 3620 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3621 """ 3622 quadric = vtki.new("Quadric") 3623 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3624 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3625 # + a3*x*y + a4*y*z + a5*x*z 3626 # + a6*x + a7*y + a8*z +a9 3627 sample = vtki.new("SampleFunction") 3628 sample.SetSampleDimensions(res, res, res) 3629 sample.SetImplicitFunction(quadric) 3630 3631 contours = vtki.new("ContourFilter") 3632 contours.SetInputConnection(sample.GetOutputPort()) 3633 contours.GenerateValues(1, 0.01, 0.01) 3634 contours.Update() 3635 3636 super().__init__(contours.GetOutput(), c, alpha) 3637 self.compute_normals().phong() 3638 self.mapper.ScalarVisibilityOff() 3639 self.pos(pos) 3640 self.name = "Paraboloid"
Build a paraboloid.
3613 def __init__(self, pos=(0, 0, 0), height=1.0, res=50, c="cyan5", alpha=1.0) -> None: 3614 """ 3615 Build a paraboloid of specified height and radius `r`, centered at `pos`. 3616 3617 Full volumetric expression is: 3618 `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` 3619 3620 ![](https://user-images.githubusercontent.com/32848391/51211547-260ef480-1916-11e9-95f6-4a677e37e355.png) 3621 """ 3622 quadric = vtki.new("Quadric") 3623 quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) 3624 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3625 # + a3*x*y + a4*y*z + a5*x*z 3626 # + a6*x + a7*y + a8*z +a9 3627 sample = vtki.new("SampleFunction") 3628 sample.SetSampleDimensions(res, res, res) 3629 sample.SetImplicitFunction(quadric) 3630 3631 contours = vtki.new("ContourFilter") 3632 contours.SetInputConnection(sample.GetOutputPort()) 3633 contours.GenerateValues(1, 0.01, 0.01) 3634 contours.Update() 3635 3636 super().__init__(contours.GetOutput(), c, alpha) 3637 self.compute_normals().phong() 3638 self.mapper.ScalarVisibilityOff() 3639 self.pos(pos) 3640 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
3643class Hyperboloid(Mesh): 3644 """ 3645 Build a hyperboloid. 3646 """ 3647 3648 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3649 """ 3650 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3651 3652 Full volumetric expression is: 3653 `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` 3654 """ 3655 q = vtki.new("Quadric") 3656 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3657 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3658 # + a3*x*y + a4*y*z + a5*x*z 3659 # + a6*x + a7*y + a8*z +a9 3660 sample = vtki.new("SampleFunction") 3661 sample.SetSampleDimensions(res, res, res) 3662 sample.SetImplicitFunction(q) 3663 3664 contours = vtki.new("ContourFilter") 3665 contours.SetInputConnection(sample.GetOutputPort()) 3666 contours.GenerateValues(1, value, value) 3667 contours.Update() 3668 3669 super().__init__(contours.GetOutput(), c, alpha) 3670 self.compute_normals().phong() 3671 self.mapper.ScalarVisibilityOff() 3672 self.pos(pos) 3673 self.name = "Hyperboloid"
Build a hyperboloid.
3648 def __init__(self, pos=(0, 0, 0), a2=1.0, value=0.5, res=100, c="pink4", alpha=1.0) -> None: 3649 """ 3650 Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. 3651 3652 Full volumetric expression is: 3653 `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` 3654 """ 3655 q = vtki.new("Quadric") 3656 q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) 3657 # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 3658 # + a3*x*y + a4*y*z + a5*x*z 3659 # + a6*x + a7*y + a8*z +a9 3660 sample = vtki.new("SampleFunction") 3661 sample.SetSampleDimensions(res, res, res) 3662 sample.SetImplicitFunction(q) 3663 3664 contours = vtki.new("ContourFilter") 3665 contours.SetInputConnection(sample.GetOutputPort()) 3666 contours.GenerateValues(1, value, value) 3667 contours.Update() 3668 3669 super().__init__(contours.GetOutput(), c, alpha) 3670 self.compute_normals().phong() 3671 self.mapper.ScalarVisibilityOff() 3672 self.pos(pos) 3673 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
4438class TextBase: 4439 "Base class." 4440 4441 def __init__(self): 4442 "Do not instantiate this base class." 4443 4444 self.rendered_at = set() 4445 self.properties = None 4446 4447 self.name = "Text" 4448 self.filename = "" 4449 self.time = 0 4450 self.info = {} 4451 4452 if isinstance(settings.default_font, int): 4453 lfonts = list(settings.font_parameters.keys()) 4454 font = settings.default_font % len(lfonts) 4455 self.fontname = lfonts[font] 4456 else: 4457 self.fontname = settings.default_font 4458 4459 def angle(self, value: float): 4460 """Orientation angle in degrees""" 4461 self.properties.SetOrientation(value) 4462 return self 4463 4464 def line_spacing(self, value: float): 4465 """Set the extra spacing between lines 4466 expressed as a text height multiplicative factor.""" 4467 self.properties.SetLineSpacing(value) 4468 return self 4469 4470 def line_offset(self, value: float): 4471 """Set/Get the vertical offset (measured in pixels).""" 4472 self.properties.SetLineOffset(value) 4473 return self 4474 4475 def bold(self, value=True): 4476 """Set bold face""" 4477 self.properties.SetBold(value) 4478 return self 4479 4480 def italic(self, value=True): 4481 """Set italic face""" 4482 self.properties.SetItalic(value) 4483 return self 4484 4485 def shadow(self, offset=(1, -1)): 4486 """Text shadowing. Set to `None` to disable it.""" 4487 if offset is None: 4488 self.properties.ShadowOff() 4489 else: 4490 self.properties.ShadowOn() 4491 self.properties.SetShadowOffset(offset) 4492 return self 4493 4494 def color(self, c=None): 4495 """Set the text color""" 4496 if c is None: 4497 return get_color(self.properties.GetColor()) 4498 self.properties.SetColor(get_color(c)) 4499 return self 4500 4501 def c(self, color=None): 4502 """Set the text color""" 4503 if color is None: 4504 return get_color(self.properties.GetColor()) 4505 return self.color(color) 4506 4507 def alpha(self, value: float): 4508 """Set the text opacity""" 4509 self.properties.SetBackgroundOpacity(value) 4510 return self 4511 4512 def background(self, color="k9", alpha=1.0): 4513 """Text background. Set to `None` to disable it.""" 4514 bg = get_color(color) 4515 if color is None: 4516 self.properties.SetBackgroundOpacity(0) 4517 else: 4518 self.properties.SetBackgroundColor(bg) 4519 if alpha: 4520 self.properties.SetBackgroundOpacity(alpha) 4521 return self 4522 4523 def frame(self, color="k1", lw=2): 4524 """Border color and width""" 4525 if color is None: 4526 self.properties.FrameOff() 4527 else: 4528 c = get_color(color) 4529 self.properties.FrameOn() 4530 self.properties.SetFrameColor(c) 4531 self.properties.SetFrameWidth(lw) 4532 return self 4533 4534 def font(self, font: str): 4535 """Text font face""" 4536 if isinstance(font, int): 4537 lfonts = list(settings.font_parameters.keys()) 4538 n = font % len(lfonts) 4539 font = lfonts[n] 4540 self.fontname = font 4541 4542 if not font: # use default font 4543 font = self.fontname 4544 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4545 elif font.startswith("https"): # user passed URL link, make it a path 4546 fpath = vedo.file_io.download(font, verbose=False, force=False) 4547 elif font.endswith(".ttf"): # user passing a local path to font file 4548 fpath = font 4549 else: # user passing name of preset font 4550 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4551 4552 if font == "Courier": self.properties.SetFontFamilyToCourier() 4553 elif font == "Times": self.properties.SetFontFamilyToTimes() 4554 elif font == "Arial": self.properties.SetFontFamilyToArial() 4555 else: 4556 fpath = utils.get_font_path(font) 4557 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4558 self.properties.SetFontFile(fpath) 4559 self.fontname = font # io.tonumpy() uses it 4560 4561 return self 4562 4563 def on(self): 4564 """Make text visible""" 4565 self.actor.SetVisibility(True) 4566 return self 4567 4568 def off(self): 4569 """Make text invisible""" 4570 self.actor.SetVisibility(False) 4571 return self
Base class.
4441 def __init__(self): 4442 "Do not instantiate this base class." 4443 4444 self.rendered_at = set() 4445 self.properties = None 4446 4447 self.name = "Text" 4448 self.filename = "" 4449 self.time = 0 4450 self.info = {} 4451 4452 if isinstance(settings.default_font, int): 4453 lfonts = list(settings.font_parameters.keys()) 4454 font = settings.default_font % len(lfonts) 4455 self.fontname = lfonts[font] 4456 else: 4457 self.fontname = settings.default_font
Do not instantiate this base class.
4459 def angle(self, value: float): 4460 """Orientation angle in degrees""" 4461 self.properties.SetOrientation(value) 4462 return self
Orientation angle in degrees
4464 def line_spacing(self, value: float): 4465 """Set the extra spacing between lines 4466 expressed as a text height multiplicative factor.""" 4467 self.properties.SetLineSpacing(value) 4468 return self
Set the extra spacing between lines expressed as a text height multiplicative factor.
4470 def line_offset(self, value: float): 4471 """Set/Get the vertical offset (measured in pixels).""" 4472 self.properties.SetLineOffset(value) 4473 return self
Set/Get the vertical offset (measured in pixels).
4475 def bold(self, value=True): 4476 """Set bold face""" 4477 self.properties.SetBold(value) 4478 return self
Set bold face
4480 def italic(self, value=True): 4481 """Set italic face""" 4482 self.properties.SetItalic(value) 4483 return self
Set italic face
4485 def shadow(self, offset=(1, -1)): 4486 """Text shadowing. Set to `None` to disable it.""" 4487 if offset is None: 4488 self.properties.ShadowOff() 4489 else: 4490 self.properties.ShadowOn() 4491 self.properties.SetShadowOffset(offset) 4492 return self
Text shadowing. Set to None
to disable it.
4494 def color(self, c=None): 4495 """Set the text color""" 4496 if c is None: 4497 return get_color(self.properties.GetColor()) 4498 self.properties.SetColor(get_color(c)) 4499 return self
Set the text color
4501 def c(self, color=None): 4502 """Set the text color""" 4503 if color is None: 4504 return get_color(self.properties.GetColor()) 4505 return self.color(color)
Set the text color
4507 def alpha(self, value: float): 4508 """Set the text opacity""" 4509 self.properties.SetBackgroundOpacity(value) 4510 return self
Set the text opacity
4512 def background(self, color="k9", alpha=1.0): 4513 """Text background. Set to `None` to disable it.""" 4514 bg = get_color(color) 4515 if color is None: 4516 self.properties.SetBackgroundOpacity(0) 4517 else: 4518 self.properties.SetBackgroundColor(bg) 4519 if alpha: 4520 self.properties.SetBackgroundOpacity(alpha) 4521 return self
Text background. Set to None
to disable it.
4523 def frame(self, color="k1", lw=2): 4524 """Border color and width""" 4525 if color is None: 4526 self.properties.FrameOff() 4527 else: 4528 c = get_color(color) 4529 self.properties.FrameOn() 4530 self.properties.SetFrameColor(c) 4531 self.properties.SetFrameWidth(lw) 4532 return self
Border color and width
4534 def font(self, font: str): 4535 """Text font face""" 4536 if isinstance(font, int): 4537 lfonts = list(settings.font_parameters.keys()) 4538 n = font % len(lfonts) 4539 font = lfonts[n] 4540 self.fontname = font 4541 4542 if not font: # use default font 4543 font = self.fontname 4544 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4545 elif font.startswith("https"): # user passed URL link, make it a path 4546 fpath = vedo.file_io.download(font, verbose=False, force=False) 4547 elif font.endswith(".ttf"): # user passing a local path to font file 4548 fpath = font 4549 else: # user passing name of preset font 4550 fpath = os.path.join(vedo.fonts_path, font + ".ttf") 4551 4552 if font == "Courier": self.properties.SetFontFamilyToCourier() 4553 elif font == "Times": self.properties.SetFontFamilyToTimes() 4554 elif font == "Arial": self.properties.SetFontFamilyToArial() 4555 else: 4556 fpath = utils.get_font_path(font) 4557 self.properties.SetFontFamily(vtki.VTK_FONT_FILE) 4558 self.properties.SetFontFile(fpath) 4559 self.fontname = font # io.tonumpy() uses it 4560 4561 return self
Text font face
4099class Text3D(Mesh): 4100 """ 4101 Generate a 3D polygonal Mesh to represent a text string. 4102 """ 4103 4104 def __init__( 4105 self, 4106 txt, 4107 pos=(0, 0, 0), 4108 s=1.0, 4109 font="", 4110 hspacing=1.15, 4111 vspacing=2.15, 4112 depth=0.0, 4113 italic=False, 4114 justify="bottom-left", 4115 literal=False, 4116 c=None, 4117 alpha=1.0, 4118 ) -> None: 4119 """ 4120 Generate a 3D polygonal `Mesh` representing a text string. 4121 4122 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4123 Most Latex symbols are also supported. 4124 4125 Symbols `~ ^ _` are reserved modifiers: 4126 - use ~ to add a short space, 1/4 of the default empty space, 4127 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4128 4129 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4130 4131 More fonts at: https://vedo.embl.es/fonts/ 4132 4133 Arguments: 4134 pos : (list) 4135 position coordinates in 3D space 4136 s : (float) 4137 vertical size of the text (as scaling factor) 4138 depth : (float) 4139 text thickness (along z) 4140 italic : (bool), float 4141 italic font type (can be a signed float too) 4142 justify : (str) 4143 text justification as centering of the bounding box 4144 (bottom-left, bottom-right, top-left, top-right, centered) 4145 font : (str, int) 4146 some of the available 3D-polygonized fonts are: 4147 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4148 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4149 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4150 4151 Check for more at https://vedo.embl.es/fonts/ 4152 4153 Or type in your terminal `vedo --run fonts`. 4154 4155 Default is Normografo, which can be changed using `settings.default_font`. 4156 4157 hspacing : (float) 4158 horizontal spacing of the font 4159 vspacing : (float) 4160 vertical spacing of the font for multiple lines text 4161 literal : (bool) 4162 if set to True will ignore modifiers like _ or ^ 4163 4164 Examples: 4165 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4166 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4167 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4168 4169 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4170 4171 .. note:: Type `vedo -r fonts` for a demo. 4172 """ 4173 if len(pos) == 2: 4174 pos = (pos[0], pos[1], 0) 4175 4176 if c is None: # automatic black or white 4177 pli = vedo.plotter_instance 4178 if pli and pli.renderer: 4179 c = (0.9, 0.9, 0.9) 4180 if pli.renderer.GetGradientBackground(): 4181 bgcol = pli.renderer.GetBackground2() 4182 else: 4183 bgcol = pli.renderer.GetBackground() 4184 if np.sum(bgcol) > 1.5: 4185 c = (0.1, 0.1, 0.1) 4186 else: 4187 c = (0.6, 0.6, 0.6) 4188 4189 tpoly = self._get_text3d_poly( 4190 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4191 ) 4192 4193 super().__init__(tpoly, c, alpha) 4194 4195 self.pos(pos) 4196 self.lighting("off") 4197 4198 self.actor.PickableOff() 4199 self.actor.DragableOff() 4200 self.init_scale = s 4201 self.name = "Text3D" 4202 self.txt = txt 4203 self.justify = justify 4204 4205 def text( 4206 self, 4207 txt=None, 4208 s=1, 4209 font="", 4210 hspacing=1.15, 4211 vspacing=2.15, 4212 depth=0, 4213 italic=False, 4214 justify="", 4215 literal=False, 4216 ) -> "Text3D": 4217 """ 4218 Update the text and some of its properties. 4219 4220 Check [available fonts here](https://vedo.embl.es/fonts). 4221 """ 4222 if txt is None: 4223 return self.txt 4224 if not justify: 4225 justify = self.justify 4226 4227 poly = self._get_text3d_poly( 4228 txt, self.init_scale * s, font, hspacing, vspacing, 4229 depth, italic, justify, literal 4230 ) 4231 4232 # apply the current transformation to the new polydata 4233 tf = vtki.new("TransformPolyDataFilter") 4234 tf.SetInputData(poly) 4235 tf.SetTransform(self.transform.T) 4236 tf.Update() 4237 tpoly = tf.GetOutput() 4238 4239 self._update(tpoly) 4240 self.txt = txt 4241 return self 4242 4243 def _get_text3d_poly( 4244 self, 4245 txt, 4246 s=1, 4247 font="", 4248 hspacing=1.15, 4249 vspacing=2.15, 4250 depth=0, 4251 italic=False, 4252 justify="bottom-left", 4253 literal=False, 4254 ) -> vtki.vtkPolyData: 4255 if not font: 4256 font = settings.default_font 4257 4258 txt = str(txt) 4259 4260 if font == "VTK": ####################################### 4261 vtt = vtki.new("VectorText") 4262 vtt.SetText(txt) 4263 vtt.Update() 4264 tpoly = vtt.GetOutput() 4265 4266 else: ################################################### 4267 4268 stxt = set(txt) # check here if null or only spaces 4269 if not txt or (len(stxt) == 1 and " " in stxt): 4270 return vtki.vtkPolyData() 4271 4272 if italic is True: 4273 italic = 1 4274 4275 if isinstance(font, int): 4276 lfonts = list(settings.font_parameters.keys()) 4277 font = font % len(lfonts) 4278 font = lfonts[font] 4279 4280 if font not in settings.font_parameters.keys(): 4281 fpars = settings.font_parameters["Normografo"] 4282 else: 4283 fpars = settings.font_parameters[font] 4284 4285 # ad hoc adjustments 4286 mono = fpars["mono"] 4287 lspacing = fpars["lspacing"] 4288 hspacing *= fpars["hspacing"] 4289 fscale = fpars["fscale"] 4290 dotsep = fpars["dotsep"] 4291 4292 # replacements 4293 if ":" in txt: 4294 for r in _reps: 4295 txt = txt.replace(r[0], r[1]) 4296 4297 if not literal: 4298 reps2 = [ 4299 (r"\_", "┭"), # trick to protect ~ _ and ^ chars 4300 (r"\^", "┮"), # 4301 (r"\~", "┯"), # 4302 ("**", "^"), # order matters 4303 ("e+0", dotsep + "10^"), 4304 ("e-0", dotsep + "10^-"), 4305 ("E+0", dotsep + "10^"), 4306 ("E-0", dotsep + "10^-"), 4307 ("e+", dotsep + "10^"), 4308 ("e-", dotsep + "10^-"), 4309 ("E+", dotsep + "10^"), 4310 ("E-", dotsep + "10^-"), 4311 ] 4312 for r in reps2: 4313 txt = txt.replace(r[0], r[1]) 4314 4315 xmax, ymax, yshift, scale = 0.0, 0.0, 0.0, 1.0 4316 save_xmax = 0.0 4317 4318 notfounds = set() 4319 polyletters = [] 4320 ntxt = len(txt) 4321 for i, t in enumerate(txt): 4322 ########## 4323 if t == "┭": 4324 t = "_" 4325 elif t == "┮": 4326 t = "^" 4327 elif t == "┯": 4328 t = "~" 4329 elif t == "^" and not literal: 4330 if yshift < 0: 4331 xmax = save_xmax 4332 yshift = 0.9 * fscale 4333 scale = 0.5 4334 continue 4335 elif t == "_" and not literal: 4336 if yshift > 0: 4337 xmax = save_xmax 4338 yshift = -0.3 * fscale 4339 scale = 0.5 4340 continue 4341 elif (t in (" ", "\\n")) and yshift: 4342 yshift = 0.0 4343 scale = 1.0 4344 save_xmax = xmax 4345 if t == " ": 4346 continue 4347 elif t == "~": 4348 if i < ntxt - 1 and txt[i + 1] == "_": 4349 continue 4350 xmax += hspacing * scale * fscale / 4 4351 continue 4352 4353 ############ 4354 if t == " ": 4355 xmax += hspacing * scale * fscale 4356 4357 elif t == "\n": 4358 xmax = 0.0 4359 save_xmax = 0.0 4360 ymax -= vspacing 4361 4362 else: 4363 poly = _get_font_letter(font, t) 4364 if not poly: 4365 notfounds.add(t) 4366 xmax += hspacing * scale * fscale 4367 continue 4368 4369 if poly.GetNumberOfPoints() == 0: 4370 continue 4371 4372 tr = vtki.vtkTransform() 4373 tr.Translate(xmax, ymax + yshift, 0) 4374 pscale = scale * fscale / 1000 4375 tr.Scale(pscale, pscale, pscale) 4376 if italic: 4377 tr.Concatenate([1, italic * 0.15, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 4378 tf = vtki.new("TransformPolyDataFilter") 4379 tf.SetInputData(poly) 4380 tf.SetTransform(tr) 4381 tf.Update() 4382 poly = tf.GetOutput() 4383 polyletters.append(poly) 4384 4385 bx = poly.GetBounds() 4386 if mono: 4387 xmax += hspacing * scale * fscale 4388 else: 4389 xmax += bx[1] - bx[0] + hspacing * scale * fscale * lspacing 4390 if yshift == 0: 4391 save_xmax = xmax 4392 4393 if len(polyletters) == 1: 4394 tpoly = polyletters[0] 4395 else: 4396 polyapp = vtki.new("AppendPolyData") 4397 for polyd in polyletters: 4398 polyapp.AddInputData(polyd) 4399 polyapp.Update() 4400 tpoly = polyapp.GetOutput() 4401 4402 if notfounds: 4403 wmsg = f"unavailable characters in font name '{font}': {notfounds}." 4404 wmsg += '\nType "vedo -r fonts" for a demo.' 4405 vedo.logger.warning(wmsg) 4406 4407 bb = tpoly.GetBounds() 4408 dx, dy = (bb[1] - bb[0]) / 2 * s, (bb[3] - bb[2]) / 2 * s 4409 shift = -np.array([(bb[1] + bb[0]), (bb[3] + bb[2]), (bb[5] + bb[4])]) * s /2 4410 if "bottom" in justify: shift += np.array([ 0, dy, 0.]) 4411 if "top" in justify: shift += np.array([ 0,-dy, 0.]) 4412 if "left" in justify: shift += np.array([ dx, 0, 0.]) 4413 if "right" in justify: shift += np.array([-dx, 0, 0.]) 4414 4415 if tpoly.GetNumberOfPoints(): 4416 t = vtki.vtkTransform() 4417 t.PostMultiply() 4418 t.Scale(s, s, s) 4419 t.Translate(shift) 4420 tf = vtki.new("TransformPolyDataFilter") 4421 tf.SetInputData(tpoly) 4422 tf.SetTransform(t) 4423 tf.Update() 4424 tpoly = tf.GetOutput() 4425 4426 if depth: 4427 extrude = vtki.new("LinearExtrusionFilter") 4428 extrude.SetInputData(tpoly) 4429 extrude.SetExtrusionTypeToVectorExtrusion() 4430 extrude.SetVector(0, 0, 1) 4431 extrude.SetScaleFactor(depth * dy) 4432 extrude.Update() 4433 tpoly = extrude.GetOutput() 4434 4435 return tpoly
Generate a 3D polygonal Mesh to represent a text string.
4104 def __init__( 4105 self, 4106 txt, 4107 pos=(0, 0, 0), 4108 s=1.0, 4109 font="", 4110 hspacing=1.15, 4111 vspacing=2.15, 4112 depth=0.0, 4113 italic=False, 4114 justify="bottom-left", 4115 literal=False, 4116 c=None, 4117 alpha=1.0, 4118 ) -> None: 4119 """ 4120 Generate a 3D polygonal `Mesh` representing a text string. 4121 4122 Can render strings like `3.7 10^9` or `H_2 O` with subscripts and superscripts. 4123 Most Latex symbols are also supported. 4124 4125 Symbols `~ ^ _` are reserved modifiers: 4126 - use ~ to add a short space, 1/4 of the default empty space, 4127 - use ^ and _ to start up/sub scripting, a space terminates their effect. 4128 4129 Monospaced fonts are: `Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino`. 4130 4131 More fonts at: https://vedo.embl.es/fonts/ 4132 4133 Arguments: 4134 pos : (list) 4135 position coordinates in 3D space 4136 s : (float) 4137 vertical size of the text (as scaling factor) 4138 depth : (float) 4139 text thickness (along z) 4140 italic : (bool), float 4141 italic font type (can be a signed float too) 4142 justify : (str) 4143 text justification as centering of the bounding box 4144 (bottom-left, bottom-right, top-left, top-right, centered) 4145 font : (str, int) 4146 some of the available 3D-polygonized fonts are: 4147 Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, 4148 LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, 4149 Capsmall, Cartoons123, Vega, Justino, Spears, Meson. 4150 4151 Check for more at https://vedo.embl.es/fonts/ 4152 4153 Or type in your terminal `vedo --run fonts`. 4154 4155 Default is Normografo, which can be changed using `settings.default_font`. 4156 4157 hspacing : (float) 4158 horizontal spacing of the font 4159 vspacing : (float) 4160 vertical spacing of the font for multiple lines text 4161 literal : (bool) 4162 if set to True will ignore modifiers like _ or ^ 4163 4164 Examples: 4165 - [markpoint.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/markpoint.py) 4166 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4167 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4168 4169 ![](https://vedo.embl.es/images/pyplot/fonts3d.png) 4170 4171 .. note:: Type `vedo -r fonts` for a demo. 4172 """ 4173 if len(pos) == 2: 4174 pos = (pos[0], pos[1], 0) 4175 4176 if c is None: # automatic black or white 4177 pli = vedo.plotter_instance 4178 if pli and pli.renderer: 4179 c = (0.9, 0.9, 0.9) 4180 if pli.renderer.GetGradientBackground(): 4181 bgcol = pli.renderer.GetBackground2() 4182 else: 4183 bgcol = pli.renderer.GetBackground() 4184 if np.sum(bgcol) > 1.5: 4185 c = (0.1, 0.1, 0.1) 4186 else: 4187 c = (0.6, 0.6, 0.6) 4188 4189 tpoly = self._get_text3d_poly( 4190 txt, s, font, hspacing, vspacing, depth, italic, justify, literal 4191 ) 4192 4193 super().__init__(tpoly, c, alpha) 4194 4195 self.pos(pos) 4196 self.lighting("off") 4197 4198 self.actor.PickableOff() 4199 self.actor.DragableOff() 4200 self.init_scale = s 4201 self.name = "Text3D" 4202 self.txt = txt 4203 self.justify = justify
Generate a 3D polygonal Mesh
representing a text string.
Can render strings like 3.7 10^9
or H_2 O
with subscripts and superscripts.
Most Latex symbols are also supported.
Symbols ~ ^ _
are reserved modifiers:
- use ~ to add a short space, 1/4 of the default empty space,
- use ^ and _ to start up/sub scripting, a space terminates their effect.
Monospaced fonts are: Calco, ComicMono, Glasgo, SmartCouric, VictorMono, Justino
.
More fonts at: https://vedo.embl.es/fonts/
Arguments:
- pos : (list) position coordinates in 3D space
- s : (float) vertical size of the text (as scaling factor)
- depth : (float) text thickness (along z)
- italic : (bool), float italic font type (can be a signed float too)
- justify : (str) text justification as centering of the bounding box (bottom-left, bottom-right, top-left, top-right, centered)
font : (str, int) some of the available 3D-polygonized fonts are: Bongas, Calco, Comae, ComicMono, Kanopus, Glasgo, Ubuntu, LogoType, Normografo, Quikhand, SmartCouric, Theemim, VictorMono, VTK, Capsmall, Cartoons123, Vega, Justino, Spears, Meson.
Check for more at https://vedo.embl.es/fonts/
Or type in your terminal
vedo --run fonts
.Default is Normografo, which can be changed using
settings.default_font
.- hspacing : (float) horizontal spacing of the font
- vspacing : (float) vertical spacing of the font for multiple lines text
- literal : (bool) if set to True will ignore modifiers like _ or ^
Examples:
Type vedo -r fonts
for a demo.
4205 def text( 4206 self, 4207 txt=None, 4208 s=1, 4209 font="", 4210 hspacing=1.15, 4211 vspacing=2.15, 4212 depth=0, 4213 italic=False, 4214 justify="", 4215 literal=False, 4216 ) -> "Text3D": 4217 """ 4218 Update the text and some of its properties. 4219 4220 Check [available fonts here](https://vedo.embl.es/fonts). 4221 """ 4222 if txt is None: 4223 return self.txt 4224 if not justify: 4225 justify = self.justify 4226 4227 poly = self._get_text3d_poly( 4228 txt, self.init_scale * s, font, hspacing, vspacing, 4229 depth, italic, justify, literal 4230 ) 4231 4232 # apply the current transformation to the new polydata 4233 tf = vtki.new("TransformPolyDataFilter") 4234 tf.SetInputData(poly) 4235 tf.SetTransform(self.transform.T) 4236 tf.Update() 4237 tpoly = tf.GetOutput() 4238 4239 self._update(tpoly) 4240 self.txt = txt 4241 return self
Update the text and some of its properties.
Check available fonts here.
4573class Text2D(TextBase, vedo.visual.Actor2D): 4574 """ 4575 Create a 2D text object. 4576 """ 4577 def __init__( 4578 self, 4579 txt="", 4580 pos="top-left", 4581 s=1.0, 4582 bg=None, 4583 font="", 4584 justify="", 4585 bold=False, 4586 italic=False, 4587 c=None, 4588 alpha=0.5, 4589 ) -> None: 4590 """ 4591 Create a 2D text object. 4592 4593 All properties of the text, and the text itself, can be changed after creation 4594 (which is especially useful in loops). 4595 4596 Arguments: 4597 pos : (str) 4598 text is placed in one of the 8 positions: 4599 - bottom-left 4600 - bottom-right 4601 - top-left 4602 - top-right 4603 - bottom-middle 4604 - middle-right 4605 - middle-left 4606 - top-middle 4607 4608 If a pair (x,y) is passed as input the 2D text is place at that 4609 position in the coordinate system of the 2D screen (with the 4610 origin sitting at the bottom left). 4611 4612 s : (float) 4613 size of text 4614 bg : (color) 4615 background color 4616 alpha : (float) 4617 background opacity 4618 justify : (str) 4619 text justification 4620 4621 font : (str) 4622 built-in available fonts are: 4623 - Antares 4624 - Arial 4625 - Bongas 4626 - Calco 4627 - Comae 4628 - ComicMono 4629 - Courier 4630 - Glasgo 4631 - Kanopus 4632 - LogoType 4633 - Normografo 4634 - Quikhand 4635 - SmartCouric 4636 - Theemim 4637 - Times 4638 - VictorMono 4639 - More fonts at: https://vedo.embl.es/fonts/ 4640 4641 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4642 4643 Examples: 4644 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4645 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4646 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4647 4648 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4649 """ 4650 super().__init__() 4651 self.name = "Text2D" 4652 4653 self.mapper = vtki.new("TextMapper") 4654 self.SetMapper(self.mapper) 4655 4656 self.properties = self.mapper.GetTextProperty() 4657 self.actor = self 4658 self.actor.retrieve_object = weak_ref_to(self) 4659 4660 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4661 4662 # automatic black or white 4663 if c is None: 4664 c = (0.1, 0.1, 0.1) 4665 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4666 if vedo.plotter_instance.renderer.GetGradientBackground(): 4667 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4668 else: 4669 bgcol = vedo.plotter_instance.renderer.GetBackground() 4670 c = (0.9, 0.9, 0.9) 4671 if np.sum(bgcol) > 1.5: 4672 c = (0.1, 0.1, 0.1) 4673 4674 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4675 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4676 self.PickableOff() 4677 4678 def pos(self, pos="top-left", justify=""): 4679 """ 4680 Set position of the text to draw. Keyword `pos` can be a string 4681 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4682 """ 4683 ajustify = "top-left" # autojustify 4684 if isinstance(pos, str): # corners 4685 ajustify = pos 4686 if "top" in pos: 4687 if "left" in pos: 4688 pos = (0.008, 0.994) 4689 elif "right" in pos: 4690 pos = (0.994, 0.994) 4691 elif "mid" in pos or "cent" in pos: 4692 pos = (0.5, 0.994) 4693 elif "bottom" in pos: 4694 if "left" in pos: 4695 pos = (0.008, 0.008) 4696 elif "right" in pos: 4697 pos = (0.994, 0.008) 4698 elif "mid" in pos or "cent" in pos: 4699 pos = (0.5, 0.008) 4700 elif "mid" in pos or "cent" in pos: 4701 if "left" in pos: 4702 pos = (0.008, 0.5) 4703 elif "right" in pos: 4704 pos = (0.994, 0.5) 4705 else: 4706 pos = (0.5, 0.5) 4707 4708 else: 4709 vedo.logger.warning(f"cannot understand text position {pos}") 4710 pos = (0.008, 0.994) 4711 ajustify = "top-left" 4712 4713 elif len(pos) != 2: 4714 vedo.logger.error("pos must be of length 2 or integer value or string") 4715 raise RuntimeError() 4716 4717 if not justify: 4718 justify = ajustify 4719 4720 self.properties.SetJustificationToLeft() 4721 if "top" in justify: 4722 self.properties.SetVerticalJustificationToTop() 4723 if "bottom" in justify: 4724 self.properties.SetVerticalJustificationToBottom() 4725 if "cent" in justify or "mid" in justify: 4726 self.properties.SetJustificationToCentered() 4727 if "left" in justify: 4728 self.properties.SetJustificationToLeft() 4729 if "right" in justify: 4730 self.properties.SetJustificationToRight() 4731 4732 self.SetPosition(pos) 4733 return self 4734 4735 def text(self, txt=None): 4736 """Set/get the input text string.""" 4737 if txt is None: 4738 return self.mapper.GetInput() 4739 4740 if ":" in txt: 4741 for r in _reps: 4742 txt = txt.replace(r[0], r[1]) 4743 else: 4744 txt = str(txt) 4745 4746 self.mapper.SetInput(txt) 4747 return self 4748 4749 def size(self, s): 4750 """Set the font size.""" 4751 self.properties.SetFontSize(int(s * 22.5)) 4752 return self
Create a 2D text object.
4577 def __init__( 4578 self, 4579 txt="", 4580 pos="top-left", 4581 s=1.0, 4582 bg=None, 4583 font="", 4584 justify="", 4585 bold=False, 4586 italic=False, 4587 c=None, 4588 alpha=0.5, 4589 ) -> None: 4590 """ 4591 Create a 2D text object. 4592 4593 All properties of the text, and the text itself, can be changed after creation 4594 (which is especially useful in loops). 4595 4596 Arguments: 4597 pos : (str) 4598 text is placed in one of the 8 positions: 4599 - bottom-left 4600 - bottom-right 4601 - top-left 4602 - top-right 4603 - bottom-middle 4604 - middle-right 4605 - middle-left 4606 - top-middle 4607 4608 If a pair (x,y) is passed as input the 2D text is place at that 4609 position in the coordinate system of the 2D screen (with the 4610 origin sitting at the bottom left). 4611 4612 s : (float) 4613 size of text 4614 bg : (color) 4615 background color 4616 alpha : (float) 4617 background opacity 4618 justify : (str) 4619 text justification 4620 4621 font : (str) 4622 built-in available fonts are: 4623 - Antares 4624 - Arial 4625 - Bongas 4626 - Calco 4627 - Comae 4628 - ComicMono 4629 - Courier 4630 - Glasgo 4631 - Kanopus 4632 - LogoType 4633 - Normografo 4634 - Quikhand 4635 - SmartCouric 4636 - Theemim 4637 - Times 4638 - VictorMono 4639 - More fonts at: https://vedo.embl.es/fonts/ 4640 4641 A path to a `.otf` or `.ttf` font-file can also be supplied as input. 4642 4643 Examples: 4644 - [fonts.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fonts.py) 4645 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 4646 - [colorcubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/colorcubes.py) 4647 4648 ![](https://vedo.embl.es/images/basic/colorcubes.png) 4649 """ 4650 super().__init__() 4651 self.name = "Text2D" 4652 4653 self.mapper = vtki.new("TextMapper") 4654 self.SetMapper(self.mapper) 4655 4656 self.properties = self.mapper.GetTextProperty() 4657 self.actor = self 4658 self.actor.retrieve_object = weak_ref_to(self) 4659 4660 self.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 4661 4662 # automatic black or white 4663 if c is None: 4664 c = (0.1, 0.1, 0.1) 4665 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4666 if vedo.plotter_instance.renderer.GetGradientBackground(): 4667 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4668 else: 4669 bgcol = vedo.plotter_instance.renderer.GetBackground() 4670 c = (0.9, 0.9, 0.9) 4671 if np.sum(bgcol) > 1.5: 4672 c = (0.1, 0.1, 0.1) 4673 4674 self.font(font).color(c).background(bg, alpha).bold(bold).italic(italic) 4675 self.pos(pos, justify).size(s).text(txt).line_spacing(1.2).line_offset(5) 4676 self.PickableOff()
Create a 2D text object.
All properties of the text, and the text itself, can be changed after creation (which is especially useful in loops).
Arguments:
pos : (str) text is placed in one of the 8 positions:
- bottom-left
- bottom-right
- top-left
- top-right
- bottom-middle
- middle-right
- middle-left
- top-middle
If a pair (x,y) is passed as input the 2D text is place at that position in the coordinate system of the 2D screen (with the origin sitting at the bottom left).
- s : (float) size of text
- bg : (color) background color
- alpha : (float) background opacity
- justify : (str) text justification
font : (str) built-in available fonts are:
- Antares
- Arial
- Bongas
- Calco
- Comae
- ComicMono
- Courier
- Glasgo
- Kanopus
- LogoType
- Normografo
- Quikhand
- SmartCouric
- Theemim
- Times
- VictorMono
- More fonts at: https://vedo.embl.es/fonts/
A path to a
.otf
or.ttf
font-file can also be supplied as input.
Examples:
557 @property 558 def mapper(self): 559 """Get the internal vtkMapper.""" 560 return self.GetMapper()
Get the internal vtkMapper.
4678 def pos(self, pos="top-left", justify=""): 4679 """ 4680 Set position of the text to draw. Keyword `pos` can be a string 4681 or 2D coordinates in the range [0,1], being (0,0) the bottom left corner. 4682 """ 4683 ajustify = "top-left" # autojustify 4684 if isinstance(pos, str): # corners 4685 ajustify = pos 4686 if "top" in pos: 4687 if "left" in pos: 4688 pos = (0.008, 0.994) 4689 elif "right" in pos: 4690 pos = (0.994, 0.994) 4691 elif "mid" in pos or "cent" in pos: 4692 pos = (0.5, 0.994) 4693 elif "bottom" in pos: 4694 if "left" in pos: 4695 pos = (0.008, 0.008) 4696 elif "right" in pos: 4697 pos = (0.994, 0.008) 4698 elif "mid" in pos or "cent" in pos: 4699 pos = (0.5, 0.008) 4700 elif "mid" in pos or "cent" in pos: 4701 if "left" in pos: 4702 pos = (0.008, 0.5) 4703 elif "right" in pos: 4704 pos = (0.994, 0.5) 4705 else: 4706 pos = (0.5, 0.5) 4707 4708 else: 4709 vedo.logger.warning(f"cannot understand text position {pos}") 4710 pos = (0.008, 0.994) 4711 ajustify = "top-left" 4712 4713 elif len(pos) != 2: 4714 vedo.logger.error("pos must be of length 2 or integer value or string") 4715 raise RuntimeError() 4716 4717 if not justify: 4718 justify = ajustify 4719 4720 self.properties.SetJustificationToLeft() 4721 if "top" in justify: 4722 self.properties.SetVerticalJustificationToTop() 4723 if "bottom" in justify: 4724 self.properties.SetVerticalJustificationToBottom() 4725 if "cent" in justify or "mid" in justify: 4726 self.properties.SetJustificationToCentered() 4727 if "left" in justify: 4728 self.properties.SetJustificationToLeft() 4729 if "right" in justify: 4730 self.properties.SetJustificationToRight() 4731 4732 self.SetPosition(pos) 4733 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.
4735 def text(self, txt=None): 4736 """Set/get the input text string.""" 4737 if txt is None: 4738 return self.mapper.GetInput() 4739 4740 if ":" in txt: 4741 for r in _reps: 4742 txt = txt.replace(r[0], r[1]) 4743 else: 4744 txt = str(txt) 4745 4746 self.mapper.SetInput(txt) 4747 return self
Set/get the input text string.
4755class CornerAnnotation(TextBase, vtki.vtkCornerAnnotation): 4756 # PROBABLY USELESS given that Text2D does pretty much the same ... 4757 """ 4758 Annotate the window corner with 2D text. 4759 4760 See `Text2D` description as the basic functionality is very similar. 4761 4762 The added value of this class is the possibility to manage with one single 4763 object the all corner annotations (instead of creating 4 `Text2D` instances). 4764 4765 Examples: 4766 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 4767 """ 4768 4769 def __init__(self, c=None) -> None: 4770 4771 super().__init__() 4772 4773 self.properties = self.GetTextProperty() 4774 4775 # automatic black or white 4776 if c is None: 4777 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4778 c = (0.9, 0.9, 0.9) 4779 if vedo.plotter_instance.renderer.GetGradientBackground(): 4780 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4781 else: 4782 bgcol = vedo.plotter_instance.renderer.GetBackground() 4783 if np.sum(bgcol) > 1.5: 4784 c = (0.1, 0.1, 0.1) 4785 else: 4786 c = (0.5, 0.5, 0.5) 4787 4788 self.SetNonlinearFontScaleFactor(1 / 2.75) 4789 self.PickableOff() 4790 self.properties.SetColor(get_color(c)) 4791 self.properties.SetBold(False) 4792 self.properties.SetItalic(False) 4793 4794 def size(self, s:float, linear=False) -> "CornerAnnotation": 4795 """ 4796 The font size is calculated as the largest possible value such that the annotations 4797 for the given viewport do not overlap. 4798 4799 This font size can be scaled non-linearly with the viewport size, to maintain an 4800 acceptable readable size at larger viewport sizes, without being too big. 4801 `f' = linearScale * pow(f,nonlinearScale)` 4802 """ 4803 if linear: 4804 self.SetLinearFontScaleFactor(s * 5.5) 4805 else: 4806 self.SetNonlinearFontScaleFactor(s / 2.75) 4807 return self 4808 4809 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4810 """Set text at the assigned position""" 4811 4812 if isinstance(pos, str): # corners 4813 if "top" in pos: 4814 if "left" in pos: pos = 2 4815 elif "right" in pos: pos = 3 4816 elif "mid" in pos or "cent" in pos: pos = 7 4817 elif "bottom" in pos: 4818 if "left" in pos: pos = 0 4819 elif "right" in pos: pos = 1 4820 elif "mid" in pos or "cent" in pos: pos = 4 4821 else: 4822 if "left" in pos: pos = 6 4823 elif "right" in pos: pos = 5 4824 else: pos = 2 4825 4826 if "\\" in repr(txt): 4827 for r in _reps: 4828 txt = txt.replace(r[0], r[1]) 4829 else: 4830 txt = str(txt) 4831 4832 self.SetText(pos, txt) 4833 return self 4834 4835 def clear(self) -> "CornerAnnotation": 4836 """Remove all text from all corners""" 4837 self.ClearAllTexts() 4838 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:
4769 def __init__(self, c=None) -> None: 4770 4771 super().__init__() 4772 4773 self.properties = self.GetTextProperty() 4774 4775 # automatic black or white 4776 if c is None: 4777 if vedo.plotter_instance and vedo.plotter_instance.renderer: 4778 c = (0.9, 0.9, 0.9) 4779 if vedo.plotter_instance.renderer.GetGradientBackground(): 4780 bgcol = vedo.plotter_instance.renderer.GetBackground2() 4781 else: 4782 bgcol = vedo.plotter_instance.renderer.GetBackground() 4783 if np.sum(bgcol) > 1.5: 4784 c = (0.1, 0.1, 0.1) 4785 else: 4786 c = (0.5, 0.5, 0.5) 4787 4788 self.SetNonlinearFontScaleFactor(1 / 2.75) 4789 self.PickableOff() 4790 self.properties.SetColor(get_color(c)) 4791 self.properties.SetBold(False) 4792 self.properties.SetItalic(False)
Do not instantiate this base class.
4794 def size(self, s:float, linear=False) -> "CornerAnnotation": 4795 """ 4796 The font size is calculated as the largest possible value such that the annotations 4797 for the given viewport do not overlap. 4798 4799 This font size can be scaled non-linearly with the viewport size, to maintain an 4800 acceptable readable size at larger viewport sizes, without being too big. 4801 `f' = linearScale * pow(f,nonlinearScale)` 4802 """ 4803 if linear: 4804 self.SetLinearFontScaleFactor(s * 5.5) 4805 else: 4806 self.SetNonlinearFontScaleFactor(s / 2.75) 4807 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)
4809 def text(self, txt: str, pos=2) -> "CornerAnnotation": 4810 """Set text at the assigned position""" 4811 4812 if isinstance(pos, str): # corners 4813 if "top" in pos: 4814 if "left" in pos: pos = 2 4815 elif "right" in pos: pos = 3 4816 elif "mid" in pos or "cent" in pos: pos = 7 4817 elif "bottom" in pos: 4818 if "left" in pos: pos = 0 4819 elif "right" in pos: pos = 1 4820 elif "mid" in pos or "cent" in pos: pos = 4 4821 else: 4822 if "left" in pos: pos = 6 4823 elif "right" in pos: pos = 5 4824 else: pos = 2 4825 4826 if "\\" in repr(txt): 4827 for r in _reps: 4828 txt = txt.replace(r[0], r[1]) 4829 else: 4830 txt = str(txt) 4831 4832 self.SetText(pos, txt) 4833 return self
Set text at the assigned position
4841class Latex(Image): 4842 """ 4843 Render Latex text and formulas. 4844 """ 4845 4846 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4847 """ 4848 Render Latex text and formulas. 4849 4850 Arguments: 4851 formula : (str) 4852 latex text string 4853 pos : (list) 4854 position coordinates in space 4855 bg : (color) 4856 background color box 4857 res : (int) 4858 dpi resolution 4859 usetex : (bool) 4860 use latex compiler of matplotlib if available 4861 4862 You can access the latex formula in `Latex.formula`. 4863 4864 Examples: 4865 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4866 4867 ![](https://vedo.embl.es/images/pyplot/latex.png) 4868 """ 4869 from tempfile import NamedTemporaryFile 4870 import matplotlib.pyplot as mpltib 4871 4872 def build_img_plt(formula, tfile): 4873 4874 mpltib.rc("text", usetex=usetex) 4875 4876 formula1 = "$" + formula + "$" 4877 mpltib.axis("off") 4878 col = get_color(c) 4879 if bg: 4880 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4881 else: 4882 bx = None 4883 mpltib.text( 4884 0.5, 4885 0.5, 4886 formula1, 4887 size=res, 4888 color=col, 4889 alpha=alpha, 4890 ha="center", 4891 va="center", 4892 bbox=bx, 4893 ) 4894 mpltib.savefig( 4895 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4896 ) 4897 mpltib.close() 4898 4899 if len(pos) == 2: 4900 pos = (pos[0], pos[1], 0) 4901 4902 tmp_file = NamedTemporaryFile(delete=True) 4903 tmp_file.name = tmp_file.name + ".png" 4904 4905 build_img_plt(formula, tmp_file.name) 4906 4907 super().__init__(tmp_file.name, channels=4) 4908 self.alpha(alpha) 4909 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4910 self.pos(pos) 4911 self.name = "Latex" 4912 self.formula = formula 4913 4914 # except: 4915 # printc("Error in Latex()\n", formula, c="r") 4916 # printc(" latex or dvipng not installed?", c="r") 4917 # printc(" Try: usetex=False", c="r") 4918 # printc(" Try: sudo apt install dvipng", c="r")
Render Latex text and formulas.
4846 def __init__(self, formula, pos=(0, 0, 0), s=1.0, bg=None, res=150, usetex=False, c="k", alpha=1.0) -> None: 4847 """ 4848 Render Latex text and formulas. 4849 4850 Arguments: 4851 formula : (str) 4852 latex text string 4853 pos : (list) 4854 position coordinates in space 4855 bg : (color) 4856 background color box 4857 res : (int) 4858 dpi resolution 4859 usetex : (bool) 4860 use latex compiler of matplotlib if available 4861 4862 You can access the latex formula in `Latex.formula`. 4863 4864 Examples: 4865 - [latex.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/latex.py) 4866 4867 ![](https://vedo.embl.es/images/pyplot/latex.png) 4868 """ 4869 from tempfile import NamedTemporaryFile 4870 import matplotlib.pyplot as mpltib 4871 4872 def build_img_plt(formula, tfile): 4873 4874 mpltib.rc("text", usetex=usetex) 4875 4876 formula1 = "$" + formula + "$" 4877 mpltib.axis("off") 4878 col = get_color(c) 4879 if bg: 4880 bx = dict(boxstyle="square", ec=col, fc=get_color(bg)) 4881 else: 4882 bx = None 4883 mpltib.text( 4884 0.5, 4885 0.5, 4886 formula1, 4887 size=res, 4888 color=col, 4889 alpha=alpha, 4890 ha="center", 4891 va="center", 4892 bbox=bx, 4893 ) 4894 mpltib.savefig( 4895 tfile, format="png", transparent=True, bbox_inches="tight", pad_inches=0 4896 ) 4897 mpltib.close() 4898 4899 if len(pos) == 2: 4900 pos = (pos[0], pos[1], 0) 4901 4902 tmp_file = NamedTemporaryFile(delete=True) 4903 tmp_file.name = tmp_file.name + ".png" 4904 4905 build_img_plt(formula, tmp_file.name) 4906 4907 super().__init__(tmp_file.name, channels=4) 4908 self.alpha(alpha) 4909 self.scale([0.25 / res * s, 0.25 / res * s, 0.25 / res * s]) 4910 self.pos(pos) 4911 self.name = "Latex" 4912 self.formula = formula 4913 4914 # except: 4915 # printc("Error in Latex()\n", formula, c="r") 4916 # printc(" latex or dvipng not installed?", c="r") 4917 # printc(" Try: usetex=False", c="r") 4918 # printc(" Try: sudo apt install dvipng", c="r")
Render Latex text and formulas.
Arguments:
- formula : (str) latex text string
- pos : (list) position coordinates in space
- bg : (color) background color box
- res : (int) dpi resolution
- usetex : (bool) use latex compiler of matplotlib if available
You can access the latex formula in Latex.formula
.
Examples:
153class Glyph(Mesh): 154 """ 155 At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with 156 various orientation options and coloring. 157 158 The input can also be a simple list of 2D or 3D coordinates. 159 Color can be specified as a colormap which maps the size of the orientation 160 vectors in `orientation_array`. 161 """ 162 163 def __init__( 164 self, 165 mesh, 166 glyph, 167 orientation_array=None, 168 scale_by_scalar=False, 169 scale_by_vector_size=False, 170 scale_by_vector_components=False, 171 color_by_scalar=False, 172 color_by_vector_size=False, 173 c="k8", 174 alpha=1.0, 175 ) -> None: 176 """ 177 Arguments: 178 orientation_array: (list, str, vtkArray) 179 list of vectors, `vtkArray` or name of an already existing pointdata array 180 scale_by_scalar : (bool) 181 glyph mesh is scaled by the active scalars 182 scale_by_vector_size : (bool) 183 glyph mesh is scaled by the size of the vectors 184 scale_by_vector_components : (bool) 185 glyph mesh is scaled by the 3 vectors components 186 color_by_scalar : (bool) 187 glyph mesh is colored based on the scalar value 188 color_by_vector_size : (bool) 189 glyph mesh is colored based on the vector size 190 191 Examples: 192 - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 193 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 194 195 ![](https://vedo.embl.es/images/basic/glyphs.png) 196 """ 197 if utils.is_sequence(mesh): 198 # create a cloud of points 199 poly = utils.buildPolyData(mesh) 200 else: 201 poly = mesh.dataset 202 203 cmap = "" 204 if isinstance(c, str) and c in cmaps_names: 205 cmap = c 206 c = None 207 elif utils.is_sequence(c): # user passing an array of point colors 208 ucols = vtki.vtkUnsignedCharArray() 209 ucols.SetNumberOfComponents(3) 210 ucols.SetName("GlyphRGB") 211 for col in c: 212 cl = get_color(col) 213 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 214 poly.GetPointData().AddArray(ucols) 215 poly.GetPointData().SetActiveScalars("GlyphRGB") 216 c = None 217 218 gly = vtki.vtkGlyph3D() 219 gly.GeneratePointIdsOn() 220 gly.SetInputData(poly) 221 try: 222 gly.SetSourceData(glyph) 223 except TypeError: 224 gly.SetSourceData(glyph.dataset) 225 226 if scale_by_scalar: 227 gly.SetScaleModeToScaleByScalar() 228 elif scale_by_vector_size: 229 gly.SetScaleModeToScaleByVector() 230 elif scale_by_vector_components: 231 gly.SetScaleModeToScaleByVectorComponents() 232 else: 233 gly.SetScaleModeToDataScalingOff() 234 235 if color_by_vector_size: 236 gly.SetVectorModeToUseVector() 237 gly.SetColorModeToColorByVector() 238 elif color_by_scalar: 239 gly.SetColorModeToColorByScalar() 240 else: 241 gly.SetColorModeToColorByScale() 242 243 if orientation_array is not None: 244 gly.OrientOn() 245 if isinstance(orientation_array, str): 246 if orientation_array.lower() == "normals": 247 gly.SetVectorModeToUseNormal() 248 else: # passing a name 249 poly.GetPointData().SetActiveVectors(orientation_array) 250 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 251 gly.SetVectorModeToUseVector() 252 elif utils.is_sequence(orientation_array): # passing a list 253 varr = vtki.vtkFloatArray() 254 varr.SetNumberOfComponents(3) 255 varr.SetName("glyph_vectors") 256 for v in orientation_array: 257 varr.InsertNextTuple(v) 258 poly.GetPointData().AddArray(varr) 259 poly.GetPointData().SetActiveVectors("glyph_vectors") 260 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 261 gly.SetVectorModeToUseVector() 262 263 gly.Update() 264 265 super().__init__(gly.GetOutput(), c, alpha) 266 self.flat() 267 268 if cmap: 269 self.cmap(cmap, "VectorMagnitude") 270 elif c is None: 271 self.pointdata.select("GlyphRGB") 272 273 self.name = "Glyph"
At each vertex of a mesh, another mesh, i.e. a "glyph", is shown with various orientation options and coloring.
The input can also be a simple list of 2D or 3D coordinates.
Color can be specified as a colormap which maps the size of the orientation
vectors in orientation_array
.
163 def __init__( 164 self, 165 mesh, 166 glyph, 167 orientation_array=None, 168 scale_by_scalar=False, 169 scale_by_vector_size=False, 170 scale_by_vector_components=False, 171 color_by_scalar=False, 172 color_by_vector_size=False, 173 c="k8", 174 alpha=1.0, 175 ) -> None: 176 """ 177 Arguments: 178 orientation_array: (list, str, vtkArray) 179 list of vectors, `vtkArray` or name of an already existing pointdata array 180 scale_by_scalar : (bool) 181 glyph mesh is scaled by the active scalars 182 scale_by_vector_size : (bool) 183 glyph mesh is scaled by the size of the vectors 184 scale_by_vector_components : (bool) 185 glyph mesh is scaled by the 3 vectors components 186 color_by_scalar : (bool) 187 glyph mesh is colored based on the scalar value 188 color_by_vector_size : (bool) 189 glyph mesh is colored based on the vector size 190 191 Examples: 192 - [glyphs1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs1.py) 193 - [glyphs2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/glyphs2.py) 194 195 ![](https://vedo.embl.es/images/basic/glyphs.png) 196 """ 197 if utils.is_sequence(mesh): 198 # create a cloud of points 199 poly = utils.buildPolyData(mesh) 200 else: 201 poly = mesh.dataset 202 203 cmap = "" 204 if isinstance(c, str) and c in cmaps_names: 205 cmap = c 206 c = None 207 elif utils.is_sequence(c): # user passing an array of point colors 208 ucols = vtki.vtkUnsignedCharArray() 209 ucols.SetNumberOfComponents(3) 210 ucols.SetName("GlyphRGB") 211 for col in c: 212 cl = get_color(col) 213 ucols.InsertNextTuple3(cl[0] * 255, cl[1] * 255, cl[2] * 255) 214 poly.GetPointData().AddArray(ucols) 215 poly.GetPointData().SetActiveScalars("GlyphRGB") 216 c = None 217 218 gly = vtki.vtkGlyph3D() 219 gly.GeneratePointIdsOn() 220 gly.SetInputData(poly) 221 try: 222 gly.SetSourceData(glyph) 223 except TypeError: 224 gly.SetSourceData(glyph.dataset) 225 226 if scale_by_scalar: 227 gly.SetScaleModeToScaleByScalar() 228 elif scale_by_vector_size: 229 gly.SetScaleModeToScaleByVector() 230 elif scale_by_vector_components: 231 gly.SetScaleModeToScaleByVectorComponents() 232 else: 233 gly.SetScaleModeToDataScalingOff() 234 235 if color_by_vector_size: 236 gly.SetVectorModeToUseVector() 237 gly.SetColorModeToColorByVector() 238 elif color_by_scalar: 239 gly.SetColorModeToColorByScalar() 240 else: 241 gly.SetColorModeToColorByScale() 242 243 if orientation_array is not None: 244 gly.OrientOn() 245 if isinstance(orientation_array, str): 246 if orientation_array.lower() == "normals": 247 gly.SetVectorModeToUseNormal() 248 else: # passing a name 249 poly.GetPointData().SetActiveVectors(orientation_array) 250 gly.SetInputArrayToProcess(0, 0, 0, 0, orientation_array) 251 gly.SetVectorModeToUseVector() 252 elif utils.is_sequence(orientation_array): # passing a list 253 varr = vtki.vtkFloatArray() 254 varr.SetNumberOfComponents(3) 255 varr.SetName("glyph_vectors") 256 for v in orientation_array: 257 varr.InsertNextTuple(v) 258 poly.GetPointData().AddArray(varr) 259 poly.GetPointData().SetActiveVectors("glyph_vectors") 260 gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") 261 gly.SetVectorModeToUseVector() 262 263 gly.Update() 264 265 super().__init__(gly.GetOutput(), c, alpha) 266 self.flat() 267 268 if cmap: 269 self.cmap(cmap, "VectorMagnitude") 270 elif c is None: 271 self.pointdata.select("GlyphRGB") 272 273 self.name = "Glyph"
Arguments:
- orientation_array: (list, str, vtkArray)
list of vectors,
vtkArray
or name of an already existing pointdata array - scale_by_scalar : (bool) glyph mesh is scaled by the active scalars
- scale_by_vector_size : (bool) glyph mesh is scaled by the size of the vectors
- scale_by_vector_components : (bool) glyph mesh is scaled by the 3 vectors components
- color_by_scalar : (bool) glyph mesh is colored based on the scalar value
- color_by_vector_size : (bool) glyph mesh is colored based on the vector size
Examples:
276class Tensors(Mesh): 277 """ 278 Geometric representation of tensors defined on a domain or set of points. 279 Tensors can be scaled and/or rotated according to the source at each input point. 280 Scaling and rotation is controlled by the eigenvalues/eigenvectors of the 281 symmetrical part of the tensor as follows: 282 283 For each tensor, the eigenvalues (and associated eigenvectors) are sorted 284 to determine the major, medium, and minor eigenvalues/eigenvectors. 285 The eigenvalue decomposition only makes sense for symmetric tensors, 286 hence the need to only consider the symmetric part of the tensor, 287 which is `1/2*(T+T.transposed())`. 288 """ 289 290 def __init__( 291 self, 292 domain, 293 source="ellipsoid", 294 use_eigenvalues=True, 295 is_symmetric=True, 296 three_axes=False, 297 scale=1.0, 298 max_scale=None, 299 length=None, 300 res=24, 301 c=None, 302 alpha=1.0, 303 ) -> None: 304 """ 305 Arguments: 306 source : (str, Mesh) 307 preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object. 308 use_eigenvalues : (bool) 309 color source glyph using the eigenvalues or by scalars 310 three_axes : (bool) 311 if `False` scale the source in the x-direction, 312 the medium in the y-direction, and the minor in the z-direction. 313 Then, the source is rotated so that the glyph's local x-axis lies 314 along the major eigenvector, y-axis along the medium eigenvector, 315 and z-axis along the minor. 316 317 If `True` three sources are produced, each of them oriented along an eigenvector 318 and scaled according to the corresponding eigenvector. 319 is_symmetric : (bool) 320 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 321 The x-axis of the source glyph will correspond to the eigenvector on output. 322 length : (float) 323 distance from the origin to the tip of the source glyph along the x-axis 324 scale : (float) 325 scaling factor of the source glyph. 326 max_scale : (float) 327 clamp scaling at this factor. 328 329 Examples: 330 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 331 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 332 - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py) 333 334 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 335 """ 336 if isinstance(source, Points): 337 src = source.dataset 338 else: # is string 339 if "ellip" in source: 340 src = vtki.new("SphereSource") 341 src.SetPhiResolution(res) 342 src.SetThetaResolution(res*2) 343 elif "cyl" in source: 344 src = vtki.new("CylinderSource") 345 src.SetResolution(res) 346 src.CappingOn() 347 elif source == "cube": 348 src = vtki.new("CubeSource") 349 else: 350 vedo.logger.error(f"Unknown source type {source}") 351 raise ValueError() 352 src.Update() 353 src = src.GetOutput() 354 355 tg = vtki.new("TensorGlyph") 356 if isinstance(domain, vtki.vtkPolyData): 357 tg.SetInputData(domain) 358 else: 359 tg.SetInputData(domain.dataset) 360 tg.SetSourceData(src) 361 362 if c is None: 363 tg.ColorGlyphsOn() 364 else: 365 tg.ColorGlyphsOff() 366 367 tg.SetSymmetric(int(is_symmetric)) 368 369 if length is not None: 370 tg.SetLength(length) 371 if use_eigenvalues: 372 tg.ExtractEigenvaluesOn() 373 tg.SetColorModeToEigenvalues() 374 else: 375 tg.SetColorModeToScalars() 376 377 tg.SetThreeGlyphs(three_axes) 378 tg.ScalingOn() 379 tg.SetScaleFactor(scale) 380 if max_scale is None: 381 tg.ClampScalingOn() 382 max_scale = scale * 10 383 tg.SetMaxScaleFactor(max_scale) 384 385 tg.Update() 386 tgn = vtki.new("PolyDataNormals") 387 tgn.ComputeCellNormalsOff() 388 tgn.SetInputData(tg.GetOutput()) 389 tgn.Update() 390 391 super().__init__(tgn.GetOutput(), c, alpha) 392 self.name = "Tensors"
Geometric representation of tensors defined on a domain or set of points. Tensors can be scaled and/or rotated according to the source at each input point. Scaling and rotation is controlled by the eigenvalues/eigenvectors of the symmetrical part of the tensor as follows:
For each tensor, the eigenvalues (and associated eigenvectors) are sorted
to determine the major, medium, and minor eigenvalues/eigenvectors.
The eigenvalue decomposition only makes sense for symmetric tensors,
hence the need to only consider the symmetric part of the tensor,
which is 1/2*(T+T.transposed())
.
290 def __init__( 291 self, 292 domain, 293 source="ellipsoid", 294 use_eigenvalues=True, 295 is_symmetric=True, 296 three_axes=False, 297 scale=1.0, 298 max_scale=None, 299 length=None, 300 res=24, 301 c=None, 302 alpha=1.0, 303 ) -> None: 304 """ 305 Arguments: 306 source : (str, Mesh) 307 preset types of source shapes is "ellipsoid", "cylinder", "cube" or a `Mesh` object. 308 use_eigenvalues : (bool) 309 color source glyph using the eigenvalues or by scalars 310 three_axes : (bool) 311 if `False` scale the source in the x-direction, 312 the medium in the y-direction, and the minor in the z-direction. 313 Then, the source is rotated so that the glyph's local x-axis lies 314 along the major eigenvector, y-axis along the medium eigenvector, 315 and z-axis along the minor. 316 317 If `True` three sources are produced, each of them oriented along an eigenvector 318 and scaled according to the corresponding eigenvector. 319 is_symmetric : (bool) 320 If `True` each source glyph is mirrored (2 or 6 glyphs will be produced). 321 The x-axis of the source glyph will correspond to the eigenvector on output. 322 length : (float) 323 distance from the origin to the tip of the source glyph along the x-axis 324 scale : (float) 325 scaling factor of the source glyph. 326 max_scale : (float) 327 clamp scaling at this factor. 328 329 Examples: 330 - [tensors.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tensors.py) 331 - [tensor_grid1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid1.py) 332 - [tensor_grid2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/tensor_grid2.py) 333 334 ![](https://vedo.embl.es/images/volumetric/tensor_grid.png) 335 """ 336 if isinstance(source, Points): 337 src = source.dataset 338 else: # is string 339 if "ellip" in source: 340 src = vtki.new("SphereSource") 341 src.SetPhiResolution(res) 342 src.SetThetaResolution(res*2) 343 elif "cyl" in source: 344 src = vtki.new("CylinderSource") 345 src.SetResolution(res) 346 src.CappingOn() 347 elif source == "cube": 348 src = vtki.new("CubeSource") 349 else: 350 vedo.logger.error(f"Unknown source type {source}") 351 raise ValueError() 352 src.Update() 353 src = src.GetOutput() 354 355 tg = vtki.new("TensorGlyph") 356 if isinstance(domain, vtki.vtkPolyData): 357 tg.SetInputData(domain) 358 else: 359 tg.SetInputData(domain.dataset) 360 tg.SetSourceData(src) 361 362 if c is None: 363 tg.ColorGlyphsOn() 364 else: 365 tg.ColorGlyphsOff() 366 367 tg.SetSymmetric(int(is_symmetric)) 368 369 if length is not None: 370 tg.SetLength(length) 371 if use_eigenvalues: 372 tg.ExtractEigenvaluesOn() 373 tg.SetColorModeToEigenvalues() 374 else: 375 tg.SetColorModeToScalars() 376 377 tg.SetThreeGlyphs(three_axes) 378 tg.ScalingOn() 379 tg.SetScaleFactor(scale) 380 if max_scale is None: 381 tg.ClampScalingOn() 382 max_scale = scale * 10 383 tg.SetMaxScaleFactor(max_scale) 384 385 tg.Update() 386 tgn = vtki.new("PolyDataNormals") 387 tgn.ComputeCellNormalsOff() 388 tgn.SetInputData(tg.GetOutput()) 389 tgn.Update() 390 391 super().__init__(tgn.GetOutput(), c, alpha) 392 self.name = "Tensors"
Arguments:
- source : (str, Mesh)
preset types of source shapes is "ellipsoid", "cylinder", "cube" or a
Mesh
object. - use_eigenvalues : (bool) color source glyph using the eigenvalues or by scalars
three_axes : (bool) if
False
scale the source in the x-direction, the medium in the y-direction, and the minor in the z-direction. Then, the source is rotated so that the glyph's local x-axis lies along the major eigenvector, y-axis along the medium eigenvector, and z-axis along the minor.If
True
three sources are produced, each of them oriented along an eigenvector and scaled according to the corresponding eigenvector.- is_symmetric : (bool)
If
True
each source glyph is mirrored (2 or 6 glyphs will be produced). The x-axis of the source glyph will correspond to the eigenvector on output. - length : (float) distance from the origin to the tip of the source glyph along the x-axis
- scale : (float) scaling factor of the source glyph.
- max_scale : (float) clamp scaling at this factor.
Examples:
3912class ParametricShape(Mesh): 3913 """ 3914 A set of built-in shapes mainly for illustration purposes. 3915 """ 3916 3917 def __init__(self, name, res=51, n=25, seed=1): 3918 """ 3919 A set of built-in shapes mainly for illustration purposes. 3920 3921 Name can be an integer or a string in this list: 3922 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3923 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3924 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3925 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3926 3927 Example: 3928 ```python 3929 from vedo import * 3930 settings.immediate_rendering = False 3931 plt = Plotter(N=18) 3932 for i in range(18): 3933 ps = ParametricShape(i).color(i) 3934 plt.at(i).show(ps, ps.name) 3935 plt.interactive().close() 3936 ``` 3937 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3938 """ 3939 3940 shapes = [ 3941 "Boy", 3942 "ConicSpiral", 3943 "CrossCap", 3944 "Enneper", 3945 "Figure8Klein", 3946 "Klein", 3947 "Dini", 3948 "Mobius", 3949 "RandomHills", 3950 "Roman", 3951 "SuperEllipsoid", 3952 "BohemianDome", 3953 "Bour", 3954 "CatalanMinimal", 3955 "Henneberg", 3956 "Kuen", 3957 "PluckerConoid", 3958 "Pseudosphere", 3959 ] 3960 3961 if isinstance(name, int): 3962 name = name % len(shapes) 3963 name = shapes[name] 3964 3965 if name == "Boy": 3966 ps = vtki.new("ParametricBoy") 3967 elif name == "ConicSpiral": 3968 ps = vtki.new("ParametricConicSpiral") 3969 elif name == "CrossCap": 3970 ps = vtki.new("ParametricCrossCap") 3971 elif name == "Dini": 3972 ps = vtki.new("ParametricDini") 3973 elif name == "Enneper": 3974 ps = vtki.new("ParametricEnneper") 3975 elif name == "Figure8Klein": 3976 ps = vtki.new("ParametricFigure8Klein") 3977 elif name == "Klein": 3978 ps = vtki.new("ParametricKlein") 3979 elif name == "Mobius": 3980 ps = vtki.new("ParametricMobius") 3981 ps.SetRadius(2.0) 3982 ps.SetMinimumV(-0.5) 3983 ps.SetMaximumV(0.5) 3984 elif name == "RandomHills": 3985 ps = vtki.new("ParametricRandomHills") 3986 ps.AllowRandomGenerationOn() 3987 ps.SetRandomSeed(seed) 3988 ps.SetNumberOfHills(n) 3989 elif name == "Roman": 3990 ps = vtki.new("ParametricRoman") 3991 elif name == "SuperEllipsoid": 3992 ps = vtki.new("ParametricSuperEllipsoid") 3993 ps.SetN1(0.5) 3994 ps.SetN2(0.4) 3995 elif name == "BohemianDome": 3996 ps = vtki.new("ParametricBohemianDome") 3997 ps.SetA(5.0) 3998 ps.SetB(1.0) 3999 ps.SetC(2.0) 4000 elif name == "Bour": 4001 ps = vtki.new("ParametricBour") 4002 elif name == "CatalanMinimal": 4003 ps = vtki.new("ParametricCatalanMinimal") 4004 elif name == "Henneberg": 4005 ps = vtki.new("ParametricHenneberg") 4006 elif name == "Kuen": 4007 ps = vtki.new("ParametricKuen") 4008 ps.SetDeltaV0(0.001) 4009 elif name == "PluckerConoid": 4010 ps = vtki.new("ParametricPluckerConoid") 4011 elif name == "Pseudosphere": 4012 ps = vtki.new("ParametricPseudosphere") 4013 else: 4014 vedo.logger.error(f"unknown ParametricShape {name}") 4015 return 4016 4017 pfs = vtki.new("ParametricFunctionSource") 4018 pfs.SetParametricFunction(ps) 4019 pfs.SetUResolution(res) 4020 pfs.SetVResolution(res) 4021 pfs.SetWResolution(res) 4022 pfs.SetScalarModeToZ() 4023 pfs.Update() 4024 4025 super().__init__(pfs.GetOutput()) 4026 4027 if name == "RandomHills": self.shift([0,-10,-2.25]) 4028 if name != 'Kuen': self.normalize() 4029 if name == 'Dini': self.scale(0.4) 4030 if name == 'Enneper': self.scale(0.4) 4031 if name == 'ConicSpiral': self.bc('tomato') 4032 self.name = name
A set of built-in shapes mainly for illustration purposes.
3917 def __init__(self, name, res=51, n=25, seed=1): 3918 """ 3919 A set of built-in shapes mainly for illustration purposes. 3920 3921 Name can be an integer or a string in this list: 3922 `['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', 3923 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', 3924 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', 3925 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere']`. 3926 3927 Example: 3928 ```python 3929 from vedo import * 3930 settings.immediate_rendering = False 3931 plt = Plotter(N=18) 3932 for i in range(18): 3933 ps = ParametricShape(i).color(i) 3934 plt.at(i).show(ps, ps.name) 3935 plt.interactive().close() 3936 ``` 3937 <img src="https://user-images.githubusercontent.com/32848391/69181075-bb6aae80-0b0e-11ea-92f7-d0cd3b9087bf.png" width="700"> 3938 """ 3939 3940 shapes = [ 3941 "Boy", 3942 "ConicSpiral", 3943 "CrossCap", 3944 "Enneper", 3945 "Figure8Klein", 3946 "Klein", 3947 "Dini", 3948 "Mobius", 3949 "RandomHills", 3950 "Roman", 3951 "SuperEllipsoid", 3952 "BohemianDome", 3953 "Bour", 3954 "CatalanMinimal", 3955 "Henneberg", 3956 "Kuen", 3957 "PluckerConoid", 3958 "Pseudosphere", 3959 ] 3960 3961 if isinstance(name, int): 3962 name = name % len(shapes) 3963 name = shapes[name] 3964 3965 if name == "Boy": 3966 ps = vtki.new("ParametricBoy") 3967 elif name == "ConicSpiral": 3968 ps = vtki.new("ParametricConicSpiral") 3969 elif name == "CrossCap": 3970 ps = vtki.new("ParametricCrossCap") 3971 elif name == "Dini": 3972 ps = vtki.new("ParametricDini") 3973 elif name == "Enneper": 3974 ps = vtki.new("ParametricEnneper") 3975 elif name == "Figure8Klein": 3976 ps = vtki.new("ParametricFigure8Klein") 3977 elif name == "Klein": 3978 ps = vtki.new("ParametricKlein") 3979 elif name == "Mobius": 3980 ps = vtki.new("ParametricMobius") 3981 ps.SetRadius(2.0) 3982 ps.SetMinimumV(-0.5) 3983 ps.SetMaximumV(0.5) 3984 elif name == "RandomHills": 3985 ps = vtki.new("ParametricRandomHills") 3986 ps.AllowRandomGenerationOn() 3987 ps.SetRandomSeed(seed) 3988 ps.SetNumberOfHills(n) 3989 elif name == "Roman": 3990 ps = vtki.new("ParametricRoman") 3991 elif name == "SuperEllipsoid": 3992 ps = vtki.new("ParametricSuperEllipsoid") 3993 ps.SetN1(0.5) 3994 ps.SetN2(0.4) 3995 elif name == "BohemianDome": 3996 ps = vtki.new("ParametricBohemianDome") 3997 ps.SetA(5.0) 3998 ps.SetB(1.0) 3999 ps.SetC(2.0) 4000 elif name == "Bour": 4001 ps = vtki.new("ParametricBour") 4002 elif name == "CatalanMinimal": 4003 ps = vtki.new("ParametricCatalanMinimal") 4004 elif name == "Henneberg": 4005 ps = vtki.new("ParametricHenneberg") 4006 elif name == "Kuen": 4007 ps = vtki.new("ParametricKuen") 4008 ps.SetDeltaV0(0.001) 4009 elif name == "PluckerConoid": 4010 ps = vtki.new("ParametricPluckerConoid") 4011 elif name == "Pseudosphere": 4012 ps = vtki.new("ParametricPseudosphere") 4013 else: 4014 vedo.logger.error(f"unknown ParametricShape {name}") 4015 return 4016 4017 pfs = vtki.new("ParametricFunctionSource") 4018 pfs.SetParametricFunction(ps) 4019 pfs.SetUResolution(res) 4020 pfs.SetVResolution(res) 4021 pfs.SetWResolution(res) 4022 pfs.SetScalarModeToZ() 4023 pfs.Update() 4024 4025 super().__init__(pfs.GetOutput()) 4026 4027 if name == "RandomHills": self.shift([0,-10,-2.25]) 4028 if name != 'Kuen': self.normalize() 4029 if name == 'Dini': self.scale(0.4) 4030 if name == 'Enneper': self.scale(0.4) 4031 if name == 'ConicSpiral': self.bc('tomato') 4032 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()
4921class ConvexHull(Mesh): 4922 """ 4923 Create the 2D/3D convex hull from a set of points. 4924 """ 4925 4926 def __init__(self, pts) -> None: 4927 """ 4928 Create the 2D/3D convex hull from a set of input points or input Mesh. 4929 4930 Examples: 4931 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4932 4933 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4934 """ 4935 if utils.is_sequence(pts): 4936 pts = utils.make3d(pts).astype(float) 4937 mesh = Points(pts) 4938 else: 4939 mesh = pts 4940 apoly = mesh.clean().dataset 4941 4942 # Create the convex hull of the pointcloud 4943 z0, z1 = mesh.zbounds() 4944 d = mesh.diagonal_size() 4945 if (z1 - z0) / d > 0.0001: 4946 delaunay = vtki.new("Delaunay3D") 4947 delaunay.SetInputData(apoly) 4948 delaunay.Update() 4949 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4950 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4951 surfaceFilter.Update() 4952 out = surfaceFilter.GetOutput() 4953 else: 4954 delaunay = vtki.new("Delaunay2D") 4955 delaunay.SetInputData(apoly) 4956 delaunay.Update() 4957 fe = vtki.new("FeatureEdges") 4958 fe.SetInputConnection(delaunay.GetOutputPort()) 4959 fe.BoundaryEdgesOn() 4960 fe.Update() 4961 out = fe.GetOutput() 4962 4963 super().__init__(out, c=mesh.color(), alpha=0.75) 4964 self.flat() 4965 self.name = "ConvexHull"
Create the 2D/3D convex hull from a set of points.
4926 def __init__(self, pts) -> None: 4927 """ 4928 Create the 2D/3D convex hull from a set of input points or input Mesh. 4929 4930 Examples: 4931 - [convex_hull.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/convex_hull.py) 4932 4933 ![](https://vedo.embl.es/images/advanced/convexHull.png) 4934 """ 4935 if utils.is_sequence(pts): 4936 pts = utils.make3d(pts).astype(float) 4937 mesh = Points(pts) 4938 else: 4939 mesh = pts 4940 apoly = mesh.clean().dataset 4941 4942 # Create the convex hull of the pointcloud 4943 z0, z1 = mesh.zbounds() 4944 d = mesh.diagonal_size() 4945 if (z1 - z0) / d > 0.0001: 4946 delaunay = vtki.new("Delaunay3D") 4947 delaunay.SetInputData(apoly) 4948 delaunay.Update() 4949 surfaceFilter = vtki.new("DataSetSurfaceFilter") 4950 surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) 4951 surfaceFilter.Update() 4952 out = surfaceFilter.GetOutput() 4953 else: 4954 delaunay = vtki.new("Delaunay2D") 4955 delaunay.SetInputData(apoly) 4956 delaunay.Update() 4957 fe = vtki.new("FeatureEdges") 4958 fe.SetInputConnection(delaunay.GetOutputPort()) 4959 fe.BoundaryEdgesOn() 4960 fe.Update() 4961 out = fe.GetOutput() 4962 4963 super().__init__(out, c=mesh.color(), alpha=0.75) 4964 self.flat() 4965 self.name = "ConvexHull"
4968def VedoLogo(distance=0.0, c=None, bc="t", version=False, frame=True) -> "vedo.Assembly": 4969 """ 4970 Create the 3D vedo logo. 4971 4972 Arguments: 4973 distance : (float) 4974 send back logo by this distance from camera 4975 version : (bool) 4976 add version text to the right end of the logo 4977 bc : (color) 4978 text back face color 4979 """ 4980 if c is None: 4981 c = (0, 0, 0) 4982 if vedo.plotter_instance: 4983 if sum(get_color(vedo.plotter_instance.backgrcol)) > 1.5: 4984 c = [0, 0, 0] 4985 else: 4986 c = "linen" 4987 4988 font = "Comae" 4989 vlogo = Text3D("vэdo", font=font, s=1350, depth=0.2, c=c, hspacing=0.8) 4990 vlogo.scale([1, 0.95, 1]).x(-2525).pickable(False).bc(bc) 4991 vlogo.properties.LightingOn() 4992 4993 vr, rul = None, None 4994 if version: 4995 vr = Text3D( 4996 vedo.__version__, font=font, s=165, depth=0.2, c=c, hspacing=1 4997 ).scale([1, 0.7, 1]) 4998 vr.rotate_z(90).pos(2450, 50, 80) 4999 vr.bc(bc).pickable(False) 5000 elif frame: 5001 rul = vedo.RulerAxes( 5002 (-2600, 2110, 0, 1650, 0, 0), 5003 xlabel="European Molecular Biology Laboratory", 5004 ylabel=vedo.__version__, 5005 font=font, 5006 xpadding=0.09, 5007 ypadding=0.04, 5008 ) 5009 fakept = vedo.Point((0, 500, distance * 1725), alpha=0, c=c, r=1).pickable(0) 5010 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