vedo.mesh
Submodule to work with polygonal meshes
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import os 4import numpy as np 5 6try: 7 import vedo.vtkclasses as vtk 8except ImportError: 9 import vtkmodules.all as vtk 10 11import vedo 12from vedo.colors import color_map 13from vedo.colors import get_color 14from vedo.pointcloud import Points 15from vedo.utils import buildPolyData, is_sequence, mag, mag2, precision 16from vedo.utils import numpy2vtk, vtk2numpy, OperationNode 17 18__docformat__ = "google" 19 20__doc__ = """ 21Submodule to work with polygonal meshes 22 23![](https://vedo.embl.es/images/advanced/mesh_smoother2.png) 24""" 25 26__all__ = ["Mesh"] 27 28 29#################################################### 30class Mesh(Points): 31 """ 32 Build an instance of object `Mesh` derived from `vedo.PointCloud`. 33 """ 34 35 def __init__(self, inputobj=None, c=None, alpha=1): 36 """ 37 Input can be a list of vertices and their connectivity (faces of the polygonal mesh), 38 or directly a `vtkPolydata` object. 39 For point clouds - e.i. no faces - just substitute the `faces` list with `None`. 40 41 Example: 42 `Mesh( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )` 43 44 Arguments: 45 c : (color) 46 color in RGB format, hex, symbol or name 47 alpha : (float) 48 mesh opacity [0,1] 49 50 Examples: 51 - [buildmesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buildmesh.py) 52 (and many others!) 53 54 ![](https://vedo.embl.es/images/basic/buildmesh.png) 55 """ 56 Points.__init__(self) 57 58 self.line_locator = None 59 60 self._mapper.SetInterpolateScalarsBeforeMapping( 61 vedo.settings.interpolate_scalars_before_mapping 62 ) 63 64 if vedo.settings.use_polygon_offset: 65 self._mapper.SetResolveCoincidentTopologyToPolygonOffset() 66 pof, pou = (vedo.settings.polygon_offset_factor, vedo.settings.polygon_offset_units) 67 self._mapper.SetResolveCoincidentTopologyPolygonOffsetParameters(pof, pou) 68 69 inputtype = str(type(inputobj)) 70 71 if inputobj is None: 72 pass 73 74 elif isinstance(inputobj, (Mesh, vtk.vtkActor)): 75 polyCopy = vtk.vtkPolyData() 76 polyCopy.DeepCopy(inputobj.GetMapper().GetInput()) 77 self._data = polyCopy 78 self._mapper.SetInputData(polyCopy) 79 self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility()) 80 pr = vtk.vtkProperty() 81 pr.DeepCopy(inputobj.GetProperty()) 82 self.SetProperty(pr) 83 self.property = pr 84 85 elif isinstance(inputobj, vtk.vtkPolyData): 86 if inputobj.GetNumberOfCells() == 0: 87 carr = vtk.vtkCellArray() 88 for i in range(inputobj.GetNumberOfPoints()): 89 carr.InsertNextCell(1) 90 carr.InsertCellPoint(i) 91 inputobj.SetVerts(carr) 92 self._data = inputobj # cache vtkPolyData and mapper for speed 93 94 elif isinstance(inputobj, (vtk.vtkStructuredGrid, vtk.vtkRectilinearGrid)): 95 gf = vtk.vtkGeometryFilter() 96 gf.SetInputData(inputobj) 97 gf.Update() 98 self._data = gf.GetOutput() 99 100 elif "trimesh" in inputtype: 101 tact = vedo.utils.trimesh2vedo(inputobj) 102 self._data = tact.polydata() 103 104 elif "meshio" in inputtype: 105 if len(inputobj.cells) > 0: 106 mcells = [] 107 for cellblock in inputobj.cells: 108 if cellblock.type in ("triangle", "quad"): 109 mcells += cellblock.data.tolist() 110 self._data = buildPolyData(inputobj.points, mcells) 111 else: 112 self._data = buildPolyData(inputobj.points, None) 113 # add arrays: 114 try: 115 if len(inputobj.point_data) > 0: 116 for k in inputobj.point_data.keys(): 117 vdata = numpy2vtk(inputobj.point_data[k]) 118 vdata.SetName(str(k)) 119 self._data.GetPointData().AddArray(vdata) 120 except AssertionError: 121 print("Could not add meshio point data, skip.") 122 # try: 123 # if len(inputobj.cell_data): 124 # for k in inputobj.cell_data.keys(): 125 # #print(inputobj.cell_data) 126 # exit() 127 # vdata = numpy2vtk(inputobj.cell_data[k]) 128 # vdata.SetName(str(k)) 129 # self._data.GetCellData().AddArray(vdata) 130 # except AssertionError: 131 # print("Could not add meshio cell data, skip.") 132 133 elif "meshlab" in inputtype: 134 self._data = vedo.utils.meshlab2vedo(inputobj) 135 136 elif is_sequence(inputobj): 137 ninp = len(inputobj) 138 if ninp == 0: 139 self._data = vtk.vtkPolyData() 140 elif ninp == 2: # assume [vertices, faces] 141 self._data = buildPolyData(inputobj[0], inputobj[1]) 142 else: # assume [vertices] or vertices 143 self._data = buildPolyData(inputobj, None) 144 145 elif hasattr(inputobj, "GetOutput"): # passing vtk object 146 if hasattr(inputobj, "Update"): 147 inputobj.Update() 148 if isinstance(inputobj.GetOutput(), vtk.vtkPolyData): 149 self._data = inputobj.GetOutput() 150 else: 151 gf = vtk.vtkGeometryFilter() 152 gf.SetInputData(inputobj.GetOutput()) 153 gf.Update() 154 self._data = gf.GetOutput() 155 156 elif isinstance(inputobj, str): 157 dataset = vedo.file_io.load(inputobj) 158 self.filename = inputobj 159 if "TetMesh" in str(type(dataset)): 160 self._data = dataset.tomesh().polydata(False) 161 else: 162 self._data = dataset.polydata(False) 163 164 else: 165 try: 166 gf = vtk.vtkGeometryFilter() 167 gf.SetInputData(inputobj) 168 gf.Update() 169 self._data = gf.GetOutput() 170 except: 171 vedo.logger.error(f"cannot build mesh from type {inputtype}") 172 raise RuntimeError() 173 174 self._mapper.SetInputData(self._data) 175 176 self.property = self.GetProperty() 177 self.property.SetInterpolationToPhong() 178 179 # set the color by c or by scalar 180 if self._data: 181 182 arrexists = False 183 184 if c is None: 185 ptdata = self._data.GetPointData() 186 cldata = self._data.GetCellData() 187 exclude = ["normals", "tcoord"] 188 189 if cldata.GetNumberOfArrays(): 190 for i in range(cldata.GetNumberOfArrays()): 191 iarr = cldata.GetArray(i) 192 if iarr: 193 icname = iarr.GetName() 194 if icname and all(s not in icname.lower() for s in exclude): 195 cldata.SetActiveScalars(icname) 196 self._mapper.ScalarVisibilityOn() 197 self._mapper.SetScalarModeToUseCellData() 198 self._mapper.SetScalarRange(iarr.GetRange()) 199 arrexists = True 200 break # stop at first good one 201 202 # point come after so it has priority 203 if ptdata.GetNumberOfArrays(): 204 for i in range(ptdata.GetNumberOfArrays()): 205 iarr = ptdata.GetArray(i) 206 if iarr: 207 ipname = iarr.GetName() 208 if ipname and all(s not in ipname.lower() for s in exclude): 209 ptdata.SetActiveScalars(ipname) 210 self._mapper.ScalarVisibilityOn() 211 self._mapper.SetScalarModeToUsePointData() 212 self._mapper.SetScalarRange(iarr.GetRange()) 213 arrexists = True 214 break # stop at first good one 215 216 if not arrexists: 217 if c is None: 218 c = "gold" 219 c = get_color(c) 220 elif isinstance(c, float) and c <= 1: 221 c = color_map(c, "rainbow", 0, 1) 222 else: 223 c = get_color(c) 224 self.property.SetColor(c) 225 self.property.SetAmbient(0.1) 226 self.property.SetDiffuse(1) 227 self.property.SetSpecular(0.05) 228 self.property.SetSpecularPower(5) 229 self._mapper.ScalarVisibilityOff() 230 231 if alpha is not None: 232 self.property.SetOpacity(alpha) 233 234 n = self._data.GetNumberOfPoints() 235 self.pipeline = OperationNode(self, comment=f"#pts {n}") 236 237 def _repr_html_(self): 238 """ 239 HTML representation of the Mesh object for Jupyter Notebooks. 240 241 Returns: 242 HTML text with the image and some properties. 243 """ 244 import io 245 import base64 246 from PIL import Image 247 248 library_name = "vedo.mesh.Mesh" 249 help_url = "https://vedo.embl.es/docs/vedo/mesh.html" 250 251 arr = self.thumbnail() 252 im = Image.fromarray(arr) 253 buffered = io.BytesIO() 254 im.save(buffered, format="PNG", quality=100) 255 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 256 url = "data:image/png;base64," + encoded 257 image = f"<img src='{url}'></img>" 258 259 bounds = "<br/>".join( 260 [ 261 precision(min_x, 4) + " ... " + precision(max_x, 4) 262 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 263 ] 264 ) 265 average_size = "{size:.3f}".format(size=self.average_size()) 266 267 help_text = "" 268 if self.name: 269 help_text += f"<b> {self.name}:   </b>" 270 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 271 if self.filename: 272 dots = "" 273 if len(self.filename) > 30: 274 dots = "..." 275 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 276 277 pdata = "" 278 if self._data.GetPointData().GetScalars(): 279 if self._data.GetPointData().GetScalars().GetName(): 280 name = self._data.GetPointData().GetScalars().GetName() 281 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 282 283 cdata = "" 284 if self._data.GetCellData().GetScalars(): 285 if self._data.GetCellData().GetScalars().GetName(): 286 name = self._data.GetCellData().GetScalars().GetName() 287 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 288 289 allt = [ 290 "<table>", 291 "<tr>", 292 "<td>", 293 image, 294 "</td>", 295 "<td style='text-align: center; vertical-align: center;'><br/>", 296 help_text, 297 "<table>", 298 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 299 "<tr><td><b> center of mass </b></td><td>" 300 + precision(self.center_of_mass(), 3) 301 + "</td></tr>", 302 "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>", 303 "<tr><td><b> nr. points / faces </b></td><td>" 304 + str(self.npoints) 305 + " / " 306 + str(self.ncells) 307 + "</td></tr>", 308 pdata, 309 cdata, 310 "</table>", 311 "</table>", 312 ] 313 return "\n".join(allt) 314 315 def faces(self): 316 """ 317 Get cell polygonal connectivity ids as a python `list`. 318 The output format is: `[[id0 ... idn], [id0 ... idm], etc]`. 319 """ 320 arr1d = vtk2numpy(self._data.GetPolys().GetData()) 321 if arr1d is None: 322 return [] 323 324 # Get cell connettivity ids as a 1D array. vtk format is: 325 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 326 if len(arr1d) == 0: 327 arr1d = vtk2numpy(self._data.GetStrips().GetData()) 328 if arr1d is None: 329 return [] 330 331 i = 0 332 conn = [] 333 n = len(arr1d) 334 if n: 335 while True: 336 cell = [arr1d[i + k] for k in range(1, arr1d[i] + 1)] 337 conn.append(cell) 338 i += arr1d[i] + 1 339 if i >= n: 340 break 341 return conn # cannot always make a numpy array of it! 342 343 def cells(self): 344 """Alias for `faces()`.""" 345 return self.faces() 346 347 def lines(self, flat=False): 348 """ 349 Get lines connectivity ids as a numpy array. 350 Default format is `[[id0,id1], [id3,id4], ...]` 351 352 Arguments: 353 flat : (bool) 354 return a 1D numpy array as e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...] 355 """ 356 # Get cell connettivity ids as a 1D array. The vtk format is: 357 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 358 arr1d = vtk2numpy(self.polydata(False).GetLines().GetData()) 359 360 if arr1d is None: 361 return [] 362 363 if flat: 364 return arr1d 365 366 i = 0 367 conn = [] 368 n = len(arr1d) 369 for _ in range(n): 370 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 371 conn.append(cell) 372 i += arr1d[i] + 1 373 if i >= n: 374 break 375 376 return conn # cannot always make a numpy array of it! 377 378 def edges(self): 379 """Return an array containing the edges connectivity.""" 380 extractEdges = vtk.vtkExtractEdges() 381 extractEdges.SetInputData(self._data) 382 # eed.UseAllPointsOn() 383 extractEdges.Update() 384 lpoly = extractEdges.GetOutput() 385 386 arr1d = vtk2numpy(lpoly.GetLines().GetData()) 387 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 388 389 i = 0 390 conn = [] 391 n = len(arr1d) 392 for _ in range(n): 393 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 394 conn.append(cell) 395 i += arr1d[i] + 1 396 if i >= n: 397 break 398 return conn # cannot always make a numpy array of it! 399 400 def texture( 401 self, 402 tname, 403 tcoords=None, 404 interpolate=True, 405 repeat=True, 406 edge_clamp=False, 407 scale=None, 408 ushift=None, 409 vshift=None, 410 seam_threshold=None, 411 ): 412 """ 413 Assign a texture to mesh from image file or predefined texture `tname`. 414 If tname is set to `None` texture is disabled. 415 Input tname can also be an array or a `vtkTexture`. 416 417 Arguments: 418 tname : (numpy.array, str, Picture, vtkTexture, None) 419 the input texture to be applied. Can be a numpy array, a path to an image file, 420 a vedo Picture. The None value disables texture. 421 tcoords : (numpy.array, str) 422 this is the (u,v) texture coordinate array. Can also be a string of an existing array 423 in the mesh. 424 interpolate : (bool) 425 turn on/off linear interpolation of the texture map when rendering. 426 repeat : (bool) 427 repeat of the texture when tcoords extend beyond the [0,1] range. 428 edge_clamp : (bool) 429 turn on/off the clamping of the texture map when 430 the texture coords extend beyond the [0,1] range. 431 Only used when repeat is False, and edge clamping is supported by the graphics card. 432 scale : (bool) 433 scale the texture image by this factor 434 ushift : (bool) 435 shift u-coordinates of texture by this amount 436 vshift : (bool) 437 shift v-coordinates of texture by this amount 438 seam_threshold : (float) 439 try to seal seams in texture by collapsing triangles 440 (test values around 1.0, lower values = stronger collapse) 441 442 Examples: 443 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 444 445 ![](https://vedo.embl.es/images/basic/texturecubes.png) 446 """ 447 pd = self.polydata(False) 448 outimg = None 449 450 if tname is None: # disable texture 451 pd.GetPointData().SetTCoords(None) 452 pd.GetPointData().Modified() 453 return self ###################################### 454 455 if isinstance(tname, vtk.vtkTexture): 456 tu = tname 457 458 elif isinstance(tname, vedo.Picture): 459 tu = vtk.vtkTexture() 460 outimg = tname.inputdata() 461 462 elif is_sequence(tname): 463 tu = vtk.vtkTexture() 464 outimg = vedo.picture._get_img(tname) 465 466 elif isinstance(tname, str): 467 tu = vtk.vtkTexture() 468 469 if "https://" in tname: 470 try: 471 tname = vedo.file_io.download(tname, verbose=False) 472 except: 473 vedo.logger.error(f"texture {tname} could not be downloaded") 474 return self 475 476 fn = tname + ".jpg" 477 if os.path.exists(tname): 478 fn = tname 479 else: 480 vedo.logger.error(f"texture file {tname} does not exist") 481 return self 482 483 fnl = fn.lower() 484 if ".jpg" in fnl or ".jpeg" in fnl: 485 reader = vtk.vtkJPEGReader() 486 elif ".png" in fnl: 487 reader = vtk.vtkPNGReader() 488 elif ".bmp" in fnl: 489 reader = vtk.vtkBMPReader() 490 else: 491 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 492 return self 493 reader.SetFileName(fn) 494 reader.Update() 495 outimg = reader.GetOutput() 496 497 else: 498 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 499 return self 500 501 if tcoords is not None: 502 503 if isinstance(tcoords, str): 504 505 vtarr = pd.GetPointData().GetArray(tcoords) 506 507 else: 508 509 tcoords = np.asarray(tcoords) 510 if tcoords.ndim != 2: 511 vedo.logger.error("tcoords must be a 2-dimensional array") 512 return self 513 if tcoords.shape[0] != pd.GetNumberOfPoints(): 514 vedo.logger.error("nr of texture coords must match nr of points") 515 return self 516 if tcoords.shape[1] != 2: 517 vedo.logger.error("tcoords texture vector must have 2 components") 518 vtarr = numpy2vtk(tcoords) 519 vtarr.SetName("TCoordinates") 520 521 pd.GetPointData().SetTCoords(vtarr) 522 pd.GetPointData().Modified() 523 524 elif not pd.GetPointData().GetTCoords(): 525 526 # TCoords still void.. 527 # check that there are no texture-like arrays: 528 names = self.pointdata.keys() 529 candidate_arr = "" 530 for name in names: 531 vtarr = pd.GetPointData().GetArray(name) 532 if vtarr.GetNumberOfComponents() != 2: 533 continue 534 t0, t1 = vtarr.GetRange() 535 if t0 >= 0 and t1 <= 1: 536 candidate_arr = name 537 538 if candidate_arr: 539 540 vtarr = pd.GetPointData().GetArray(candidate_arr) 541 pd.GetPointData().SetTCoords(vtarr) 542 pd.GetPointData().Modified() 543 544 else: 545 # last resource is automatic mapping 546 tmapper = vtk.vtkTextureMapToPlane() 547 tmapper.AutomaticPlaneGenerationOn() 548 tmapper.SetInputData(pd) 549 tmapper.Update() 550 tc = tmapper.GetOutput().GetPointData().GetTCoords() 551 if scale or ushift or vshift: 552 ntc = vtk2numpy(tc) 553 if scale: 554 ntc *= scale 555 if ushift: 556 ntc[:, 0] += ushift 557 if vshift: 558 ntc[:, 1] += vshift 559 tc = numpy2vtk(tc) 560 pd.GetPointData().SetTCoords(tc) 561 pd.GetPointData().Modified() 562 563 if outimg: 564 tu.SetInputData(outimg) 565 tu.SetInterpolate(interpolate) 566 tu.SetRepeat(repeat) 567 tu.SetEdgeClamp(edge_clamp) 568 569 self.property.SetColor(1, 1, 1) 570 self._mapper.ScalarVisibilityOff() 571 self.SetTexture(tu) 572 573 if seam_threshold is not None: 574 tname = self._data.GetPointData().GetTCoords().GetName() 575 grad = self.gradient(tname) 576 ugrad, vgrad = np.split(grad, 2, axis=1) 577 ugradm, vgradm = vedo.utils.mag2(ugrad), vedo.utils.mag2(vgrad) 578 gradm = np.log(ugradm + vgradm) 579 largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 580 uvmap = self.pointdata[tname] 581 # collapse triangles that have large gradient 582 new_points = self.points(transformed=False) 583 for f in self.faces(): 584 if np.isin(f, largegrad_ids).all(): 585 id1, id2, id3 = f 586 uv1, uv2, uv3 = uvmap[f] 587 d12 = vedo.mag2(uv1 - uv2) 588 d23 = vedo.mag2(uv2 - uv3) 589 d31 = vedo.mag2(uv3 - uv1) 590 idm = np.argmin([d12, d23, d31]) 591 if idm == 0: 592 new_points[id1] = new_points[id3] 593 new_points[id2] = new_points[id3] 594 elif idm == 1: 595 new_points[id2] = new_points[id1] 596 new_points[id3] = new_points[id1] 597 self.points(new_points) 598 599 self.Modified() 600 return self 601 602 def compute_normals(self, points=True, cells=True, feature_angle=None, consistency=True): 603 """ 604 Compute cell and vertex normals for the mesh. 605 606 Arguments: 607 points : (bool) 608 do the computation for the vertices too 609 cells : (bool) 610 do the computation for the cells too 611 feature_angle : (float) 612 specify the angle that defines a sharp edge. 613 If the difference in angle across neighboring polygons is greater than this value, 614 the shared edge is considered "sharp" and it is split. 615 consistency : (bool) 616 turn on/off the enforcement of consistent polygon ordering. 617 618 .. warning:: 619 If feature_angle is set to a float the Mesh can be modified, and it 620 can have a different nr. of vertices from the original. 621 """ 622 poly = self.polydata(False) 623 pdnorm = vtk.vtkPolyDataNormals() 624 pdnorm.SetInputData(poly) 625 pdnorm.SetComputePointNormals(points) 626 pdnorm.SetComputeCellNormals(cells) 627 pdnorm.SetConsistency(consistency) 628 pdnorm.FlipNormalsOff() 629 if feature_angle: 630 pdnorm.SetSplitting(True) 631 pdnorm.SetFeatureAngle(feature_angle) 632 else: 633 pdnorm.SetSplitting(False) 634 # print(pdnorm.GetNonManifoldTraversal()) 635 pdnorm.Update() 636 return self._update(pdnorm.GetOutput()) 637 638 def reverse(self, cells=True, normals=False): 639 """ 640 Reverse the order of polygonal cells 641 and/or reverse the direction of point and cell normals. 642 Two flags are used to control these operations: 643 644 - `cells=True` reverses the order of the indices in the cell connectivity list. 645 If cell is a list of IDs only those cells will be reversed. 646 647 - `normals=True` reverses the normals by multiplying the normal vector by -1 648 (both point and cell normals, if present). 649 """ 650 poly = self.polydata(False) 651 652 if is_sequence(cells): 653 for cell in cells: 654 poly.ReverseCell(cell) 655 poly.GetCellData().Modified() 656 return self ############## 657 658 rev = vtk.vtkReverseSense() 659 if cells: 660 rev.ReverseCellsOn() 661 else: 662 rev.ReverseCellsOff() 663 if normals: 664 rev.ReverseNormalsOn() 665 else: 666 rev.ReverseNormalsOff() 667 rev.SetInputData(poly) 668 rev.Update() 669 out = self._update(rev.GetOutput()) 670 out.pipeline = OperationNode("reverse", parents=[self]) 671 return out 672 673 def wireframe(self, value=True): 674 """Set mesh's representation as wireframe or solid surface.""" 675 if value: 676 self.property.SetRepresentationToWireframe() 677 else: 678 self.property.SetRepresentationToSurface() 679 return self 680 681 def flat(self): 682 """Set surface interpolation to flat. 683 684 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 685 """ 686 self.property.SetInterpolationToFlat() 687 return self 688 689 def phong(self): 690 """Set surface interpolation to "phong".""" 691 self.property.SetInterpolationToPhong() 692 return self 693 694 def backface_culling(self, value=True): 695 """Set culling of polygons based on orientation of normal with respect to camera.""" 696 self.property.SetBackfaceCulling(value) 697 return self 698 699 def render_lines_as_tubes(self, value=True): 700 """Wrap a fake tube around a simple line for visualization""" 701 self.property.SetRenderLinesAsTubes(value) 702 return self 703 704 def frontface_culling(self, value=True): 705 """Set culling of polygons based on orientation of normal with respect to camera.""" 706 self.property.SetFrontfaceCulling(value) 707 return self 708 709 def backcolor(self, bc=None): 710 """ 711 Set/get mesh's backface color. 712 """ 713 backProp = self.GetBackfaceProperty() 714 715 if bc is None: 716 if backProp: 717 return backProp.GetDiffuseColor() 718 return self 719 720 if self.property.GetOpacity() < 1: 721 return self 722 723 if not backProp: 724 backProp = vtk.vtkProperty() 725 726 backProp.SetDiffuseColor(get_color(bc)) 727 backProp.SetOpacity(self.property.GetOpacity()) 728 self.SetBackfaceProperty(backProp) 729 self._mapper.ScalarVisibilityOff() 730 return self 731 732 def bc(self, backcolor=False): 733 """Shortcut for `mesh.backcolor()`.""" 734 return self.backcolor(backcolor) 735 736 def linewidth(self, lw=None): 737 """Set/get width of mesh edges. Same as `lw()`.""" 738 if lw is not None: 739 if lw == 0: 740 self.property.EdgeVisibilityOff() 741 self.property.SetRepresentationToSurface() 742 return self 743 self.property.EdgeVisibilityOn() 744 self.property.SetLineWidth(lw) 745 else: 746 return self.property.GetLineWidth() 747 return self 748 749 def lw(self, linewidth=None): 750 """Set/get width of mesh edges. Same as `linewidth()`.""" 751 return self.linewidth(linewidth) 752 753 def linecolor(self, lc=None): 754 """Set/get color of mesh edges. Same as `lc()`.""" 755 if lc is None: 756 return self.property.GetEdgeColor() 757 self.property.EdgeVisibilityOn() 758 self.property.SetEdgeColor(get_color(lc)) 759 return self 760 761 def lc(self, linecolor=None): 762 """Set/get color of mesh edges. Same as `linecolor()`.""" 763 return self.linecolor(linecolor) 764 765 def volume(self): 766 """Get/set the volume occupied by mesh.""" 767 mass = vtk.vtkMassProperties() 768 mass.SetGlobalWarningDisplay(0) 769 mass.SetInputData(self.polydata()) 770 mass.Update() 771 return mass.GetVolume() 772 773 def area(self): 774 """ 775 Compute the surface area of mesh. 776 The mesh must be triangular for this to work. 777 See also `mesh.triangulate()`. 778 """ 779 mass = vtk.vtkMassProperties() 780 mass.SetGlobalWarningDisplay(0) 781 mass.SetInputData(self.polydata()) 782 mass.Update() 783 return mass.GetSurfaceArea() 784 785 def is_closed(self): 786 """Return `True` if the mesh is watertight.""" 787 fe = vtk.vtkFeatureEdges() 788 fe.BoundaryEdgesOn() 789 fe.FeatureEdgesOff() 790 fe.NonManifoldEdgesOn() 791 fe.SetInputData(self.polydata(False)) 792 fe.Update() 793 ne = fe.GetOutput().GetNumberOfCells() 794 return not bool(ne) 795 796 def is_manifold(self): 797 """Return `True` if the mesh is manifold.""" 798 fe = vtk.vtkFeatureEdges() 799 fe.BoundaryEdgesOff() 800 fe.FeatureEdgesOff() 801 fe.NonManifoldEdgesOn() 802 fe.SetInputData(self.polydata(False)) 803 fe.Update() 804 ne = fe.GetOutput().GetNumberOfCells() 805 return not bool(ne) 806 807 def non_manifold_faces(self, remove=True, tol="auto"): 808 """ 809 Detect and (try to) remove non-manifold faces of a triangular mesh. 810 811 Set `remove` to `False` to mark cells without removing them. 812 Set `tol=0` for zero-tolerance, the result will be manifold but with holes. 813 Set `tol>0` to cut off non-manifold faces, and try to recover the good ones. 814 Set `tol="auto"` to make an automatic choice of the tolerance. 815 """ 816 # mark original point and cell ids 817 self.add_ids() 818 toremove = self.boundaries( 819 boundary_edges=False, non_manifold_edges=True, cell_edge=True, return_cell_ids=True 820 ) 821 if len(toremove) == 0: 822 return self 823 824 points = self.points() 825 faces = self.faces() 826 centers = self.cell_centers() 827 828 copy = self.clone() 829 copy.delete_cells(toremove).clean() 830 copy.compute_normals(cells=False) 831 normals = copy.normals() 832 deltas, deltas_i = [], [] 833 834 for i in vedo.utils.progressbar(toremove, delay=3, title="recover faces"): 835 pids = copy.closest_point(centers[i], n=3, return_point_id=True) 836 norms = normals[pids] 837 n = np.mean(norms, axis=0) 838 dn = np.linalg.norm(n) 839 if not dn: 840 continue 841 n = n / dn 842 843 p0, p1, p2 = points[faces[i]][:3] 844 v = np.cross(p1 - p0, p2 - p0) 845 lv = np.linalg.norm(v) 846 if not lv: 847 continue 848 v = v / lv 849 850 cosa = 1 - np.dot(n, v) 851 deltas.append(cosa) 852 deltas_i.append(i) 853 854 recover = [] 855 if len(deltas) > 0: 856 mean_delta = np.mean(deltas) 857 err_delta = np.std(deltas) 858 txt = "" 859 if tol == "auto": # automatic choice 860 tol = mean_delta / 5 861 txt = f"\n Automatic tol. : {tol: .4f}" 862 for i, cosa in zip(deltas_i, deltas): 863 if cosa < tol: 864 recover.append(i) 865 866 vedo.logger.info( 867 f"\n --------- Non manifold faces ---------" 868 f"\n Average tol. : {mean_delta: .4f} +- {err_delta: .4f}{txt}" 869 f"\n Removed faces : {len(toremove)}" 870 f"\n Recovered faces: {len(recover)}" 871 ) 872 873 toremove = list(set(toremove) - set(recover)) 874 875 if not remove: 876 mark = np.zeros(self.ncells, dtype=np.uint8) 877 mark[recover] = 1 878 mark[toremove] = 2 879 self.celldata["NonManifoldCell"] = mark 880 else: 881 self.delete_cells(toremove) 882 883 self.pipeline = OperationNode( 884 "non_manifold_faces", parents=[self], comment=f"#cells {self._data.GetNumberOfCells()}" 885 ) 886 return self 887 888 def shrink(self, fraction=0.85): 889 """Shrink the triangle polydata in the representation of the input mesh. 890 891 Examples: 892 - [shrink.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shrink.py) 893 894 ![](https://vedo.embl.es/images/basic/shrink.png) 895 """ 896 shrink = vtk.vtkShrinkPolyData() 897 shrink.SetInputData(self._data) 898 shrink.SetShrinkFactor(fraction) 899 shrink.Update() 900 self.point_locator = None 901 self.cell_locator = None 902 out = self._update(shrink.GetOutput()) 903 904 out.pipeline = OperationNode("shrink", parents=[self]) 905 return out 906 907 def stretch(self, q1, q2): 908 """ 909 Stretch mesh between points `q1` and `q2`. 910 911 Examples: 912 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 913 914 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 915 916 .. note:: 917 for `Mesh` objects, two vectors `mesh.base`, and `mesh.top` must be defined. 918 """ 919 if self.base is None: 920 vedo.logger.error("in stretch() must define vectors mesh.base and mesh.top at creation") 921 raise RuntimeError() 922 923 p1, p2 = self.base, self.top 924 q1, q2, z = np.asarray(q1), np.asarray(q2), np.array([0, 0, 1]) 925 a = p2 - p1 926 b = q2 - q1 927 plength = np.linalg.norm(a) 928 qlength = np.linalg.norm(b) 929 T = vtk.vtkTransform() 930 T.PostMultiply() 931 T.Translate(-p1) 932 cosa = np.dot(a, z) / plength 933 n = np.cross(a, z) 934 if np.linalg.norm(n): 935 T.RotateWXYZ(np.rad2deg(np.arccos(cosa)), n) 936 T.Scale(1, 1, qlength / plength) 937 938 cosa = np.dot(b, z) / qlength 939 n = np.cross(b, z) 940 if np.linalg.norm(n): 941 T.RotateWXYZ(-np.rad2deg(np.arccos(cosa)), n) 942 else: 943 if np.dot(b, z) < 0: 944 T.RotateWXYZ(180, [1, 0, 0]) 945 946 T.Translate(q1) 947 948 self.SetUserMatrix(T.GetMatrix()) 949 return self 950 951 def crop(self, top=None, bottom=None, right=None, left=None, front=None, back=None): 952 """ 953 Crop an `Mesh` object. 954 Use this method at creation (before moving the object). 955 956 Arguments: 957 top : (float) 958 fraction to crop from the top plane (positive z) 959 bottom : (float) 960 fraction to crop from the bottom plane (negative z) 961 front : (float) 962 fraction to crop from the front plane (positive y) 963 back : (float) 964 fraction to crop from the back plane (negative y) 965 right : (float) 966 fraction to crop from the right plane (positive x) 967 left : (float) 968 fraction to crop from the left plane (negative x) 969 970 Example: 971 ```python 972 from vedo import Sphere 973 Sphere().crop(right=0.3, left=0.1).show() 974 ``` 975 ![](https://user-images.githubusercontent.com/32848391/57081955-0ef1e800-6cf6-11e9-99de-b45220939bc9.png) 976 """ 977 cu = vtk.vtkBox() 978 pos = np.array(self.GetPosition()) 979 x0, x1, y0, y1, z0, z1 = self.bounds() 980 x0, y0, z0 = [x0, y0, z0] - pos 981 x1, y1, z1 = [x1, y1, z1] - pos 982 983 dx, dy, dz = x1 - x0, y1 - y0, z1 - z0 984 if top: 985 z1 = z1 - top * dz 986 if bottom: 987 z0 = z0 + bottom * dz 988 if front: 989 y1 = y1 - front * dy 990 if back: 991 y0 = y0 + back * dy 992 if right: 993 x1 = x1 - right * dx 994 if left: 995 x0 = x0 + left * dx 996 bounds = (x0, x1, y0, y1, z0, z1) 997 998 cu.SetBounds(bounds) 999 1000 clipper = vtk.vtkClipPolyData() 1001 clipper.SetInputData(self._data) 1002 clipper.SetClipFunction(cu) 1003 clipper.InsideOutOn() 1004 clipper.GenerateClippedOutputOff() 1005 clipper.GenerateClipScalarsOff() 1006 clipper.SetValue(0) 1007 clipper.Update() 1008 self._update(clipper.GetOutput()) 1009 self.point_locator = None 1010 1011 self.pipeline = OperationNode( 1012 "crop", parents=[self], comment=f"#pts {self._data.GetNumberOfPoints()}" 1013 ) 1014 return self 1015 1016 def cap(self, return_cap=False): 1017 """ 1018 Generate a "cap" on a clipped mesh, or caps sharp edges. 1019 1020 Examples: 1021 - [cut_and_cap.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/cut_and_cap.py) 1022 1023 ![](https://vedo.embl.es/images/advanced/cutAndCap.png) 1024 1025 See also: `join()`, `join_segments()`, `slice()`. 1026 """ 1027 poly = self._data 1028 1029 fe = vtk.vtkFeatureEdges() 1030 fe.SetInputData(poly) 1031 fe.BoundaryEdgesOn() 1032 fe.FeatureEdgesOff() 1033 fe.NonManifoldEdgesOff() 1034 fe.ManifoldEdgesOff() 1035 fe.Update() 1036 1037 stripper = vtk.vtkStripper() 1038 stripper.SetInputData(fe.GetOutput()) 1039 stripper.JoinContiguousSegmentsOn() 1040 stripper.Update() 1041 1042 boundaryPoly = vtk.vtkPolyData() 1043 boundaryPoly.SetPoints(stripper.GetOutput().GetPoints()) 1044 boundaryPoly.SetPolys(stripper.GetOutput().GetLines()) 1045 1046 rev = vtk.vtkReverseSense() 1047 rev.ReverseCellsOn() 1048 rev.SetInputData(boundaryPoly) 1049 rev.Update() 1050 1051 tf = vtk.vtkTriangleFilter() 1052 tf.SetInputData(rev.GetOutput()) 1053 tf.Update() 1054 1055 if return_cap: 1056 m = Mesh(tf.GetOutput()) 1057 # assign the same transformation to the copy 1058 m.SetOrigin(self.GetOrigin()) 1059 m.SetScale(self.GetScale()) 1060 m.SetOrientation(self.GetOrientation()) 1061 m.SetPosition(self.GetPosition()) 1062 1063 m.pipeline = OperationNode( 1064 "cap", parents=[self], comment=f"#pts {m._data.GetNumberOfPoints()}" 1065 ) 1066 return m 1067 1068 polyapp = vtk.vtkAppendPolyData() 1069 polyapp.AddInputData(poly) 1070 polyapp.AddInputData(tf.GetOutput()) 1071 polyapp.Update() 1072 out = self._update(polyapp.GetOutput()).clean() 1073 1074 out.pipeline = OperationNode( 1075 "capped", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1076 ) 1077 return out 1078 1079 def join(self, polys=True, reset=False): 1080 """ 1081 Generate triangle strips and/or polylines from 1082 input polygons, triangle strips, and lines. 1083 1084 Input polygons are assembled into triangle strips only if they are triangles; 1085 other types of polygons are passed through to the output and not stripped. 1086 Use mesh.triangulate() to triangulate non-triangular polygons prior to running 1087 this filter if you need to strip all the data. 1088 1089 Also note that if triangle strips or polylines are present in the input 1090 they are passed through and not joined nor extended. 1091 If you wish to strip these use mesh.triangulate() to fragment the input 1092 into triangles and lines prior to applying join(). 1093 1094 Arguments: 1095 polys : (bool) 1096 polygonal segments will be joined if they are contiguous 1097 reset : (bool) 1098 reset points ordering 1099 1100 Warning: 1101 If triangle strips or polylines exist in the input data 1102 they will be passed through to the output data. 1103 This filter will only construct triangle strips if triangle polygons 1104 are available; and will only construct polylines if lines are available. 1105 1106 Example: 1107 ```python 1108 from vedo import * 1109 c1 = Cylinder(pos=(0,0,0), r=2, height=3, axis=(1,.0,0), alpha=.1).triangulate() 1110 c2 = Cylinder(pos=(0,0,2), r=1, height=2, axis=(0,.3,1), alpha=.1).triangulate() 1111 intersect = c1.intersect_with(c2).join(reset=True) 1112 spline = Spline(intersect).c('blue').lw(5) 1113 show(c1, c2, spline, intersect.labels('id'), axes=1).close() 1114 ``` 1115 ![](https://vedo.embl.es/images/feats/line_join.png) 1116 """ 1117 sf = vtk.vtkStripper() 1118 sf.SetPassThroughCellIds(True) 1119 sf.SetPassThroughPointIds(True) 1120 sf.SetJoinContiguousSegments(polys) 1121 sf.SetInputData(self.polydata(False)) 1122 sf.Update() 1123 if reset: 1124 poly = sf.GetOutput() 1125 cpd = vtk.vtkCleanPolyData() 1126 cpd.PointMergingOn() 1127 cpd.ConvertLinesToPointsOn() 1128 cpd.ConvertPolysToLinesOn() 1129 cpd.ConvertStripsToPolysOn() 1130 cpd.SetInputData(poly) 1131 cpd.Update() 1132 poly = cpd.GetOutput() 1133 vpts = poly.GetCell(0).GetPoints().GetData() 1134 poly.GetPoints().SetData(vpts) 1135 return self._update(poly) 1136 1137 out = self._update(sf.GetOutput()) 1138 out.pipeline = OperationNode( 1139 "join", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1140 ) 1141 return out 1142 1143 def join_segments(self, closed=True, tol=1e-03): 1144 """ 1145 Join line segments into contiguous lines. 1146 Useful to call with `triangulate()` method. 1147 1148 Returns: 1149 list of `shapes.Lines` 1150 1151 Example: 1152 ```python 1153 from vedo import * 1154 msh = Torus().alpha(0.1).wireframe() 1155 intersection = msh.intersect_with_plane(normal=[1,1,1]).c('purple5') 1156 slices = [s.triangulate() for s in intersection.join_segments()] 1157 show(msh, intersection, merge(slices), axes=1, viewup='z') 1158 ``` 1159 ![](https://vedo.embl.es/images/feats/join_segments.jpg) 1160 """ 1161 vlines = [] 1162 for ipiece, outline in enumerate(self.split(must_share_edge=False)): 1163 1164 outline.clean() 1165 pts = outline.points() 1166 if len(pts) < 3: 1167 continue 1168 avesize = outline.average_size() 1169 lines = outline.lines() 1170 # print("---lines", lines, "in piece", ipiece) 1171 tol = avesize / pts.shape[0] * tol 1172 1173 k = 0 1174 joinedpts = [pts[k]] 1175 for _ in range(len(pts)): 1176 pk = pts[k] 1177 for j, line in enumerate(lines): 1178 1179 id0, id1 = line[0], line[-1] 1180 p0, p1 = pts[id0], pts[id1] 1181 1182 if np.linalg.norm(p0 - pk) < tol: 1183 n = len(line) 1184 for m in range(1, n): 1185 joinedpts.append(pts[line[m]]) 1186 # joinedpts.append(p1) 1187 k = id1 1188 lines.pop(j) 1189 break 1190 1191 elif np.linalg.norm(p1 - pk) < tol: 1192 n = len(line) 1193 for m in reversed(range(0, n - 1)): 1194 joinedpts.append(pts[line[m]]) 1195 # joinedpts.append(p0) 1196 k = id0 1197 lines.pop(j) 1198 break 1199 1200 if len(joinedpts) > 1: 1201 newline = vedo.shapes.Line(joinedpts, closed=closed) 1202 newline.clean() 1203 newline.SetProperty(self.GetProperty()) 1204 newline.property = self.GetProperty() 1205 newline.pipeline = OperationNode( 1206 "join_segments", 1207 parents=[self], 1208 comment=f"#pts {newline._data.GetNumberOfPoints()}", 1209 ) 1210 vlines.append(newline) 1211 1212 return vlines 1213 1214 def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)): 1215 """ 1216 Slice a mesh with a plane and fill the contour. 1217 1218 Example: 1219 ```python 1220 from vedo import * 1221 msh = Mesh(dataurl+"bunny.obj").alpha(0.1).wireframe() 1222 mslice = msh.slice(normal=[0,1,0.3], origin=[0,0.16,0]) 1223 mslice.c('purple5') 1224 show(msh, mslice, axes=1) 1225 ``` 1226 ![](https://vedo.embl.es/images/feats/mesh_slice.jpg) 1227 1228 See also: `join()`, `join_segments()`, `cap()`, `cut_with_plane()`. 1229 """ 1230 intersection = self.intersect_with_plane(origin=origin, normal=normal) 1231 slices = [s.triangulate() for s in intersection.join_segments()] 1232 mslices = vedo.pointcloud.merge(slices) 1233 if mslices: 1234 mslices.name = "MeshSlice" 1235 mslices.pipeline = OperationNode("slice", parents=[self], comment=f"normal = {normal}") 1236 return mslices 1237 1238 def triangulate(self, verts=True, lines=True): 1239 """ 1240 Converts mesh polygons into triangles. 1241 1242 If the input mesh is only made of 2D lines (no faces) the output will be a triangulation 1243 that fills the internal area. The contours may be concave, and may even contain holes, 1244 i.e. a contour may contain an internal contour winding in the opposite 1245 direction to indicate that it is a hole. 1246 1247 Arguments: 1248 verts : (bool) 1249 if True, break input vertex cells into individual vertex cells 1250 (one point per cell). If False, the input vertex cells will be ignored. 1251 lines : (bool) 1252 if True, break input polylines into line segments. 1253 If False, input lines will be ignored and the output will have no lines. 1254 """ 1255 if self._data.GetNumberOfPolys() or self._data.GetNumberOfStrips(): 1256 # print("vtkTriangleFilter") 1257 tf = vtk.vtkTriangleFilter() 1258 tf.SetPassLines(lines) 1259 tf.SetPassVerts(verts) 1260 1261 elif self._data.GetNumberOfLines(): 1262 # print("vtkContourTriangulator") 1263 tf = vtk.vtkContourTriangulator() 1264 tf.TriangulationErrorDisplayOn() 1265 1266 else: 1267 vedo.logger.debug("input in triangulate() seems to be void! Skip.") 1268 return self 1269 1270 tf.SetInputData(self._data) 1271 tf.Update() 1272 out = self._update(tf.GetOutput()).lw(0).lighting("default") 1273 out.PickableOn() 1274 1275 out.pipeline = OperationNode( 1276 "triangulate", parents=[self], comment=f"#cells {out.inputdata().GetNumberOfCells()}" 1277 ) 1278 return out 1279 1280 def compute_cell_area(self, name="Area"): 1281 """Add to this mesh a cell data array containing the areas of the polygonal faces""" 1282 csf = vtk.vtkCellSizeFilter() 1283 csf.SetInputData(self.polydata(False)) 1284 csf.SetComputeArea(True) 1285 csf.SetComputeVolume(False) 1286 csf.SetComputeLength(False) 1287 csf.SetComputeVertexCount(False) 1288 csf.SetAreaArrayName(name) 1289 csf.Update() 1290 return self._update(csf.GetOutput()) 1291 1292 def compute_cell_vertex_count(self, name="VertexCount"): 1293 """Add to this mesh a cell data array containing the nr of vertices 1294 that a polygonal face has.""" 1295 csf = vtk.vtkCellSizeFilter() 1296 csf.SetInputData(self.polydata(False)) 1297 csf.SetComputeArea(False) 1298 csf.SetComputeVolume(False) 1299 csf.SetComputeLength(False) 1300 csf.SetComputeVertexCount(True) 1301 csf.SetVertexCountArrayName(name) 1302 csf.Update() 1303 return self._update(csf.GetOutput()) 1304 1305 def compute_quality(self, metric=6): 1306 """ 1307 Calculate metrics of quality for the elements of a triangular mesh. 1308 This method adds to the mesh a cell array named "Quality". 1309 See class [vtkMeshQuality](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html) 1310 for explanation. 1311 1312 Arguments: 1313 metric : (int) 1314 type of available estimators are: 1315 - EDGE RATIO, 0 1316 - ASPECT RATIO, 1 1317 - RADIUS RATIO, 2 1318 - ASPECT FROBENIUS, 3 1319 - MED ASPECT FROBENIUS, 4 1320 - MAX ASPECT FROBENIUS, 5 1321 - MIN_ANGLE, 6 1322 - COLLAPSE RATIO, 7 1323 - MAX ANGLE, 8 1324 - CONDITION, 9 1325 - SCALED JACOBIAN, 10 1326 - SHEAR, 11 1327 - RELATIVE SIZE SQUARED, 12 1328 - SHAPE, 13 1329 - SHAPE AND SIZE, 14 1330 - DISTORTION, 15 1331 - MAX EDGE RATIO, 16 1332 - SKEW, 17 1333 - TAPER, 18 1334 - VOLUME, 19 1335 - STRETCH, 20 1336 - DIAGONAL, 21 1337 - DIMENSION, 22 1338 - ODDY, 23 1339 - SHEAR AND SIZE, 24 1340 - JACOBIAN, 25 1341 - WARPAGE, 26 1342 - ASPECT GAMMA, 27 1343 - AREA, 28 1344 - ASPECT BETA, 29 1345 1346 Examples: 1347 - [meshquality.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/meshquality.py) 1348 1349 ![](https://vedo.embl.es/images/advanced/meshquality.png) 1350 """ 1351 qf = vtk.vtkMeshQuality() 1352 qf.SetInputData(self.polydata(False)) 1353 qf.SetTriangleQualityMeasure(metric) 1354 qf.SaveCellQualityOn() 1355 qf.Update() 1356 pd = qf.GetOutput() 1357 self._update(pd) 1358 self.pipeline = OperationNode("compute_quality", parents=[self]) 1359 return self 1360 1361 def check_validity(self, tol=0): 1362 """ 1363 Return an array of possible problematic faces following this convention: 1364 - Valid = 0 1365 - WrongNumberOfPoints = 1 1366 - IntersectingEdges = 2 1367 - IntersectingFaces = 4 1368 - NoncontiguousEdges = 8 1369 - Nonconvex = 10 1370 - OrientedIncorrectly = 20 1371 1372 Arguments: 1373 tol : (float) 1374 value is used as an epsilon for floating point 1375 equality checks throughout the cell checking process. 1376 """ 1377 vald = vtk.vtkCellValidator() 1378 if tol: 1379 vald.SetTolerance(tol) 1380 vald.SetInputData(self._data) 1381 vald.Update() 1382 varr = vald.GetOutput().GetCellData().GetArray("ValidityState") 1383 return vtk2numpy(varr) 1384 1385 def compute_curvature(self, method=0): 1386 """ 1387 Add scalars to `Mesh` that contains the curvature calculated in three different ways. 1388 1389 Variable `method` can be: 1390 - 0 = gaussian 1391 - 1 = mean curvature 1392 - 2 = max curvature 1393 - 3 = min curvature 1394 1395 Example: 1396 ```python 1397 from vedo import Torus 1398 Torus().compute_curvature().add_scalarbar().show(axes=1).close() 1399 ``` 1400 ![](https://user-images.githubusercontent.com/32848391/51934810-c2e88c00-2404-11e9-8e7e-ca0b7984bbb7.png) 1401 """ 1402 curve = vtk.vtkCurvatures() 1403 curve.SetInputData(self._data) 1404 curve.SetCurvatureType(method) 1405 curve.Update() 1406 self._update(curve.GetOutput()) 1407 self._mapper.ScalarVisibilityOn() 1408 return self 1409 1410 def compute_elevation(self, low=(0, 0, 0), high=(0, 0, 1), vrange=(0, 1)): 1411 """ 1412 Add to `Mesh` a scalar array that contains distance along a specified direction. 1413 1414 Arguments: 1415 low : (list) 1416 one end of the line (small scalar values) 1417 high : (list) 1418 other end of the line (large scalar values) 1419 vrange : (list) 1420 set the range of the scalar 1421 1422 Example: 1423 ```python 1424 from vedo import Sphere 1425 s = Sphere().compute_elevation(low=(0,0,0), high=(1,1,1)) 1426 s.add_scalarbar().show(axes=1).close() 1427 ``` 1428 ![](https://user-images.githubusercontent.com/32848391/68478872-3986a580-0231-11ea-8245-b68a683aa295.png) 1429 """ 1430 ef = vtk.vtkElevationFilter() 1431 ef.SetInputData(self.polydata()) 1432 ef.SetLowPoint(low) 1433 ef.SetHighPoint(high) 1434 ef.SetScalarRange(vrange) 1435 ef.Update() 1436 self._update(ef.GetOutput()) 1437 self._mapper.ScalarVisibilityOn() 1438 return self 1439 1440 def subdivide(self, n=1, method=0, mel=None): 1441 """ 1442 Increase the number of vertices of a surface mesh. 1443 1444 Arguments: 1445 n : (int) 1446 number of subdivisions. 1447 method : (int) 1448 Loop(0), Linear(1), Adaptive(2), Butterfly(3), Centroid(4) 1449 mel : (float) 1450 Maximum Edge Length (applicable to Adaptive method only). 1451 """ 1452 triangles = vtk.vtkTriangleFilter() 1453 triangles.SetInputData(self._data) 1454 triangles.Update() 1455 originalMesh = triangles.GetOutput() 1456 if method == 0: 1457 sdf = vtk.vtkLoopSubdivisionFilter() 1458 elif method == 1: 1459 sdf = vtk.vtkLinearSubdivisionFilter() 1460 elif method == 2: 1461 sdf = vtk.vtkAdaptiveSubdivisionFilter() 1462 if mel is None: 1463 mel = self.diagonal_size() / np.sqrt(self._data.GetNumberOfPoints()) / n 1464 sdf.SetMaximumEdgeLength(mel) 1465 elif method == 3: 1466 sdf = vtk.vtkButterflySubdivisionFilter() 1467 elif method == 4: 1468 sdf = vtk.vtkDensifyPolyData() 1469 else: 1470 vedo.logger.error(f"in subdivide() unknown method {method}") 1471 raise RuntimeError() 1472 1473 if method != 2: 1474 sdf.SetNumberOfSubdivisions(n) 1475 1476 sdf.SetInputData(originalMesh) 1477 sdf.Update() 1478 out = sdf.GetOutput() 1479 1480 self.pipeline = OperationNode( 1481 "subdivide", parents=[self], comment=f"#pts {out.GetNumberOfPoints()}" 1482 ) 1483 return self._update(out) 1484 1485 def decimate(self, fraction=0.5, n=None, method="quadric", boundaries=False): 1486 """ 1487 Downsample the number of vertices in a mesh to `fraction`. 1488 1489 Arguments: 1490 fraction : (float) 1491 the desired target of reduction. 1492 n : (int) 1493 the desired number of final points (`fraction` is recalculated based on it). 1494 method : (str) 1495 can be either 'quadric' or 'pro'. In the first case triagulation 1496 will look like more regular, irrespective of the mesh original curvature. 1497 In the second case triangles are more irregular but mesh is more precise on more 1498 curved regions. 1499 boundaries : (bool) 1500 in "pro" mode decide whether to leave boundaries untouched or not 1501 1502 .. note:: Setting `fraction=0.1` leaves 10% of the original number of vertices 1503 """ 1504 poly = self._data 1505 if n: # N = desired number of points 1506 npt = poly.GetNumberOfPoints() 1507 fraction = n / npt 1508 if fraction >= 1: 1509 return self 1510 1511 if "quad" in method: 1512 decimate = vtk.vtkQuadricDecimation() 1513 # decimate.SetVolumePreservation(True) 1514 1515 else: 1516 decimate = vtk.vtkDecimatePro() 1517 decimate.PreserveTopologyOn() 1518 if boundaries: 1519 decimate.BoundaryVertexDeletionOff() 1520 else: 1521 decimate.BoundaryVertexDeletionOn() 1522 decimate.SetInputData(poly) 1523 decimate.SetTargetReduction(1 - fraction) 1524 decimate.Update() 1525 out = decimate.GetOutput() 1526 1527 self.pipeline = OperationNode( 1528 "decimate", parents=[self], comment=f"#pts {out.GetNumberOfPoints()}" 1529 ) 1530 return self._update(out) 1531 1532 def collapse_edges(self, distance, iterations=1): 1533 """Collapse mesh edges so that are all above distance.""" 1534 self.clean() 1535 x0, x1, y0, y1, z0, z1 = self.bounds() 1536 fs = min(x1 - x0, y1 - y0, z1 - z0) / 10 1537 d2 = distance * distance 1538 if distance > fs: 1539 vedo.logger.error(f"distance parameter is too large, should be < {fs}, skip!") 1540 return self 1541 for _ in range(iterations): 1542 medges = self.edges() 1543 pts = self.points() 1544 newpts = np.array(pts) 1545 moved = [] 1546 for e in medges: 1547 if len(e) == 2: 1548 id0, id1 = e 1549 p0, p1 = pts[id0], pts[id1] 1550 d = mag2(p1 - p0) 1551 if d < d2 and id0 not in moved and id1 not in moved: 1552 p = (p0 + p1) / 2 1553 newpts[id0] = p 1554 newpts[id1] = p 1555 moved += [id0, id1] 1556 1557 self.points(newpts) 1558 self.clean() 1559 self.compute_normals() 1560 1561 self.pipeline = OperationNode( 1562 "collapse_edges", parents=[self], comment=f"#pts {self._data.GetNumberOfPoints()}" 1563 ) 1564 return self 1565 1566 def smooth(self, niter=15, pass_band=0.1, edge_angle=15, feature_angle=60, boundary=False): 1567 """ 1568 Adjust mesh point positions using the `Windowed Sinc` function interpolation kernel. 1569 1570 Arguments: 1571 niter : (int) 1572 number of iterations. 1573 pass_band : (float) 1574 set the pass_band value for the windowed sinc filter. 1575 edge_angle : (float) 1576 edge angle to control smoothing along edges (either interior or boundary). 1577 feature_angle : (float) 1578 specifies the feature angle for sharp edge identification. 1579 boundary : (bool) 1580 specify if boundary should also be smoothed or kept unmodified 1581 1582 Examples: 1583 - [mesh_smoother1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/mesh_smoother1.py) 1584 1585 ![](https://vedo.embl.es/images/advanced/mesh_smoother2.png) 1586 """ 1587 poly = self._data 1588 cl = vtk.vtkCleanPolyData() 1589 cl.SetInputData(poly) 1590 cl.Update() 1591 smf = vtk.vtkWindowedSincPolyDataFilter() 1592 smf.SetInputData(cl.GetOutput()) 1593 smf.SetNumberOfIterations(niter) 1594 smf.SetEdgeAngle(edge_angle) 1595 smf.SetFeatureAngle(feature_angle) 1596 smf.SetPassBand(pass_band) 1597 smf.NormalizeCoordinatesOn() 1598 smf.NonManifoldSmoothingOn() 1599 smf.FeatureEdgeSmoothingOn() 1600 smf.SetBoundarySmoothing(boundary) 1601 smf.Update() 1602 out = self._update(smf.GetOutput()) 1603 1604 out.pipeline = OperationNode( 1605 "smooth", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1606 ) 1607 return out 1608 1609 1610 def fill_holes(self, size=None): 1611 """ 1612 Identifies and fills holes in input mesh. 1613 Holes are identified by locating boundary edges, linking them together into loops, 1614 and then triangulating the resulting loops. 1615 1616 Arguments: 1617 size : (float) 1618 Approximate limit to the size of the hole that can be filled. The default is None. 1619 1620 Examples: 1621 - [fillholes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fillholes.py) 1622 """ 1623 fh = vtk.vtkFillHolesFilter() 1624 if not size: 1625 mb = self.diagonal_size() 1626 size = mb / 10 1627 fh.SetHoleSize(size) 1628 fh.SetInputData(self._data) 1629 fh.Update() 1630 out = self._update(fh.GetOutput()) 1631 1632 out.pipeline = OperationNode( 1633 "fill_holes", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1634 ) 1635 return out 1636 1637 def is_inside(self, point, tol=1e-05): 1638 """Return True if point is inside a polydata closed surface.""" 1639 poly = self.polydata() 1640 points = vtk.vtkPoints() 1641 points.InsertNextPoint(point) 1642 pointsPolydata = vtk.vtkPolyData() 1643 pointsPolydata.SetPoints(points) 1644 sep = vtk.vtkSelectEnclosedPoints() 1645 sep.SetTolerance(tol) 1646 sep.CheckSurfaceOff() 1647 sep.SetInputData(pointsPolydata) 1648 sep.SetSurfaceData(poly) 1649 sep.Update() 1650 return sep.IsInside(0) 1651 1652 def inside_points(self, pts, invert=False, tol=1e-05, return_ids=False): 1653 """ 1654 Return the point cloud that is inside mesh surface as a new Points object. 1655 1656 If return_ids is True a list of IDs is returned and in addition input points 1657 are marked by a pointdata array named "IsInside". 1658 1659 Example: 1660 `print(pts.pointdata["IsInside"])` 1661 1662 Examples: 1663 - [pca_ellipsoid.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/pca_ellipsoid.py) 1664 1665 ![](https://vedo.embl.es/images/basic/pca.png) 1666 """ 1667 if isinstance(pts, Points): 1668 pointsPolydata = pts.polydata() 1669 ptsa = pts.points() 1670 else: 1671 ptsa = np.asarray(pts) 1672 vpoints = vtk.vtkPoints() 1673 vpoints.SetData(numpy2vtk(ptsa, dtype=np.float32)) 1674 pointsPolydata = vtk.vtkPolyData() 1675 pointsPolydata.SetPoints(vpoints) 1676 1677 sep = vtk.vtkSelectEnclosedPoints() 1678 # sep = vtk.vtkExtractEnclosedPoints() 1679 sep.SetTolerance(tol) 1680 sep.SetInputData(pointsPolydata) 1681 sep.SetSurfaceData(self.polydata()) 1682 sep.SetInsideOut(invert) 1683 sep.Update() 1684 1685 varr = sep.GetOutput().GetPointData().GetArray("SelectedPoints") 1686 mask = vtk2numpy(varr).astype(bool) 1687 ids = np.array(range(len(ptsa)), dtype=int)[mask] 1688 1689 if isinstance(pts, Points): 1690 varr.SetName("IsInside") 1691 pts.inputdata().GetPointData().AddArray(varr) 1692 1693 if return_ids: 1694 return ids 1695 1696 pcl = Points(ptsa[ids]) 1697 pcl.name = "InsidePoints" 1698 1699 pcl.pipeline = OperationNode( 1700 "inside_points", parents=[self, ptsa], 1701 comment=f"#pts {pcl.inputdata().GetNumberOfPoints()}" 1702 ) 1703 return pcl 1704 1705 def boundaries( 1706 self, 1707 boundary_edges=True, 1708 manifold_edges=False, 1709 non_manifold_edges=False, 1710 feature_angle=None, 1711 return_point_ids=False, 1712 return_cell_ids=False, 1713 cell_edge=False, 1714 ): 1715 """ 1716 Return the boundary lines of an input mesh. 1717 Check also `vedo.base.BaseActor.mark_boundaries()` method. 1718 1719 Arguments: 1720 boundary_edges : (bool) 1721 Turn on/off the extraction of boundary edges. 1722 manifold_edges : (bool) 1723 Turn on/off the extraction of manifold edges. 1724 non_manifold_edges : (bool) 1725 Turn on/off the extraction of non-manifold edges. 1726 feature_angle : (bool) 1727 Specify the min angle btw 2 faces for extracting edges. 1728 return_point_ids : (bool) 1729 return a numpy array of point indices 1730 return_cell_ids : (bool) 1731 return a numpy array of cell indices 1732 cell_edge : (bool) 1733 set to `True` if a cell need to share an edge with 1734 the boundary line, or `False` if a single vertex is enough 1735 1736 Examples: 1737 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1738 1739 ![](https://vedo.embl.es/images/basic/boundaries.png) 1740 """ 1741 fe = vtk.vtkFeatureEdges() 1742 fe.SetBoundaryEdges(boundary_edges) 1743 fe.SetNonManifoldEdges(non_manifold_edges) 1744 fe.SetManifoldEdges(manifold_edges) 1745 # fe.SetPassLines(True) # vtk9.2 1746 fe.ColoringOff() 1747 fe.SetFeatureEdges(False) 1748 if feature_angle is not None: 1749 fe.SetFeatureEdges(True) 1750 fe.SetFeatureAngle(feature_angle) 1751 1752 if return_point_ids or return_cell_ids: 1753 idf = vtk.vtkIdFilter() 1754 idf.SetInputData(self.polydata()) 1755 idf.SetPointIdsArrayName("BoundaryIds") 1756 idf.SetPointIds(True) 1757 idf.Update() 1758 1759 fe.SetInputData(idf.GetOutput()) 1760 fe.Update() 1761 1762 vid = fe.GetOutput().GetPointData().GetArray("BoundaryIds") 1763 npid = vtk2numpy(vid).astype(int) 1764 1765 if return_point_ids: 1766 return npid 1767 1768 if return_cell_ids: 1769 n = 1 if cell_edge else 0 1770 inface = [] 1771 for i, face in enumerate(self.faces()): 1772 # isin = np.any([vtx in npid for vtx in face]) 1773 isin = 0 1774 for vtx in face: 1775 isin += int(vtx in npid) 1776 if isin > n: 1777 break 1778 if isin > n: 1779 inface.append(i) 1780 return np.array(inface).astype(int) 1781 1782 return self 1783 1784 else: 1785 1786 fe.SetInputData(self.polydata()) 1787 fe.Update() 1788 msh = Mesh(fe.GetOutput(), c="p").lw(5).lighting("off") 1789 1790 msh.pipeline = OperationNode( 1791 "boundaries", 1792 parents=[self], 1793 shape="octagon", 1794 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 1795 ) 1796 return msh 1797 1798 def imprint(self, loopline, tol=0.01): 1799 """ 1800 Imprint the contact surface of one object onto another surface. 1801 1802 Arguments: 1803 loopline : vedo.shapes.Line 1804 a Line object to be imprinted onto the mesh. 1805 tol : (float), optional 1806 projection tolerance which controls how close the imprint 1807 surface must be to the target. 1808 1809 Example: 1810 ```python 1811 from vedo import * 1812 grid = Grid()#.triangulate() 1813 circle = Circle(r=0.3, res=24).pos(0.11,0.12) 1814 line = Line(circle, closed=True, lw=4, c='r4') 1815 grid.imprint(line) 1816 show(grid, line, axes=1).close() 1817 ``` 1818 ![](https://vedo.embl.es/images/feats/imprint.png) 1819 """ 1820 loop = vtk.vtkContourLoopExtraction() 1821 loop.SetInputData(loopline.polydata()) 1822 loop.Update() 1823 1824 clean_loop = vtk.vtkCleanPolyData() 1825 clean_loop.SetInputData(loop.GetOutput()) 1826 clean_loop.Update() 1827 1828 imp = vtk.vtkImprintFilter() 1829 imp.SetTargetData(self.polydata()) 1830 imp.SetImprintData(clean_loop.GetOutput()) 1831 imp.SetTolerance(tol) 1832 imp.BoundaryEdgeInsertionOn() 1833 imp.TriangulateOutputOn() 1834 imp.Update() 1835 out = self._update(imp.GetOutput()) 1836 1837 out.pipeline = OperationNode( 1838 "imprint", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1839 ) 1840 return out 1841 1842 def connected_vertices(self, index): 1843 """Find all vertices connected to an input vertex specified by its index. 1844 1845 Examples: 1846 - [connected_vtx.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/connected_vtx.py) 1847 1848 ![](https://vedo.embl.es/images/basic/connVtx.png) 1849 """ 1850 poly = self._data 1851 1852 cell_idlist = vtk.vtkIdList() 1853 poly.GetPointCells(index, cell_idlist) 1854 1855 idxs = [] 1856 for i in range(cell_idlist.GetNumberOfIds()): 1857 point_idlist = vtk.vtkIdList() 1858 poly.GetCellPoints(cell_idlist.GetId(i), point_idlist) 1859 for j in range(point_idlist.GetNumberOfIds()): 1860 idj = point_idlist.GetId(j) 1861 if idj == index: 1862 continue 1863 if idj in idxs: 1864 continue 1865 idxs.append(idj) 1866 1867 return idxs 1868 1869 def connected_cells(self, index, return_ids=False): 1870 """Find all cellls connected to an input vertex specified by its index.""" 1871 1872 # Find all cells connected to point index 1873 dpoly = self._data 1874 idlist = vtk.vtkIdList() 1875 dpoly.GetPointCells(index, idlist) 1876 1877 ids = vtk.vtkIdTypeArray() 1878 ids.SetNumberOfComponents(1) 1879 rids = [] 1880 for k in range(idlist.GetNumberOfIds()): 1881 cid = idlist.GetId(k) 1882 ids.InsertNextValue(cid) 1883 rids.append(int(cid)) 1884 if return_ids: 1885 return rids 1886 1887 selection_node = vtk.vtkSelectionNode() 1888 selection_node.SetFieldType(vtk.vtkSelectionNode.CELL) 1889 selection_node.SetContentType(vtk.vtkSelectionNode.INDICES) 1890 selection_node.SetSelectionList(ids) 1891 selection = vtk.vtkSelection() 1892 selection.AddNode(selection_node) 1893 extractSelection = vtk.vtkExtractSelection() 1894 extractSelection.SetInputData(0, dpoly) 1895 extractSelection.SetInputData(1, selection) 1896 extractSelection.Update() 1897 gf = vtk.vtkGeometryFilter() 1898 gf.SetInputData(extractSelection.GetOutput()) 1899 gf.Update() 1900 return Mesh(gf.GetOutput()).lw(1) 1901 1902 def silhouette(self, direction=None, border_edges=True, feature_angle=False): 1903 """ 1904 Return a new line `Mesh` which corresponds to the outer `silhouette` 1905 of the input as seen along a specified `direction`, this can also be 1906 a `vtkCamera` object. 1907 1908 Arguments: 1909 direction : (list) 1910 viewpoint direction vector. 1911 If *None* this is guessed by looking at the minimum 1912 of the sides of the bounding box. 1913 border_edges : (bool) 1914 enable or disable generation of border edges 1915 feature_angle : (float) 1916 minimal angle for sharp edges detection. 1917 If set to `False` the functionality is disabled. 1918 1919 Examples: 1920 - [silhouette1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette1.py) 1921 1922 ![](https://vedo.embl.es/images/basic/silhouette1.png) 1923 """ 1924 sil = vtk.vtkPolyDataSilhouette() 1925 sil.SetInputData(self.polydata()) 1926 sil.SetBorderEdges(border_edges) 1927 if feature_angle is False: 1928 sil.SetEnableFeatureAngle(0) 1929 else: 1930 sil.SetEnableFeatureAngle(1) 1931 sil.SetFeatureAngle(feature_angle) 1932 1933 if direction is None and vedo.plotter_instance and vedo.plotter_instance.camera: 1934 sil.SetCamera(vedo.plotter_instance.camera) 1935 m = Mesh() 1936 m.mapper().SetInputConnection(sil.GetOutputPort()) 1937 1938 elif isinstance(direction, vtk.vtkCamera): 1939 sil.SetCamera(direction) 1940 m = Mesh() 1941 m.mapper().SetInputConnection(sil.GetOutputPort()) 1942 1943 elif direction == "2d": 1944 sil.SetVector(3.4, 4.5, 5.6) # random 1945 sil.SetDirectionToSpecifiedVector() 1946 sil.Update() 1947 m = Mesh(sil.GetOutput()) 1948 1949 elif is_sequence(direction): 1950 sil.SetVector(direction) 1951 sil.SetDirectionToSpecifiedVector() 1952 sil.Update() 1953 m = Mesh(sil.GetOutput()) 1954 else: 1955 vedo.logger.error(f"in silhouette() unknown direction type {type(direction)}") 1956 vedo.logger.error("first render the scene with show() or specify camera/direction") 1957 return self 1958 1959 m.lw(2).c((0, 0, 0)).lighting("off") 1960 m.mapper().SetResolveCoincidentTopologyToPolygonOffset() 1961 m.pipeline = OperationNode("silhouette", parents=[self]) 1962 return m 1963 1964 1965 def follow_camera(self, camera=None): 1966 """ 1967 Return an object that will follow camera movements and stay locked to it. 1968 Use `mesh.follow_camera(False)` to disable it. 1969 1970 A `vtkCamera` object can also be passed. 1971 """ 1972 if camera is False: 1973 try: 1974 self.SetCamera(None) 1975 return self 1976 except AttributeError: 1977 return self 1978 1979 factor = Follower(self, camera) 1980 1981 if isinstance(camera, vtk.vtkCamera): 1982 factor.SetCamera(camera) 1983 else: 1984 plt = vedo.plotter_instance 1985 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 1986 factor.SetCamera(plt.renderer.GetActiveCamera()) 1987 else: 1988 factor._isfollower = True # postpone to show() call 1989 1990 return factor 1991 1992 def isobands(self, n=10, vmin=None, vmax=None): 1993 """ 1994 Return a new `Mesh` representing the isobands of the active scalars. 1995 This is a new mesh where the scalar is now associated to cell faces and 1996 used to colorize the mesh. 1997 1998 Arguments: 1999 n : (int) 2000 number of isobands in the range 2001 vmin : (float) 2002 minimum of the range 2003 vmax : (float) 2004 maximum of the range 2005 2006 Examples: 2007 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/isolines.py) 2008 """ 2009 r0, r1 = self._data.GetScalarRange() 2010 if vmin is None: 2011 vmin = r0 2012 if vmax is None: 2013 vmax = r1 2014 2015 # -------------------------------- 2016 bands = [] 2017 dx = (vmax - vmin) / float(n) 2018 b = [vmin, vmin + dx / 2.0, vmin + dx] 2019 i = 0 2020 while i < n: 2021 bands.append(b) 2022 b = [b[0] + dx, b[1] + dx, b[2] + dx] 2023 i += 1 2024 2025 # annotate, use the midpoint of the band as the label 2026 lut = self.mapper().GetLookupTable() 2027 labels = [] 2028 for b in bands: 2029 labels.append("{:4.2f}".format(b[1])) 2030 values = vtk.vtkVariantArray() 2031 for la in labels: 2032 values.InsertNextValue(vtk.vtkVariant(la)) 2033 for i in range(values.GetNumberOfTuples()): 2034 lut.SetAnnotation(i, values.GetValue(i).ToString()) 2035 2036 bcf = vtk.vtkBandedPolyDataContourFilter() 2037 bcf.SetInputData(self.polydata()) 2038 # Use either the minimum or maximum value for each band. 2039 for i, band in enumerate(bands): 2040 bcf.SetValue(i, band[2]) 2041 # We will use an indexed lookup table. 2042 bcf.SetScalarModeToIndex() 2043 bcf.GenerateContourEdgesOff() 2044 bcf.Update() 2045 bcf.GetOutput().GetCellData().GetScalars().SetName("IsoBands") 2046 m1 = Mesh(bcf.GetOutput()).compute_normals(cells=True) 2047 m1.mapper().SetLookupTable(lut) 2048 2049 m1.pipeline = OperationNode("isobands", parents=[self]) 2050 return m1 2051 2052 def isolines(self, n=10, vmin=None, vmax=None): 2053 """ 2054 Return a new `Mesh` representing the isolines of the active scalars. 2055 2056 Arguments: 2057 n : (int) 2058 number of isolines in the range 2059 vmin : (float) 2060 minimum of the range 2061 vmax : (float) 2062 maximum of the range 2063 2064 Examples: 2065 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/isolines.py) 2066 2067 ![](https://vedo.embl.es/images/pyplot/isolines.png) 2068 """ 2069 bcf = vtk.vtkContourFilter() 2070 bcf.SetInputData(self.polydata()) 2071 r0, r1 = self._data.GetScalarRange() 2072 if vmin is None: 2073 vmin = r0 2074 if vmax is None: 2075 vmax = r1 2076 bcf.GenerateValues(n, vmin, vmax) 2077 bcf.Update() 2078 sf = vtk.vtkStripper() 2079 sf.SetJoinContiguousSegments(True) 2080 sf.SetInputData(bcf.GetOutput()) 2081 sf.Update() 2082 cl = vtk.vtkCleanPolyData() 2083 cl.SetInputData(sf.GetOutput()) 2084 cl.Update() 2085 msh = Mesh(cl.GetOutput(), c="k").lighting("off") 2086 msh.mapper().SetResolveCoincidentTopologyToPolygonOffset() 2087 msh.pipeline = OperationNode("isolines", parents=[self]) 2088 return msh 2089 2090 def extrude(self, zshift=1, rotation=0, dR=0, cap=True, res=1): 2091 """ 2092 Sweep a polygonal data creating a "skirt" from free edges and lines, and lines from vertices. 2093 The input dataset is swept around the z-axis to create new polygonal primitives. 2094 For example, sweeping a line results in a cylindrical shell, and sweeping a circle creates a torus. 2095 2096 You can control whether the sweep of a 2D object (i.e., polygon or triangle strip) 2097 is capped with the generating geometry. 2098 Also, you can control the angle of rotation, and whether translation along the z-axis 2099 is performed along with the rotation. (Translation is useful for creating "springs"). 2100 You also can adjust the radius of the generating geometry using the "dR" keyword. 2101 2102 The skirt is generated by locating certain topological features. 2103 Free edges (edges of polygons or triangle strips only used by one polygon or triangle strips) 2104 generate surfaces. This is true also of lines or polylines. Vertices generate lines. 2105 2106 This filter can be used to model axisymmetric objects like cylinders, bottles, and wine glasses; 2107 or translational/rotational symmetric objects like springs or corkscrews. 2108 2109 Warning: 2110 Some polygonal objects have no free edges (e.g., sphere). When swept, this will result 2111 in two separate surfaces if capping is on, or no surface if capping is off. 2112 2113 Examples: 2114 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2115 2116 ![](https://vedo.embl.es/images/basic/extrude.png) 2117 """ 2118 if is_sequence(zshift): 2119 # ms = [] # todo 2120 # poly0 = self.clone().polydata() 2121 # for i in range(len(zshift)-1): 2122 # rf = vtk.vtkRotationalExtrusionFilter() 2123 # rf.SetInputData(poly0) 2124 # rf.SetResolution(res) 2125 # rf.SetCapping(0) 2126 # rf.SetAngle(rotation) 2127 # rf.SetTranslation(zshift) 2128 # rf.SetDeltaRadius(dR) 2129 # rf.Update() 2130 # poly1 = rf.GetOutput() 2131 return self 2132 2133 rf = vtk.vtkRotationalExtrusionFilter() 2134 # rf = vtk.vtkLinearExtrusionFilter() 2135 rf.SetInputData(self.polydata(False)) # must not be transformed 2136 rf.SetResolution(res) 2137 rf.SetCapping(cap) 2138 rf.SetAngle(rotation) 2139 rf.SetTranslation(zshift) 2140 rf.SetDeltaRadius(dR) 2141 rf.Update() 2142 m = Mesh(rf.GetOutput(), c=self.c(), alpha=self.alpha()) 2143 prop = vtk.vtkProperty() 2144 prop.DeepCopy(self.property) 2145 m.SetProperty(prop) 2146 m.property = prop 2147 # assign the same transformation 2148 m.SetOrigin(self.GetOrigin()) 2149 m.SetScale(self.GetScale()) 2150 m.SetOrientation(self.GetOrientation()) 2151 m.SetPosition(self.GetPosition()) 2152 2153 m.compute_normals(cells=False).flat().lighting("default") 2154 2155 m.pipeline = OperationNode( 2156 "extrude", parents=[self], 2157 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 2158 ) 2159 return m 2160 2161 def split(self, maxdepth=1000, flag=False, must_share_edge=False, sort_by_area=True): 2162 """ 2163 Split a mesh by connectivity and order the pieces by increasing area. 2164 2165 Arguments: 2166 maxdepth : (int) 2167 only consider this maximum number of mesh parts. 2168 flag : (bool) 2169 if set to True return the same single object, 2170 but add a "RegionId" array to flag the mesh subparts 2171 must_share_edge : (bool) 2172 if True, mesh regions that only share single points will be split. 2173 sort_by_area : (bool) 2174 if True, sort the mesh parts by decreasing area. 2175 2176 Examples: 2177 - [splitmesh.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/splitmesh.py) 2178 2179 ![](https://vedo.embl.es/images/advanced/splitmesh.png) 2180 """ 2181 pd = self.polydata(False) 2182 if must_share_edge: 2183 if pd.GetNumberOfPolys() == 0: 2184 vedo.logger.warning("in split(): no polygons found. Skip.") 2185 return [self] 2186 cf = vtk.vtkPolyDataEdgeConnectivityFilter() 2187 cf.BarrierEdgesOff() 2188 else: 2189 cf = vtk.vtkPolyDataConnectivityFilter() 2190 2191 cf.SetInputData(pd) 2192 cf.SetExtractionModeToAllRegions() 2193 cf.SetColorRegions(True) 2194 cf.Update() 2195 out = cf.GetOutput() 2196 2197 if not out.GetNumberOfPoints(): 2198 return [self] 2199 2200 if flag: 2201 self.pipeline = OperationNode("split mesh", parents=[self]) 2202 return self._update(out) 2203 2204 a = Mesh(out) 2205 if must_share_edge: 2206 arr = a.celldata["RegionId"] 2207 on = "cells" 2208 else: 2209 arr = a.pointdata["RegionId"] 2210 on = "points" 2211 2212 alist = [] 2213 for t in range(max(arr) + 1): 2214 if t == maxdepth: 2215 break 2216 suba = a.clone().threshold("RegionId", t, t, on=on) 2217 if sort_by_area: 2218 area = suba.area() 2219 else: 2220 area = 0 # dummy 2221 alist.append([suba, area]) 2222 2223 if sort_by_area: 2224 alist.sort(key=lambda x: x[1]) 2225 alist.reverse() 2226 2227 blist = [] 2228 for i, l in enumerate(alist): 2229 l[0].color(i + 1).phong() 2230 l[0].mapper().ScalarVisibilityOff() 2231 blist.append(l[0]) 2232 if i < 10: 2233 l[0].pipeline = OperationNode( 2234 f"split mesh {i}", 2235 parents=[self], 2236 comment=f"#pts {l[0].inputdata().GetNumberOfPoints()}", 2237 ) 2238 return blist 2239 2240 2241 def extract_largest_region(self): 2242 """ 2243 Extract the largest connected part of a mesh and discard all the smaller pieces. 2244 2245 Examples: 2246 - [largestregion.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/largestregion.py) 2247 """ 2248 conn = vtk.vtkPolyDataConnectivityFilter() 2249 conn.SetExtractionModeToLargestRegion() 2250 conn.ScalarConnectivityOff() 2251 conn.SetInputData(self._data) 2252 conn.Update() 2253 m = Mesh(conn.GetOutput()) 2254 pr = vtk.vtkProperty() 2255 pr.DeepCopy(self.property) 2256 m.SetProperty(pr) 2257 m.property = pr 2258 # assign the same transformation 2259 m.SetOrigin(self.GetOrigin()) 2260 m.SetScale(self.GetScale()) 2261 m.SetOrientation(self.GetOrientation()) 2262 m.SetPosition(self.GetPosition()) 2263 vis = self._mapper.GetScalarVisibility() 2264 m.mapper().SetScalarVisibility(vis) 2265 2266 m.pipeline = OperationNode( 2267 "extract_largest_region", parents=[self], 2268 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 2269 ) 2270 return m 2271 2272 def boolean(self, operation, mesh2, method=0, tol=None): 2273 """Volumetric union, intersection and subtraction of surfaces. 2274 2275 Use `operation` for the allowed operations `['plus', 'intersect', 'minus']`. 2276 2277 Two possible algorithms are available by changing `method`. 2278 2279 Example: 2280 - [boolean.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boolean.py) 2281 2282 ![](https://vedo.embl.es/images/basic/boolean.png) 2283 """ 2284 if method == 0: 2285 bf = vtk.vtkBooleanOperationPolyDataFilter() 2286 elif method == 1: 2287 bf = vtk.vtkLoopBooleanPolyDataFilter() 2288 else: 2289 raise ValueError(f"Unknown method={method}") 2290 2291 poly1 = self.compute_normals().polydata() 2292 poly2 = mesh2.compute_normals().polydata() 2293 2294 if operation.lower() in ("plus", "+"): 2295 bf.SetOperationToUnion() 2296 elif operation.lower() == "intersect": 2297 bf.SetOperationToIntersection() 2298 elif operation.lower() in ("minus", "-"): 2299 bf.SetOperationToDifference() 2300 2301 if tol: 2302 bf.SetTolerance(tol) 2303 2304 bf.SetInputData(0, poly1) 2305 bf.SetInputData(1, poly2) 2306 bf.Update() 2307 2308 msh = Mesh(bf.GetOutput(), c=None) 2309 msh.flat() 2310 msh.name = self.name + operation + mesh2.name 2311 2312 msh.pipeline = OperationNode( 2313 "boolean " + operation, 2314 parents=[self, mesh2], 2315 shape="cylinder", 2316 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 2317 ) 2318 return msh 2319 2320 def intersect_with(self, mesh2, tol=1e-06): 2321 """ 2322 Intersect this Mesh with the input surface to return a set of lines. 2323 2324 Examples: 2325 - [surf_intersect.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/surf_intersect.py) 2326 2327 ![](https://vedo.embl.es/images/basic/surfIntersect.png) 2328 """ 2329 bf = vtk.vtkIntersectionPolyDataFilter() 2330 bf.SetGlobalWarningDisplay(0) 2331 poly1 = self.polydata() 2332 poly2 = mesh2.polydata() 2333 bf.SetTolerance(tol) 2334 bf.SetInputData(0, poly1) 2335 bf.SetInputData(1, poly2) 2336 bf.Update() 2337 msh = Mesh(bf.GetOutput(), "k", 1).lighting("off") 2338 msh.GetProperty().SetLineWidth(3) 2339 msh.name = "SurfaceIntersection" 2340 2341 msh.pipeline = OperationNode( 2342 "intersect_with", parents=[self, mesh2], comment=f"#pts {msh.npoints}" 2343 ) 2344 return msh 2345 2346 def intersect_with_line(self, p0, p1=None, return_ids=False, tol=0): 2347 """ 2348 Return the list of points intersecting the mesh 2349 along the segment defined by two points `p0` and `p1`. 2350 2351 Use `return_ids` to return the cell ids along with point coords 2352 2353 Example: 2354 ```python 2355 from vedo import * 2356 s = Spring() 2357 pts = s.intersect_with_line([0,0,0], [1,0.1,0]) 2358 ln = Line([0,0,0], [1,0.1,0], c='blue') 2359 ps = Points(pts, r=10, c='r') 2360 show(s, ln, ps, bg='white').close() 2361 ``` 2362 ![](https://user-images.githubusercontent.com/32848391/55967065-eee08300-5c79-11e9-8933-265e1bab9f7e.png) 2363 """ 2364 if isinstance(p0, Points): 2365 p0, p1 = p0.points() 2366 2367 if not self.line_locator: 2368 self.line_locator = vtk.vtkOBBTree() 2369 self.line_locator.SetDataSet(self.polydata()) 2370 if not tol: 2371 tol = mag(np.asarray(p1) - np.asarray(p0)) / 10000 2372 self.line_locator.SetTolerance(tol) 2373 self.line_locator.BuildLocator() 2374 2375 vpts = vtk.vtkPoints() 2376 idlist = vtk.vtkIdList() 2377 self.line_locator.IntersectWithLine(p0, p1, vpts, idlist) 2378 pts = [] 2379 for i in range(vpts.GetNumberOfPoints()): 2380 intersection = [0, 0, 0] 2381 vpts.GetPoint(i, intersection) 2382 pts.append(intersection) 2383 pts = np.array(pts) 2384 2385 if return_ids: 2386 pts_ids = [] 2387 for i in range(idlist.GetNumberOfIds()): 2388 cid = idlist.GetId(i) 2389 pts_ids.append(cid) 2390 return (pts, np.array(pts_ids).astype(np.uint32)) 2391 2392 return pts 2393 2394 def intersect_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0)): 2395 """ 2396 Intersect this Mesh with a plane to return a set of lines. 2397 2398 Example: 2399 ```python 2400 from vedo import * 2401 sph = Sphere() 2402 mi = sph.clone().intersect_with_plane().join() 2403 print(mi.lines()) 2404 show(sph, mi, axes=1).close() 2405 ``` 2406 ![](https://vedo.embl.es/images/feats/intersect_plane.png) 2407 """ 2408 plane = vtk.vtkPlane() 2409 plane.SetOrigin(origin) 2410 plane.SetNormal(normal) 2411 2412 cutter = vtk.vtkPolyDataPlaneCutter() 2413 cutter.SetInputData(self.polydata()) 2414 cutter.SetPlane(plane) 2415 cutter.InterpolateAttributesOn() 2416 cutter.ComputeNormalsOff() 2417 cutter.Update() 2418 2419 msh = Mesh(cutter.GetOutput(), "k", 1).lighting("off") 2420 msh.GetProperty().SetLineWidth(3) 2421 msh.name = "PlaneIntersection" 2422 2423 msh.pipeline = OperationNode( 2424 "intersect_with_plan", parents=[self], 2425 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}" 2426 ) 2427 return msh 2428 2429 # def intersect_with_multiplanes(self, origins, normals): ## WRONG 2430 # """ 2431 # Generate a set of lines from cutting a mesh in n intervals 2432 # between a minimum and maximum distance from a plane of given origin and normal. 2433 2434 # Arguments: 2435 # origin : (list) 2436 # the point of the cutting plane 2437 # normal : (list) 2438 # normal vector to the cutting plane 2439 # n : (int) 2440 # number of cuts 2441 # """ 2442 # poly = self.polydata() 2443 2444 # planes = vtk.vtkPlanes() 2445 # planes.SetOrigin(numpy2vtk(origins)) 2446 # planes.SetNormals(numpy2vtk(normals)) 2447 2448 # cutter = vtk.vtkCutter() 2449 # cutter.SetCutFunction(planes) 2450 # cutter.SetInputData(poly) 2451 # cutter.SetValue(0, 0.0) 2452 # cutter.Update() 2453 2454 # msh = Mesh(cutter.GetOutput()) 2455 # msh.property.LightingOff() 2456 # msh.property.SetColor(get_color("k2")) 2457 2458 # msh.pipeline = OperationNode( 2459 # "intersect_with_multiplanes", 2460 # parents=[self], 2461 # comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 2462 # ) 2463 # return msh 2464 2465 def collide_with(self, mesh2, tol=0, return_bool=False): 2466 """ 2467 Collide this Mesh with the input surface. 2468 Information is stored in `ContactCells1` and `ContactCells2`. 2469 """ 2470 ipdf = vtk.vtkCollisionDetectionFilter() 2471 # ipdf.SetGlobalWarningDisplay(0) 2472 2473 transform0 = vtk.vtkTransform() 2474 transform1 = vtk.vtkTransform() 2475 2476 # ipdf.SetBoxTolerance(tol) 2477 ipdf.SetCellTolerance(tol) 2478 ipdf.SetInputData(0, self.polydata()) 2479 ipdf.SetInputData(1, mesh2.polydata()) 2480 ipdf.SetTransform(0, transform0) 2481 ipdf.SetTransform(1, transform1) 2482 if return_bool: 2483 ipdf.SetCollisionModeToFirstContact() 2484 else: 2485 ipdf.SetCollisionModeToAllContacts() 2486 ipdf.Update() 2487 2488 if return_bool: 2489 return bool(ipdf.GetNumberOfContacts()) 2490 2491 msh = Mesh(ipdf.GetContactsOutput(), "k", 1).lighting("off") 2492 msh.metadata["ContactCells1"] = vtk2numpy( 2493 ipdf.GetOutput(0).GetFieldData().GetArray("ContactCells") 2494 ) 2495 msh.metadata["ContactCells2"] = vtk2numpy( 2496 ipdf.GetOutput(1).GetFieldData().GetArray("ContactCells") 2497 ) 2498 msh.GetProperty().SetLineWidth(3) 2499 msh.name = "SurfaceCollision" 2500 2501 msh.pipeline = OperationNode( 2502 "collide_with", parents=[self, mesh2], 2503 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}" 2504 ) 2505 return msh 2506 2507 def geodesic(self, start, end): 2508 """ 2509 Dijkstra algorithm to compute the geodesic line. 2510 Takes as input a polygonal mesh and performs a single source shortest path calculation. 2511 2512 Arguments: 2513 start : (int, list) 2514 start vertex index or close point `[x,y,z]` 2515 end : (int, list) 2516 end vertex index or close point `[x,y,z]` 2517 2518 Examples: 2519 - [geodesic.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic.py) 2520 2521 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2522 """ 2523 if is_sequence(start): 2524 cc = self.points() 2525 pa = Points(cc) 2526 start = pa.closest_point(start, return_point_id=True) 2527 end = pa.closest_point(end, return_point_id=True) 2528 2529 dijkstra = vtk.vtkDijkstraGraphGeodesicPath() 2530 dijkstra.SetInputData(self.polydata()) 2531 dijkstra.SetStartVertex(end) # inverted in vtk 2532 dijkstra.SetEndVertex(start) 2533 dijkstra.Update() 2534 2535 weights = vtk.vtkDoubleArray() 2536 dijkstra.GetCumulativeWeights(weights) 2537 2538 idlist = dijkstra.GetIdList() 2539 ids = [idlist.GetId(i) for i in range(idlist.GetNumberOfIds())] 2540 2541 length = weights.GetMaxId() + 1 2542 arr = np.zeros(length) 2543 for i in range(length): 2544 arr[i] = weights.GetTuple(i)[0] 2545 2546 poly = dijkstra.GetOutput() 2547 2548 vdata = numpy2vtk(arr) 2549 vdata.SetName("CumulativeWeights") 2550 poly.GetPointData().AddArray(vdata) 2551 2552 vdata2 = numpy2vtk(ids, dtype=np.uint) 2553 vdata2.SetName("VertexIDs") 2554 poly.GetPointData().AddArray(vdata2) 2555 poly.GetPointData().Modified() 2556 2557 dmesh = Mesh(poly, c="k") 2558 prop = vtk.vtkProperty() 2559 prop.DeepCopy(self.property) 2560 prop.SetLineWidth(3) 2561 prop.SetOpacity(1) 2562 dmesh.SetProperty(prop) 2563 dmesh.property = prop 2564 dmesh.name = "GeodesicLine" 2565 2566 dmesh.pipeline = OperationNode( 2567 "GeodesicLine", parents=[self], comment=f"#pts {dmesh._data.GetNumberOfPoints()}" 2568 ) 2569 return dmesh 2570 2571 ##################################################################### 2572 ### Stuff returning a Volume 2573 ### 2574 def binarize( 2575 self, 2576 spacing=(1, 1, 1), 2577 invert=False, 2578 direction_matrix=None, 2579 image_size=None, 2580 origin=None, 2581 fg_val=255, 2582 bg_val=0, 2583 ): 2584 """ 2585 Convert a `Mesh` into a `Volume` 2586 where the foreground (exterior) voxels value is fg_val (255 by default) 2587 and the background (interior) voxels value is bg_val (0 by default). 2588 2589 Examples: 2590 - [mesh2volume.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/mesh2volume.py) 2591 2592 ![](https://vedo.embl.es/images/volumetric/mesh2volume.png) 2593 """ 2594 # https://vtk.org/Wiki/VTK/Examples/Cxx/PolyData/PolyDataToImageData 2595 pd = self.polydata() 2596 2597 whiteImage = vtk.vtkImageData() 2598 if direction_matrix: 2599 whiteImage.SetDirectionMatrix(direction_matrix) 2600 2601 dim = [0, 0, 0] if not image_size else image_size 2602 2603 bounds = self.bounds() 2604 if not image_size: # compute dimensions 2605 dim = [0, 0, 0] 2606 for i in [0, 1, 2]: 2607 dim[i] = int(np.ceil((bounds[i * 2 + 1] - bounds[i * 2]) / spacing[i])) 2608 2609 whiteImage.SetDimensions(dim) 2610 whiteImage.SetSpacing(spacing) 2611 whiteImage.SetExtent(0, dim[0] - 1, 0, dim[1] - 1, 0, dim[2] - 1) 2612 2613 if not origin: 2614 origin = [0, 0, 0] 2615 origin[0] = bounds[0] + spacing[0] / 2 2616 origin[1] = bounds[2] + spacing[1] / 2 2617 origin[2] = bounds[4] + spacing[2] / 2 2618 whiteImage.SetOrigin(origin) 2619 whiteImage.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1) 2620 2621 # fill the image with foreground voxels: 2622 inval = bg_val if invert else fg_val 2623 whiteImage.GetPointData().GetScalars().Fill(inval) 2624 2625 # polygonal data --> image stencil: 2626 pol2stenc = vtk.vtkPolyDataToImageStencil() 2627 pol2stenc.SetInputData(pd) 2628 pol2stenc.SetOutputOrigin(whiteImage.GetOrigin()) 2629 pol2stenc.SetOutputSpacing(whiteImage.GetSpacing()) 2630 pol2stenc.SetOutputWholeExtent(whiteImage.GetExtent()) 2631 pol2stenc.Update() 2632 2633 # cut the corresponding white image and set the background: 2634 outval = fg_val if invert else bg_val 2635 2636 imgstenc = vtk.vtkImageStencil() 2637 imgstenc.SetInputData(whiteImage) 2638 imgstenc.SetStencilConnection(pol2stenc.GetOutputPort()) 2639 imgstenc.SetReverseStencil(invert) 2640 imgstenc.SetBackgroundValue(outval) 2641 imgstenc.Update() 2642 vol = vedo.Volume(imgstenc.GetOutput()) 2643 2644 vol.pipeline = OperationNode( 2645 "binarize", 2646 parents=[self], 2647 comment=f"dim = {tuple(vol.dimensions())}", 2648 c="#e9c46a:#0096c7", 2649 ) 2650 return vol 2651 2652 def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None): 2653 """ 2654 Compute the `Volume` object whose voxels contains the signed distance from 2655 the mesh. 2656 2657 Arguments: 2658 bounds : (list) 2659 bounds of the output volume 2660 dims : (list) 2661 dimensions (nr. of voxels) of the output volume 2662 invert : (bool) 2663 flip the sign 2664 2665 Examples: 2666 - [volumeFromMesh.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/volumeFromMesh.py) 2667 """ 2668 if maxradius is not None: 2669 vedo.logger.warning( 2670 "in signedDistance(maxradius=...) is ignored. (Only valid for pointclouds)." 2671 ) 2672 if bounds is None: 2673 bounds = self.bounds() 2674 sx = (bounds[1] - bounds[0]) / dims[0] 2675 sy = (bounds[3] - bounds[2]) / dims[1] 2676 sz = (bounds[5] - bounds[4]) / dims[2] 2677 2678 img = vtk.vtkImageData() 2679 img.SetDimensions(dims) 2680 img.SetSpacing(sx, sy, sz) 2681 img.SetOrigin(bounds[0], bounds[2], bounds[4]) 2682 img.AllocateScalars(vtk.VTK_FLOAT, 1) 2683 2684 imp = vtk.vtkImplicitPolyDataDistance() 2685 imp.SetInput(self.polydata()) 2686 b2 = bounds[2] 2687 b4 = bounds[4] 2688 d0, d1, d2 = dims 2689 2690 for i in range(d0): 2691 x = i * sx + bounds[0] 2692 for j in range(d1): 2693 y = j * sy + b2 2694 for k in range(d2): 2695 v = imp.EvaluateFunction((x, y, k * sz + b4)) 2696 if invert: 2697 v = -v 2698 img.SetScalarComponentFromFloat(i, j, k, 0, v) 2699 2700 vol = vedo.Volume(img) 2701 vol.name = "SignedVolume" 2702 2703 vol.pipeline = OperationNode( 2704 "signed_distance", 2705 parents=[self], 2706 comment=f"dim = {tuple(vol.dimensions())}", 2707 c="#e9c46a:#0096c7", 2708 ) 2709 return vol 2710 2711 def tetralize( 2712 self, side=0.02, nmax=300_000, gap=None, subsample=False, uniform=True, seed=0, debug=False 2713 ): 2714 """ 2715 Tetralize a closed polygonal mesh. Return a `TetMesh`. 2716 2717 Arguments: 2718 side : (float) 2719 desired side of the single tetras as fraction of the bounding box diagonal. 2720 Typical values are in the range (0.01 - 0.03) 2721 nmax : (int) 2722 maximum random numbers to be sampled in the bounding box 2723 gap : (float) 2724 keep this minimum distance from the surface, 2725 if None an automatic choice is made. 2726 subsample : (bool) 2727 subsample input surface, the geometry might be affected 2728 (the number of original faces reduceed), but higher tet quality might be obtained. 2729 uniform : (bool) 2730 generate tets more uniformly packed in the interior of the mesh 2731 seed : (int) 2732 random number generator seed 2733 debug : (bool) 2734 show an intermediate plot with sampled points 2735 2736 Examples: 2737 - [tetralize_surface.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tetralize_surface.py) 2738 2739 ![](https://vedo.embl.es/images/volumetric/tetralize_surface.jpg) 2740 """ 2741 surf = self.clone().clean().compute_normals() 2742 d = surf.diagonal_size() 2743 if gap is None: 2744 gap = side * d * np.sqrt(2 / 3) 2745 n = int(min((1 / side) ** 3, nmax)) 2746 2747 # fill the space w/ points 2748 x0, x1, y0, y1, z0, z1 = surf.bounds() 2749 2750 if uniform: 2751 pts = vedo.utils.pack_spheres([x0, x1, y0, y1, z0, z1], side * d * 1.42) 2752 pts += np.random.randn(len(pts), 3) * side * d * 1.42 / 100 # some small jitter 2753 else: 2754 disp = np.array([x0 + x1, y0 + y1, z0 + z1]) / 2 2755 np.random.seed(seed) 2756 pts = (np.random.rand(n, 3) - 0.5) * np.array([x1 - x0, y1 - y0, z1 - z0]) + disp 2757 2758 normals = surf.celldata["Normals"] 2759 cc = surf.cell_centers() 2760 subpts = cc - normals * gap * 1.05 2761 pts = pts.tolist() + subpts.tolist() 2762 2763 if debug: 2764 print(".. tetralize(): subsampling and cleaning") 2765 2766 fillpts = surf.inside_points(pts) 2767 fillpts.subsample(side) 2768 2769 if gap: 2770 fillpts.distance_to(surf) 2771 fillpts.threshold("Distance", above=gap) 2772 2773 if subsample: 2774 surf.subsample(side) 2775 2776 tmesh = vedo.tetmesh.delaunay3d(vedo.merge(fillpts, surf)) 2777 tcenters = tmesh.cell_centers() 2778 2779 ids = surf.inside_points(tcenters, return_ids=True) 2780 ins = np.zeros(tmesh.ncells) 2781 ins[ids] = 1 2782 2783 if debug: 2784 # vedo.pyplot.histogram(fillpts.pointdata["Distance"], xtitle=f"gap={gap}").show().close() 2785 edges = self.edges() 2786 points = self.points() 2787 elen = mag(points[edges][:, 0, :] - points[edges][:, 1, :]) 2788 histo = vedo.pyplot.histogram(elen, xtitle="edge length", xlim=(0, 3 * side * d)) 2789 print(".. edges min, max", elen.min(), elen.max()) 2790 fillpts.cmap("bone") 2791 vedo.show( 2792 [ 2793 [ 2794 f"This is a debug plot.\n\nGenerated points: {n}\ngap: {gap}", 2795 surf.wireframe().alpha(0.2), 2796 vedo.addons.Axes(surf), 2797 fillpts, 2798 Points(subpts).c("r4").ps(3), 2799 ], 2800 [f"Edges mean length: {np.mean(elen)}\n\nPress q to continue", histo], 2801 ], 2802 N=2, 2803 sharecam=False, 2804 new=True, 2805 ).close() 2806 print(".. thresholding") 2807 2808 tmesh.celldata["inside"] = ins.astype(np.uint8) 2809 tmesh.threshold("inside", above=0.9) 2810 tmesh.celldata.remove("inside") 2811 2812 if debug: 2813 print(f".. tetralize() completed, ntets = {tmesh.ncells}") 2814 2815 tmesh.pipeline = OperationNode( 2816 "tetralize", parents=[self], comment=f"#tets = {tmesh.ncells}", c="#e9c46a:#9e2a2b" 2817 ) 2818 return tmesh 2819 2820 2821#################################################### 2822class Follower(vedo.base.BaseActor, vtk.vtkFollower): 2823 2824 def __init__(self, actor, camera=None): 2825 2826 vtk.vtkFollower.__init__(self) 2827 vedo.base.BaseActor.__init__(self) 2828 2829 self.name = actor.name 2830 self._isfollower = False 2831 2832 self.SetMapper(actor.GetMapper()) 2833 2834 self.SetProperty(actor.GetProperty()) 2835 self.SetBackfaceProperty(actor.GetBackfaceProperty()) 2836 self.SetTexture(actor.GetTexture()) 2837 2838 self.SetCamera(camera) 2839 self.SetOrigin(actor.GetOrigin()) 2840 self.SetScale(actor.GetScale()) 2841 self.SetOrientation(actor.GetOrientation()) 2842 self.SetPosition(actor.GetPosition()) 2843 self.SetUseBounds(actor.GetUseBounds()) 2844 2845 self.PickableOff() 2846 2847 self.pipeline = OperationNode("Follower", parents=[actor], shape="component", c="#d9ed92")
31class Mesh(Points): 32 """ 33 Build an instance of object `Mesh` derived from `vedo.PointCloud`. 34 """ 35 36 def __init__(self, inputobj=None, c=None, alpha=1): 37 """ 38 Input can be a list of vertices and their connectivity (faces of the polygonal mesh), 39 or directly a `vtkPolydata` object. 40 For point clouds - e.i. no faces - just substitute the `faces` list with `None`. 41 42 Example: 43 `Mesh( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )` 44 45 Arguments: 46 c : (color) 47 color in RGB format, hex, symbol or name 48 alpha : (float) 49 mesh opacity [0,1] 50 51 Examples: 52 - [buildmesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buildmesh.py) 53 (and many others!) 54 55 ![](https://vedo.embl.es/images/basic/buildmesh.png) 56 """ 57 Points.__init__(self) 58 59 self.line_locator = None 60 61 self._mapper.SetInterpolateScalarsBeforeMapping( 62 vedo.settings.interpolate_scalars_before_mapping 63 ) 64 65 if vedo.settings.use_polygon_offset: 66 self._mapper.SetResolveCoincidentTopologyToPolygonOffset() 67 pof, pou = (vedo.settings.polygon_offset_factor, vedo.settings.polygon_offset_units) 68 self._mapper.SetResolveCoincidentTopologyPolygonOffsetParameters(pof, pou) 69 70 inputtype = str(type(inputobj)) 71 72 if inputobj is None: 73 pass 74 75 elif isinstance(inputobj, (Mesh, vtk.vtkActor)): 76 polyCopy = vtk.vtkPolyData() 77 polyCopy.DeepCopy(inputobj.GetMapper().GetInput()) 78 self._data = polyCopy 79 self._mapper.SetInputData(polyCopy) 80 self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility()) 81 pr = vtk.vtkProperty() 82 pr.DeepCopy(inputobj.GetProperty()) 83 self.SetProperty(pr) 84 self.property = pr 85 86 elif isinstance(inputobj, vtk.vtkPolyData): 87 if inputobj.GetNumberOfCells() == 0: 88 carr = vtk.vtkCellArray() 89 for i in range(inputobj.GetNumberOfPoints()): 90 carr.InsertNextCell(1) 91 carr.InsertCellPoint(i) 92 inputobj.SetVerts(carr) 93 self._data = inputobj # cache vtkPolyData and mapper for speed 94 95 elif isinstance(inputobj, (vtk.vtkStructuredGrid, vtk.vtkRectilinearGrid)): 96 gf = vtk.vtkGeometryFilter() 97 gf.SetInputData(inputobj) 98 gf.Update() 99 self._data = gf.GetOutput() 100 101 elif "trimesh" in inputtype: 102 tact = vedo.utils.trimesh2vedo(inputobj) 103 self._data = tact.polydata() 104 105 elif "meshio" in inputtype: 106 if len(inputobj.cells) > 0: 107 mcells = [] 108 for cellblock in inputobj.cells: 109 if cellblock.type in ("triangle", "quad"): 110 mcells += cellblock.data.tolist() 111 self._data = buildPolyData(inputobj.points, mcells) 112 else: 113 self._data = buildPolyData(inputobj.points, None) 114 # add arrays: 115 try: 116 if len(inputobj.point_data) > 0: 117 for k in inputobj.point_data.keys(): 118 vdata = numpy2vtk(inputobj.point_data[k]) 119 vdata.SetName(str(k)) 120 self._data.GetPointData().AddArray(vdata) 121 except AssertionError: 122 print("Could not add meshio point data, skip.") 123 # try: 124 # if len(inputobj.cell_data): 125 # for k in inputobj.cell_data.keys(): 126 # #print(inputobj.cell_data) 127 # exit() 128 # vdata = numpy2vtk(inputobj.cell_data[k]) 129 # vdata.SetName(str(k)) 130 # self._data.GetCellData().AddArray(vdata) 131 # except AssertionError: 132 # print("Could not add meshio cell data, skip.") 133 134 elif "meshlab" in inputtype: 135 self._data = vedo.utils.meshlab2vedo(inputobj) 136 137 elif is_sequence(inputobj): 138 ninp = len(inputobj) 139 if ninp == 0: 140 self._data = vtk.vtkPolyData() 141 elif ninp == 2: # assume [vertices, faces] 142 self._data = buildPolyData(inputobj[0], inputobj[1]) 143 else: # assume [vertices] or vertices 144 self._data = buildPolyData(inputobj, None) 145 146 elif hasattr(inputobj, "GetOutput"): # passing vtk object 147 if hasattr(inputobj, "Update"): 148 inputobj.Update() 149 if isinstance(inputobj.GetOutput(), vtk.vtkPolyData): 150 self._data = inputobj.GetOutput() 151 else: 152 gf = vtk.vtkGeometryFilter() 153 gf.SetInputData(inputobj.GetOutput()) 154 gf.Update() 155 self._data = gf.GetOutput() 156 157 elif isinstance(inputobj, str): 158 dataset = vedo.file_io.load(inputobj) 159 self.filename = inputobj 160 if "TetMesh" in str(type(dataset)): 161 self._data = dataset.tomesh().polydata(False) 162 else: 163 self._data = dataset.polydata(False) 164 165 else: 166 try: 167 gf = vtk.vtkGeometryFilter() 168 gf.SetInputData(inputobj) 169 gf.Update() 170 self._data = gf.GetOutput() 171 except: 172 vedo.logger.error(f"cannot build mesh from type {inputtype}") 173 raise RuntimeError() 174 175 self._mapper.SetInputData(self._data) 176 177 self.property = self.GetProperty() 178 self.property.SetInterpolationToPhong() 179 180 # set the color by c or by scalar 181 if self._data: 182 183 arrexists = False 184 185 if c is None: 186 ptdata = self._data.GetPointData() 187 cldata = self._data.GetCellData() 188 exclude = ["normals", "tcoord"] 189 190 if cldata.GetNumberOfArrays(): 191 for i in range(cldata.GetNumberOfArrays()): 192 iarr = cldata.GetArray(i) 193 if iarr: 194 icname = iarr.GetName() 195 if icname and all(s not in icname.lower() for s in exclude): 196 cldata.SetActiveScalars(icname) 197 self._mapper.ScalarVisibilityOn() 198 self._mapper.SetScalarModeToUseCellData() 199 self._mapper.SetScalarRange(iarr.GetRange()) 200 arrexists = True 201 break # stop at first good one 202 203 # point come after so it has priority 204 if ptdata.GetNumberOfArrays(): 205 for i in range(ptdata.GetNumberOfArrays()): 206 iarr = ptdata.GetArray(i) 207 if iarr: 208 ipname = iarr.GetName() 209 if ipname and all(s not in ipname.lower() for s in exclude): 210 ptdata.SetActiveScalars(ipname) 211 self._mapper.ScalarVisibilityOn() 212 self._mapper.SetScalarModeToUsePointData() 213 self._mapper.SetScalarRange(iarr.GetRange()) 214 arrexists = True 215 break # stop at first good one 216 217 if not arrexists: 218 if c is None: 219 c = "gold" 220 c = get_color(c) 221 elif isinstance(c, float) and c <= 1: 222 c = color_map(c, "rainbow", 0, 1) 223 else: 224 c = get_color(c) 225 self.property.SetColor(c) 226 self.property.SetAmbient(0.1) 227 self.property.SetDiffuse(1) 228 self.property.SetSpecular(0.05) 229 self.property.SetSpecularPower(5) 230 self._mapper.ScalarVisibilityOff() 231 232 if alpha is not None: 233 self.property.SetOpacity(alpha) 234 235 n = self._data.GetNumberOfPoints() 236 self.pipeline = OperationNode(self, comment=f"#pts {n}") 237 238 def _repr_html_(self): 239 """ 240 HTML representation of the Mesh object for Jupyter Notebooks. 241 242 Returns: 243 HTML text with the image and some properties. 244 """ 245 import io 246 import base64 247 from PIL import Image 248 249 library_name = "vedo.mesh.Mesh" 250 help_url = "https://vedo.embl.es/docs/vedo/mesh.html" 251 252 arr = self.thumbnail() 253 im = Image.fromarray(arr) 254 buffered = io.BytesIO() 255 im.save(buffered, format="PNG", quality=100) 256 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 257 url = "data:image/png;base64," + encoded 258 image = f"<img src='{url}'></img>" 259 260 bounds = "<br/>".join( 261 [ 262 precision(min_x, 4) + " ... " + precision(max_x, 4) 263 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 264 ] 265 ) 266 average_size = "{size:.3f}".format(size=self.average_size()) 267 268 help_text = "" 269 if self.name: 270 help_text += f"<b> {self.name}:   </b>" 271 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 272 if self.filename: 273 dots = "" 274 if len(self.filename) > 30: 275 dots = "..." 276 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 277 278 pdata = "" 279 if self._data.GetPointData().GetScalars(): 280 if self._data.GetPointData().GetScalars().GetName(): 281 name = self._data.GetPointData().GetScalars().GetName() 282 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 283 284 cdata = "" 285 if self._data.GetCellData().GetScalars(): 286 if self._data.GetCellData().GetScalars().GetName(): 287 name = self._data.GetCellData().GetScalars().GetName() 288 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 289 290 allt = [ 291 "<table>", 292 "<tr>", 293 "<td>", 294 image, 295 "</td>", 296 "<td style='text-align: center; vertical-align: center;'><br/>", 297 help_text, 298 "<table>", 299 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 300 "<tr><td><b> center of mass </b></td><td>" 301 + precision(self.center_of_mass(), 3) 302 + "</td></tr>", 303 "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>", 304 "<tr><td><b> nr. points / faces </b></td><td>" 305 + str(self.npoints) 306 + " / " 307 + str(self.ncells) 308 + "</td></tr>", 309 pdata, 310 cdata, 311 "</table>", 312 "</table>", 313 ] 314 return "\n".join(allt) 315 316 def faces(self): 317 """ 318 Get cell polygonal connectivity ids as a python `list`. 319 The output format is: `[[id0 ... idn], [id0 ... idm], etc]`. 320 """ 321 arr1d = vtk2numpy(self._data.GetPolys().GetData()) 322 if arr1d is None: 323 return [] 324 325 # Get cell connettivity ids as a 1D array. vtk format is: 326 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 327 if len(arr1d) == 0: 328 arr1d = vtk2numpy(self._data.GetStrips().GetData()) 329 if arr1d is None: 330 return [] 331 332 i = 0 333 conn = [] 334 n = len(arr1d) 335 if n: 336 while True: 337 cell = [arr1d[i + k] for k in range(1, arr1d[i] + 1)] 338 conn.append(cell) 339 i += arr1d[i] + 1 340 if i >= n: 341 break 342 return conn # cannot always make a numpy array of it! 343 344 def cells(self): 345 """Alias for `faces()`.""" 346 return self.faces() 347 348 def lines(self, flat=False): 349 """ 350 Get lines connectivity ids as a numpy array. 351 Default format is `[[id0,id1], [id3,id4], ...]` 352 353 Arguments: 354 flat : (bool) 355 return a 1D numpy array as e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...] 356 """ 357 # Get cell connettivity ids as a 1D array. The vtk format is: 358 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 359 arr1d = vtk2numpy(self.polydata(False).GetLines().GetData()) 360 361 if arr1d is None: 362 return [] 363 364 if flat: 365 return arr1d 366 367 i = 0 368 conn = [] 369 n = len(arr1d) 370 for _ in range(n): 371 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 372 conn.append(cell) 373 i += arr1d[i] + 1 374 if i >= n: 375 break 376 377 return conn # cannot always make a numpy array of it! 378 379 def edges(self): 380 """Return an array containing the edges connectivity.""" 381 extractEdges = vtk.vtkExtractEdges() 382 extractEdges.SetInputData(self._data) 383 # eed.UseAllPointsOn() 384 extractEdges.Update() 385 lpoly = extractEdges.GetOutput() 386 387 arr1d = vtk2numpy(lpoly.GetLines().GetData()) 388 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 389 390 i = 0 391 conn = [] 392 n = len(arr1d) 393 for _ in range(n): 394 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 395 conn.append(cell) 396 i += arr1d[i] + 1 397 if i >= n: 398 break 399 return conn # cannot always make a numpy array of it! 400 401 def texture( 402 self, 403 tname, 404 tcoords=None, 405 interpolate=True, 406 repeat=True, 407 edge_clamp=False, 408 scale=None, 409 ushift=None, 410 vshift=None, 411 seam_threshold=None, 412 ): 413 """ 414 Assign a texture to mesh from image file or predefined texture `tname`. 415 If tname is set to `None` texture is disabled. 416 Input tname can also be an array or a `vtkTexture`. 417 418 Arguments: 419 tname : (numpy.array, str, Picture, vtkTexture, None) 420 the input texture to be applied. Can be a numpy array, a path to an image file, 421 a vedo Picture. The None value disables texture. 422 tcoords : (numpy.array, str) 423 this is the (u,v) texture coordinate array. Can also be a string of an existing array 424 in the mesh. 425 interpolate : (bool) 426 turn on/off linear interpolation of the texture map when rendering. 427 repeat : (bool) 428 repeat of the texture when tcoords extend beyond the [0,1] range. 429 edge_clamp : (bool) 430 turn on/off the clamping of the texture map when 431 the texture coords extend beyond the [0,1] range. 432 Only used when repeat is False, and edge clamping is supported by the graphics card. 433 scale : (bool) 434 scale the texture image by this factor 435 ushift : (bool) 436 shift u-coordinates of texture by this amount 437 vshift : (bool) 438 shift v-coordinates of texture by this amount 439 seam_threshold : (float) 440 try to seal seams in texture by collapsing triangles 441 (test values around 1.0, lower values = stronger collapse) 442 443 Examples: 444 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 445 446 ![](https://vedo.embl.es/images/basic/texturecubes.png) 447 """ 448 pd = self.polydata(False) 449 outimg = None 450 451 if tname is None: # disable texture 452 pd.GetPointData().SetTCoords(None) 453 pd.GetPointData().Modified() 454 return self ###################################### 455 456 if isinstance(tname, vtk.vtkTexture): 457 tu = tname 458 459 elif isinstance(tname, vedo.Picture): 460 tu = vtk.vtkTexture() 461 outimg = tname.inputdata() 462 463 elif is_sequence(tname): 464 tu = vtk.vtkTexture() 465 outimg = vedo.picture._get_img(tname) 466 467 elif isinstance(tname, str): 468 tu = vtk.vtkTexture() 469 470 if "https://" in tname: 471 try: 472 tname = vedo.file_io.download(tname, verbose=False) 473 except: 474 vedo.logger.error(f"texture {tname} could not be downloaded") 475 return self 476 477 fn = tname + ".jpg" 478 if os.path.exists(tname): 479 fn = tname 480 else: 481 vedo.logger.error(f"texture file {tname} does not exist") 482 return self 483 484 fnl = fn.lower() 485 if ".jpg" in fnl or ".jpeg" in fnl: 486 reader = vtk.vtkJPEGReader() 487 elif ".png" in fnl: 488 reader = vtk.vtkPNGReader() 489 elif ".bmp" in fnl: 490 reader = vtk.vtkBMPReader() 491 else: 492 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 493 return self 494 reader.SetFileName(fn) 495 reader.Update() 496 outimg = reader.GetOutput() 497 498 else: 499 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 500 return self 501 502 if tcoords is not None: 503 504 if isinstance(tcoords, str): 505 506 vtarr = pd.GetPointData().GetArray(tcoords) 507 508 else: 509 510 tcoords = np.asarray(tcoords) 511 if tcoords.ndim != 2: 512 vedo.logger.error("tcoords must be a 2-dimensional array") 513 return self 514 if tcoords.shape[0] != pd.GetNumberOfPoints(): 515 vedo.logger.error("nr of texture coords must match nr of points") 516 return self 517 if tcoords.shape[1] != 2: 518 vedo.logger.error("tcoords texture vector must have 2 components") 519 vtarr = numpy2vtk(tcoords) 520 vtarr.SetName("TCoordinates") 521 522 pd.GetPointData().SetTCoords(vtarr) 523 pd.GetPointData().Modified() 524 525 elif not pd.GetPointData().GetTCoords(): 526 527 # TCoords still void.. 528 # check that there are no texture-like arrays: 529 names = self.pointdata.keys() 530 candidate_arr = "" 531 for name in names: 532 vtarr = pd.GetPointData().GetArray(name) 533 if vtarr.GetNumberOfComponents() != 2: 534 continue 535 t0, t1 = vtarr.GetRange() 536 if t0 >= 0 and t1 <= 1: 537 candidate_arr = name 538 539 if candidate_arr: 540 541 vtarr = pd.GetPointData().GetArray(candidate_arr) 542 pd.GetPointData().SetTCoords(vtarr) 543 pd.GetPointData().Modified() 544 545 else: 546 # last resource is automatic mapping 547 tmapper = vtk.vtkTextureMapToPlane() 548 tmapper.AutomaticPlaneGenerationOn() 549 tmapper.SetInputData(pd) 550 tmapper.Update() 551 tc = tmapper.GetOutput().GetPointData().GetTCoords() 552 if scale or ushift or vshift: 553 ntc = vtk2numpy(tc) 554 if scale: 555 ntc *= scale 556 if ushift: 557 ntc[:, 0] += ushift 558 if vshift: 559 ntc[:, 1] += vshift 560 tc = numpy2vtk(tc) 561 pd.GetPointData().SetTCoords(tc) 562 pd.GetPointData().Modified() 563 564 if outimg: 565 tu.SetInputData(outimg) 566 tu.SetInterpolate(interpolate) 567 tu.SetRepeat(repeat) 568 tu.SetEdgeClamp(edge_clamp) 569 570 self.property.SetColor(1, 1, 1) 571 self._mapper.ScalarVisibilityOff() 572 self.SetTexture(tu) 573 574 if seam_threshold is not None: 575 tname = self._data.GetPointData().GetTCoords().GetName() 576 grad = self.gradient(tname) 577 ugrad, vgrad = np.split(grad, 2, axis=1) 578 ugradm, vgradm = vedo.utils.mag2(ugrad), vedo.utils.mag2(vgrad) 579 gradm = np.log(ugradm + vgradm) 580 largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 581 uvmap = self.pointdata[tname] 582 # collapse triangles that have large gradient 583 new_points = self.points(transformed=False) 584 for f in self.faces(): 585 if np.isin(f, largegrad_ids).all(): 586 id1, id2, id3 = f 587 uv1, uv2, uv3 = uvmap[f] 588 d12 = vedo.mag2(uv1 - uv2) 589 d23 = vedo.mag2(uv2 - uv3) 590 d31 = vedo.mag2(uv3 - uv1) 591 idm = np.argmin([d12, d23, d31]) 592 if idm == 0: 593 new_points[id1] = new_points[id3] 594 new_points[id2] = new_points[id3] 595 elif idm == 1: 596 new_points[id2] = new_points[id1] 597 new_points[id3] = new_points[id1] 598 self.points(new_points) 599 600 self.Modified() 601 return self 602 603 def compute_normals(self, points=True, cells=True, feature_angle=None, consistency=True): 604 """ 605 Compute cell and vertex normals for the mesh. 606 607 Arguments: 608 points : (bool) 609 do the computation for the vertices too 610 cells : (bool) 611 do the computation for the cells too 612 feature_angle : (float) 613 specify the angle that defines a sharp edge. 614 If the difference in angle across neighboring polygons is greater than this value, 615 the shared edge is considered "sharp" and it is split. 616 consistency : (bool) 617 turn on/off the enforcement of consistent polygon ordering. 618 619 .. warning:: 620 If feature_angle is set to a float the Mesh can be modified, and it 621 can have a different nr. of vertices from the original. 622 """ 623 poly = self.polydata(False) 624 pdnorm = vtk.vtkPolyDataNormals() 625 pdnorm.SetInputData(poly) 626 pdnorm.SetComputePointNormals(points) 627 pdnorm.SetComputeCellNormals(cells) 628 pdnorm.SetConsistency(consistency) 629 pdnorm.FlipNormalsOff() 630 if feature_angle: 631 pdnorm.SetSplitting(True) 632 pdnorm.SetFeatureAngle(feature_angle) 633 else: 634 pdnorm.SetSplitting(False) 635 # print(pdnorm.GetNonManifoldTraversal()) 636 pdnorm.Update() 637 return self._update(pdnorm.GetOutput()) 638 639 def reverse(self, cells=True, normals=False): 640 """ 641 Reverse the order of polygonal cells 642 and/or reverse the direction of point and cell normals. 643 Two flags are used to control these operations: 644 645 - `cells=True` reverses the order of the indices in the cell connectivity list. 646 If cell is a list of IDs only those cells will be reversed. 647 648 - `normals=True` reverses the normals by multiplying the normal vector by -1 649 (both point and cell normals, if present). 650 """ 651 poly = self.polydata(False) 652 653 if is_sequence(cells): 654 for cell in cells: 655 poly.ReverseCell(cell) 656 poly.GetCellData().Modified() 657 return self ############## 658 659 rev = vtk.vtkReverseSense() 660 if cells: 661 rev.ReverseCellsOn() 662 else: 663 rev.ReverseCellsOff() 664 if normals: 665 rev.ReverseNormalsOn() 666 else: 667 rev.ReverseNormalsOff() 668 rev.SetInputData(poly) 669 rev.Update() 670 out = self._update(rev.GetOutput()) 671 out.pipeline = OperationNode("reverse", parents=[self]) 672 return out 673 674 def wireframe(self, value=True): 675 """Set mesh's representation as wireframe or solid surface.""" 676 if value: 677 self.property.SetRepresentationToWireframe() 678 else: 679 self.property.SetRepresentationToSurface() 680 return self 681 682 def flat(self): 683 """Set surface interpolation to flat. 684 685 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 686 """ 687 self.property.SetInterpolationToFlat() 688 return self 689 690 def phong(self): 691 """Set surface interpolation to "phong".""" 692 self.property.SetInterpolationToPhong() 693 return self 694 695 def backface_culling(self, value=True): 696 """Set culling of polygons based on orientation of normal with respect to camera.""" 697 self.property.SetBackfaceCulling(value) 698 return self 699 700 def render_lines_as_tubes(self, value=True): 701 """Wrap a fake tube around a simple line for visualization""" 702 self.property.SetRenderLinesAsTubes(value) 703 return self 704 705 def frontface_culling(self, value=True): 706 """Set culling of polygons based on orientation of normal with respect to camera.""" 707 self.property.SetFrontfaceCulling(value) 708 return self 709 710 def backcolor(self, bc=None): 711 """ 712 Set/get mesh's backface color. 713 """ 714 backProp = self.GetBackfaceProperty() 715 716 if bc is None: 717 if backProp: 718 return backProp.GetDiffuseColor() 719 return self 720 721 if self.property.GetOpacity() < 1: 722 return self 723 724 if not backProp: 725 backProp = vtk.vtkProperty() 726 727 backProp.SetDiffuseColor(get_color(bc)) 728 backProp.SetOpacity(self.property.GetOpacity()) 729 self.SetBackfaceProperty(backProp) 730 self._mapper.ScalarVisibilityOff() 731 return self 732 733 def bc(self, backcolor=False): 734 """Shortcut for `mesh.backcolor()`.""" 735 return self.backcolor(backcolor) 736 737 def linewidth(self, lw=None): 738 """Set/get width of mesh edges. Same as `lw()`.""" 739 if lw is not None: 740 if lw == 0: 741 self.property.EdgeVisibilityOff() 742 self.property.SetRepresentationToSurface() 743 return self 744 self.property.EdgeVisibilityOn() 745 self.property.SetLineWidth(lw) 746 else: 747 return self.property.GetLineWidth() 748 return self 749 750 def lw(self, linewidth=None): 751 """Set/get width of mesh edges. Same as `linewidth()`.""" 752 return self.linewidth(linewidth) 753 754 def linecolor(self, lc=None): 755 """Set/get color of mesh edges. Same as `lc()`.""" 756 if lc is None: 757 return self.property.GetEdgeColor() 758 self.property.EdgeVisibilityOn() 759 self.property.SetEdgeColor(get_color(lc)) 760 return self 761 762 def lc(self, linecolor=None): 763 """Set/get color of mesh edges. Same as `linecolor()`.""" 764 return self.linecolor(linecolor) 765 766 def volume(self): 767 """Get/set the volume occupied by mesh.""" 768 mass = vtk.vtkMassProperties() 769 mass.SetGlobalWarningDisplay(0) 770 mass.SetInputData(self.polydata()) 771 mass.Update() 772 return mass.GetVolume() 773 774 def area(self): 775 """ 776 Compute the surface area of mesh. 777 The mesh must be triangular for this to work. 778 See also `mesh.triangulate()`. 779 """ 780 mass = vtk.vtkMassProperties() 781 mass.SetGlobalWarningDisplay(0) 782 mass.SetInputData(self.polydata()) 783 mass.Update() 784 return mass.GetSurfaceArea() 785 786 def is_closed(self): 787 """Return `True` if the mesh is watertight.""" 788 fe = vtk.vtkFeatureEdges() 789 fe.BoundaryEdgesOn() 790 fe.FeatureEdgesOff() 791 fe.NonManifoldEdgesOn() 792 fe.SetInputData(self.polydata(False)) 793 fe.Update() 794 ne = fe.GetOutput().GetNumberOfCells() 795 return not bool(ne) 796 797 def is_manifold(self): 798 """Return `True` if the mesh is manifold.""" 799 fe = vtk.vtkFeatureEdges() 800 fe.BoundaryEdgesOff() 801 fe.FeatureEdgesOff() 802 fe.NonManifoldEdgesOn() 803 fe.SetInputData(self.polydata(False)) 804 fe.Update() 805 ne = fe.GetOutput().GetNumberOfCells() 806 return not bool(ne) 807 808 def non_manifold_faces(self, remove=True, tol="auto"): 809 """ 810 Detect and (try to) remove non-manifold faces of a triangular mesh. 811 812 Set `remove` to `False` to mark cells without removing them. 813 Set `tol=0` for zero-tolerance, the result will be manifold but with holes. 814 Set `tol>0` to cut off non-manifold faces, and try to recover the good ones. 815 Set `tol="auto"` to make an automatic choice of the tolerance. 816 """ 817 # mark original point and cell ids 818 self.add_ids() 819 toremove = self.boundaries( 820 boundary_edges=False, non_manifold_edges=True, cell_edge=True, return_cell_ids=True 821 ) 822 if len(toremove) == 0: 823 return self 824 825 points = self.points() 826 faces = self.faces() 827 centers = self.cell_centers() 828 829 copy = self.clone() 830 copy.delete_cells(toremove).clean() 831 copy.compute_normals(cells=False) 832 normals = copy.normals() 833 deltas, deltas_i = [], [] 834 835 for i in vedo.utils.progressbar(toremove, delay=3, title="recover faces"): 836 pids = copy.closest_point(centers[i], n=3, return_point_id=True) 837 norms = normals[pids] 838 n = np.mean(norms, axis=0) 839 dn = np.linalg.norm(n) 840 if not dn: 841 continue 842 n = n / dn 843 844 p0, p1, p2 = points[faces[i]][:3] 845 v = np.cross(p1 - p0, p2 - p0) 846 lv = np.linalg.norm(v) 847 if not lv: 848 continue 849 v = v / lv 850 851 cosa = 1 - np.dot(n, v) 852 deltas.append(cosa) 853 deltas_i.append(i) 854 855 recover = [] 856 if len(deltas) > 0: 857 mean_delta = np.mean(deltas) 858 err_delta = np.std(deltas) 859 txt = "" 860 if tol == "auto": # automatic choice 861 tol = mean_delta / 5 862 txt = f"\n Automatic tol. : {tol: .4f}" 863 for i, cosa in zip(deltas_i, deltas): 864 if cosa < tol: 865 recover.append(i) 866 867 vedo.logger.info( 868 f"\n --------- Non manifold faces ---------" 869 f"\n Average tol. : {mean_delta: .4f} +- {err_delta: .4f}{txt}" 870 f"\n Removed faces : {len(toremove)}" 871 f"\n Recovered faces: {len(recover)}" 872 ) 873 874 toremove = list(set(toremove) - set(recover)) 875 876 if not remove: 877 mark = np.zeros(self.ncells, dtype=np.uint8) 878 mark[recover] = 1 879 mark[toremove] = 2 880 self.celldata["NonManifoldCell"] = mark 881 else: 882 self.delete_cells(toremove) 883 884 self.pipeline = OperationNode( 885 "non_manifold_faces", parents=[self], comment=f"#cells {self._data.GetNumberOfCells()}" 886 ) 887 return self 888 889 def shrink(self, fraction=0.85): 890 """Shrink the triangle polydata in the representation of the input mesh. 891 892 Examples: 893 - [shrink.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shrink.py) 894 895 ![](https://vedo.embl.es/images/basic/shrink.png) 896 """ 897 shrink = vtk.vtkShrinkPolyData() 898 shrink.SetInputData(self._data) 899 shrink.SetShrinkFactor(fraction) 900 shrink.Update() 901 self.point_locator = None 902 self.cell_locator = None 903 out = self._update(shrink.GetOutput()) 904 905 out.pipeline = OperationNode("shrink", parents=[self]) 906 return out 907 908 def stretch(self, q1, q2): 909 """ 910 Stretch mesh between points `q1` and `q2`. 911 912 Examples: 913 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 914 915 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 916 917 .. note:: 918 for `Mesh` objects, two vectors `mesh.base`, and `mesh.top` must be defined. 919 """ 920 if self.base is None: 921 vedo.logger.error("in stretch() must define vectors mesh.base and mesh.top at creation") 922 raise RuntimeError() 923 924 p1, p2 = self.base, self.top 925 q1, q2, z = np.asarray(q1), np.asarray(q2), np.array([0, 0, 1]) 926 a = p2 - p1 927 b = q2 - q1 928 plength = np.linalg.norm(a) 929 qlength = np.linalg.norm(b) 930 T = vtk.vtkTransform() 931 T.PostMultiply() 932 T.Translate(-p1) 933 cosa = np.dot(a, z) / plength 934 n = np.cross(a, z) 935 if np.linalg.norm(n): 936 T.RotateWXYZ(np.rad2deg(np.arccos(cosa)), n) 937 T.Scale(1, 1, qlength / plength) 938 939 cosa = np.dot(b, z) / qlength 940 n = np.cross(b, z) 941 if np.linalg.norm(n): 942 T.RotateWXYZ(-np.rad2deg(np.arccos(cosa)), n) 943 else: 944 if np.dot(b, z) < 0: 945 T.RotateWXYZ(180, [1, 0, 0]) 946 947 T.Translate(q1) 948 949 self.SetUserMatrix(T.GetMatrix()) 950 return self 951 952 def crop(self, top=None, bottom=None, right=None, left=None, front=None, back=None): 953 """ 954 Crop an `Mesh` object. 955 Use this method at creation (before moving the object). 956 957 Arguments: 958 top : (float) 959 fraction to crop from the top plane (positive z) 960 bottom : (float) 961 fraction to crop from the bottom plane (negative z) 962 front : (float) 963 fraction to crop from the front plane (positive y) 964 back : (float) 965 fraction to crop from the back plane (negative y) 966 right : (float) 967 fraction to crop from the right plane (positive x) 968 left : (float) 969 fraction to crop from the left plane (negative x) 970 971 Example: 972 ```python 973 from vedo import Sphere 974 Sphere().crop(right=0.3, left=0.1).show() 975 ``` 976 ![](https://user-images.githubusercontent.com/32848391/57081955-0ef1e800-6cf6-11e9-99de-b45220939bc9.png) 977 """ 978 cu = vtk.vtkBox() 979 pos = np.array(self.GetPosition()) 980 x0, x1, y0, y1, z0, z1 = self.bounds() 981 x0, y0, z0 = [x0, y0, z0] - pos 982 x1, y1, z1 = [x1, y1, z1] - pos 983 984 dx, dy, dz = x1 - x0, y1 - y0, z1 - z0 985 if top: 986 z1 = z1 - top * dz 987 if bottom: 988 z0 = z0 + bottom * dz 989 if front: 990 y1 = y1 - front * dy 991 if back: 992 y0 = y0 + back * dy 993 if right: 994 x1 = x1 - right * dx 995 if left: 996 x0 = x0 + left * dx 997 bounds = (x0, x1, y0, y1, z0, z1) 998 999 cu.SetBounds(bounds) 1000 1001 clipper = vtk.vtkClipPolyData() 1002 clipper.SetInputData(self._data) 1003 clipper.SetClipFunction(cu) 1004 clipper.InsideOutOn() 1005 clipper.GenerateClippedOutputOff() 1006 clipper.GenerateClipScalarsOff() 1007 clipper.SetValue(0) 1008 clipper.Update() 1009 self._update(clipper.GetOutput()) 1010 self.point_locator = None 1011 1012 self.pipeline = OperationNode( 1013 "crop", parents=[self], comment=f"#pts {self._data.GetNumberOfPoints()}" 1014 ) 1015 return self 1016 1017 def cap(self, return_cap=False): 1018 """ 1019 Generate a "cap" on a clipped mesh, or caps sharp edges. 1020 1021 Examples: 1022 - [cut_and_cap.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/cut_and_cap.py) 1023 1024 ![](https://vedo.embl.es/images/advanced/cutAndCap.png) 1025 1026 See also: `join()`, `join_segments()`, `slice()`. 1027 """ 1028 poly = self._data 1029 1030 fe = vtk.vtkFeatureEdges() 1031 fe.SetInputData(poly) 1032 fe.BoundaryEdgesOn() 1033 fe.FeatureEdgesOff() 1034 fe.NonManifoldEdgesOff() 1035 fe.ManifoldEdgesOff() 1036 fe.Update() 1037 1038 stripper = vtk.vtkStripper() 1039 stripper.SetInputData(fe.GetOutput()) 1040 stripper.JoinContiguousSegmentsOn() 1041 stripper.Update() 1042 1043 boundaryPoly = vtk.vtkPolyData() 1044 boundaryPoly.SetPoints(stripper.GetOutput().GetPoints()) 1045 boundaryPoly.SetPolys(stripper.GetOutput().GetLines()) 1046 1047 rev = vtk.vtkReverseSense() 1048 rev.ReverseCellsOn() 1049 rev.SetInputData(boundaryPoly) 1050 rev.Update() 1051 1052 tf = vtk.vtkTriangleFilter() 1053 tf.SetInputData(rev.GetOutput()) 1054 tf.Update() 1055 1056 if return_cap: 1057 m = Mesh(tf.GetOutput()) 1058 # assign the same transformation to the copy 1059 m.SetOrigin(self.GetOrigin()) 1060 m.SetScale(self.GetScale()) 1061 m.SetOrientation(self.GetOrientation()) 1062 m.SetPosition(self.GetPosition()) 1063 1064 m.pipeline = OperationNode( 1065 "cap", parents=[self], comment=f"#pts {m._data.GetNumberOfPoints()}" 1066 ) 1067 return m 1068 1069 polyapp = vtk.vtkAppendPolyData() 1070 polyapp.AddInputData(poly) 1071 polyapp.AddInputData(tf.GetOutput()) 1072 polyapp.Update() 1073 out = self._update(polyapp.GetOutput()).clean() 1074 1075 out.pipeline = OperationNode( 1076 "capped", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1077 ) 1078 return out 1079 1080 def join(self, polys=True, reset=False): 1081 """ 1082 Generate triangle strips and/or polylines from 1083 input polygons, triangle strips, and lines. 1084 1085 Input polygons are assembled into triangle strips only if they are triangles; 1086 other types of polygons are passed through to the output and not stripped. 1087 Use mesh.triangulate() to triangulate non-triangular polygons prior to running 1088 this filter if you need to strip all the data. 1089 1090 Also note that if triangle strips or polylines are present in the input 1091 they are passed through and not joined nor extended. 1092 If you wish to strip these use mesh.triangulate() to fragment the input 1093 into triangles and lines prior to applying join(). 1094 1095 Arguments: 1096 polys : (bool) 1097 polygonal segments will be joined if they are contiguous 1098 reset : (bool) 1099 reset points ordering 1100 1101 Warning: 1102 If triangle strips or polylines exist in the input data 1103 they will be passed through to the output data. 1104 This filter will only construct triangle strips if triangle polygons 1105 are available; and will only construct polylines if lines are available. 1106 1107 Example: 1108 ```python 1109 from vedo import * 1110 c1 = Cylinder(pos=(0,0,0), r=2, height=3, axis=(1,.0,0), alpha=.1).triangulate() 1111 c2 = Cylinder(pos=(0,0,2), r=1, height=2, axis=(0,.3,1), alpha=.1).triangulate() 1112 intersect = c1.intersect_with(c2).join(reset=True) 1113 spline = Spline(intersect).c('blue').lw(5) 1114 show(c1, c2, spline, intersect.labels('id'), axes=1).close() 1115 ``` 1116 ![](https://vedo.embl.es/images/feats/line_join.png) 1117 """ 1118 sf = vtk.vtkStripper() 1119 sf.SetPassThroughCellIds(True) 1120 sf.SetPassThroughPointIds(True) 1121 sf.SetJoinContiguousSegments(polys) 1122 sf.SetInputData(self.polydata(False)) 1123 sf.Update() 1124 if reset: 1125 poly = sf.GetOutput() 1126 cpd = vtk.vtkCleanPolyData() 1127 cpd.PointMergingOn() 1128 cpd.ConvertLinesToPointsOn() 1129 cpd.ConvertPolysToLinesOn() 1130 cpd.ConvertStripsToPolysOn() 1131 cpd.SetInputData(poly) 1132 cpd.Update() 1133 poly = cpd.GetOutput() 1134 vpts = poly.GetCell(0).GetPoints().GetData() 1135 poly.GetPoints().SetData(vpts) 1136 return self._update(poly) 1137 1138 out = self._update(sf.GetOutput()) 1139 out.pipeline = OperationNode( 1140 "join", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1141 ) 1142 return out 1143 1144 def join_segments(self, closed=True, tol=1e-03): 1145 """ 1146 Join line segments into contiguous lines. 1147 Useful to call with `triangulate()` method. 1148 1149 Returns: 1150 list of `shapes.Lines` 1151 1152 Example: 1153 ```python 1154 from vedo import * 1155 msh = Torus().alpha(0.1).wireframe() 1156 intersection = msh.intersect_with_plane(normal=[1,1,1]).c('purple5') 1157 slices = [s.triangulate() for s in intersection.join_segments()] 1158 show(msh, intersection, merge(slices), axes=1, viewup='z') 1159 ``` 1160 ![](https://vedo.embl.es/images/feats/join_segments.jpg) 1161 """ 1162 vlines = [] 1163 for ipiece, outline in enumerate(self.split(must_share_edge=False)): 1164 1165 outline.clean() 1166 pts = outline.points() 1167 if len(pts) < 3: 1168 continue 1169 avesize = outline.average_size() 1170 lines = outline.lines() 1171 # print("---lines", lines, "in piece", ipiece) 1172 tol = avesize / pts.shape[0] * tol 1173 1174 k = 0 1175 joinedpts = [pts[k]] 1176 for _ in range(len(pts)): 1177 pk = pts[k] 1178 for j, line in enumerate(lines): 1179 1180 id0, id1 = line[0], line[-1] 1181 p0, p1 = pts[id0], pts[id1] 1182 1183 if np.linalg.norm(p0 - pk) < tol: 1184 n = len(line) 1185 for m in range(1, n): 1186 joinedpts.append(pts[line[m]]) 1187 # joinedpts.append(p1) 1188 k = id1 1189 lines.pop(j) 1190 break 1191 1192 elif np.linalg.norm(p1 - pk) < tol: 1193 n = len(line) 1194 for m in reversed(range(0, n - 1)): 1195 joinedpts.append(pts[line[m]]) 1196 # joinedpts.append(p0) 1197 k = id0 1198 lines.pop(j) 1199 break 1200 1201 if len(joinedpts) > 1: 1202 newline = vedo.shapes.Line(joinedpts, closed=closed) 1203 newline.clean() 1204 newline.SetProperty(self.GetProperty()) 1205 newline.property = self.GetProperty() 1206 newline.pipeline = OperationNode( 1207 "join_segments", 1208 parents=[self], 1209 comment=f"#pts {newline._data.GetNumberOfPoints()}", 1210 ) 1211 vlines.append(newline) 1212 1213 return vlines 1214 1215 def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)): 1216 """ 1217 Slice a mesh with a plane and fill the contour. 1218 1219 Example: 1220 ```python 1221 from vedo import * 1222 msh = Mesh(dataurl+"bunny.obj").alpha(0.1).wireframe() 1223 mslice = msh.slice(normal=[0,1,0.3], origin=[0,0.16,0]) 1224 mslice.c('purple5') 1225 show(msh, mslice, axes=1) 1226 ``` 1227 ![](https://vedo.embl.es/images/feats/mesh_slice.jpg) 1228 1229 See also: `join()`, `join_segments()`, `cap()`, `cut_with_plane()`. 1230 """ 1231 intersection = self.intersect_with_plane(origin=origin, normal=normal) 1232 slices = [s.triangulate() for s in intersection.join_segments()] 1233 mslices = vedo.pointcloud.merge(slices) 1234 if mslices: 1235 mslices.name = "MeshSlice" 1236 mslices.pipeline = OperationNode("slice", parents=[self], comment=f"normal = {normal}") 1237 return mslices 1238 1239 def triangulate(self, verts=True, lines=True): 1240 """ 1241 Converts mesh polygons into triangles. 1242 1243 If the input mesh is only made of 2D lines (no faces) the output will be a triangulation 1244 that fills the internal area. The contours may be concave, and may even contain holes, 1245 i.e. a contour may contain an internal contour winding in the opposite 1246 direction to indicate that it is a hole. 1247 1248 Arguments: 1249 verts : (bool) 1250 if True, break input vertex cells into individual vertex cells 1251 (one point per cell). If False, the input vertex cells will be ignored. 1252 lines : (bool) 1253 if True, break input polylines into line segments. 1254 If False, input lines will be ignored and the output will have no lines. 1255 """ 1256 if self._data.GetNumberOfPolys() or self._data.GetNumberOfStrips(): 1257 # print("vtkTriangleFilter") 1258 tf = vtk.vtkTriangleFilter() 1259 tf.SetPassLines(lines) 1260 tf.SetPassVerts(verts) 1261 1262 elif self._data.GetNumberOfLines(): 1263 # print("vtkContourTriangulator") 1264 tf = vtk.vtkContourTriangulator() 1265 tf.TriangulationErrorDisplayOn() 1266 1267 else: 1268 vedo.logger.debug("input in triangulate() seems to be void! Skip.") 1269 return self 1270 1271 tf.SetInputData(self._data) 1272 tf.Update() 1273 out = self._update(tf.GetOutput()).lw(0).lighting("default") 1274 out.PickableOn() 1275 1276 out.pipeline = OperationNode( 1277 "triangulate", parents=[self], comment=f"#cells {out.inputdata().GetNumberOfCells()}" 1278 ) 1279 return out 1280 1281 def compute_cell_area(self, name="Area"): 1282 """Add to this mesh a cell data array containing the areas of the polygonal faces""" 1283 csf = vtk.vtkCellSizeFilter() 1284 csf.SetInputData(self.polydata(False)) 1285 csf.SetComputeArea(True) 1286 csf.SetComputeVolume(False) 1287 csf.SetComputeLength(False) 1288 csf.SetComputeVertexCount(False) 1289 csf.SetAreaArrayName(name) 1290 csf.Update() 1291 return self._update(csf.GetOutput()) 1292 1293 def compute_cell_vertex_count(self, name="VertexCount"): 1294 """Add to this mesh a cell data array containing the nr of vertices 1295 that a polygonal face has.""" 1296 csf = vtk.vtkCellSizeFilter() 1297 csf.SetInputData(self.polydata(False)) 1298 csf.SetComputeArea(False) 1299 csf.SetComputeVolume(False) 1300 csf.SetComputeLength(False) 1301 csf.SetComputeVertexCount(True) 1302 csf.SetVertexCountArrayName(name) 1303 csf.Update() 1304 return self._update(csf.GetOutput()) 1305 1306 def compute_quality(self, metric=6): 1307 """ 1308 Calculate metrics of quality for the elements of a triangular mesh. 1309 This method adds to the mesh a cell array named "Quality". 1310 See class [vtkMeshQuality](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html) 1311 for explanation. 1312 1313 Arguments: 1314 metric : (int) 1315 type of available estimators are: 1316 - EDGE RATIO, 0 1317 - ASPECT RATIO, 1 1318 - RADIUS RATIO, 2 1319 - ASPECT FROBENIUS, 3 1320 - MED ASPECT FROBENIUS, 4 1321 - MAX ASPECT FROBENIUS, 5 1322 - MIN_ANGLE, 6 1323 - COLLAPSE RATIO, 7 1324 - MAX ANGLE, 8 1325 - CONDITION, 9 1326 - SCALED JACOBIAN, 10 1327 - SHEAR, 11 1328 - RELATIVE SIZE SQUARED, 12 1329 - SHAPE, 13 1330 - SHAPE AND SIZE, 14 1331 - DISTORTION, 15 1332 - MAX EDGE RATIO, 16 1333 - SKEW, 17 1334 - TAPER, 18 1335 - VOLUME, 19 1336 - STRETCH, 20 1337 - DIAGONAL, 21 1338 - DIMENSION, 22 1339 - ODDY, 23 1340 - SHEAR AND SIZE, 24 1341 - JACOBIAN, 25 1342 - WARPAGE, 26 1343 - ASPECT GAMMA, 27 1344 - AREA, 28 1345 - ASPECT BETA, 29 1346 1347 Examples: 1348 - [meshquality.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/meshquality.py) 1349 1350 ![](https://vedo.embl.es/images/advanced/meshquality.png) 1351 """ 1352 qf = vtk.vtkMeshQuality() 1353 qf.SetInputData(self.polydata(False)) 1354 qf.SetTriangleQualityMeasure(metric) 1355 qf.SaveCellQualityOn() 1356 qf.Update() 1357 pd = qf.GetOutput() 1358 self._update(pd) 1359 self.pipeline = OperationNode("compute_quality", parents=[self]) 1360 return self 1361 1362 def check_validity(self, tol=0): 1363 """ 1364 Return an array of possible problematic faces following this convention: 1365 - Valid = 0 1366 - WrongNumberOfPoints = 1 1367 - IntersectingEdges = 2 1368 - IntersectingFaces = 4 1369 - NoncontiguousEdges = 8 1370 - Nonconvex = 10 1371 - OrientedIncorrectly = 20 1372 1373 Arguments: 1374 tol : (float) 1375 value is used as an epsilon for floating point 1376 equality checks throughout the cell checking process. 1377 """ 1378 vald = vtk.vtkCellValidator() 1379 if tol: 1380 vald.SetTolerance(tol) 1381 vald.SetInputData(self._data) 1382 vald.Update() 1383 varr = vald.GetOutput().GetCellData().GetArray("ValidityState") 1384 return vtk2numpy(varr) 1385 1386 def compute_curvature(self, method=0): 1387 """ 1388 Add scalars to `Mesh` that contains the curvature calculated in three different ways. 1389 1390 Variable `method` can be: 1391 - 0 = gaussian 1392 - 1 = mean curvature 1393 - 2 = max curvature 1394 - 3 = min curvature 1395 1396 Example: 1397 ```python 1398 from vedo import Torus 1399 Torus().compute_curvature().add_scalarbar().show(axes=1).close() 1400 ``` 1401 ![](https://user-images.githubusercontent.com/32848391/51934810-c2e88c00-2404-11e9-8e7e-ca0b7984bbb7.png) 1402 """ 1403 curve = vtk.vtkCurvatures() 1404 curve.SetInputData(self._data) 1405 curve.SetCurvatureType(method) 1406 curve.Update() 1407 self._update(curve.GetOutput()) 1408 self._mapper.ScalarVisibilityOn() 1409 return self 1410 1411 def compute_elevation(self, low=(0, 0, 0), high=(0, 0, 1), vrange=(0, 1)): 1412 """ 1413 Add to `Mesh` a scalar array that contains distance along a specified direction. 1414 1415 Arguments: 1416 low : (list) 1417 one end of the line (small scalar values) 1418 high : (list) 1419 other end of the line (large scalar values) 1420 vrange : (list) 1421 set the range of the scalar 1422 1423 Example: 1424 ```python 1425 from vedo import Sphere 1426 s = Sphere().compute_elevation(low=(0,0,0), high=(1,1,1)) 1427 s.add_scalarbar().show(axes=1).close() 1428 ``` 1429 ![](https://user-images.githubusercontent.com/32848391/68478872-3986a580-0231-11ea-8245-b68a683aa295.png) 1430 """ 1431 ef = vtk.vtkElevationFilter() 1432 ef.SetInputData(self.polydata()) 1433 ef.SetLowPoint(low) 1434 ef.SetHighPoint(high) 1435 ef.SetScalarRange(vrange) 1436 ef.Update() 1437 self._update(ef.GetOutput()) 1438 self._mapper.ScalarVisibilityOn() 1439 return self 1440 1441 def subdivide(self, n=1, method=0, mel=None): 1442 """ 1443 Increase the number of vertices of a surface mesh. 1444 1445 Arguments: 1446 n : (int) 1447 number of subdivisions. 1448 method : (int) 1449 Loop(0), Linear(1), Adaptive(2), Butterfly(3), Centroid(4) 1450 mel : (float) 1451 Maximum Edge Length (applicable to Adaptive method only). 1452 """ 1453 triangles = vtk.vtkTriangleFilter() 1454 triangles.SetInputData(self._data) 1455 triangles.Update() 1456 originalMesh = triangles.GetOutput() 1457 if method == 0: 1458 sdf = vtk.vtkLoopSubdivisionFilter() 1459 elif method == 1: 1460 sdf = vtk.vtkLinearSubdivisionFilter() 1461 elif method == 2: 1462 sdf = vtk.vtkAdaptiveSubdivisionFilter() 1463 if mel is None: 1464 mel = self.diagonal_size() / np.sqrt(self._data.GetNumberOfPoints()) / n 1465 sdf.SetMaximumEdgeLength(mel) 1466 elif method == 3: 1467 sdf = vtk.vtkButterflySubdivisionFilter() 1468 elif method == 4: 1469 sdf = vtk.vtkDensifyPolyData() 1470 else: 1471 vedo.logger.error(f"in subdivide() unknown method {method}") 1472 raise RuntimeError() 1473 1474 if method != 2: 1475 sdf.SetNumberOfSubdivisions(n) 1476 1477 sdf.SetInputData(originalMesh) 1478 sdf.Update() 1479 out = sdf.GetOutput() 1480 1481 self.pipeline = OperationNode( 1482 "subdivide", parents=[self], comment=f"#pts {out.GetNumberOfPoints()}" 1483 ) 1484 return self._update(out) 1485 1486 def decimate(self, fraction=0.5, n=None, method="quadric", boundaries=False): 1487 """ 1488 Downsample the number of vertices in a mesh to `fraction`. 1489 1490 Arguments: 1491 fraction : (float) 1492 the desired target of reduction. 1493 n : (int) 1494 the desired number of final points (`fraction` is recalculated based on it). 1495 method : (str) 1496 can be either 'quadric' or 'pro'. In the first case triagulation 1497 will look like more regular, irrespective of the mesh original curvature. 1498 In the second case triangles are more irregular but mesh is more precise on more 1499 curved regions. 1500 boundaries : (bool) 1501 in "pro" mode decide whether to leave boundaries untouched or not 1502 1503 .. note:: Setting `fraction=0.1` leaves 10% of the original number of vertices 1504 """ 1505 poly = self._data 1506 if n: # N = desired number of points 1507 npt = poly.GetNumberOfPoints() 1508 fraction = n / npt 1509 if fraction >= 1: 1510 return self 1511 1512 if "quad" in method: 1513 decimate = vtk.vtkQuadricDecimation() 1514 # decimate.SetVolumePreservation(True) 1515 1516 else: 1517 decimate = vtk.vtkDecimatePro() 1518 decimate.PreserveTopologyOn() 1519 if boundaries: 1520 decimate.BoundaryVertexDeletionOff() 1521 else: 1522 decimate.BoundaryVertexDeletionOn() 1523 decimate.SetInputData(poly) 1524 decimate.SetTargetReduction(1 - fraction) 1525 decimate.Update() 1526 out = decimate.GetOutput() 1527 1528 self.pipeline = OperationNode( 1529 "decimate", parents=[self], comment=f"#pts {out.GetNumberOfPoints()}" 1530 ) 1531 return self._update(out) 1532 1533 def collapse_edges(self, distance, iterations=1): 1534 """Collapse mesh edges so that are all above distance.""" 1535 self.clean() 1536 x0, x1, y0, y1, z0, z1 = self.bounds() 1537 fs = min(x1 - x0, y1 - y0, z1 - z0) / 10 1538 d2 = distance * distance 1539 if distance > fs: 1540 vedo.logger.error(f"distance parameter is too large, should be < {fs}, skip!") 1541 return self 1542 for _ in range(iterations): 1543 medges = self.edges() 1544 pts = self.points() 1545 newpts = np.array(pts) 1546 moved = [] 1547 for e in medges: 1548 if len(e) == 2: 1549 id0, id1 = e 1550 p0, p1 = pts[id0], pts[id1] 1551 d = mag2(p1 - p0) 1552 if d < d2 and id0 not in moved and id1 not in moved: 1553 p = (p0 + p1) / 2 1554 newpts[id0] = p 1555 newpts[id1] = p 1556 moved += [id0, id1] 1557 1558 self.points(newpts) 1559 self.clean() 1560 self.compute_normals() 1561 1562 self.pipeline = OperationNode( 1563 "collapse_edges", parents=[self], comment=f"#pts {self._data.GetNumberOfPoints()}" 1564 ) 1565 return self 1566 1567 def smooth(self, niter=15, pass_band=0.1, edge_angle=15, feature_angle=60, boundary=False): 1568 """ 1569 Adjust mesh point positions using the `Windowed Sinc` function interpolation kernel. 1570 1571 Arguments: 1572 niter : (int) 1573 number of iterations. 1574 pass_band : (float) 1575 set the pass_band value for the windowed sinc filter. 1576 edge_angle : (float) 1577 edge angle to control smoothing along edges (either interior or boundary). 1578 feature_angle : (float) 1579 specifies the feature angle for sharp edge identification. 1580 boundary : (bool) 1581 specify if boundary should also be smoothed or kept unmodified 1582 1583 Examples: 1584 - [mesh_smoother1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/mesh_smoother1.py) 1585 1586 ![](https://vedo.embl.es/images/advanced/mesh_smoother2.png) 1587 """ 1588 poly = self._data 1589 cl = vtk.vtkCleanPolyData() 1590 cl.SetInputData(poly) 1591 cl.Update() 1592 smf = vtk.vtkWindowedSincPolyDataFilter() 1593 smf.SetInputData(cl.GetOutput()) 1594 smf.SetNumberOfIterations(niter) 1595 smf.SetEdgeAngle(edge_angle) 1596 smf.SetFeatureAngle(feature_angle) 1597 smf.SetPassBand(pass_band) 1598 smf.NormalizeCoordinatesOn() 1599 smf.NonManifoldSmoothingOn() 1600 smf.FeatureEdgeSmoothingOn() 1601 smf.SetBoundarySmoothing(boundary) 1602 smf.Update() 1603 out = self._update(smf.GetOutput()) 1604 1605 out.pipeline = OperationNode( 1606 "smooth", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1607 ) 1608 return out 1609 1610 1611 def fill_holes(self, size=None): 1612 """ 1613 Identifies and fills holes in input mesh. 1614 Holes are identified by locating boundary edges, linking them together into loops, 1615 and then triangulating the resulting loops. 1616 1617 Arguments: 1618 size : (float) 1619 Approximate limit to the size of the hole that can be filled. The default is None. 1620 1621 Examples: 1622 - [fillholes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fillholes.py) 1623 """ 1624 fh = vtk.vtkFillHolesFilter() 1625 if not size: 1626 mb = self.diagonal_size() 1627 size = mb / 10 1628 fh.SetHoleSize(size) 1629 fh.SetInputData(self._data) 1630 fh.Update() 1631 out = self._update(fh.GetOutput()) 1632 1633 out.pipeline = OperationNode( 1634 "fill_holes", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1635 ) 1636 return out 1637 1638 def is_inside(self, point, tol=1e-05): 1639 """Return True if point is inside a polydata closed surface.""" 1640 poly = self.polydata() 1641 points = vtk.vtkPoints() 1642 points.InsertNextPoint(point) 1643 pointsPolydata = vtk.vtkPolyData() 1644 pointsPolydata.SetPoints(points) 1645 sep = vtk.vtkSelectEnclosedPoints() 1646 sep.SetTolerance(tol) 1647 sep.CheckSurfaceOff() 1648 sep.SetInputData(pointsPolydata) 1649 sep.SetSurfaceData(poly) 1650 sep.Update() 1651 return sep.IsInside(0) 1652 1653 def inside_points(self, pts, invert=False, tol=1e-05, return_ids=False): 1654 """ 1655 Return the point cloud that is inside mesh surface as a new Points object. 1656 1657 If return_ids is True a list of IDs is returned and in addition input points 1658 are marked by a pointdata array named "IsInside". 1659 1660 Example: 1661 `print(pts.pointdata["IsInside"])` 1662 1663 Examples: 1664 - [pca_ellipsoid.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/pca_ellipsoid.py) 1665 1666 ![](https://vedo.embl.es/images/basic/pca.png) 1667 """ 1668 if isinstance(pts, Points): 1669 pointsPolydata = pts.polydata() 1670 ptsa = pts.points() 1671 else: 1672 ptsa = np.asarray(pts) 1673 vpoints = vtk.vtkPoints() 1674 vpoints.SetData(numpy2vtk(ptsa, dtype=np.float32)) 1675 pointsPolydata = vtk.vtkPolyData() 1676 pointsPolydata.SetPoints(vpoints) 1677 1678 sep = vtk.vtkSelectEnclosedPoints() 1679 # sep = vtk.vtkExtractEnclosedPoints() 1680 sep.SetTolerance(tol) 1681 sep.SetInputData(pointsPolydata) 1682 sep.SetSurfaceData(self.polydata()) 1683 sep.SetInsideOut(invert) 1684 sep.Update() 1685 1686 varr = sep.GetOutput().GetPointData().GetArray("SelectedPoints") 1687 mask = vtk2numpy(varr).astype(bool) 1688 ids = np.array(range(len(ptsa)), dtype=int)[mask] 1689 1690 if isinstance(pts, Points): 1691 varr.SetName("IsInside") 1692 pts.inputdata().GetPointData().AddArray(varr) 1693 1694 if return_ids: 1695 return ids 1696 1697 pcl = Points(ptsa[ids]) 1698 pcl.name = "InsidePoints" 1699 1700 pcl.pipeline = OperationNode( 1701 "inside_points", parents=[self, ptsa], 1702 comment=f"#pts {pcl.inputdata().GetNumberOfPoints()}" 1703 ) 1704 return pcl 1705 1706 def boundaries( 1707 self, 1708 boundary_edges=True, 1709 manifold_edges=False, 1710 non_manifold_edges=False, 1711 feature_angle=None, 1712 return_point_ids=False, 1713 return_cell_ids=False, 1714 cell_edge=False, 1715 ): 1716 """ 1717 Return the boundary lines of an input mesh. 1718 Check also `vedo.base.BaseActor.mark_boundaries()` method. 1719 1720 Arguments: 1721 boundary_edges : (bool) 1722 Turn on/off the extraction of boundary edges. 1723 manifold_edges : (bool) 1724 Turn on/off the extraction of manifold edges. 1725 non_manifold_edges : (bool) 1726 Turn on/off the extraction of non-manifold edges. 1727 feature_angle : (bool) 1728 Specify the min angle btw 2 faces for extracting edges. 1729 return_point_ids : (bool) 1730 return a numpy array of point indices 1731 return_cell_ids : (bool) 1732 return a numpy array of cell indices 1733 cell_edge : (bool) 1734 set to `True` if a cell need to share an edge with 1735 the boundary line, or `False` if a single vertex is enough 1736 1737 Examples: 1738 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1739 1740 ![](https://vedo.embl.es/images/basic/boundaries.png) 1741 """ 1742 fe = vtk.vtkFeatureEdges() 1743 fe.SetBoundaryEdges(boundary_edges) 1744 fe.SetNonManifoldEdges(non_manifold_edges) 1745 fe.SetManifoldEdges(manifold_edges) 1746 # fe.SetPassLines(True) # vtk9.2 1747 fe.ColoringOff() 1748 fe.SetFeatureEdges(False) 1749 if feature_angle is not None: 1750 fe.SetFeatureEdges(True) 1751 fe.SetFeatureAngle(feature_angle) 1752 1753 if return_point_ids or return_cell_ids: 1754 idf = vtk.vtkIdFilter() 1755 idf.SetInputData(self.polydata()) 1756 idf.SetPointIdsArrayName("BoundaryIds") 1757 idf.SetPointIds(True) 1758 idf.Update() 1759 1760 fe.SetInputData(idf.GetOutput()) 1761 fe.Update() 1762 1763 vid = fe.GetOutput().GetPointData().GetArray("BoundaryIds") 1764 npid = vtk2numpy(vid).astype(int) 1765 1766 if return_point_ids: 1767 return npid 1768 1769 if return_cell_ids: 1770 n = 1 if cell_edge else 0 1771 inface = [] 1772 for i, face in enumerate(self.faces()): 1773 # isin = np.any([vtx in npid for vtx in face]) 1774 isin = 0 1775 for vtx in face: 1776 isin += int(vtx in npid) 1777 if isin > n: 1778 break 1779 if isin > n: 1780 inface.append(i) 1781 return np.array(inface).astype(int) 1782 1783 return self 1784 1785 else: 1786 1787 fe.SetInputData(self.polydata()) 1788 fe.Update() 1789 msh = Mesh(fe.GetOutput(), c="p").lw(5).lighting("off") 1790 1791 msh.pipeline = OperationNode( 1792 "boundaries", 1793 parents=[self], 1794 shape="octagon", 1795 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 1796 ) 1797 return msh 1798 1799 def imprint(self, loopline, tol=0.01): 1800 """ 1801 Imprint the contact surface of one object onto another surface. 1802 1803 Arguments: 1804 loopline : vedo.shapes.Line 1805 a Line object to be imprinted onto the mesh. 1806 tol : (float), optional 1807 projection tolerance which controls how close the imprint 1808 surface must be to the target. 1809 1810 Example: 1811 ```python 1812 from vedo import * 1813 grid = Grid()#.triangulate() 1814 circle = Circle(r=0.3, res=24).pos(0.11,0.12) 1815 line = Line(circle, closed=True, lw=4, c='r4') 1816 grid.imprint(line) 1817 show(grid, line, axes=1).close() 1818 ``` 1819 ![](https://vedo.embl.es/images/feats/imprint.png) 1820 """ 1821 loop = vtk.vtkContourLoopExtraction() 1822 loop.SetInputData(loopline.polydata()) 1823 loop.Update() 1824 1825 clean_loop = vtk.vtkCleanPolyData() 1826 clean_loop.SetInputData(loop.GetOutput()) 1827 clean_loop.Update() 1828 1829 imp = vtk.vtkImprintFilter() 1830 imp.SetTargetData(self.polydata()) 1831 imp.SetImprintData(clean_loop.GetOutput()) 1832 imp.SetTolerance(tol) 1833 imp.BoundaryEdgeInsertionOn() 1834 imp.TriangulateOutputOn() 1835 imp.Update() 1836 out = self._update(imp.GetOutput()) 1837 1838 out.pipeline = OperationNode( 1839 "imprint", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1840 ) 1841 return out 1842 1843 def connected_vertices(self, index): 1844 """Find all vertices connected to an input vertex specified by its index. 1845 1846 Examples: 1847 - [connected_vtx.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/connected_vtx.py) 1848 1849 ![](https://vedo.embl.es/images/basic/connVtx.png) 1850 """ 1851 poly = self._data 1852 1853 cell_idlist = vtk.vtkIdList() 1854 poly.GetPointCells(index, cell_idlist) 1855 1856 idxs = [] 1857 for i in range(cell_idlist.GetNumberOfIds()): 1858 point_idlist = vtk.vtkIdList() 1859 poly.GetCellPoints(cell_idlist.GetId(i), point_idlist) 1860 for j in range(point_idlist.GetNumberOfIds()): 1861 idj = point_idlist.GetId(j) 1862 if idj == index: 1863 continue 1864 if idj in idxs: 1865 continue 1866 idxs.append(idj) 1867 1868 return idxs 1869 1870 def connected_cells(self, index, return_ids=False): 1871 """Find all cellls connected to an input vertex specified by its index.""" 1872 1873 # Find all cells connected to point index 1874 dpoly = self._data 1875 idlist = vtk.vtkIdList() 1876 dpoly.GetPointCells(index, idlist) 1877 1878 ids = vtk.vtkIdTypeArray() 1879 ids.SetNumberOfComponents(1) 1880 rids = [] 1881 for k in range(idlist.GetNumberOfIds()): 1882 cid = idlist.GetId(k) 1883 ids.InsertNextValue(cid) 1884 rids.append(int(cid)) 1885 if return_ids: 1886 return rids 1887 1888 selection_node = vtk.vtkSelectionNode() 1889 selection_node.SetFieldType(vtk.vtkSelectionNode.CELL) 1890 selection_node.SetContentType(vtk.vtkSelectionNode.INDICES) 1891 selection_node.SetSelectionList(ids) 1892 selection = vtk.vtkSelection() 1893 selection.AddNode(selection_node) 1894 extractSelection = vtk.vtkExtractSelection() 1895 extractSelection.SetInputData(0, dpoly) 1896 extractSelection.SetInputData(1, selection) 1897 extractSelection.Update() 1898 gf = vtk.vtkGeometryFilter() 1899 gf.SetInputData(extractSelection.GetOutput()) 1900 gf.Update() 1901 return Mesh(gf.GetOutput()).lw(1) 1902 1903 def silhouette(self, direction=None, border_edges=True, feature_angle=False): 1904 """ 1905 Return a new line `Mesh` which corresponds to the outer `silhouette` 1906 of the input as seen along a specified `direction`, this can also be 1907 a `vtkCamera` object. 1908 1909 Arguments: 1910 direction : (list) 1911 viewpoint direction vector. 1912 If *None* this is guessed by looking at the minimum 1913 of the sides of the bounding box. 1914 border_edges : (bool) 1915 enable or disable generation of border edges 1916 feature_angle : (float) 1917 minimal angle for sharp edges detection. 1918 If set to `False` the functionality is disabled. 1919 1920 Examples: 1921 - [silhouette1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette1.py) 1922 1923 ![](https://vedo.embl.es/images/basic/silhouette1.png) 1924 """ 1925 sil = vtk.vtkPolyDataSilhouette() 1926 sil.SetInputData(self.polydata()) 1927 sil.SetBorderEdges(border_edges) 1928 if feature_angle is False: 1929 sil.SetEnableFeatureAngle(0) 1930 else: 1931 sil.SetEnableFeatureAngle(1) 1932 sil.SetFeatureAngle(feature_angle) 1933 1934 if direction is None and vedo.plotter_instance and vedo.plotter_instance.camera: 1935 sil.SetCamera(vedo.plotter_instance.camera) 1936 m = Mesh() 1937 m.mapper().SetInputConnection(sil.GetOutputPort()) 1938 1939 elif isinstance(direction, vtk.vtkCamera): 1940 sil.SetCamera(direction) 1941 m = Mesh() 1942 m.mapper().SetInputConnection(sil.GetOutputPort()) 1943 1944 elif direction == "2d": 1945 sil.SetVector(3.4, 4.5, 5.6) # random 1946 sil.SetDirectionToSpecifiedVector() 1947 sil.Update() 1948 m = Mesh(sil.GetOutput()) 1949 1950 elif is_sequence(direction): 1951 sil.SetVector(direction) 1952 sil.SetDirectionToSpecifiedVector() 1953 sil.Update() 1954 m = Mesh(sil.GetOutput()) 1955 else: 1956 vedo.logger.error(f"in silhouette() unknown direction type {type(direction)}") 1957 vedo.logger.error("first render the scene with show() or specify camera/direction") 1958 return self 1959 1960 m.lw(2).c((0, 0, 0)).lighting("off") 1961 m.mapper().SetResolveCoincidentTopologyToPolygonOffset() 1962 m.pipeline = OperationNode("silhouette", parents=[self]) 1963 return m 1964 1965 1966 def follow_camera(self, camera=None): 1967 """ 1968 Return an object that will follow camera movements and stay locked to it. 1969 Use `mesh.follow_camera(False)` to disable it. 1970 1971 A `vtkCamera` object can also be passed. 1972 """ 1973 if camera is False: 1974 try: 1975 self.SetCamera(None) 1976 return self 1977 except AttributeError: 1978 return self 1979 1980 factor = Follower(self, camera) 1981 1982 if isinstance(camera, vtk.vtkCamera): 1983 factor.SetCamera(camera) 1984 else: 1985 plt = vedo.plotter_instance 1986 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 1987 factor.SetCamera(plt.renderer.GetActiveCamera()) 1988 else: 1989 factor._isfollower = True # postpone to show() call 1990 1991 return factor 1992 1993 def isobands(self, n=10, vmin=None, vmax=None): 1994 """ 1995 Return a new `Mesh` representing the isobands of the active scalars. 1996 This is a new mesh where the scalar is now associated to cell faces and 1997 used to colorize the mesh. 1998 1999 Arguments: 2000 n : (int) 2001 number of isobands in the range 2002 vmin : (float) 2003 minimum of the range 2004 vmax : (float) 2005 maximum of the range 2006 2007 Examples: 2008 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/isolines.py) 2009 """ 2010 r0, r1 = self._data.GetScalarRange() 2011 if vmin is None: 2012 vmin = r0 2013 if vmax is None: 2014 vmax = r1 2015 2016 # -------------------------------- 2017 bands = [] 2018 dx = (vmax - vmin) / float(n) 2019 b = [vmin, vmin + dx / 2.0, vmin + dx] 2020 i = 0 2021 while i < n: 2022 bands.append(b) 2023 b = [b[0] + dx, b[1] + dx, b[2] + dx] 2024 i += 1 2025 2026 # annotate, use the midpoint of the band as the label 2027 lut = self.mapper().GetLookupTable() 2028 labels = [] 2029 for b in bands: 2030 labels.append("{:4.2f}".format(b[1])) 2031 values = vtk.vtkVariantArray() 2032 for la in labels: 2033 values.InsertNextValue(vtk.vtkVariant(la)) 2034 for i in range(values.GetNumberOfTuples()): 2035 lut.SetAnnotation(i, values.GetValue(i).ToString()) 2036 2037 bcf = vtk.vtkBandedPolyDataContourFilter() 2038 bcf.SetInputData(self.polydata()) 2039 # Use either the minimum or maximum value for each band. 2040 for i, band in enumerate(bands): 2041 bcf.SetValue(i, band[2]) 2042 # We will use an indexed lookup table. 2043 bcf.SetScalarModeToIndex() 2044 bcf.GenerateContourEdgesOff() 2045 bcf.Update() 2046 bcf.GetOutput().GetCellData().GetScalars().SetName("IsoBands") 2047 m1 = Mesh(bcf.GetOutput()).compute_normals(cells=True) 2048 m1.mapper().SetLookupTable(lut) 2049 2050 m1.pipeline = OperationNode("isobands", parents=[self]) 2051 return m1 2052 2053 def isolines(self, n=10, vmin=None, vmax=None): 2054 """ 2055 Return a new `Mesh` representing the isolines of the active scalars. 2056 2057 Arguments: 2058 n : (int) 2059 number of isolines in the range 2060 vmin : (float) 2061 minimum of the range 2062 vmax : (float) 2063 maximum of the range 2064 2065 Examples: 2066 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/isolines.py) 2067 2068 ![](https://vedo.embl.es/images/pyplot/isolines.png) 2069 """ 2070 bcf = vtk.vtkContourFilter() 2071 bcf.SetInputData(self.polydata()) 2072 r0, r1 = self._data.GetScalarRange() 2073 if vmin is None: 2074 vmin = r0 2075 if vmax is None: 2076 vmax = r1 2077 bcf.GenerateValues(n, vmin, vmax) 2078 bcf.Update() 2079 sf = vtk.vtkStripper() 2080 sf.SetJoinContiguousSegments(True) 2081 sf.SetInputData(bcf.GetOutput()) 2082 sf.Update() 2083 cl = vtk.vtkCleanPolyData() 2084 cl.SetInputData(sf.GetOutput()) 2085 cl.Update() 2086 msh = Mesh(cl.GetOutput(), c="k").lighting("off") 2087 msh.mapper().SetResolveCoincidentTopologyToPolygonOffset() 2088 msh.pipeline = OperationNode("isolines", parents=[self]) 2089 return msh 2090 2091 def extrude(self, zshift=1, rotation=0, dR=0, cap=True, res=1): 2092 """ 2093 Sweep a polygonal data creating a "skirt" from free edges and lines, and lines from vertices. 2094 The input dataset is swept around the z-axis to create new polygonal primitives. 2095 For example, sweeping a line results in a cylindrical shell, and sweeping a circle creates a torus. 2096 2097 You can control whether the sweep of a 2D object (i.e., polygon or triangle strip) 2098 is capped with the generating geometry. 2099 Also, you can control the angle of rotation, and whether translation along the z-axis 2100 is performed along with the rotation. (Translation is useful for creating "springs"). 2101 You also can adjust the radius of the generating geometry using the "dR" keyword. 2102 2103 The skirt is generated by locating certain topological features. 2104 Free edges (edges of polygons or triangle strips only used by one polygon or triangle strips) 2105 generate surfaces. This is true also of lines or polylines. Vertices generate lines. 2106 2107 This filter can be used to model axisymmetric objects like cylinders, bottles, and wine glasses; 2108 or translational/rotational symmetric objects like springs or corkscrews. 2109 2110 Warning: 2111 Some polygonal objects have no free edges (e.g., sphere). When swept, this will result 2112 in two separate surfaces if capping is on, or no surface if capping is off. 2113 2114 Examples: 2115 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2116 2117 ![](https://vedo.embl.es/images/basic/extrude.png) 2118 """ 2119 if is_sequence(zshift): 2120 # ms = [] # todo 2121 # poly0 = self.clone().polydata() 2122 # for i in range(len(zshift)-1): 2123 # rf = vtk.vtkRotationalExtrusionFilter() 2124 # rf.SetInputData(poly0) 2125 # rf.SetResolution(res) 2126 # rf.SetCapping(0) 2127 # rf.SetAngle(rotation) 2128 # rf.SetTranslation(zshift) 2129 # rf.SetDeltaRadius(dR) 2130 # rf.Update() 2131 # poly1 = rf.GetOutput() 2132 return self 2133 2134 rf = vtk.vtkRotationalExtrusionFilter() 2135 # rf = vtk.vtkLinearExtrusionFilter() 2136 rf.SetInputData(self.polydata(False)) # must not be transformed 2137 rf.SetResolution(res) 2138 rf.SetCapping(cap) 2139 rf.SetAngle(rotation) 2140 rf.SetTranslation(zshift) 2141 rf.SetDeltaRadius(dR) 2142 rf.Update() 2143 m = Mesh(rf.GetOutput(), c=self.c(), alpha=self.alpha()) 2144 prop = vtk.vtkProperty() 2145 prop.DeepCopy(self.property) 2146 m.SetProperty(prop) 2147 m.property = prop 2148 # assign the same transformation 2149 m.SetOrigin(self.GetOrigin()) 2150 m.SetScale(self.GetScale()) 2151 m.SetOrientation(self.GetOrientation()) 2152 m.SetPosition(self.GetPosition()) 2153 2154 m.compute_normals(cells=False).flat().lighting("default") 2155 2156 m.pipeline = OperationNode( 2157 "extrude", parents=[self], 2158 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 2159 ) 2160 return m 2161 2162 def split(self, maxdepth=1000, flag=False, must_share_edge=False, sort_by_area=True): 2163 """ 2164 Split a mesh by connectivity and order the pieces by increasing area. 2165 2166 Arguments: 2167 maxdepth : (int) 2168 only consider this maximum number of mesh parts. 2169 flag : (bool) 2170 if set to True return the same single object, 2171 but add a "RegionId" array to flag the mesh subparts 2172 must_share_edge : (bool) 2173 if True, mesh regions that only share single points will be split. 2174 sort_by_area : (bool) 2175 if True, sort the mesh parts by decreasing area. 2176 2177 Examples: 2178 - [splitmesh.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/splitmesh.py) 2179 2180 ![](https://vedo.embl.es/images/advanced/splitmesh.png) 2181 """ 2182 pd = self.polydata(False) 2183 if must_share_edge: 2184 if pd.GetNumberOfPolys() == 0: 2185 vedo.logger.warning("in split(): no polygons found. Skip.") 2186 return [self] 2187 cf = vtk.vtkPolyDataEdgeConnectivityFilter() 2188 cf.BarrierEdgesOff() 2189 else: 2190 cf = vtk.vtkPolyDataConnectivityFilter() 2191 2192 cf.SetInputData(pd) 2193 cf.SetExtractionModeToAllRegions() 2194 cf.SetColorRegions(True) 2195 cf.Update() 2196 out = cf.GetOutput() 2197 2198 if not out.GetNumberOfPoints(): 2199 return [self] 2200 2201 if flag: 2202 self.pipeline = OperationNode("split mesh", parents=[self]) 2203 return self._update(out) 2204 2205 a = Mesh(out) 2206 if must_share_edge: 2207 arr = a.celldata["RegionId"] 2208 on = "cells" 2209 else: 2210 arr = a.pointdata["RegionId"] 2211 on = "points" 2212 2213 alist = [] 2214 for t in range(max(arr) + 1): 2215 if t == maxdepth: 2216 break 2217 suba = a.clone().threshold("RegionId", t, t, on=on) 2218 if sort_by_area: 2219 area = suba.area() 2220 else: 2221 area = 0 # dummy 2222 alist.append([suba, area]) 2223 2224 if sort_by_area: 2225 alist.sort(key=lambda x: x[1]) 2226 alist.reverse() 2227 2228 blist = [] 2229 for i, l in enumerate(alist): 2230 l[0].color(i + 1).phong() 2231 l[0].mapper().ScalarVisibilityOff() 2232 blist.append(l[0]) 2233 if i < 10: 2234 l[0].pipeline = OperationNode( 2235 f"split mesh {i}", 2236 parents=[self], 2237 comment=f"#pts {l[0].inputdata().GetNumberOfPoints()}", 2238 ) 2239 return blist 2240 2241 2242 def extract_largest_region(self): 2243 """ 2244 Extract the largest connected part of a mesh and discard all the smaller pieces. 2245 2246 Examples: 2247 - [largestregion.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/largestregion.py) 2248 """ 2249 conn = vtk.vtkPolyDataConnectivityFilter() 2250 conn.SetExtractionModeToLargestRegion() 2251 conn.ScalarConnectivityOff() 2252 conn.SetInputData(self._data) 2253 conn.Update() 2254 m = Mesh(conn.GetOutput()) 2255 pr = vtk.vtkProperty() 2256 pr.DeepCopy(self.property) 2257 m.SetProperty(pr) 2258 m.property = pr 2259 # assign the same transformation 2260 m.SetOrigin(self.GetOrigin()) 2261 m.SetScale(self.GetScale()) 2262 m.SetOrientation(self.GetOrientation()) 2263 m.SetPosition(self.GetPosition()) 2264 vis = self._mapper.GetScalarVisibility() 2265 m.mapper().SetScalarVisibility(vis) 2266 2267 m.pipeline = OperationNode( 2268 "extract_largest_region", parents=[self], 2269 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 2270 ) 2271 return m 2272 2273 def boolean(self, operation, mesh2, method=0, tol=None): 2274 """Volumetric union, intersection and subtraction of surfaces. 2275 2276 Use `operation` for the allowed operations `['plus', 'intersect', 'minus']`. 2277 2278 Two possible algorithms are available by changing `method`. 2279 2280 Example: 2281 - [boolean.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boolean.py) 2282 2283 ![](https://vedo.embl.es/images/basic/boolean.png) 2284 """ 2285 if method == 0: 2286 bf = vtk.vtkBooleanOperationPolyDataFilter() 2287 elif method == 1: 2288 bf = vtk.vtkLoopBooleanPolyDataFilter() 2289 else: 2290 raise ValueError(f"Unknown method={method}") 2291 2292 poly1 = self.compute_normals().polydata() 2293 poly2 = mesh2.compute_normals().polydata() 2294 2295 if operation.lower() in ("plus", "+"): 2296 bf.SetOperationToUnion() 2297 elif operation.lower() == "intersect": 2298 bf.SetOperationToIntersection() 2299 elif operation.lower() in ("minus", "-"): 2300 bf.SetOperationToDifference() 2301 2302 if tol: 2303 bf.SetTolerance(tol) 2304 2305 bf.SetInputData(0, poly1) 2306 bf.SetInputData(1, poly2) 2307 bf.Update() 2308 2309 msh = Mesh(bf.GetOutput(), c=None) 2310 msh.flat() 2311 msh.name = self.name + operation + mesh2.name 2312 2313 msh.pipeline = OperationNode( 2314 "boolean " + operation, 2315 parents=[self, mesh2], 2316 shape="cylinder", 2317 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 2318 ) 2319 return msh 2320 2321 def intersect_with(self, mesh2, tol=1e-06): 2322 """ 2323 Intersect this Mesh with the input surface to return a set of lines. 2324 2325 Examples: 2326 - [surf_intersect.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/surf_intersect.py) 2327 2328 ![](https://vedo.embl.es/images/basic/surfIntersect.png) 2329 """ 2330 bf = vtk.vtkIntersectionPolyDataFilter() 2331 bf.SetGlobalWarningDisplay(0) 2332 poly1 = self.polydata() 2333 poly2 = mesh2.polydata() 2334 bf.SetTolerance(tol) 2335 bf.SetInputData(0, poly1) 2336 bf.SetInputData(1, poly2) 2337 bf.Update() 2338 msh = Mesh(bf.GetOutput(), "k", 1).lighting("off") 2339 msh.GetProperty().SetLineWidth(3) 2340 msh.name = "SurfaceIntersection" 2341 2342 msh.pipeline = OperationNode( 2343 "intersect_with", parents=[self, mesh2], comment=f"#pts {msh.npoints}" 2344 ) 2345 return msh 2346 2347 def intersect_with_line(self, p0, p1=None, return_ids=False, tol=0): 2348 """ 2349 Return the list of points intersecting the mesh 2350 along the segment defined by two points `p0` and `p1`. 2351 2352 Use `return_ids` to return the cell ids along with point coords 2353 2354 Example: 2355 ```python 2356 from vedo import * 2357 s = Spring() 2358 pts = s.intersect_with_line([0,0,0], [1,0.1,0]) 2359 ln = Line([0,0,0], [1,0.1,0], c='blue') 2360 ps = Points(pts, r=10, c='r') 2361 show(s, ln, ps, bg='white').close() 2362 ``` 2363 ![](https://user-images.githubusercontent.com/32848391/55967065-eee08300-5c79-11e9-8933-265e1bab9f7e.png) 2364 """ 2365 if isinstance(p0, Points): 2366 p0, p1 = p0.points() 2367 2368 if not self.line_locator: 2369 self.line_locator = vtk.vtkOBBTree() 2370 self.line_locator.SetDataSet(self.polydata()) 2371 if not tol: 2372 tol = mag(np.asarray(p1) - np.asarray(p0)) / 10000 2373 self.line_locator.SetTolerance(tol) 2374 self.line_locator.BuildLocator() 2375 2376 vpts = vtk.vtkPoints() 2377 idlist = vtk.vtkIdList() 2378 self.line_locator.IntersectWithLine(p0, p1, vpts, idlist) 2379 pts = [] 2380 for i in range(vpts.GetNumberOfPoints()): 2381 intersection = [0, 0, 0] 2382 vpts.GetPoint(i, intersection) 2383 pts.append(intersection) 2384 pts = np.array(pts) 2385 2386 if return_ids: 2387 pts_ids = [] 2388 for i in range(idlist.GetNumberOfIds()): 2389 cid = idlist.GetId(i) 2390 pts_ids.append(cid) 2391 return (pts, np.array(pts_ids).astype(np.uint32)) 2392 2393 return pts 2394 2395 def intersect_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0)): 2396 """ 2397 Intersect this Mesh with a plane to return a set of lines. 2398 2399 Example: 2400 ```python 2401 from vedo import * 2402 sph = Sphere() 2403 mi = sph.clone().intersect_with_plane().join() 2404 print(mi.lines()) 2405 show(sph, mi, axes=1).close() 2406 ``` 2407 ![](https://vedo.embl.es/images/feats/intersect_plane.png) 2408 """ 2409 plane = vtk.vtkPlane() 2410 plane.SetOrigin(origin) 2411 plane.SetNormal(normal) 2412 2413 cutter = vtk.vtkPolyDataPlaneCutter() 2414 cutter.SetInputData(self.polydata()) 2415 cutter.SetPlane(plane) 2416 cutter.InterpolateAttributesOn() 2417 cutter.ComputeNormalsOff() 2418 cutter.Update() 2419 2420 msh = Mesh(cutter.GetOutput(), "k", 1).lighting("off") 2421 msh.GetProperty().SetLineWidth(3) 2422 msh.name = "PlaneIntersection" 2423 2424 msh.pipeline = OperationNode( 2425 "intersect_with_plan", parents=[self], 2426 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}" 2427 ) 2428 return msh 2429 2430 # def intersect_with_multiplanes(self, origins, normals): ## WRONG 2431 # """ 2432 # Generate a set of lines from cutting a mesh in n intervals 2433 # between a minimum and maximum distance from a plane of given origin and normal. 2434 2435 # Arguments: 2436 # origin : (list) 2437 # the point of the cutting plane 2438 # normal : (list) 2439 # normal vector to the cutting plane 2440 # n : (int) 2441 # number of cuts 2442 # """ 2443 # poly = self.polydata() 2444 2445 # planes = vtk.vtkPlanes() 2446 # planes.SetOrigin(numpy2vtk(origins)) 2447 # planes.SetNormals(numpy2vtk(normals)) 2448 2449 # cutter = vtk.vtkCutter() 2450 # cutter.SetCutFunction(planes) 2451 # cutter.SetInputData(poly) 2452 # cutter.SetValue(0, 0.0) 2453 # cutter.Update() 2454 2455 # msh = Mesh(cutter.GetOutput()) 2456 # msh.property.LightingOff() 2457 # msh.property.SetColor(get_color("k2")) 2458 2459 # msh.pipeline = OperationNode( 2460 # "intersect_with_multiplanes", 2461 # parents=[self], 2462 # comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 2463 # ) 2464 # return msh 2465 2466 def collide_with(self, mesh2, tol=0, return_bool=False): 2467 """ 2468 Collide this Mesh with the input surface. 2469 Information is stored in `ContactCells1` and `ContactCells2`. 2470 """ 2471 ipdf = vtk.vtkCollisionDetectionFilter() 2472 # ipdf.SetGlobalWarningDisplay(0) 2473 2474 transform0 = vtk.vtkTransform() 2475 transform1 = vtk.vtkTransform() 2476 2477 # ipdf.SetBoxTolerance(tol) 2478 ipdf.SetCellTolerance(tol) 2479 ipdf.SetInputData(0, self.polydata()) 2480 ipdf.SetInputData(1, mesh2.polydata()) 2481 ipdf.SetTransform(0, transform0) 2482 ipdf.SetTransform(1, transform1) 2483 if return_bool: 2484 ipdf.SetCollisionModeToFirstContact() 2485 else: 2486 ipdf.SetCollisionModeToAllContacts() 2487 ipdf.Update() 2488 2489 if return_bool: 2490 return bool(ipdf.GetNumberOfContacts()) 2491 2492 msh = Mesh(ipdf.GetContactsOutput(), "k", 1).lighting("off") 2493 msh.metadata["ContactCells1"] = vtk2numpy( 2494 ipdf.GetOutput(0).GetFieldData().GetArray("ContactCells") 2495 ) 2496 msh.metadata["ContactCells2"] = vtk2numpy( 2497 ipdf.GetOutput(1).GetFieldData().GetArray("ContactCells") 2498 ) 2499 msh.GetProperty().SetLineWidth(3) 2500 msh.name = "SurfaceCollision" 2501 2502 msh.pipeline = OperationNode( 2503 "collide_with", parents=[self, mesh2], 2504 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}" 2505 ) 2506 return msh 2507 2508 def geodesic(self, start, end): 2509 """ 2510 Dijkstra algorithm to compute the geodesic line. 2511 Takes as input a polygonal mesh and performs a single source shortest path calculation. 2512 2513 Arguments: 2514 start : (int, list) 2515 start vertex index or close point `[x,y,z]` 2516 end : (int, list) 2517 end vertex index or close point `[x,y,z]` 2518 2519 Examples: 2520 - [geodesic.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic.py) 2521 2522 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2523 """ 2524 if is_sequence(start): 2525 cc = self.points() 2526 pa = Points(cc) 2527 start = pa.closest_point(start, return_point_id=True) 2528 end = pa.closest_point(end, return_point_id=True) 2529 2530 dijkstra = vtk.vtkDijkstraGraphGeodesicPath() 2531 dijkstra.SetInputData(self.polydata()) 2532 dijkstra.SetStartVertex(end) # inverted in vtk 2533 dijkstra.SetEndVertex(start) 2534 dijkstra.Update() 2535 2536 weights = vtk.vtkDoubleArray() 2537 dijkstra.GetCumulativeWeights(weights) 2538 2539 idlist = dijkstra.GetIdList() 2540 ids = [idlist.GetId(i) for i in range(idlist.GetNumberOfIds())] 2541 2542 length = weights.GetMaxId() + 1 2543 arr = np.zeros(length) 2544 for i in range(length): 2545 arr[i] = weights.GetTuple(i)[0] 2546 2547 poly = dijkstra.GetOutput() 2548 2549 vdata = numpy2vtk(arr) 2550 vdata.SetName("CumulativeWeights") 2551 poly.GetPointData().AddArray(vdata) 2552 2553 vdata2 = numpy2vtk(ids, dtype=np.uint) 2554 vdata2.SetName("VertexIDs") 2555 poly.GetPointData().AddArray(vdata2) 2556 poly.GetPointData().Modified() 2557 2558 dmesh = Mesh(poly, c="k") 2559 prop = vtk.vtkProperty() 2560 prop.DeepCopy(self.property) 2561 prop.SetLineWidth(3) 2562 prop.SetOpacity(1) 2563 dmesh.SetProperty(prop) 2564 dmesh.property = prop 2565 dmesh.name = "GeodesicLine" 2566 2567 dmesh.pipeline = OperationNode( 2568 "GeodesicLine", parents=[self], comment=f"#pts {dmesh._data.GetNumberOfPoints()}" 2569 ) 2570 return dmesh 2571 2572 ##################################################################### 2573 ### Stuff returning a Volume 2574 ### 2575 def binarize( 2576 self, 2577 spacing=(1, 1, 1), 2578 invert=False, 2579 direction_matrix=None, 2580 image_size=None, 2581 origin=None, 2582 fg_val=255, 2583 bg_val=0, 2584 ): 2585 """ 2586 Convert a `Mesh` into a `Volume` 2587 where the foreground (exterior) voxels value is fg_val (255 by default) 2588 and the background (interior) voxels value is bg_val (0 by default). 2589 2590 Examples: 2591 - [mesh2volume.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/mesh2volume.py) 2592 2593 ![](https://vedo.embl.es/images/volumetric/mesh2volume.png) 2594 """ 2595 # https://vtk.org/Wiki/VTK/Examples/Cxx/PolyData/PolyDataToImageData 2596 pd = self.polydata() 2597 2598 whiteImage = vtk.vtkImageData() 2599 if direction_matrix: 2600 whiteImage.SetDirectionMatrix(direction_matrix) 2601 2602 dim = [0, 0, 0] if not image_size else image_size 2603 2604 bounds = self.bounds() 2605 if not image_size: # compute dimensions 2606 dim = [0, 0, 0] 2607 for i in [0, 1, 2]: 2608 dim[i] = int(np.ceil((bounds[i * 2 + 1] - bounds[i * 2]) / spacing[i])) 2609 2610 whiteImage.SetDimensions(dim) 2611 whiteImage.SetSpacing(spacing) 2612 whiteImage.SetExtent(0, dim[0] - 1, 0, dim[1] - 1, 0, dim[2] - 1) 2613 2614 if not origin: 2615 origin = [0, 0, 0] 2616 origin[0] = bounds[0] + spacing[0] / 2 2617 origin[1] = bounds[2] + spacing[1] / 2 2618 origin[2] = bounds[4] + spacing[2] / 2 2619 whiteImage.SetOrigin(origin) 2620 whiteImage.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1) 2621 2622 # fill the image with foreground voxels: 2623 inval = bg_val if invert else fg_val 2624 whiteImage.GetPointData().GetScalars().Fill(inval) 2625 2626 # polygonal data --> image stencil: 2627 pol2stenc = vtk.vtkPolyDataToImageStencil() 2628 pol2stenc.SetInputData(pd) 2629 pol2stenc.SetOutputOrigin(whiteImage.GetOrigin()) 2630 pol2stenc.SetOutputSpacing(whiteImage.GetSpacing()) 2631 pol2stenc.SetOutputWholeExtent(whiteImage.GetExtent()) 2632 pol2stenc.Update() 2633 2634 # cut the corresponding white image and set the background: 2635 outval = fg_val if invert else bg_val 2636 2637 imgstenc = vtk.vtkImageStencil() 2638 imgstenc.SetInputData(whiteImage) 2639 imgstenc.SetStencilConnection(pol2stenc.GetOutputPort()) 2640 imgstenc.SetReverseStencil(invert) 2641 imgstenc.SetBackgroundValue(outval) 2642 imgstenc.Update() 2643 vol = vedo.Volume(imgstenc.GetOutput()) 2644 2645 vol.pipeline = OperationNode( 2646 "binarize", 2647 parents=[self], 2648 comment=f"dim = {tuple(vol.dimensions())}", 2649 c="#e9c46a:#0096c7", 2650 ) 2651 return vol 2652 2653 def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None): 2654 """ 2655 Compute the `Volume` object whose voxels contains the signed distance from 2656 the mesh. 2657 2658 Arguments: 2659 bounds : (list) 2660 bounds of the output volume 2661 dims : (list) 2662 dimensions (nr. of voxels) of the output volume 2663 invert : (bool) 2664 flip the sign 2665 2666 Examples: 2667 - [volumeFromMesh.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/volumeFromMesh.py) 2668 """ 2669 if maxradius is not None: 2670 vedo.logger.warning( 2671 "in signedDistance(maxradius=...) is ignored. (Only valid for pointclouds)." 2672 ) 2673 if bounds is None: 2674 bounds = self.bounds() 2675 sx = (bounds[1] - bounds[0]) / dims[0] 2676 sy = (bounds[3] - bounds[2]) / dims[1] 2677 sz = (bounds[5] - bounds[4]) / dims[2] 2678 2679 img = vtk.vtkImageData() 2680 img.SetDimensions(dims) 2681 img.SetSpacing(sx, sy, sz) 2682 img.SetOrigin(bounds[0], bounds[2], bounds[4]) 2683 img.AllocateScalars(vtk.VTK_FLOAT, 1) 2684 2685 imp = vtk.vtkImplicitPolyDataDistance() 2686 imp.SetInput(self.polydata()) 2687 b2 = bounds[2] 2688 b4 = bounds[4] 2689 d0, d1, d2 = dims 2690 2691 for i in range(d0): 2692 x = i * sx + bounds[0] 2693 for j in range(d1): 2694 y = j * sy + b2 2695 for k in range(d2): 2696 v = imp.EvaluateFunction((x, y, k * sz + b4)) 2697 if invert: 2698 v = -v 2699 img.SetScalarComponentFromFloat(i, j, k, 0, v) 2700 2701 vol = vedo.Volume(img) 2702 vol.name = "SignedVolume" 2703 2704 vol.pipeline = OperationNode( 2705 "signed_distance", 2706 parents=[self], 2707 comment=f"dim = {tuple(vol.dimensions())}", 2708 c="#e9c46a:#0096c7", 2709 ) 2710 return vol 2711 2712 def tetralize( 2713 self, side=0.02, nmax=300_000, gap=None, subsample=False, uniform=True, seed=0, debug=False 2714 ): 2715 """ 2716 Tetralize a closed polygonal mesh. Return a `TetMesh`. 2717 2718 Arguments: 2719 side : (float) 2720 desired side of the single tetras as fraction of the bounding box diagonal. 2721 Typical values are in the range (0.01 - 0.03) 2722 nmax : (int) 2723 maximum random numbers to be sampled in the bounding box 2724 gap : (float) 2725 keep this minimum distance from the surface, 2726 if None an automatic choice is made. 2727 subsample : (bool) 2728 subsample input surface, the geometry might be affected 2729 (the number of original faces reduceed), but higher tet quality might be obtained. 2730 uniform : (bool) 2731 generate tets more uniformly packed in the interior of the mesh 2732 seed : (int) 2733 random number generator seed 2734 debug : (bool) 2735 show an intermediate plot with sampled points 2736 2737 Examples: 2738 - [tetralize_surface.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tetralize_surface.py) 2739 2740 ![](https://vedo.embl.es/images/volumetric/tetralize_surface.jpg) 2741 """ 2742 surf = self.clone().clean().compute_normals() 2743 d = surf.diagonal_size() 2744 if gap is None: 2745 gap = side * d * np.sqrt(2 / 3) 2746 n = int(min((1 / side) ** 3, nmax)) 2747 2748 # fill the space w/ points 2749 x0, x1, y0, y1, z0, z1 = surf.bounds() 2750 2751 if uniform: 2752 pts = vedo.utils.pack_spheres([x0, x1, y0, y1, z0, z1], side * d * 1.42) 2753 pts += np.random.randn(len(pts), 3) * side * d * 1.42 / 100 # some small jitter 2754 else: 2755 disp = np.array([x0 + x1, y0 + y1, z0 + z1]) / 2 2756 np.random.seed(seed) 2757 pts = (np.random.rand(n, 3) - 0.5) * np.array([x1 - x0, y1 - y0, z1 - z0]) + disp 2758 2759 normals = surf.celldata["Normals"] 2760 cc = surf.cell_centers() 2761 subpts = cc - normals * gap * 1.05 2762 pts = pts.tolist() + subpts.tolist() 2763 2764 if debug: 2765 print(".. tetralize(): subsampling and cleaning") 2766 2767 fillpts = surf.inside_points(pts) 2768 fillpts.subsample(side) 2769 2770 if gap: 2771 fillpts.distance_to(surf) 2772 fillpts.threshold("Distance", above=gap) 2773 2774 if subsample: 2775 surf.subsample(side) 2776 2777 tmesh = vedo.tetmesh.delaunay3d(vedo.merge(fillpts, surf)) 2778 tcenters = tmesh.cell_centers() 2779 2780 ids = surf.inside_points(tcenters, return_ids=True) 2781 ins = np.zeros(tmesh.ncells) 2782 ins[ids] = 1 2783 2784 if debug: 2785 # vedo.pyplot.histogram(fillpts.pointdata["Distance"], xtitle=f"gap={gap}").show().close() 2786 edges = self.edges() 2787 points = self.points() 2788 elen = mag(points[edges][:, 0, :] - points[edges][:, 1, :]) 2789 histo = vedo.pyplot.histogram(elen, xtitle="edge length", xlim=(0, 3 * side * d)) 2790 print(".. edges min, max", elen.min(), elen.max()) 2791 fillpts.cmap("bone") 2792 vedo.show( 2793 [ 2794 [ 2795 f"This is a debug plot.\n\nGenerated points: {n}\ngap: {gap}", 2796 surf.wireframe().alpha(0.2), 2797 vedo.addons.Axes(surf), 2798 fillpts, 2799 Points(subpts).c("r4").ps(3), 2800 ], 2801 [f"Edges mean length: {np.mean(elen)}\n\nPress q to continue", histo], 2802 ], 2803 N=2, 2804 sharecam=False, 2805 new=True, 2806 ).close() 2807 print(".. thresholding") 2808 2809 tmesh.celldata["inside"] = ins.astype(np.uint8) 2810 tmesh.threshold("inside", above=0.9) 2811 tmesh.celldata.remove("inside") 2812 2813 if debug: 2814 print(f".. tetralize() completed, ntets = {tmesh.ncells}") 2815 2816 tmesh.pipeline = OperationNode( 2817 "tetralize", parents=[self], comment=f"#tets = {tmesh.ncells}", c="#e9c46a:#9e2a2b" 2818 ) 2819 return tmesh
Build an instance of object Mesh
derived from vedo.PointCloud
.
36 def __init__(self, inputobj=None, c=None, alpha=1): 37 """ 38 Input can be a list of vertices and their connectivity (faces of the polygonal mesh), 39 or directly a `vtkPolydata` object. 40 For point clouds - e.i. no faces - just substitute the `faces` list with `None`. 41 42 Example: 43 `Mesh( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )` 44 45 Arguments: 46 c : (color) 47 color in RGB format, hex, symbol or name 48 alpha : (float) 49 mesh opacity [0,1] 50 51 Examples: 52 - [buildmesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buildmesh.py) 53 (and many others!) 54 55 ![](https://vedo.embl.es/images/basic/buildmesh.png) 56 """ 57 Points.__init__(self) 58 59 self.line_locator = None 60 61 self._mapper.SetInterpolateScalarsBeforeMapping( 62 vedo.settings.interpolate_scalars_before_mapping 63 ) 64 65 if vedo.settings.use_polygon_offset: 66 self._mapper.SetResolveCoincidentTopologyToPolygonOffset() 67 pof, pou = (vedo.settings.polygon_offset_factor, vedo.settings.polygon_offset_units) 68 self._mapper.SetResolveCoincidentTopologyPolygonOffsetParameters(pof, pou) 69 70 inputtype = str(type(inputobj)) 71 72 if inputobj is None: 73 pass 74 75 elif isinstance(inputobj, (Mesh, vtk.vtkActor)): 76 polyCopy = vtk.vtkPolyData() 77 polyCopy.DeepCopy(inputobj.GetMapper().GetInput()) 78 self._data = polyCopy 79 self._mapper.SetInputData(polyCopy) 80 self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility()) 81 pr = vtk.vtkProperty() 82 pr.DeepCopy(inputobj.GetProperty()) 83 self.SetProperty(pr) 84 self.property = pr 85 86 elif isinstance(inputobj, vtk.vtkPolyData): 87 if inputobj.GetNumberOfCells() == 0: 88 carr = vtk.vtkCellArray() 89 for i in range(inputobj.GetNumberOfPoints()): 90 carr.InsertNextCell(1) 91 carr.InsertCellPoint(i) 92 inputobj.SetVerts(carr) 93 self._data = inputobj # cache vtkPolyData and mapper for speed 94 95 elif isinstance(inputobj, (vtk.vtkStructuredGrid, vtk.vtkRectilinearGrid)): 96 gf = vtk.vtkGeometryFilter() 97 gf.SetInputData(inputobj) 98 gf.Update() 99 self._data = gf.GetOutput() 100 101 elif "trimesh" in inputtype: 102 tact = vedo.utils.trimesh2vedo(inputobj) 103 self._data = tact.polydata() 104 105 elif "meshio" in inputtype: 106 if len(inputobj.cells) > 0: 107 mcells = [] 108 for cellblock in inputobj.cells: 109 if cellblock.type in ("triangle", "quad"): 110 mcells += cellblock.data.tolist() 111 self._data = buildPolyData(inputobj.points, mcells) 112 else: 113 self._data = buildPolyData(inputobj.points, None) 114 # add arrays: 115 try: 116 if len(inputobj.point_data) > 0: 117 for k in inputobj.point_data.keys(): 118 vdata = numpy2vtk(inputobj.point_data[k]) 119 vdata.SetName(str(k)) 120 self._data.GetPointData().AddArray(vdata) 121 except AssertionError: 122 print("Could not add meshio point data, skip.") 123 # try: 124 # if len(inputobj.cell_data): 125 # for k in inputobj.cell_data.keys(): 126 # #print(inputobj.cell_data) 127 # exit() 128 # vdata = numpy2vtk(inputobj.cell_data[k]) 129 # vdata.SetName(str(k)) 130 # self._data.GetCellData().AddArray(vdata) 131 # except AssertionError: 132 # print("Could not add meshio cell data, skip.") 133 134 elif "meshlab" in inputtype: 135 self._data = vedo.utils.meshlab2vedo(inputobj) 136 137 elif is_sequence(inputobj): 138 ninp = len(inputobj) 139 if ninp == 0: 140 self._data = vtk.vtkPolyData() 141 elif ninp == 2: # assume [vertices, faces] 142 self._data = buildPolyData(inputobj[0], inputobj[1]) 143 else: # assume [vertices] or vertices 144 self._data = buildPolyData(inputobj, None) 145 146 elif hasattr(inputobj, "GetOutput"): # passing vtk object 147 if hasattr(inputobj, "Update"): 148 inputobj.Update() 149 if isinstance(inputobj.GetOutput(), vtk.vtkPolyData): 150 self._data = inputobj.GetOutput() 151 else: 152 gf = vtk.vtkGeometryFilter() 153 gf.SetInputData(inputobj.GetOutput()) 154 gf.Update() 155 self._data = gf.GetOutput() 156 157 elif isinstance(inputobj, str): 158 dataset = vedo.file_io.load(inputobj) 159 self.filename = inputobj 160 if "TetMesh" in str(type(dataset)): 161 self._data = dataset.tomesh().polydata(False) 162 else: 163 self._data = dataset.polydata(False) 164 165 else: 166 try: 167 gf = vtk.vtkGeometryFilter() 168 gf.SetInputData(inputobj) 169 gf.Update() 170 self._data = gf.GetOutput() 171 except: 172 vedo.logger.error(f"cannot build mesh from type {inputtype}") 173 raise RuntimeError() 174 175 self._mapper.SetInputData(self._data) 176 177 self.property = self.GetProperty() 178 self.property.SetInterpolationToPhong() 179 180 # set the color by c or by scalar 181 if self._data: 182 183 arrexists = False 184 185 if c is None: 186 ptdata = self._data.GetPointData() 187 cldata = self._data.GetCellData() 188 exclude = ["normals", "tcoord"] 189 190 if cldata.GetNumberOfArrays(): 191 for i in range(cldata.GetNumberOfArrays()): 192 iarr = cldata.GetArray(i) 193 if iarr: 194 icname = iarr.GetName() 195 if icname and all(s not in icname.lower() for s in exclude): 196 cldata.SetActiveScalars(icname) 197 self._mapper.ScalarVisibilityOn() 198 self._mapper.SetScalarModeToUseCellData() 199 self._mapper.SetScalarRange(iarr.GetRange()) 200 arrexists = True 201 break # stop at first good one 202 203 # point come after so it has priority 204 if ptdata.GetNumberOfArrays(): 205 for i in range(ptdata.GetNumberOfArrays()): 206 iarr = ptdata.GetArray(i) 207 if iarr: 208 ipname = iarr.GetName() 209 if ipname and all(s not in ipname.lower() for s in exclude): 210 ptdata.SetActiveScalars(ipname) 211 self._mapper.ScalarVisibilityOn() 212 self._mapper.SetScalarModeToUsePointData() 213 self._mapper.SetScalarRange(iarr.GetRange()) 214 arrexists = True 215 break # stop at first good one 216 217 if not arrexists: 218 if c is None: 219 c = "gold" 220 c = get_color(c) 221 elif isinstance(c, float) and c <= 1: 222 c = color_map(c, "rainbow", 0, 1) 223 else: 224 c = get_color(c) 225 self.property.SetColor(c) 226 self.property.SetAmbient(0.1) 227 self.property.SetDiffuse(1) 228 self.property.SetSpecular(0.05) 229 self.property.SetSpecularPower(5) 230 self._mapper.ScalarVisibilityOff() 231 232 if alpha is not None: 233 self.property.SetOpacity(alpha) 234 235 n = self._data.GetNumberOfPoints() 236 self.pipeline = OperationNode(self, comment=f"#pts {n}")
Input can be a list of vertices and their connectivity (faces of the polygonal mesh),
or directly a vtkPolydata
object.
For point clouds - e.i. no faces - just substitute the faces
list with None
.
Example:
Mesh( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )
Arguments:
- c : (color) color in RGB format, hex, symbol or name
- alpha : (float) mesh opacity [0,1]
Examples:
- buildmesh.py (and many others!)
316 def faces(self): 317 """ 318 Get cell polygonal connectivity ids as a python `list`. 319 The output format is: `[[id0 ... idn], [id0 ... idm], etc]`. 320 """ 321 arr1d = vtk2numpy(self._data.GetPolys().GetData()) 322 if arr1d is None: 323 return [] 324 325 # Get cell connettivity ids as a 1D array. vtk format is: 326 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 327 if len(arr1d) == 0: 328 arr1d = vtk2numpy(self._data.GetStrips().GetData()) 329 if arr1d is None: 330 return [] 331 332 i = 0 333 conn = [] 334 n = len(arr1d) 335 if n: 336 while True: 337 cell = [arr1d[i + k] for k in range(1, arr1d[i] + 1)] 338 conn.append(cell) 339 i += arr1d[i] + 1 340 if i >= n: 341 break 342 return conn # cannot always make a numpy array of it!
Get cell polygonal connectivity ids as a python list
.
The output format is: [[id0 ... idn], [id0 ... idm], etc]
.
348 def lines(self, flat=False): 349 """ 350 Get lines connectivity ids as a numpy array. 351 Default format is `[[id0,id1], [id3,id4], ...]` 352 353 Arguments: 354 flat : (bool) 355 return a 1D numpy array as e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...] 356 """ 357 # Get cell connettivity ids as a 1D array. The vtk format is: 358 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 359 arr1d = vtk2numpy(self.polydata(False).GetLines().GetData()) 360 361 if arr1d is None: 362 return [] 363 364 if flat: 365 return arr1d 366 367 i = 0 368 conn = [] 369 n = len(arr1d) 370 for _ in range(n): 371 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 372 conn.append(cell) 373 i += arr1d[i] + 1 374 if i >= n: 375 break 376 377 return conn # cannot always make a numpy array of it!
Get lines connectivity ids as a numpy array.
Default format is [[id0,id1], [id3,id4], ...]
Arguments:
- flat : (bool) return a 1D numpy array as e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...]
379 def edges(self): 380 """Return an array containing the edges connectivity.""" 381 extractEdges = vtk.vtkExtractEdges() 382 extractEdges.SetInputData(self._data) 383 # eed.UseAllPointsOn() 384 extractEdges.Update() 385 lpoly = extractEdges.GetOutput() 386 387 arr1d = vtk2numpy(lpoly.GetLines().GetData()) 388 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 389 390 i = 0 391 conn = [] 392 n = len(arr1d) 393 for _ in range(n): 394 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 395 conn.append(cell) 396 i += arr1d[i] + 1 397 if i >= n: 398 break 399 return conn # cannot always make a numpy array of it!
Return an array containing the edges connectivity.
401 def texture( 402 self, 403 tname, 404 tcoords=None, 405 interpolate=True, 406 repeat=True, 407 edge_clamp=False, 408 scale=None, 409 ushift=None, 410 vshift=None, 411 seam_threshold=None, 412 ): 413 """ 414 Assign a texture to mesh from image file or predefined texture `tname`. 415 If tname is set to `None` texture is disabled. 416 Input tname can also be an array or a `vtkTexture`. 417 418 Arguments: 419 tname : (numpy.array, str, Picture, vtkTexture, None) 420 the input texture to be applied. Can be a numpy array, a path to an image file, 421 a vedo Picture. The None value disables texture. 422 tcoords : (numpy.array, str) 423 this is the (u,v) texture coordinate array. Can also be a string of an existing array 424 in the mesh. 425 interpolate : (bool) 426 turn on/off linear interpolation of the texture map when rendering. 427 repeat : (bool) 428 repeat of the texture when tcoords extend beyond the [0,1] range. 429 edge_clamp : (bool) 430 turn on/off the clamping of the texture map when 431 the texture coords extend beyond the [0,1] range. 432 Only used when repeat is False, and edge clamping is supported by the graphics card. 433 scale : (bool) 434 scale the texture image by this factor 435 ushift : (bool) 436 shift u-coordinates of texture by this amount 437 vshift : (bool) 438 shift v-coordinates of texture by this amount 439 seam_threshold : (float) 440 try to seal seams in texture by collapsing triangles 441 (test values around 1.0, lower values = stronger collapse) 442 443 Examples: 444 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 445 446 ![](https://vedo.embl.es/images/basic/texturecubes.png) 447 """ 448 pd = self.polydata(False) 449 outimg = None 450 451 if tname is None: # disable texture 452 pd.GetPointData().SetTCoords(None) 453 pd.GetPointData().Modified() 454 return self ###################################### 455 456 if isinstance(tname, vtk.vtkTexture): 457 tu = tname 458 459 elif isinstance(tname, vedo.Picture): 460 tu = vtk.vtkTexture() 461 outimg = tname.inputdata() 462 463 elif is_sequence(tname): 464 tu = vtk.vtkTexture() 465 outimg = vedo.picture._get_img(tname) 466 467 elif isinstance(tname, str): 468 tu = vtk.vtkTexture() 469 470 if "https://" in tname: 471 try: 472 tname = vedo.file_io.download(tname, verbose=False) 473 except: 474 vedo.logger.error(f"texture {tname} could not be downloaded") 475 return self 476 477 fn = tname + ".jpg" 478 if os.path.exists(tname): 479 fn = tname 480 else: 481 vedo.logger.error(f"texture file {tname} does not exist") 482 return self 483 484 fnl = fn.lower() 485 if ".jpg" in fnl or ".jpeg" in fnl: 486 reader = vtk.vtkJPEGReader() 487 elif ".png" in fnl: 488 reader = vtk.vtkPNGReader() 489 elif ".bmp" in fnl: 490 reader = vtk.vtkBMPReader() 491 else: 492 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 493 return self 494 reader.SetFileName(fn) 495 reader.Update() 496 outimg = reader.GetOutput() 497 498 else: 499 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 500 return self 501 502 if tcoords is not None: 503 504 if isinstance(tcoords, str): 505 506 vtarr = pd.GetPointData().GetArray(tcoords) 507 508 else: 509 510 tcoords = np.asarray(tcoords) 511 if tcoords.ndim != 2: 512 vedo.logger.error("tcoords must be a 2-dimensional array") 513 return self 514 if tcoords.shape[0] != pd.GetNumberOfPoints(): 515 vedo.logger.error("nr of texture coords must match nr of points") 516 return self 517 if tcoords.shape[1] != 2: 518 vedo.logger.error("tcoords texture vector must have 2 components") 519 vtarr = numpy2vtk(tcoords) 520 vtarr.SetName("TCoordinates") 521 522 pd.GetPointData().SetTCoords(vtarr) 523 pd.GetPointData().Modified() 524 525 elif not pd.GetPointData().GetTCoords(): 526 527 # TCoords still void.. 528 # check that there are no texture-like arrays: 529 names = self.pointdata.keys() 530 candidate_arr = "" 531 for name in names: 532 vtarr = pd.GetPointData().GetArray(name) 533 if vtarr.GetNumberOfComponents() != 2: 534 continue 535 t0, t1 = vtarr.GetRange() 536 if t0 >= 0 and t1 <= 1: 537 candidate_arr = name 538 539 if candidate_arr: 540 541 vtarr = pd.GetPointData().GetArray(candidate_arr) 542 pd.GetPointData().SetTCoords(vtarr) 543 pd.GetPointData().Modified() 544 545 else: 546 # last resource is automatic mapping 547 tmapper = vtk.vtkTextureMapToPlane() 548 tmapper.AutomaticPlaneGenerationOn() 549 tmapper.SetInputData(pd) 550 tmapper.Update() 551 tc = tmapper.GetOutput().GetPointData().GetTCoords() 552 if scale or ushift or vshift: 553 ntc = vtk2numpy(tc) 554 if scale: 555 ntc *= scale 556 if ushift: 557 ntc[:, 0] += ushift 558 if vshift: 559 ntc[:, 1] += vshift 560 tc = numpy2vtk(tc) 561 pd.GetPointData().SetTCoords(tc) 562 pd.GetPointData().Modified() 563 564 if outimg: 565 tu.SetInputData(outimg) 566 tu.SetInterpolate(interpolate) 567 tu.SetRepeat(repeat) 568 tu.SetEdgeClamp(edge_clamp) 569 570 self.property.SetColor(1, 1, 1) 571 self._mapper.ScalarVisibilityOff() 572 self.SetTexture(tu) 573 574 if seam_threshold is not None: 575 tname = self._data.GetPointData().GetTCoords().GetName() 576 grad = self.gradient(tname) 577 ugrad, vgrad = np.split(grad, 2, axis=1) 578 ugradm, vgradm = vedo.utils.mag2(ugrad), vedo.utils.mag2(vgrad) 579 gradm = np.log(ugradm + vgradm) 580 largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 581 uvmap = self.pointdata[tname] 582 # collapse triangles that have large gradient 583 new_points = self.points(transformed=False) 584 for f in self.faces(): 585 if np.isin(f, largegrad_ids).all(): 586 id1, id2, id3 = f 587 uv1, uv2, uv3 = uvmap[f] 588 d12 = vedo.mag2(uv1 - uv2) 589 d23 = vedo.mag2(uv2 - uv3) 590 d31 = vedo.mag2(uv3 - uv1) 591 idm = np.argmin([d12, d23, d31]) 592 if idm == 0: 593 new_points[id1] = new_points[id3] 594 new_points[id2] = new_points[id3] 595 elif idm == 1: 596 new_points[id2] = new_points[id1] 597 new_points[id3] = new_points[id1] 598 self.points(new_points) 599 600 self.Modified() 601 return self
Assign a texture to mesh from image file or predefined texture tname
.
If tname is set to None
texture is disabled.
Input tname can also be an array or a vtkTexture
.
Arguments:
- tname : (numpy.array, str, Picture, vtkTexture, None) the input texture to be applied. Can be a numpy array, a path to an image file, a vedo Picture. The None value disables texture.
- tcoords : (numpy.array, str) this is the (u,v) texture coordinate array. Can also be a string of an existing array in the mesh.
- interpolate : (bool) turn on/off linear interpolation of the texture map when rendering.
- repeat : (bool) repeat of the texture when tcoords extend beyond the [0,1] range.
- edge_clamp : (bool) turn on/off the clamping of the texture map when the texture coords extend beyond the [0,1] range. Only used when repeat is False, and edge clamping is supported by the graphics card.
- scale : (bool) scale the texture image by this factor
- ushift : (bool) shift u-coordinates of texture by this amount
- vshift : (bool) shift v-coordinates of texture by this amount
- seam_threshold : (float) try to seal seams in texture by collapsing triangles (test values around 1.0, lower values = stronger collapse)
Examples:
603 def compute_normals(self, points=True, cells=True, feature_angle=None, consistency=True): 604 """ 605 Compute cell and vertex normals for the mesh. 606 607 Arguments: 608 points : (bool) 609 do the computation for the vertices too 610 cells : (bool) 611 do the computation for the cells too 612 feature_angle : (float) 613 specify the angle that defines a sharp edge. 614 If the difference in angle across neighboring polygons is greater than this value, 615 the shared edge is considered "sharp" and it is split. 616 consistency : (bool) 617 turn on/off the enforcement of consistent polygon ordering. 618 619 .. warning:: 620 If feature_angle is set to a float the Mesh can be modified, and it 621 can have a different nr. of vertices from the original. 622 """ 623 poly = self.polydata(False) 624 pdnorm = vtk.vtkPolyDataNormals() 625 pdnorm.SetInputData(poly) 626 pdnorm.SetComputePointNormals(points) 627 pdnorm.SetComputeCellNormals(cells) 628 pdnorm.SetConsistency(consistency) 629 pdnorm.FlipNormalsOff() 630 if feature_angle: 631 pdnorm.SetSplitting(True) 632 pdnorm.SetFeatureAngle(feature_angle) 633 else: 634 pdnorm.SetSplitting(False) 635 # print(pdnorm.GetNonManifoldTraversal()) 636 pdnorm.Update() 637 return self._update(pdnorm.GetOutput())
Compute cell and vertex normals for the mesh.
Arguments:
- points : (bool) do the computation for the vertices too
- cells : (bool) do the computation for the cells too
- feature_angle : (float) specify the angle that defines a sharp edge. If the difference in angle across neighboring polygons is greater than this value, the shared edge is considered "sharp" and it is split.
- consistency : (bool) turn on/off the enforcement of consistent polygon ordering.
If feature_angle is set to a float the Mesh can be modified, and it can have a different nr. of vertices from the original.
639 def reverse(self, cells=True, normals=False): 640 """ 641 Reverse the order of polygonal cells 642 and/or reverse the direction of point and cell normals. 643 Two flags are used to control these operations: 644 645 - `cells=True` reverses the order of the indices in the cell connectivity list. 646 If cell is a list of IDs only those cells will be reversed. 647 648 - `normals=True` reverses the normals by multiplying the normal vector by -1 649 (both point and cell normals, if present). 650 """ 651 poly = self.polydata(False) 652 653 if is_sequence(cells): 654 for cell in cells: 655 poly.ReverseCell(cell) 656 poly.GetCellData().Modified() 657 return self ############## 658 659 rev = vtk.vtkReverseSense() 660 if cells: 661 rev.ReverseCellsOn() 662 else: 663 rev.ReverseCellsOff() 664 if normals: 665 rev.ReverseNormalsOn() 666 else: 667 rev.ReverseNormalsOff() 668 rev.SetInputData(poly) 669 rev.Update() 670 out = self._update(rev.GetOutput()) 671 out.pipeline = OperationNode("reverse", parents=[self]) 672 return out
Reverse the order of polygonal cells and/or reverse the direction of point and cell normals. Two flags are used to control these operations:
cells=True
reverses the order of the indices in the cell connectivity list. If cell is a list of IDs only those cells will be reversed.normals=True
reverses the normals by multiplying the normal vector by -1 (both point and cell normals, if present).
674 def wireframe(self, value=True): 675 """Set mesh's representation as wireframe or solid surface.""" 676 if value: 677 self.property.SetRepresentationToWireframe() 678 else: 679 self.property.SetRepresentationToSurface() 680 return self
Set mesh's representation as wireframe or solid surface.
682 def flat(self): 683 """Set surface interpolation to flat. 684 685 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 686 """ 687 self.property.SetInterpolationToFlat() 688 return self
Set surface interpolation to flat.
690 def phong(self): 691 """Set surface interpolation to "phong".""" 692 self.property.SetInterpolationToPhong() 693 return self
Set surface interpolation to "phong".
695 def backface_culling(self, value=True): 696 """Set culling of polygons based on orientation of normal with respect to camera.""" 697 self.property.SetBackfaceCulling(value) 698 return self
Set culling of polygons based on orientation of normal with respect to camera.
700 def render_lines_as_tubes(self, value=True): 701 """Wrap a fake tube around a simple line for visualization""" 702 self.property.SetRenderLinesAsTubes(value) 703 return self
Wrap a fake tube around a simple line for visualization
705 def frontface_culling(self, value=True): 706 """Set culling of polygons based on orientation of normal with respect to camera.""" 707 self.property.SetFrontfaceCulling(value) 708 return self
Set culling of polygons based on orientation of normal with respect to camera.
710 def backcolor(self, bc=None): 711 """ 712 Set/get mesh's backface color. 713 """ 714 backProp = self.GetBackfaceProperty() 715 716 if bc is None: 717 if backProp: 718 return backProp.GetDiffuseColor() 719 return self 720 721 if self.property.GetOpacity() < 1: 722 return self 723 724 if not backProp: 725 backProp = vtk.vtkProperty() 726 727 backProp.SetDiffuseColor(get_color(bc)) 728 backProp.SetOpacity(self.property.GetOpacity()) 729 self.SetBackfaceProperty(backProp) 730 self._mapper.ScalarVisibilityOff() 731 return self
Set/get mesh's backface color.
733 def bc(self, backcolor=False): 734 """Shortcut for `mesh.backcolor()`.""" 735 return self.backcolor(backcolor)
Shortcut for mesh.backcolor()
.
737 def linewidth(self, lw=None): 738 """Set/get width of mesh edges. Same as `lw()`.""" 739 if lw is not None: 740 if lw == 0: 741 self.property.EdgeVisibilityOff() 742 self.property.SetRepresentationToSurface() 743 return self 744 self.property.EdgeVisibilityOn() 745 self.property.SetLineWidth(lw) 746 else: 747 return self.property.GetLineWidth() 748 return self
Set/get width of mesh edges. Same as lw()
.
750 def lw(self, linewidth=None): 751 """Set/get width of mesh edges. Same as `linewidth()`.""" 752 return self.linewidth(linewidth)
Set/get width of mesh edges. Same as linewidth()
.
754 def linecolor(self, lc=None): 755 """Set/get color of mesh edges. Same as `lc()`.""" 756 if lc is None: 757 return self.property.GetEdgeColor() 758 self.property.EdgeVisibilityOn() 759 self.property.SetEdgeColor(get_color(lc)) 760 return self
Set/get color of mesh edges. Same as lc()
.
762 def lc(self, linecolor=None): 763 """Set/get color of mesh edges. Same as `linecolor()`.""" 764 return self.linecolor(linecolor)
Set/get color of mesh edges. Same as linecolor()
.
766 def volume(self): 767 """Get/set the volume occupied by mesh.""" 768 mass = vtk.vtkMassProperties() 769 mass.SetGlobalWarningDisplay(0) 770 mass.SetInputData(self.polydata()) 771 mass.Update() 772 return mass.GetVolume()
Get/set the volume occupied by mesh.
774 def area(self): 775 """ 776 Compute the surface area of mesh. 777 The mesh must be triangular for this to work. 778 See also `mesh.triangulate()`. 779 """ 780 mass = vtk.vtkMassProperties() 781 mass.SetGlobalWarningDisplay(0) 782 mass.SetInputData(self.polydata()) 783 mass.Update() 784 return mass.GetSurfaceArea()
Compute the surface area of mesh.
The mesh must be triangular for this to work.
See also mesh.triangulate()
.
786 def is_closed(self): 787 """Return `True` if the mesh is watertight.""" 788 fe = vtk.vtkFeatureEdges() 789 fe.BoundaryEdgesOn() 790 fe.FeatureEdgesOff() 791 fe.NonManifoldEdgesOn() 792 fe.SetInputData(self.polydata(False)) 793 fe.Update() 794 ne = fe.GetOutput().GetNumberOfCells() 795 return not bool(ne)
Return True
if the mesh is watertight.
797 def is_manifold(self): 798 """Return `True` if the mesh is manifold.""" 799 fe = vtk.vtkFeatureEdges() 800 fe.BoundaryEdgesOff() 801 fe.FeatureEdgesOff() 802 fe.NonManifoldEdgesOn() 803 fe.SetInputData(self.polydata(False)) 804 fe.Update() 805 ne = fe.GetOutput().GetNumberOfCells() 806 return not bool(ne)
Return True
if the mesh is manifold.
808 def non_manifold_faces(self, remove=True, tol="auto"): 809 """ 810 Detect and (try to) remove non-manifold faces of a triangular mesh. 811 812 Set `remove` to `False` to mark cells without removing them. 813 Set `tol=0` for zero-tolerance, the result will be manifold but with holes. 814 Set `tol>0` to cut off non-manifold faces, and try to recover the good ones. 815 Set `tol="auto"` to make an automatic choice of the tolerance. 816 """ 817 # mark original point and cell ids 818 self.add_ids() 819 toremove = self.boundaries( 820 boundary_edges=False, non_manifold_edges=True, cell_edge=True, return_cell_ids=True 821 ) 822 if len(toremove) == 0: 823 return self 824 825 points = self.points() 826 faces = self.faces() 827 centers = self.cell_centers() 828 829 copy = self.clone() 830 copy.delete_cells(toremove).clean() 831 copy.compute_normals(cells=False) 832 normals = copy.normals() 833 deltas, deltas_i = [], [] 834 835 for i in vedo.utils.progressbar(toremove, delay=3, title="recover faces"): 836 pids = copy.closest_point(centers[i], n=3, return_point_id=True) 837 norms = normals[pids] 838 n = np.mean(norms, axis=0) 839 dn = np.linalg.norm(n) 840 if not dn: 841 continue 842 n = n / dn 843 844 p0, p1, p2 = points[faces[i]][:3] 845 v = np.cross(p1 - p0, p2 - p0) 846 lv = np.linalg.norm(v) 847 if not lv: 848 continue 849 v = v / lv 850 851 cosa = 1 - np.dot(n, v) 852 deltas.append(cosa) 853 deltas_i.append(i) 854 855 recover = [] 856 if len(deltas) > 0: 857 mean_delta = np.mean(deltas) 858 err_delta = np.std(deltas) 859 txt = "" 860 if tol == "auto": # automatic choice 861 tol = mean_delta / 5 862 txt = f"\n Automatic tol. : {tol: .4f}" 863 for i, cosa in zip(deltas_i, deltas): 864 if cosa < tol: 865 recover.append(i) 866 867 vedo.logger.info( 868 f"\n --------- Non manifold faces ---------" 869 f"\n Average tol. : {mean_delta: .4f} +- {err_delta: .4f}{txt}" 870 f"\n Removed faces : {len(toremove)}" 871 f"\n Recovered faces: {len(recover)}" 872 ) 873 874 toremove = list(set(toremove) - set(recover)) 875 876 if not remove: 877 mark = np.zeros(self.ncells, dtype=np.uint8) 878 mark[recover] = 1 879 mark[toremove] = 2 880 self.celldata["NonManifoldCell"] = mark 881 else: 882 self.delete_cells(toremove) 883 884 self.pipeline = OperationNode( 885 "non_manifold_faces", parents=[self], comment=f"#cells {self._data.GetNumberOfCells()}" 886 ) 887 return self
Detect and (try to) remove non-manifold faces of a triangular mesh.
Set remove
to False
to mark cells without removing them.
Set tol=0
for zero-tolerance, the result will be manifold but with holes.
Set tol>0
to cut off non-manifold faces, and try to recover the good ones.
Set tol="auto"
to make an automatic choice of the tolerance.
889 def shrink(self, fraction=0.85): 890 """Shrink the triangle polydata in the representation of the input mesh. 891 892 Examples: 893 - [shrink.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shrink.py) 894 895 ![](https://vedo.embl.es/images/basic/shrink.png) 896 """ 897 shrink = vtk.vtkShrinkPolyData() 898 shrink.SetInputData(self._data) 899 shrink.SetShrinkFactor(fraction) 900 shrink.Update() 901 self.point_locator = None 902 self.cell_locator = None 903 out = self._update(shrink.GetOutput()) 904 905 out.pipeline = OperationNode("shrink", parents=[self]) 906 return out
908 def stretch(self, q1, q2): 909 """ 910 Stretch mesh between points `q1` and `q2`. 911 912 Examples: 913 - [aspring1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring1.py) 914 915 ![](https://vedo.embl.es/images/simulations/50738955-7e891800-11d9-11e9-85cd-02bd4f3f13ea.gif) 916 917 .. note:: 918 for `Mesh` objects, two vectors `mesh.base`, and `mesh.top` must be defined. 919 """ 920 if self.base is None: 921 vedo.logger.error("in stretch() must define vectors mesh.base and mesh.top at creation") 922 raise RuntimeError() 923 924 p1, p2 = self.base, self.top 925 q1, q2, z = np.asarray(q1), np.asarray(q2), np.array([0, 0, 1]) 926 a = p2 - p1 927 b = q2 - q1 928 plength = np.linalg.norm(a) 929 qlength = np.linalg.norm(b) 930 T = vtk.vtkTransform() 931 T.PostMultiply() 932 T.Translate(-p1) 933 cosa = np.dot(a, z) / plength 934 n = np.cross(a, z) 935 if np.linalg.norm(n): 936 T.RotateWXYZ(np.rad2deg(np.arccos(cosa)), n) 937 T.Scale(1, 1, qlength / plength) 938 939 cosa = np.dot(b, z) / qlength 940 n = np.cross(b, z) 941 if np.linalg.norm(n): 942 T.RotateWXYZ(-np.rad2deg(np.arccos(cosa)), n) 943 else: 944 if np.dot(b, z) < 0: 945 T.RotateWXYZ(180, [1, 0, 0]) 946 947 T.Translate(q1) 948 949 self.SetUserMatrix(T.GetMatrix()) 950 return self
Stretch mesh between points q1
and q2
.
Examples:
for Mesh
objects, two vectors mesh.base
, and mesh.top
must be defined.
952 def crop(self, top=None, bottom=None, right=None, left=None, front=None, back=None): 953 """ 954 Crop an `Mesh` object. 955 Use this method at creation (before moving the object). 956 957 Arguments: 958 top : (float) 959 fraction to crop from the top plane (positive z) 960 bottom : (float) 961 fraction to crop from the bottom plane (negative z) 962 front : (float) 963 fraction to crop from the front plane (positive y) 964 back : (float) 965 fraction to crop from the back plane (negative y) 966 right : (float) 967 fraction to crop from the right plane (positive x) 968 left : (float) 969 fraction to crop from the left plane (negative x) 970 971 Example: 972 ```python 973 from vedo import Sphere 974 Sphere().crop(right=0.3, left=0.1).show() 975 ``` 976 ![](https://user-images.githubusercontent.com/32848391/57081955-0ef1e800-6cf6-11e9-99de-b45220939bc9.png) 977 """ 978 cu = vtk.vtkBox() 979 pos = np.array(self.GetPosition()) 980 x0, x1, y0, y1, z0, z1 = self.bounds() 981 x0, y0, z0 = [x0, y0, z0] - pos 982 x1, y1, z1 = [x1, y1, z1] - pos 983 984 dx, dy, dz = x1 - x0, y1 - y0, z1 - z0 985 if top: 986 z1 = z1 - top * dz 987 if bottom: 988 z0 = z0 + bottom * dz 989 if front: 990 y1 = y1 - front * dy 991 if back: 992 y0 = y0 + back * dy 993 if right: 994 x1 = x1 - right * dx 995 if left: 996 x0 = x0 + left * dx 997 bounds = (x0, x1, y0, y1, z0, z1) 998 999 cu.SetBounds(bounds) 1000 1001 clipper = vtk.vtkClipPolyData() 1002 clipper.SetInputData(self._data) 1003 clipper.SetClipFunction(cu) 1004 clipper.InsideOutOn() 1005 clipper.GenerateClippedOutputOff() 1006 clipper.GenerateClipScalarsOff() 1007 clipper.SetValue(0) 1008 clipper.Update() 1009 self._update(clipper.GetOutput()) 1010 self.point_locator = None 1011 1012 self.pipeline = OperationNode( 1013 "crop", parents=[self], comment=f"#pts {self._data.GetNumberOfPoints()}" 1014 ) 1015 return self
Crop an Mesh
object.
Use this method at creation (before moving the object).
Arguments:
- top : (float) fraction to crop from the top plane (positive z)
- bottom : (float) fraction to crop from the bottom plane (negative z)
- front : (float) fraction to crop from the front plane (positive y)
- back : (float) fraction to crop from the back plane (negative y)
- right : (float) fraction to crop from the right plane (positive x)
- left : (float) fraction to crop from the left plane (negative x)
Example:
from vedo import Sphere Sphere().crop(right=0.3, left=0.1).show()
1017 def cap(self, return_cap=False): 1018 """ 1019 Generate a "cap" on a clipped mesh, or caps sharp edges. 1020 1021 Examples: 1022 - [cut_and_cap.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/cut_and_cap.py) 1023 1024 ![](https://vedo.embl.es/images/advanced/cutAndCap.png) 1025 1026 See also: `join()`, `join_segments()`, `slice()`. 1027 """ 1028 poly = self._data 1029 1030 fe = vtk.vtkFeatureEdges() 1031 fe.SetInputData(poly) 1032 fe.BoundaryEdgesOn() 1033 fe.FeatureEdgesOff() 1034 fe.NonManifoldEdgesOff() 1035 fe.ManifoldEdgesOff() 1036 fe.Update() 1037 1038 stripper = vtk.vtkStripper() 1039 stripper.SetInputData(fe.GetOutput()) 1040 stripper.JoinContiguousSegmentsOn() 1041 stripper.Update() 1042 1043 boundaryPoly = vtk.vtkPolyData() 1044 boundaryPoly.SetPoints(stripper.GetOutput().GetPoints()) 1045 boundaryPoly.SetPolys(stripper.GetOutput().GetLines()) 1046 1047 rev = vtk.vtkReverseSense() 1048 rev.ReverseCellsOn() 1049 rev.SetInputData(boundaryPoly) 1050 rev.Update() 1051 1052 tf = vtk.vtkTriangleFilter() 1053 tf.SetInputData(rev.GetOutput()) 1054 tf.Update() 1055 1056 if return_cap: 1057 m = Mesh(tf.GetOutput()) 1058 # assign the same transformation to the copy 1059 m.SetOrigin(self.GetOrigin()) 1060 m.SetScale(self.GetScale()) 1061 m.SetOrientation(self.GetOrientation()) 1062 m.SetPosition(self.GetPosition()) 1063 1064 m.pipeline = OperationNode( 1065 "cap", parents=[self], comment=f"#pts {m._data.GetNumberOfPoints()}" 1066 ) 1067 return m 1068 1069 polyapp = vtk.vtkAppendPolyData() 1070 polyapp.AddInputData(poly) 1071 polyapp.AddInputData(tf.GetOutput()) 1072 polyapp.Update() 1073 out = self._update(polyapp.GetOutput()).clean() 1074 1075 out.pipeline = OperationNode( 1076 "capped", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1077 ) 1078 return out
Generate a "cap" on a clipped mesh, or caps sharp edges.
Examples:
See also: join()
, join_segments()
, slice()
.
1080 def join(self, polys=True, reset=False): 1081 """ 1082 Generate triangle strips and/or polylines from 1083 input polygons, triangle strips, and lines. 1084 1085 Input polygons are assembled into triangle strips only if they are triangles; 1086 other types of polygons are passed through to the output and not stripped. 1087 Use mesh.triangulate() to triangulate non-triangular polygons prior to running 1088 this filter if you need to strip all the data. 1089 1090 Also note that if triangle strips or polylines are present in the input 1091 they are passed through and not joined nor extended. 1092 If you wish to strip these use mesh.triangulate() to fragment the input 1093 into triangles and lines prior to applying join(). 1094 1095 Arguments: 1096 polys : (bool) 1097 polygonal segments will be joined if they are contiguous 1098 reset : (bool) 1099 reset points ordering 1100 1101 Warning: 1102 If triangle strips or polylines exist in the input data 1103 they will be passed through to the output data. 1104 This filter will only construct triangle strips if triangle polygons 1105 are available; and will only construct polylines if lines are available. 1106 1107 Example: 1108 ```python 1109 from vedo import * 1110 c1 = Cylinder(pos=(0,0,0), r=2, height=3, axis=(1,.0,0), alpha=.1).triangulate() 1111 c2 = Cylinder(pos=(0,0,2), r=1, height=2, axis=(0,.3,1), alpha=.1).triangulate() 1112 intersect = c1.intersect_with(c2).join(reset=True) 1113 spline = Spline(intersect).c('blue').lw(5) 1114 show(c1, c2, spline, intersect.labels('id'), axes=1).close() 1115 ``` 1116 ![](https://vedo.embl.es/images/feats/line_join.png) 1117 """ 1118 sf = vtk.vtkStripper() 1119 sf.SetPassThroughCellIds(True) 1120 sf.SetPassThroughPointIds(True) 1121 sf.SetJoinContiguousSegments(polys) 1122 sf.SetInputData(self.polydata(False)) 1123 sf.Update() 1124 if reset: 1125 poly = sf.GetOutput() 1126 cpd = vtk.vtkCleanPolyData() 1127 cpd.PointMergingOn() 1128 cpd.ConvertLinesToPointsOn() 1129 cpd.ConvertPolysToLinesOn() 1130 cpd.ConvertStripsToPolysOn() 1131 cpd.SetInputData(poly) 1132 cpd.Update() 1133 poly = cpd.GetOutput() 1134 vpts = poly.GetCell(0).GetPoints().GetData() 1135 poly.GetPoints().SetData(vpts) 1136 return self._update(poly) 1137 1138 out = self._update(sf.GetOutput()) 1139 out.pipeline = OperationNode( 1140 "join", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1141 ) 1142 return out
Generate triangle strips and/or polylines from input polygons, triangle strips, and lines.
Input polygons are assembled into triangle strips only if they are triangles; other types of polygons are passed through to the output and not stripped. Use mesh.triangulate() to triangulate non-triangular polygons prior to running this filter if you need to strip all the data.
Also note that if triangle strips or polylines are present in the input they are passed through and not joined nor extended. If you wish to strip these use mesh.triangulate() to fragment the input into triangles and lines prior to applying join().
Arguments:
- polys : (bool) polygonal segments will be joined if they are contiguous
- reset : (bool) reset points ordering
Warning:
If triangle strips or polylines exist in the input data they will be passed through to the output data. This filter will only construct triangle strips if triangle polygons are available; and will only construct polylines if lines are available.
Example:
from vedo import * c1 = Cylinder(pos=(0,0,0), r=2, height=3, axis=(1,.0,0), alpha=.1).triangulate() c2 = Cylinder(pos=(0,0,2), r=1, height=2, axis=(0,.3,1), alpha=.1).triangulate() intersect = c1.intersect_with(c2).join(reset=True) spline = Spline(intersect).c('blue').lw(5) show(c1, c2, spline, intersect.labels('id'), axes=1).close()
1144 def join_segments(self, closed=True, tol=1e-03): 1145 """ 1146 Join line segments into contiguous lines. 1147 Useful to call with `triangulate()` method. 1148 1149 Returns: 1150 list of `shapes.Lines` 1151 1152 Example: 1153 ```python 1154 from vedo import * 1155 msh = Torus().alpha(0.1).wireframe() 1156 intersection = msh.intersect_with_plane(normal=[1,1,1]).c('purple5') 1157 slices = [s.triangulate() for s in intersection.join_segments()] 1158 show(msh, intersection, merge(slices), axes=1, viewup='z') 1159 ``` 1160 ![](https://vedo.embl.es/images/feats/join_segments.jpg) 1161 """ 1162 vlines = [] 1163 for ipiece, outline in enumerate(self.split(must_share_edge=False)): 1164 1165 outline.clean() 1166 pts = outline.points() 1167 if len(pts) < 3: 1168 continue 1169 avesize = outline.average_size() 1170 lines = outline.lines() 1171 # print("---lines", lines, "in piece", ipiece) 1172 tol = avesize / pts.shape[0] * tol 1173 1174 k = 0 1175 joinedpts = [pts[k]] 1176 for _ in range(len(pts)): 1177 pk = pts[k] 1178 for j, line in enumerate(lines): 1179 1180 id0, id1 = line[0], line[-1] 1181 p0, p1 = pts[id0], pts[id1] 1182 1183 if np.linalg.norm(p0 - pk) < tol: 1184 n = len(line) 1185 for m in range(1, n): 1186 joinedpts.append(pts[line[m]]) 1187 # joinedpts.append(p1) 1188 k = id1 1189 lines.pop(j) 1190 break 1191 1192 elif np.linalg.norm(p1 - pk) < tol: 1193 n = len(line) 1194 for m in reversed(range(0, n - 1)): 1195 joinedpts.append(pts[line[m]]) 1196 # joinedpts.append(p0) 1197 k = id0 1198 lines.pop(j) 1199 break 1200 1201 if len(joinedpts) > 1: 1202 newline = vedo.shapes.Line(joinedpts, closed=closed) 1203 newline.clean() 1204 newline.SetProperty(self.GetProperty()) 1205 newline.property = self.GetProperty() 1206 newline.pipeline = OperationNode( 1207 "join_segments", 1208 parents=[self], 1209 comment=f"#pts {newline._data.GetNumberOfPoints()}", 1210 ) 1211 vlines.append(newline) 1212 1213 return vlines
Join line segments into contiguous lines.
Useful to call with triangulate()
method.
Returns:
list of
shapes.Lines
Example:
from vedo import * msh = Torus().alpha(0.1).wireframe() intersection = msh.intersect_with_plane(normal=[1,1,1]).c('purple5') slices = [s.triangulate() for s in intersection.join_segments()] show(msh, intersection, merge(slices), axes=1, viewup='z')
1215 def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)): 1216 """ 1217 Slice a mesh with a plane and fill the contour. 1218 1219 Example: 1220 ```python 1221 from vedo import * 1222 msh = Mesh(dataurl+"bunny.obj").alpha(0.1).wireframe() 1223 mslice = msh.slice(normal=[0,1,0.3], origin=[0,0.16,0]) 1224 mslice.c('purple5') 1225 show(msh, mslice, axes=1) 1226 ``` 1227 ![](https://vedo.embl.es/images/feats/mesh_slice.jpg) 1228 1229 See also: `join()`, `join_segments()`, `cap()`, `cut_with_plane()`. 1230 """ 1231 intersection = self.intersect_with_plane(origin=origin, normal=normal) 1232 slices = [s.triangulate() for s in intersection.join_segments()] 1233 mslices = vedo.pointcloud.merge(slices) 1234 if mslices: 1235 mslices.name = "MeshSlice" 1236 mslices.pipeline = OperationNode("slice", parents=[self], comment=f"normal = {normal}") 1237 return mslices
Slice a mesh with a plane and fill the contour.
Example:
from vedo import * msh = Mesh(dataurl+"bunny.obj").alpha(0.1).wireframe() mslice = msh.slice(normal=[0,1,0.3], origin=[0,0.16,0]) mslice.c('purple5') show(msh, mslice, axes=1)
See also: join()
, join_segments()
, cap()
, cut_with_plane()
.
1239 def triangulate(self, verts=True, lines=True): 1240 """ 1241 Converts mesh polygons into triangles. 1242 1243 If the input mesh is only made of 2D lines (no faces) the output will be a triangulation 1244 that fills the internal area. The contours may be concave, and may even contain holes, 1245 i.e. a contour may contain an internal contour winding in the opposite 1246 direction to indicate that it is a hole. 1247 1248 Arguments: 1249 verts : (bool) 1250 if True, break input vertex cells into individual vertex cells 1251 (one point per cell). If False, the input vertex cells will be ignored. 1252 lines : (bool) 1253 if True, break input polylines into line segments. 1254 If False, input lines will be ignored and the output will have no lines. 1255 """ 1256 if self._data.GetNumberOfPolys() or self._data.GetNumberOfStrips(): 1257 # print("vtkTriangleFilter") 1258 tf = vtk.vtkTriangleFilter() 1259 tf.SetPassLines(lines) 1260 tf.SetPassVerts(verts) 1261 1262 elif self._data.GetNumberOfLines(): 1263 # print("vtkContourTriangulator") 1264 tf = vtk.vtkContourTriangulator() 1265 tf.TriangulationErrorDisplayOn() 1266 1267 else: 1268 vedo.logger.debug("input in triangulate() seems to be void! Skip.") 1269 return self 1270 1271 tf.SetInputData(self._data) 1272 tf.Update() 1273 out = self._update(tf.GetOutput()).lw(0).lighting("default") 1274 out.PickableOn() 1275 1276 out.pipeline = OperationNode( 1277 "triangulate", parents=[self], comment=f"#cells {out.inputdata().GetNumberOfCells()}" 1278 ) 1279 return out
Converts mesh polygons into triangles.
If the input mesh is only made of 2D lines (no faces) the output will be a triangulation that fills the internal area. The contours may be concave, and may even contain holes, i.e. a contour may contain an internal contour winding in the opposite direction to indicate that it is a hole.
Arguments:
- verts : (bool) if True, break input vertex cells into individual vertex cells (one point per cell). If False, the input vertex cells will be ignored.
- lines : (bool) if True, break input polylines into line segments. If False, input lines will be ignored and the output will have no lines.
1281 def compute_cell_area(self, name="Area"): 1282 """Add to this mesh a cell data array containing the areas of the polygonal faces""" 1283 csf = vtk.vtkCellSizeFilter() 1284 csf.SetInputData(self.polydata(False)) 1285 csf.SetComputeArea(True) 1286 csf.SetComputeVolume(False) 1287 csf.SetComputeLength(False) 1288 csf.SetComputeVertexCount(False) 1289 csf.SetAreaArrayName(name) 1290 csf.Update() 1291 return self._update(csf.GetOutput())
Add to this mesh a cell data array containing the areas of the polygonal faces
1293 def compute_cell_vertex_count(self, name="VertexCount"): 1294 """Add to this mesh a cell data array containing the nr of vertices 1295 that a polygonal face has.""" 1296 csf = vtk.vtkCellSizeFilter() 1297 csf.SetInputData(self.polydata(False)) 1298 csf.SetComputeArea(False) 1299 csf.SetComputeVolume(False) 1300 csf.SetComputeLength(False) 1301 csf.SetComputeVertexCount(True) 1302 csf.SetVertexCountArrayName(name) 1303 csf.Update() 1304 return self._update(csf.GetOutput())
Add to this mesh a cell data array containing the nr of vertices that a polygonal face has.
1306 def compute_quality(self, metric=6): 1307 """ 1308 Calculate metrics of quality for the elements of a triangular mesh. 1309 This method adds to the mesh a cell array named "Quality". 1310 See class [vtkMeshQuality](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html) 1311 for explanation. 1312 1313 Arguments: 1314 metric : (int) 1315 type of available estimators are: 1316 - EDGE RATIO, 0 1317 - ASPECT RATIO, 1 1318 - RADIUS RATIO, 2 1319 - ASPECT FROBENIUS, 3 1320 - MED ASPECT FROBENIUS, 4 1321 - MAX ASPECT FROBENIUS, 5 1322 - MIN_ANGLE, 6 1323 - COLLAPSE RATIO, 7 1324 - MAX ANGLE, 8 1325 - CONDITION, 9 1326 - SCALED JACOBIAN, 10 1327 - SHEAR, 11 1328 - RELATIVE SIZE SQUARED, 12 1329 - SHAPE, 13 1330 - SHAPE AND SIZE, 14 1331 - DISTORTION, 15 1332 - MAX EDGE RATIO, 16 1333 - SKEW, 17 1334 - TAPER, 18 1335 - VOLUME, 19 1336 - STRETCH, 20 1337 - DIAGONAL, 21 1338 - DIMENSION, 22 1339 - ODDY, 23 1340 - SHEAR AND SIZE, 24 1341 - JACOBIAN, 25 1342 - WARPAGE, 26 1343 - ASPECT GAMMA, 27 1344 - AREA, 28 1345 - ASPECT BETA, 29 1346 1347 Examples: 1348 - [meshquality.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/meshquality.py) 1349 1350 ![](https://vedo.embl.es/images/advanced/meshquality.png) 1351 """ 1352 qf = vtk.vtkMeshQuality() 1353 qf.SetInputData(self.polydata(False)) 1354 qf.SetTriangleQualityMeasure(metric) 1355 qf.SaveCellQualityOn() 1356 qf.Update() 1357 pd = qf.GetOutput() 1358 self._update(pd) 1359 self.pipeline = OperationNode("compute_quality", parents=[self]) 1360 return self
Calculate metrics of quality for the elements of a triangular mesh. This method adds to the mesh a cell array named "Quality". See class vtkMeshQuality for explanation.
Arguments:
- metric : (int)
type of available estimators are:
- EDGE RATIO, 0
- ASPECT RATIO, 1
- RADIUS RATIO, 2
- ASPECT FROBENIUS, 3
- MED ASPECT FROBENIUS, 4
- MAX ASPECT FROBENIUS, 5
- MIN_ANGLE, 6
- COLLAPSE RATIO, 7
- MAX ANGLE, 8
- CONDITION, 9
- SCALED JACOBIAN, 10
- SHEAR, 11
- RELATIVE SIZE SQUARED, 12
- SHAPE, 13
- SHAPE AND SIZE, 14
- DISTORTION, 15
- MAX EDGE RATIO, 16
- SKEW, 17
- TAPER, 18
- VOLUME, 19
- STRETCH, 20
- DIAGONAL, 21
- DIMENSION, 22
- ODDY, 23
- SHEAR AND SIZE, 24
- JACOBIAN, 25
- WARPAGE, 26
- ASPECT GAMMA, 27
- AREA, 28
- ASPECT BETA, 29
Examples:
1362 def check_validity(self, tol=0): 1363 """ 1364 Return an array of possible problematic faces following this convention: 1365 - Valid = 0 1366 - WrongNumberOfPoints = 1 1367 - IntersectingEdges = 2 1368 - IntersectingFaces = 4 1369 - NoncontiguousEdges = 8 1370 - Nonconvex = 10 1371 - OrientedIncorrectly = 20 1372 1373 Arguments: 1374 tol : (float) 1375 value is used as an epsilon for floating point 1376 equality checks throughout the cell checking process. 1377 """ 1378 vald = vtk.vtkCellValidator() 1379 if tol: 1380 vald.SetTolerance(tol) 1381 vald.SetInputData(self._data) 1382 vald.Update() 1383 varr = vald.GetOutput().GetCellData().GetArray("ValidityState") 1384 return vtk2numpy(varr)
Return an array of possible problematic faces following this convention:
- Valid = 0
- WrongNumberOfPoints = 1
- IntersectingEdges = 2
- IntersectingFaces = 4
- NoncontiguousEdges = 8
- Nonconvex = 10
- OrientedIncorrectly = 20
Arguments:
- tol : (float) value is used as an epsilon for floating point equality checks throughout the cell checking process.
1386 def compute_curvature(self, method=0): 1387 """ 1388 Add scalars to `Mesh` that contains the curvature calculated in three different ways. 1389 1390 Variable `method` can be: 1391 - 0 = gaussian 1392 - 1 = mean curvature 1393 - 2 = max curvature 1394 - 3 = min curvature 1395 1396 Example: 1397 ```python 1398 from vedo import Torus 1399 Torus().compute_curvature().add_scalarbar().show(axes=1).close() 1400 ``` 1401 ![](https://user-images.githubusercontent.com/32848391/51934810-c2e88c00-2404-11e9-8e7e-ca0b7984bbb7.png) 1402 """ 1403 curve = vtk.vtkCurvatures() 1404 curve.SetInputData(self._data) 1405 curve.SetCurvatureType(method) 1406 curve.Update() 1407 self._update(curve.GetOutput()) 1408 self._mapper.ScalarVisibilityOn() 1409 return self
Add scalars to Mesh
that contains the curvature calculated in three different ways.
Variable method
can be:
- 0 = gaussian
- 1 = mean curvature
- 2 = max curvature
- 3 = min curvature
Example:
from vedo import Torus Torus().compute_curvature().add_scalarbar().show(axes=1).close()
1411 def compute_elevation(self, low=(0, 0, 0), high=(0, 0, 1), vrange=(0, 1)): 1412 """ 1413 Add to `Mesh` a scalar array that contains distance along a specified direction. 1414 1415 Arguments: 1416 low : (list) 1417 one end of the line (small scalar values) 1418 high : (list) 1419 other end of the line (large scalar values) 1420 vrange : (list) 1421 set the range of the scalar 1422 1423 Example: 1424 ```python 1425 from vedo import Sphere 1426 s = Sphere().compute_elevation(low=(0,0,0), high=(1,1,1)) 1427 s.add_scalarbar().show(axes=1).close() 1428 ``` 1429 ![](https://user-images.githubusercontent.com/32848391/68478872-3986a580-0231-11ea-8245-b68a683aa295.png) 1430 """ 1431 ef = vtk.vtkElevationFilter() 1432 ef.SetInputData(self.polydata()) 1433 ef.SetLowPoint(low) 1434 ef.SetHighPoint(high) 1435 ef.SetScalarRange(vrange) 1436 ef.Update() 1437 self._update(ef.GetOutput()) 1438 self._mapper.ScalarVisibilityOn() 1439 return self
Add to Mesh
a scalar array that contains distance along a specified direction.
Arguments:
- low : (list) one end of the line (small scalar values)
- high : (list) other end of the line (large scalar values)
- vrange : (list) set the range of the scalar
Example:
from vedo import Sphere s = Sphere().compute_elevation(low=(0,0,0), high=(1,1,1)) s.add_scalarbar().show(axes=1).close()
1441 def subdivide(self, n=1, method=0, mel=None): 1442 """ 1443 Increase the number of vertices of a surface mesh. 1444 1445 Arguments: 1446 n : (int) 1447 number of subdivisions. 1448 method : (int) 1449 Loop(0), Linear(1), Adaptive(2), Butterfly(3), Centroid(4) 1450 mel : (float) 1451 Maximum Edge Length (applicable to Adaptive method only). 1452 """ 1453 triangles = vtk.vtkTriangleFilter() 1454 triangles.SetInputData(self._data) 1455 triangles.Update() 1456 originalMesh = triangles.GetOutput() 1457 if method == 0: 1458 sdf = vtk.vtkLoopSubdivisionFilter() 1459 elif method == 1: 1460 sdf = vtk.vtkLinearSubdivisionFilter() 1461 elif method == 2: 1462 sdf = vtk.vtkAdaptiveSubdivisionFilter() 1463 if mel is None: 1464 mel = self.diagonal_size() / np.sqrt(self._data.GetNumberOfPoints()) / n 1465 sdf.SetMaximumEdgeLength(mel) 1466 elif method == 3: 1467 sdf = vtk.vtkButterflySubdivisionFilter() 1468 elif method == 4: 1469 sdf = vtk.vtkDensifyPolyData() 1470 else: 1471 vedo.logger.error(f"in subdivide() unknown method {method}") 1472 raise RuntimeError() 1473 1474 if method != 2: 1475 sdf.SetNumberOfSubdivisions(n) 1476 1477 sdf.SetInputData(originalMesh) 1478 sdf.Update() 1479 out = sdf.GetOutput() 1480 1481 self.pipeline = OperationNode( 1482 "subdivide", parents=[self], comment=f"#pts {out.GetNumberOfPoints()}" 1483 ) 1484 return self._update(out)
Increase the number of vertices of a surface mesh.
Arguments:
- n : (int) number of subdivisions.
- method : (int) Loop(0), Linear(1), Adaptive(2), Butterfly(3), Centroid(4)
- mel : (float) Maximum Edge Length (applicable to Adaptive method only).
1486 def decimate(self, fraction=0.5, n=None, method="quadric", boundaries=False): 1487 """ 1488 Downsample the number of vertices in a mesh to `fraction`. 1489 1490 Arguments: 1491 fraction : (float) 1492 the desired target of reduction. 1493 n : (int) 1494 the desired number of final points (`fraction` is recalculated based on it). 1495 method : (str) 1496 can be either 'quadric' or 'pro'. In the first case triagulation 1497 will look like more regular, irrespective of the mesh original curvature. 1498 In the second case triangles are more irregular but mesh is more precise on more 1499 curved regions. 1500 boundaries : (bool) 1501 in "pro" mode decide whether to leave boundaries untouched or not 1502 1503 .. note:: Setting `fraction=0.1` leaves 10% of the original number of vertices 1504 """ 1505 poly = self._data 1506 if n: # N = desired number of points 1507 npt = poly.GetNumberOfPoints() 1508 fraction = n / npt 1509 if fraction >= 1: 1510 return self 1511 1512 if "quad" in method: 1513 decimate = vtk.vtkQuadricDecimation() 1514 # decimate.SetVolumePreservation(True) 1515 1516 else: 1517 decimate = vtk.vtkDecimatePro() 1518 decimate.PreserveTopologyOn() 1519 if boundaries: 1520 decimate.BoundaryVertexDeletionOff() 1521 else: 1522 decimate.BoundaryVertexDeletionOn() 1523 decimate.SetInputData(poly) 1524 decimate.SetTargetReduction(1 - fraction) 1525 decimate.Update() 1526 out = decimate.GetOutput() 1527 1528 self.pipeline = OperationNode( 1529 "decimate", parents=[self], comment=f"#pts {out.GetNumberOfPoints()}" 1530 ) 1531 return self._update(out)
Downsample the number of vertices in a mesh to fraction
.
Arguments:
- fraction : (float) the desired target of reduction.
- n : (int)
the desired number of final points (
fraction
is recalculated based on it). - method : (str) can be either 'quadric' or 'pro'. In the first case triagulation will look like more regular, irrespective of the mesh original curvature. In the second case triangles are more irregular but mesh is more precise on more curved regions.
- boundaries : (bool) in "pro" mode decide whether to leave boundaries untouched or not
Setting fraction=0.1
leaves 10% of the original number of vertices
1533 def collapse_edges(self, distance, iterations=1): 1534 """Collapse mesh edges so that are all above distance.""" 1535 self.clean() 1536 x0, x1, y0, y1, z0, z1 = self.bounds() 1537 fs = min(x1 - x0, y1 - y0, z1 - z0) / 10 1538 d2 = distance * distance 1539 if distance > fs: 1540 vedo.logger.error(f"distance parameter is too large, should be < {fs}, skip!") 1541 return self 1542 for _ in range(iterations): 1543 medges = self.edges() 1544 pts = self.points() 1545 newpts = np.array(pts) 1546 moved = [] 1547 for e in medges: 1548 if len(e) == 2: 1549 id0, id1 = e 1550 p0, p1 = pts[id0], pts[id1] 1551 d = mag2(p1 - p0) 1552 if d < d2 and id0 not in moved and id1 not in moved: 1553 p = (p0 + p1) / 2 1554 newpts[id0] = p 1555 newpts[id1] = p 1556 moved += [id0, id1] 1557 1558 self.points(newpts) 1559 self.clean() 1560 self.compute_normals() 1561 1562 self.pipeline = OperationNode( 1563 "collapse_edges", parents=[self], comment=f"#pts {self._data.GetNumberOfPoints()}" 1564 ) 1565 return self
Collapse mesh edges so that are all above distance.
1567 def smooth(self, niter=15, pass_band=0.1, edge_angle=15, feature_angle=60, boundary=False): 1568 """ 1569 Adjust mesh point positions using the `Windowed Sinc` function interpolation kernel. 1570 1571 Arguments: 1572 niter : (int) 1573 number of iterations. 1574 pass_band : (float) 1575 set the pass_band value for the windowed sinc filter. 1576 edge_angle : (float) 1577 edge angle to control smoothing along edges (either interior or boundary). 1578 feature_angle : (float) 1579 specifies the feature angle for sharp edge identification. 1580 boundary : (bool) 1581 specify if boundary should also be smoothed or kept unmodified 1582 1583 Examples: 1584 - [mesh_smoother1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/mesh_smoother1.py) 1585 1586 ![](https://vedo.embl.es/images/advanced/mesh_smoother2.png) 1587 """ 1588 poly = self._data 1589 cl = vtk.vtkCleanPolyData() 1590 cl.SetInputData(poly) 1591 cl.Update() 1592 smf = vtk.vtkWindowedSincPolyDataFilter() 1593 smf.SetInputData(cl.GetOutput()) 1594 smf.SetNumberOfIterations(niter) 1595 smf.SetEdgeAngle(edge_angle) 1596 smf.SetFeatureAngle(feature_angle) 1597 smf.SetPassBand(pass_band) 1598 smf.NormalizeCoordinatesOn() 1599 smf.NonManifoldSmoothingOn() 1600 smf.FeatureEdgeSmoothingOn() 1601 smf.SetBoundarySmoothing(boundary) 1602 smf.Update() 1603 out = self._update(smf.GetOutput()) 1604 1605 out.pipeline = OperationNode( 1606 "smooth", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1607 ) 1608 return out
Adjust mesh point positions using the Windowed Sinc
function interpolation kernel.
Arguments:
- niter : (int) number of iterations.
- pass_band : (float) set the pass_band value for the windowed sinc filter.
- edge_angle : (float) edge angle to control smoothing along edges (either interior or boundary).
- feature_angle : (float) specifies the feature angle for sharp edge identification.
- boundary : (bool) specify if boundary should also be smoothed or kept unmodified
Examples:
1611 def fill_holes(self, size=None): 1612 """ 1613 Identifies and fills holes in input mesh. 1614 Holes are identified by locating boundary edges, linking them together into loops, 1615 and then triangulating the resulting loops. 1616 1617 Arguments: 1618 size : (float) 1619 Approximate limit to the size of the hole that can be filled. The default is None. 1620 1621 Examples: 1622 - [fillholes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fillholes.py) 1623 """ 1624 fh = vtk.vtkFillHolesFilter() 1625 if not size: 1626 mb = self.diagonal_size() 1627 size = mb / 10 1628 fh.SetHoleSize(size) 1629 fh.SetInputData(self._data) 1630 fh.Update() 1631 out = self._update(fh.GetOutput()) 1632 1633 out.pipeline = OperationNode( 1634 "fill_holes", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1635 ) 1636 return out
Identifies and fills holes in input mesh. Holes are identified by locating boundary edges, linking them together into loops, and then triangulating the resulting loops.
Arguments:
- size : (float) Approximate limit to the size of the hole that can be filled. The default is None.
Examples:
1638 def is_inside(self, point, tol=1e-05): 1639 """Return True if point is inside a polydata closed surface.""" 1640 poly = self.polydata() 1641 points = vtk.vtkPoints() 1642 points.InsertNextPoint(point) 1643 pointsPolydata = vtk.vtkPolyData() 1644 pointsPolydata.SetPoints(points) 1645 sep = vtk.vtkSelectEnclosedPoints() 1646 sep.SetTolerance(tol) 1647 sep.CheckSurfaceOff() 1648 sep.SetInputData(pointsPolydata) 1649 sep.SetSurfaceData(poly) 1650 sep.Update() 1651 return sep.IsInside(0)
Return True if point is inside a polydata closed surface.
1653 def inside_points(self, pts, invert=False, tol=1e-05, return_ids=False): 1654 """ 1655 Return the point cloud that is inside mesh surface as a new Points object. 1656 1657 If return_ids is True a list of IDs is returned and in addition input points 1658 are marked by a pointdata array named "IsInside". 1659 1660 Example: 1661 `print(pts.pointdata["IsInside"])` 1662 1663 Examples: 1664 - [pca_ellipsoid.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/pca_ellipsoid.py) 1665 1666 ![](https://vedo.embl.es/images/basic/pca.png) 1667 """ 1668 if isinstance(pts, Points): 1669 pointsPolydata = pts.polydata() 1670 ptsa = pts.points() 1671 else: 1672 ptsa = np.asarray(pts) 1673 vpoints = vtk.vtkPoints() 1674 vpoints.SetData(numpy2vtk(ptsa, dtype=np.float32)) 1675 pointsPolydata = vtk.vtkPolyData() 1676 pointsPolydata.SetPoints(vpoints) 1677 1678 sep = vtk.vtkSelectEnclosedPoints() 1679 # sep = vtk.vtkExtractEnclosedPoints() 1680 sep.SetTolerance(tol) 1681 sep.SetInputData(pointsPolydata) 1682 sep.SetSurfaceData(self.polydata()) 1683 sep.SetInsideOut(invert) 1684 sep.Update() 1685 1686 varr = sep.GetOutput().GetPointData().GetArray("SelectedPoints") 1687 mask = vtk2numpy(varr).astype(bool) 1688 ids = np.array(range(len(ptsa)), dtype=int)[mask] 1689 1690 if isinstance(pts, Points): 1691 varr.SetName("IsInside") 1692 pts.inputdata().GetPointData().AddArray(varr) 1693 1694 if return_ids: 1695 return ids 1696 1697 pcl = Points(ptsa[ids]) 1698 pcl.name = "InsidePoints" 1699 1700 pcl.pipeline = OperationNode( 1701 "inside_points", parents=[self, ptsa], 1702 comment=f"#pts {pcl.inputdata().GetNumberOfPoints()}" 1703 ) 1704 return pcl
Return the point cloud that is inside mesh surface as a new Points object.
If return_ids is True a list of IDs is returned and in addition input points are marked by a pointdata array named "IsInside".
Example:
print(pts.pointdata["IsInside"])
Examples:
1706 def boundaries( 1707 self, 1708 boundary_edges=True, 1709 manifold_edges=False, 1710 non_manifold_edges=False, 1711 feature_angle=None, 1712 return_point_ids=False, 1713 return_cell_ids=False, 1714 cell_edge=False, 1715 ): 1716 """ 1717 Return the boundary lines of an input mesh. 1718 Check also `vedo.base.BaseActor.mark_boundaries()` method. 1719 1720 Arguments: 1721 boundary_edges : (bool) 1722 Turn on/off the extraction of boundary edges. 1723 manifold_edges : (bool) 1724 Turn on/off the extraction of manifold edges. 1725 non_manifold_edges : (bool) 1726 Turn on/off the extraction of non-manifold edges. 1727 feature_angle : (bool) 1728 Specify the min angle btw 2 faces for extracting edges. 1729 return_point_ids : (bool) 1730 return a numpy array of point indices 1731 return_cell_ids : (bool) 1732 return a numpy array of cell indices 1733 cell_edge : (bool) 1734 set to `True` if a cell need to share an edge with 1735 the boundary line, or `False` if a single vertex is enough 1736 1737 Examples: 1738 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1739 1740 ![](https://vedo.embl.es/images/basic/boundaries.png) 1741 """ 1742 fe = vtk.vtkFeatureEdges() 1743 fe.SetBoundaryEdges(boundary_edges) 1744 fe.SetNonManifoldEdges(non_manifold_edges) 1745 fe.SetManifoldEdges(manifold_edges) 1746 # fe.SetPassLines(True) # vtk9.2 1747 fe.ColoringOff() 1748 fe.SetFeatureEdges(False) 1749 if feature_angle is not None: 1750 fe.SetFeatureEdges(True) 1751 fe.SetFeatureAngle(feature_angle) 1752 1753 if return_point_ids or return_cell_ids: 1754 idf = vtk.vtkIdFilter() 1755 idf.SetInputData(self.polydata()) 1756 idf.SetPointIdsArrayName("BoundaryIds") 1757 idf.SetPointIds(True) 1758 idf.Update() 1759 1760 fe.SetInputData(idf.GetOutput()) 1761 fe.Update() 1762 1763 vid = fe.GetOutput().GetPointData().GetArray("BoundaryIds") 1764 npid = vtk2numpy(vid).astype(int) 1765 1766 if return_point_ids: 1767 return npid 1768 1769 if return_cell_ids: 1770 n = 1 if cell_edge else 0 1771 inface = [] 1772 for i, face in enumerate(self.faces()): 1773 # isin = np.any([vtx in npid for vtx in face]) 1774 isin = 0 1775 for vtx in face: 1776 isin += int(vtx in npid) 1777 if isin > n: 1778 break 1779 if isin > n: 1780 inface.append(i) 1781 return np.array(inface).astype(int) 1782 1783 return self 1784 1785 else: 1786 1787 fe.SetInputData(self.polydata()) 1788 fe.Update() 1789 msh = Mesh(fe.GetOutput(), c="p").lw(5).lighting("off") 1790 1791 msh.pipeline = OperationNode( 1792 "boundaries", 1793 parents=[self], 1794 shape="octagon", 1795 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 1796 ) 1797 return msh
Return the boundary lines of an input mesh.
Check also vedo.base.BaseActor.mark_boundaries()
method.
Arguments:
- boundary_edges : (bool) Turn on/off the extraction of boundary edges.
- manifold_edges : (bool) Turn on/off the extraction of manifold edges.
- non_manifold_edges : (bool) Turn on/off the extraction of non-manifold edges.
- feature_angle : (bool) Specify the min angle btw 2 faces for extracting edges.
- return_point_ids : (bool) return a numpy array of point indices
- return_cell_ids : (bool) return a numpy array of cell indices
- cell_edge : (bool)
set to
True
if a cell need to share an edge with the boundary line, orFalse
if a single vertex is enough
Examples:
1799 def imprint(self, loopline, tol=0.01): 1800 """ 1801 Imprint the contact surface of one object onto another surface. 1802 1803 Arguments: 1804 loopline : vedo.shapes.Line 1805 a Line object to be imprinted onto the mesh. 1806 tol : (float), optional 1807 projection tolerance which controls how close the imprint 1808 surface must be to the target. 1809 1810 Example: 1811 ```python 1812 from vedo import * 1813 grid = Grid()#.triangulate() 1814 circle = Circle(r=0.3, res=24).pos(0.11,0.12) 1815 line = Line(circle, closed=True, lw=4, c='r4') 1816 grid.imprint(line) 1817 show(grid, line, axes=1).close() 1818 ``` 1819 ![](https://vedo.embl.es/images/feats/imprint.png) 1820 """ 1821 loop = vtk.vtkContourLoopExtraction() 1822 loop.SetInputData(loopline.polydata()) 1823 loop.Update() 1824 1825 clean_loop = vtk.vtkCleanPolyData() 1826 clean_loop.SetInputData(loop.GetOutput()) 1827 clean_loop.Update() 1828 1829 imp = vtk.vtkImprintFilter() 1830 imp.SetTargetData(self.polydata()) 1831 imp.SetImprintData(clean_loop.GetOutput()) 1832 imp.SetTolerance(tol) 1833 imp.BoundaryEdgeInsertionOn() 1834 imp.TriangulateOutputOn() 1835 imp.Update() 1836 out = self._update(imp.GetOutput()) 1837 1838 out.pipeline = OperationNode( 1839 "imprint", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1840 ) 1841 return out
Imprint the contact surface of one object onto another surface.
Arguments:
- loopline : vedo.shapes.Line a Line object to be imprinted onto the mesh.
- tol : (float), optional projection tolerance which controls how close the imprint surface must be to the target.
Example:
from vedo import * grid = Grid()#.triangulate() circle = Circle(r=0.3, res=24).pos(0.11,0.12) line = Line(circle, closed=True, lw=4, c='r4') grid.imprint(line) show(grid, line, axes=1).close()
1843 def connected_vertices(self, index): 1844 """Find all vertices connected to an input vertex specified by its index. 1845 1846 Examples: 1847 - [connected_vtx.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/connected_vtx.py) 1848 1849 ![](https://vedo.embl.es/images/basic/connVtx.png) 1850 """ 1851 poly = self._data 1852 1853 cell_idlist = vtk.vtkIdList() 1854 poly.GetPointCells(index, cell_idlist) 1855 1856 idxs = [] 1857 for i in range(cell_idlist.GetNumberOfIds()): 1858 point_idlist = vtk.vtkIdList() 1859 poly.GetCellPoints(cell_idlist.GetId(i), point_idlist) 1860 for j in range(point_idlist.GetNumberOfIds()): 1861 idj = point_idlist.GetId(j) 1862 if idj == index: 1863 continue 1864 if idj in idxs: 1865 continue 1866 idxs.append(idj) 1867 1868 return idxs
1870 def connected_cells(self, index, return_ids=False): 1871 """Find all cellls connected to an input vertex specified by its index.""" 1872 1873 # Find all cells connected to point index 1874 dpoly = self._data 1875 idlist = vtk.vtkIdList() 1876 dpoly.GetPointCells(index, idlist) 1877 1878 ids = vtk.vtkIdTypeArray() 1879 ids.SetNumberOfComponents(1) 1880 rids = [] 1881 for k in range(idlist.GetNumberOfIds()): 1882 cid = idlist.GetId(k) 1883 ids.InsertNextValue(cid) 1884 rids.append(int(cid)) 1885 if return_ids: 1886 return rids 1887 1888 selection_node = vtk.vtkSelectionNode() 1889 selection_node.SetFieldType(vtk.vtkSelectionNode.CELL) 1890 selection_node.SetContentType(vtk.vtkSelectionNode.INDICES) 1891 selection_node.SetSelectionList(ids) 1892 selection = vtk.vtkSelection() 1893 selection.AddNode(selection_node) 1894 extractSelection = vtk.vtkExtractSelection() 1895 extractSelection.SetInputData(0, dpoly) 1896 extractSelection.SetInputData(1, selection) 1897 extractSelection.Update() 1898 gf = vtk.vtkGeometryFilter() 1899 gf.SetInputData(extractSelection.GetOutput()) 1900 gf.Update() 1901 return Mesh(gf.GetOutput()).lw(1)
Find all cellls connected to an input vertex specified by its index.
1903 def silhouette(self, direction=None, border_edges=True, feature_angle=False): 1904 """ 1905 Return a new line `Mesh` which corresponds to the outer `silhouette` 1906 of the input as seen along a specified `direction`, this can also be 1907 a `vtkCamera` object. 1908 1909 Arguments: 1910 direction : (list) 1911 viewpoint direction vector. 1912 If *None* this is guessed by looking at the minimum 1913 of the sides of the bounding box. 1914 border_edges : (bool) 1915 enable or disable generation of border edges 1916 feature_angle : (float) 1917 minimal angle for sharp edges detection. 1918 If set to `False` the functionality is disabled. 1919 1920 Examples: 1921 - [silhouette1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette1.py) 1922 1923 ![](https://vedo.embl.es/images/basic/silhouette1.png) 1924 """ 1925 sil = vtk.vtkPolyDataSilhouette() 1926 sil.SetInputData(self.polydata()) 1927 sil.SetBorderEdges(border_edges) 1928 if feature_angle is False: 1929 sil.SetEnableFeatureAngle(0) 1930 else: 1931 sil.SetEnableFeatureAngle(1) 1932 sil.SetFeatureAngle(feature_angle) 1933 1934 if direction is None and vedo.plotter_instance and vedo.plotter_instance.camera: 1935 sil.SetCamera(vedo.plotter_instance.camera) 1936 m = Mesh() 1937 m.mapper().SetInputConnection(sil.GetOutputPort()) 1938 1939 elif isinstance(direction, vtk.vtkCamera): 1940 sil.SetCamera(direction) 1941 m = Mesh() 1942 m.mapper().SetInputConnection(sil.GetOutputPort()) 1943 1944 elif direction == "2d": 1945 sil.SetVector(3.4, 4.5, 5.6) # random 1946 sil.SetDirectionToSpecifiedVector() 1947 sil.Update() 1948 m = Mesh(sil.GetOutput()) 1949 1950 elif is_sequence(direction): 1951 sil.SetVector(direction) 1952 sil.SetDirectionToSpecifiedVector() 1953 sil.Update() 1954 m = Mesh(sil.GetOutput()) 1955 else: 1956 vedo.logger.error(f"in silhouette() unknown direction type {type(direction)}") 1957 vedo.logger.error("first render the scene with show() or specify camera/direction") 1958 return self 1959 1960 m.lw(2).c((0, 0, 0)).lighting("off") 1961 m.mapper().SetResolveCoincidentTopologyToPolygonOffset() 1962 m.pipeline = OperationNode("silhouette", parents=[self]) 1963 return m
Return a new line Mesh
which corresponds to the outer silhouette
of the input as seen along a specified direction
, this can also be
a vtkCamera
object.
Arguments:
- direction : (list) viewpoint direction vector. If None this is guessed by looking at the minimum of the sides of the bounding box.
- border_edges : (bool) enable or disable generation of border edges
- feature_angle : (float)
minimal angle for sharp edges detection.
If set to
False
the functionality is disabled.
Examples:
1966 def follow_camera(self, camera=None): 1967 """ 1968 Return an object that will follow camera movements and stay locked to it. 1969 Use `mesh.follow_camera(False)` to disable it. 1970 1971 A `vtkCamera` object can also be passed. 1972 """ 1973 if camera is False: 1974 try: 1975 self.SetCamera(None) 1976 return self 1977 except AttributeError: 1978 return self 1979 1980 factor = Follower(self, camera) 1981 1982 if isinstance(camera, vtk.vtkCamera): 1983 factor.SetCamera(camera) 1984 else: 1985 plt = vedo.plotter_instance 1986 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 1987 factor.SetCamera(plt.renderer.GetActiveCamera()) 1988 else: 1989 factor._isfollower = True # postpone to show() call 1990 1991 return factor
Return an object that will follow camera movements and stay locked to it.
Use mesh.follow_camera(False)
to disable it.
A vtkCamera
object can also be passed.
1993 def isobands(self, n=10, vmin=None, vmax=None): 1994 """ 1995 Return a new `Mesh` representing the isobands of the active scalars. 1996 This is a new mesh where the scalar is now associated to cell faces and 1997 used to colorize the mesh. 1998 1999 Arguments: 2000 n : (int) 2001 number of isobands in the range 2002 vmin : (float) 2003 minimum of the range 2004 vmax : (float) 2005 maximum of the range 2006 2007 Examples: 2008 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/isolines.py) 2009 """ 2010 r0, r1 = self._data.GetScalarRange() 2011 if vmin is None: 2012 vmin = r0 2013 if vmax is None: 2014 vmax = r1 2015 2016 # -------------------------------- 2017 bands = [] 2018 dx = (vmax - vmin) / float(n) 2019 b = [vmin, vmin + dx / 2.0, vmin + dx] 2020 i = 0 2021 while i < n: 2022 bands.append(b) 2023 b = [b[0] + dx, b[1] + dx, b[2] + dx] 2024 i += 1 2025 2026 # annotate, use the midpoint of the band as the label 2027 lut = self.mapper().GetLookupTable() 2028 labels = [] 2029 for b in bands: 2030 labels.append("{:4.2f}".format(b[1])) 2031 values = vtk.vtkVariantArray() 2032 for la in labels: 2033 values.InsertNextValue(vtk.vtkVariant(la)) 2034 for i in range(values.GetNumberOfTuples()): 2035 lut.SetAnnotation(i, values.GetValue(i).ToString()) 2036 2037 bcf = vtk.vtkBandedPolyDataContourFilter() 2038 bcf.SetInputData(self.polydata()) 2039 # Use either the minimum or maximum value for each band. 2040 for i, band in enumerate(bands): 2041 bcf.SetValue(i, band[2]) 2042 # We will use an indexed lookup table. 2043 bcf.SetScalarModeToIndex() 2044 bcf.GenerateContourEdgesOff() 2045 bcf.Update() 2046 bcf.GetOutput().GetCellData().GetScalars().SetName("IsoBands") 2047 m1 = Mesh(bcf.GetOutput()).compute_normals(cells=True) 2048 m1.mapper().SetLookupTable(lut) 2049 2050 m1.pipeline = OperationNode("isobands", parents=[self]) 2051 return m1
Return a new Mesh
representing the isobands of the active scalars.
This is a new mesh where the scalar is now associated to cell faces and
used to colorize the mesh.
Arguments:
- n : (int) number of isobands in the range
- vmin : (float) minimum of the range
- vmax : (float) maximum of the range
Examples:
2053 def isolines(self, n=10, vmin=None, vmax=None): 2054 """ 2055 Return a new `Mesh` representing the isolines of the active scalars. 2056 2057 Arguments: 2058 n : (int) 2059 number of isolines in the range 2060 vmin : (float) 2061 minimum of the range 2062 vmax : (float) 2063 maximum of the range 2064 2065 Examples: 2066 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/isolines.py) 2067 2068 ![](https://vedo.embl.es/images/pyplot/isolines.png) 2069 """ 2070 bcf = vtk.vtkContourFilter() 2071 bcf.SetInputData(self.polydata()) 2072 r0, r1 = self._data.GetScalarRange() 2073 if vmin is None: 2074 vmin = r0 2075 if vmax is None: 2076 vmax = r1 2077 bcf.GenerateValues(n, vmin, vmax) 2078 bcf.Update() 2079 sf = vtk.vtkStripper() 2080 sf.SetJoinContiguousSegments(True) 2081 sf.SetInputData(bcf.GetOutput()) 2082 sf.Update() 2083 cl = vtk.vtkCleanPolyData() 2084 cl.SetInputData(sf.GetOutput()) 2085 cl.Update() 2086 msh = Mesh(cl.GetOutput(), c="k").lighting("off") 2087 msh.mapper().SetResolveCoincidentTopologyToPolygonOffset() 2088 msh.pipeline = OperationNode("isolines", parents=[self]) 2089 return msh
Return a new Mesh
representing the isolines of the active scalars.
Arguments:
- n : (int) number of isolines in the range
- vmin : (float) minimum of the range
- vmax : (float) maximum of the range
Examples:
2091 def extrude(self, zshift=1, rotation=0, dR=0, cap=True, res=1): 2092 """ 2093 Sweep a polygonal data creating a "skirt" from free edges and lines, and lines from vertices. 2094 The input dataset is swept around the z-axis to create new polygonal primitives. 2095 For example, sweeping a line results in a cylindrical shell, and sweeping a circle creates a torus. 2096 2097 You can control whether the sweep of a 2D object (i.e., polygon or triangle strip) 2098 is capped with the generating geometry. 2099 Also, you can control the angle of rotation, and whether translation along the z-axis 2100 is performed along with the rotation. (Translation is useful for creating "springs"). 2101 You also can adjust the radius of the generating geometry using the "dR" keyword. 2102 2103 The skirt is generated by locating certain topological features. 2104 Free edges (edges of polygons or triangle strips only used by one polygon or triangle strips) 2105 generate surfaces. This is true also of lines or polylines. Vertices generate lines. 2106 2107 This filter can be used to model axisymmetric objects like cylinders, bottles, and wine glasses; 2108 or translational/rotational symmetric objects like springs or corkscrews. 2109 2110 Warning: 2111 Some polygonal objects have no free edges (e.g., sphere). When swept, this will result 2112 in two separate surfaces if capping is on, or no surface if capping is off. 2113 2114 Examples: 2115 - [extrude.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/extrude.py) 2116 2117 ![](https://vedo.embl.es/images/basic/extrude.png) 2118 """ 2119 if is_sequence(zshift): 2120 # ms = [] # todo 2121 # poly0 = self.clone().polydata() 2122 # for i in range(len(zshift)-1): 2123 # rf = vtk.vtkRotationalExtrusionFilter() 2124 # rf.SetInputData(poly0) 2125 # rf.SetResolution(res) 2126 # rf.SetCapping(0) 2127 # rf.SetAngle(rotation) 2128 # rf.SetTranslation(zshift) 2129 # rf.SetDeltaRadius(dR) 2130 # rf.Update() 2131 # poly1 = rf.GetOutput() 2132 return self 2133 2134 rf = vtk.vtkRotationalExtrusionFilter() 2135 # rf = vtk.vtkLinearExtrusionFilter() 2136 rf.SetInputData(self.polydata(False)) # must not be transformed 2137 rf.SetResolution(res) 2138 rf.SetCapping(cap) 2139 rf.SetAngle(rotation) 2140 rf.SetTranslation(zshift) 2141 rf.SetDeltaRadius(dR) 2142 rf.Update() 2143 m = Mesh(rf.GetOutput(), c=self.c(), alpha=self.alpha()) 2144 prop = vtk.vtkProperty() 2145 prop.DeepCopy(self.property) 2146 m.SetProperty(prop) 2147 m.property = prop 2148 # assign the same transformation 2149 m.SetOrigin(self.GetOrigin()) 2150 m.SetScale(self.GetScale()) 2151 m.SetOrientation(self.GetOrientation()) 2152 m.SetPosition(self.GetPosition()) 2153 2154 m.compute_normals(cells=False).flat().lighting("default") 2155 2156 m.pipeline = OperationNode( 2157 "extrude", parents=[self], 2158 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 2159 ) 2160 return m
Sweep a polygonal data creating a "skirt" from free edges and lines, and lines from vertices. The input dataset is swept around the z-axis to create new polygonal primitives. For example, sweeping a line results in a cylindrical shell, and sweeping a circle creates a torus.
You can control whether the sweep of a 2D object (i.e., polygon or triangle strip) is capped with the generating geometry. Also, you can control the angle of rotation, and whether translation along the z-axis is performed along with the rotation. (Translation is useful for creating "springs"). You also can adjust the radius of the generating geometry using the "dR" keyword.
The skirt is generated by locating certain topological features. Free edges (edges of polygons or triangle strips only used by one polygon or triangle strips) generate surfaces. This is true also of lines or polylines. Vertices generate lines.
This filter can be used to model axisymmetric objects like cylinders, bottles, and wine glasses; or translational/rotational symmetric objects like springs or corkscrews.
Warning:
Some polygonal objects have no free edges (e.g., sphere). When swept, this will result in two separate surfaces if capping is on, or no surface if capping is off.
Examples:
2162 def split(self, maxdepth=1000, flag=False, must_share_edge=False, sort_by_area=True): 2163 """ 2164 Split a mesh by connectivity and order the pieces by increasing area. 2165 2166 Arguments: 2167 maxdepth : (int) 2168 only consider this maximum number of mesh parts. 2169 flag : (bool) 2170 if set to True return the same single object, 2171 but add a "RegionId" array to flag the mesh subparts 2172 must_share_edge : (bool) 2173 if True, mesh regions that only share single points will be split. 2174 sort_by_area : (bool) 2175 if True, sort the mesh parts by decreasing area. 2176 2177 Examples: 2178 - [splitmesh.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/splitmesh.py) 2179 2180 ![](https://vedo.embl.es/images/advanced/splitmesh.png) 2181 """ 2182 pd = self.polydata(False) 2183 if must_share_edge: 2184 if pd.GetNumberOfPolys() == 0: 2185 vedo.logger.warning("in split(): no polygons found. Skip.") 2186 return [self] 2187 cf = vtk.vtkPolyDataEdgeConnectivityFilter() 2188 cf.BarrierEdgesOff() 2189 else: 2190 cf = vtk.vtkPolyDataConnectivityFilter() 2191 2192 cf.SetInputData(pd) 2193 cf.SetExtractionModeToAllRegions() 2194 cf.SetColorRegions(True) 2195 cf.Update() 2196 out = cf.GetOutput() 2197 2198 if not out.GetNumberOfPoints(): 2199 return [self] 2200 2201 if flag: 2202 self.pipeline = OperationNode("split mesh", parents=[self]) 2203 return self._update(out) 2204 2205 a = Mesh(out) 2206 if must_share_edge: 2207 arr = a.celldata["RegionId"] 2208 on = "cells" 2209 else: 2210 arr = a.pointdata["RegionId"] 2211 on = "points" 2212 2213 alist = [] 2214 for t in range(max(arr) + 1): 2215 if t == maxdepth: 2216 break 2217 suba = a.clone().threshold("RegionId", t, t, on=on) 2218 if sort_by_area: 2219 area = suba.area() 2220 else: 2221 area = 0 # dummy 2222 alist.append([suba, area]) 2223 2224 if sort_by_area: 2225 alist.sort(key=lambda x: x[1]) 2226 alist.reverse() 2227 2228 blist = [] 2229 for i, l in enumerate(alist): 2230 l[0].color(i + 1).phong() 2231 l[0].mapper().ScalarVisibilityOff() 2232 blist.append(l[0]) 2233 if i < 10: 2234 l[0].pipeline = OperationNode( 2235 f"split mesh {i}", 2236 parents=[self], 2237 comment=f"#pts {l[0].inputdata().GetNumberOfPoints()}", 2238 ) 2239 return blist
Split a mesh by connectivity and order the pieces by increasing area.
Arguments:
- maxdepth : (int) only consider this maximum number of mesh parts.
- flag : (bool) if set to True return the same single object, but add a "RegionId" array to flag the mesh subparts
- must_share_edge : (bool) if True, mesh regions that only share single points will be split.
- sort_by_area : (bool) if True, sort the mesh parts by decreasing area.
Examples:
2242 def extract_largest_region(self): 2243 """ 2244 Extract the largest connected part of a mesh and discard all the smaller pieces. 2245 2246 Examples: 2247 - [largestregion.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/largestregion.py) 2248 """ 2249 conn = vtk.vtkPolyDataConnectivityFilter() 2250 conn.SetExtractionModeToLargestRegion() 2251 conn.ScalarConnectivityOff() 2252 conn.SetInputData(self._data) 2253 conn.Update() 2254 m = Mesh(conn.GetOutput()) 2255 pr = vtk.vtkProperty() 2256 pr.DeepCopy(self.property) 2257 m.SetProperty(pr) 2258 m.property = pr 2259 # assign the same transformation 2260 m.SetOrigin(self.GetOrigin()) 2261 m.SetScale(self.GetScale()) 2262 m.SetOrientation(self.GetOrientation()) 2263 m.SetPosition(self.GetPosition()) 2264 vis = self._mapper.GetScalarVisibility() 2265 m.mapper().SetScalarVisibility(vis) 2266 2267 m.pipeline = OperationNode( 2268 "extract_largest_region", parents=[self], 2269 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 2270 ) 2271 return m
Extract the largest connected part of a mesh and discard all the smaller pieces.
Examples:
2273 def boolean(self, operation, mesh2, method=0, tol=None): 2274 """Volumetric union, intersection and subtraction of surfaces. 2275 2276 Use `operation` for the allowed operations `['plus', 'intersect', 'minus']`. 2277 2278 Two possible algorithms are available by changing `method`. 2279 2280 Example: 2281 - [boolean.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boolean.py) 2282 2283 ![](https://vedo.embl.es/images/basic/boolean.png) 2284 """ 2285 if method == 0: 2286 bf = vtk.vtkBooleanOperationPolyDataFilter() 2287 elif method == 1: 2288 bf = vtk.vtkLoopBooleanPolyDataFilter() 2289 else: 2290 raise ValueError(f"Unknown method={method}") 2291 2292 poly1 = self.compute_normals().polydata() 2293 poly2 = mesh2.compute_normals().polydata() 2294 2295 if operation.lower() in ("plus", "+"): 2296 bf.SetOperationToUnion() 2297 elif operation.lower() == "intersect": 2298 bf.SetOperationToIntersection() 2299 elif operation.lower() in ("minus", "-"): 2300 bf.SetOperationToDifference() 2301 2302 if tol: 2303 bf.SetTolerance(tol) 2304 2305 bf.SetInputData(0, poly1) 2306 bf.SetInputData(1, poly2) 2307 bf.Update() 2308 2309 msh = Mesh(bf.GetOutput(), c=None) 2310 msh.flat() 2311 msh.name = self.name + operation + mesh2.name 2312 2313 msh.pipeline = OperationNode( 2314 "boolean " + operation, 2315 parents=[self, mesh2], 2316 shape="cylinder", 2317 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 2318 ) 2319 return msh
Volumetric union, intersection and subtraction of surfaces.
Use operation
for the allowed operations ['plus', 'intersect', 'minus']
.
Two possible algorithms are available by changing method
.
Example:
2321 def intersect_with(self, mesh2, tol=1e-06): 2322 """ 2323 Intersect this Mesh with the input surface to return a set of lines. 2324 2325 Examples: 2326 - [surf_intersect.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/surf_intersect.py) 2327 2328 ![](https://vedo.embl.es/images/basic/surfIntersect.png) 2329 """ 2330 bf = vtk.vtkIntersectionPolyDataFilter() 2331 bf.SetGlobalWarningDisplay(0) 2332 poly1 = self.polydata() 2333 poly2 = mesh2.polydata() 2334 bf.SetTolerance(tol) 2335 bf.SetInputData(0, poly1) 2336 bf.SetInputData(1, poly2) 2337 bf.Update() 2338 msh = Mesh(bf.GetOutput(), "k", 1).lighting("off") 2339 msh.GetProperty().SetLineWidth(3) 2340 msh.name = "SurfaceIntersection" 2341 2342 msh.pipeline = OperationNode( 2343 "intersect_with", parents=[self, mesh2], comment=f"#pts {msh.npoints}" 2344 ) 2345 return msh
2347 def intersect_with_line(self, p0, p1=None, return_ids=False, tol=0): 2348 """ 2349 Return the list of points intersecting the mesh 2350 along the segment defined by two points `p0` and `p1`. 2351 2352 Use `return_ids` to return the cell ids along with point coords 2353 2354 Example: 2355 ```python 2356 from vedo import * 2357 s = Spring() 2358 pts = s.intersect_with_line([0,0,0], [1,0.1,0]) 2359 ln = Line([0,0,0], [1,0.1,0], c='blue') 2360 ps = Points(pts, r=10, c='r') 2361 show(s, ln, ps, bg='white').close() 2362 ``` 2363 ![](https://user-images.githubusercontent.com/32848391/55967065-eee08300-5c79-11e9-8933-265e1bab9f7e.png) 2364 """ 2365 if isinstance(p0, Points): 2366 p0, p1 = p0.points() 2367 2368 if not self.line_locator: 2369 self.line_locator = vtk.vtkOBBTree() 2370 self.line_locator.SetDataSet(self.polydata()) 2371 if not tol: 2372 tol = mag(np.asarray(p1) - np.asarray(p0)) / 10000 2373 self.line_locator.SetTolerance(tol) 2374 self.line_locator.BuildLocator() 2375 2376 vpts = vtk.vtkPoints() 2377 idlist = vtk.vtkIdList() 2378 self.line_locator.IntersectWithLine(p0, p1, vpts, idlist) 2379 pts = [] 2380 for i in range(vpts.GetNumberOfPoints()): 2381 intersection = [0, 0, 0] 2382 vpts.GetPoint(i, intersection) 2383 pts.append(intersection) 2384 pts = np.array(pts) 2385 2386 if return_ids: 2387 pts_ids = [] 2388 for i in range(idlist.GetNumberOfIds()): 2389 cid = idlist.GetId(i) 2390 pts_ids.append(cid) 2391 return (pts, np.array(pts_ids).astype(np.uint32)) 2392 2393 return pts
Return the list of points intersecting the mesh
along the segment defined by two points p0
and p1
.
Use return_ids
to return the cell ids along with point coords
Example:
from vedo import * s = Spring() pts = s.intersect_with_line([0,0,0], [1,0.1,0]) ln = Line([0,0,0], [1,0.1,0], c='blue') ps = Points(pts, r=10, c='r') show(s, ln, ps, bg='white').close()
2395 def intersect_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0)): 2396 """ 2397 Intersect this Mesh with a plane to return a set of lines. 2398 2399 Example: 2400 ```python 2401 from vedo import * 2402 sph = Sphere() 2403 mi = sph.clone().intersect_with_plane().join() 2404 print(mi.lines()) 2405 show(sph, mi, axes=1).close() 2406 ``` 2407 ![](https://vedo.embl.es/images/feats/intersect_plane.png) 2408 """ 2409 plane = vtk.vtkPlane() 2410 plane.SetOrigin(origin) 2411 plane.SetNormal(normal) 2412 2413 cutter = vtk.vtkPolyDataPlaneCutter() 2414 cutter.SetInputData(self.polydata()) 2415 cutter.SetPlane(plane) 2416 cutter.InterpolateAttributesOn() 2417 cutter.ComputeNormalsOff() 2418 cutter.Update() 2419 2420 msh = Mesh(cutter.GetOutput(), "k", 1).lighting("off") 2421 msh.GetProperty().SetLineWidth(3) 2422 msh.name = "PlaneIntersection" 2423 2424 msh.pipeline = OperationNode( 2425 "intersect_with_plan", parents=[self], 2426 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}" 2427 ) 2428 return msh
Intersect this Mesh with a plane to return a set of lines.
Example:
from vedo import * sph = Sphere() mi = sph.clone().intersect_with_plane().join() print(mi.lines()) show(sph, mi, axes=1).close()
2466 def collide_with(self, mesh2, tol=0, return_bool=False): 2467 """ 2468 Collide this Mesh with the input surface. 2469 Information is stored in `ContactCells1` and `ContactCells2`. 2470 """ 2471 ipdf = vtk.vtkCollisionDetectionFilter() 2472 # ipdf.SetGlobalWarningDisplay(0) 2473 2474 transform0 = vtk.vtkTransform() 2475 transform1 = vtk.vtkTransform() 2476 2477 # ipdf.SetBoxTolerance(tol) 2478 ipdf.SetCellTolerance(tol) 2479 ipdf.SetInputData(0, self.polydata()) 2480 ipdf.SetInputData(1, mesh2.polydata()) 2481 ipdf.SetTransform(0, transform0) 2482 ipdf.SetTransform(1, transform1) 2483 if return_bool: 2484 ipdf.SetCollisionModeToFirstContact() 2485 else: 2486 ipdf.SetCollisionModeToAllContacts() 2487 ipdf.Update() 2488 2489 if return_bool: 2490 return bool(ipdf.GetNumberOfContacts()) 2491 2492 msh = Mesh(ipdf.GetContactsOutput(), "k", 1).lighting("off") 2493 msh.metadata["ContactCells1"] = vtk2numpy( 2494 ipdf.GetOutput(0).GetFieldData().GetArray("ContactCells") 2495 ) 2496 msh.metadata["ContactCells2"] = vtk2numpy( 2497 ipdf.GetOutput(1).GetFieldData().GetArray("ContactCells") 2498 ) 2499 msh.GetProperty().SetLineWidth(3) 2500 msh.name = "SurfaceCollision" 2501 2502 msh.pipeline = OperationNode( 2503 "collide_with", parents=[self, mesh2], 2504 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}" 2505 ) 2506 return msh
Collide this Mesh with the input surface.
Information is stored in ContactCells1
and ContactCells2
.
2508 def geodesic(self, start, end): 2509 """ 2510 Dijkstra algorithm to compute the geodesic line. 2511 Takes as input a polygonal mesh and performs a single source shortest path calculation. 2512 2513 Arguments: 2514 start : (int, list) 2515 start vertex index or close point `[x,y,z]` 2516 end : (int, list) 2517 end vertex index or close point `[x,y,z]` 2518 2519 Examples: 2520 - [geodesic.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/geodesic.py) 2521 2522 ![](https://vedo.embl.es/images/advanced/geodesic.png) 2523 """ 2524 if is_sequence(start): 2525 cc = self.points() 2526 pa = Points(cc) 2527 start = pa.closest_point(start, return_point_id=True) 2528 end = pa.closest_point(end, return_point_id=True) 2529 2530 dijkstra = vtk.vtkDijkstraGraphGeodesicPath() 2531 dijkstra.SetInputData(self.polydata()) 2532 dijkstra.SetStartVertex(end) # inverted in vtk 2533 dijkstra.SetEndVertex(start) 2534 dijkstra.Update() 2535 2536 weights = vtk.vtkDoubleArray() 2537 dijkstra.GetCumulativeWeights(weights) 2538 2539 idlist = dijkstra.GetIdList() 2540 ids = [idlist.GetId(i) for i in range(idlist.GetNumberOfIds())] 2541 2542 length = weights.GetMaxId() + 1 2543 arr = np.zeros(length) 2544 for i in range(length): 2545 arr[i] = weights.GetTuple(i)[0] 2546 2547 poly = dijkstra.GetOutput() 2548 2549 vdata = numpy2vtk(arr) 2550 vdata.SetName("CumulativeWeights") 2551 poly.GetPointData().AddArray(vdata) 2552 2553 vdata2 = numpy2vtk(ids, dtype=np.uint) 2554 vdata2.SetName("VertexIDs") 2555 poly.GetPointData().AddArray(vdata2) 2556 poly.GetPointData().Modified() 2557 2558 dmesh = Mesh(poly, c="k") 2559 prop = vtk.vtkProperty() 2560 prop.DeepCopy(self.property) 2561 prop.SetLineWidth(3) 2562 prop.SetOpacity(1) 2563 dmesh.SetProperty(prop) 2564 dmesh.property = prop 2565 dmesh.name = "GeodesicLine" 2566 2567 dmesh.pipeline = OperationNode( 2568 "GeodesicLine", parents=[self], comment=f"#pts {dmesh._data.GetNumberOfPoints()}" 2569 ) 2570 return dmesh
Dijkstra algorithm to compute the geodesic line. Takes as input a polygonal mesh and performs a single source shortest path calculation.
Arguments:
- start : (int, list)
start vertex index or close point
[x,y,z]
- end : (int, list)
end vertex index or close point
[x,y,z]
Examples:
2575 def binarize( 2576 self, 2577 spacing=(1, 1, 1), 2578 invert=False, 2579 direction_matrix=None, 2580 image_size=None, 2581 origin=None, 2582 fg_val=255, 2583 bg_val=0, 2584 ): 2585 """ 2586 Convert a `Mesh` into a `Volume` 2587 where the foreground (exterior) voxels value is fg_val (255 by default) 2588 and the background (interior) voxels value is bg_val (0 by default). 2589 2590 Examples: 2591 - [mesh2volume.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/mesh2volume.py) 2592 2593 ![](https://vedo.embl.es/images/volumetric/mesh2volume.png) 2594 """ 2595 # https://vtk.org/Wiki/VTK/Examples/Cxx/PolyData/PolyDataToImageData 2596 pd = self.polydata() 2597 2598 whiteImage = vtk.vtkImageData() 2599 if direction_matrix: 2600 whiteImage.SetDirectionMatrix(direction_matrix) 2601 2602 dim = [0, 0, 0] if not image_size else image_size 2603 2604 bounds = self.bounds() 2605 if not image_size: # compute dimensions 2606 dim = [0, 0, 0] 2607 for i in [0, 1, 2]: 2608 dim[i] = int(np.ceil((bounds[i * 2 + 1] - bounds[i * 2]) / spacing[i])) 2609 2610 whiteImage.SetDimensions(dim) 2611 whiteImage.SetSpacing(spacing) 2612 whiteImage.SetExtent(0, dim[0] - 1, 0, dim[1] - 1, 0, dim[2] - 1) 2613 2614 if not origin: 2615 origin = [0, 0, 0] 2616 origin[0] = bounds[0] + spacing[0] / 2 2617 origin[1] = bounds[2] + spacing[1] / 2 2618 origin[2] = bounds[4] + spacing[2] / 2 2619 whiteImage.SetOrigin(origin) 2620 whiteImage.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1) 2621 2622 # fill the image with foreground voxels: 2623 inval = bg_val if invert else fg_val 2624 whiteImage.GetPointData().GetScalars().Fill(inval) 2625 2626 # polygonal data --> image stencil: 2627 pol2stenc = vtk.vtkPolyDataToImageStencil() 2628 pol2stenc.SetInputData(pd) 2629 pol2stenc.SetOutputOrigin(whiteImage.GetOrigin()) 2630 pol2stenc.SetOutputSpacing(whiteImage.GetSpacing()) 2631 pol2stenc.SetOutputWholeExtent(whiteImage.GetExtent()) 2632 pol2stenc.Update() 2633 2634 # cut the corresponding white image and set the background: 2635 outval = fg_val if invert else bg_val 2636 2637 imgstenc = vtk.vtkImageStencil() 2638 imgstenc.SetInputData(whiteImage) 2639 imgstenc.SetStencilConnection(pol2stenc.GetOutputPort()) 2640 imgstenc.SetReverseStencil(invert) 2641 imgstenc.SetBackgroundValue(outval) 2642 imgstenc.Update() 2643 vol = vedo.Volume(imgstenc.GetOutput()) 2644 2645 vol.pipeline = OperationNode( 2646 "binarize", 2647 parents=[self], 2648 comment=f"dim = {tuple(vol.dimensions())}", 2649 c="#e9c46a:#0096c7", 2650 ) 2651 return vol
Convert a Mesh
into a Volume
where the foreground (exterior) voxels value is fg_val (255 by default)
and the background (interior) voxels value is bg_val (0 by default).
Examples:
2653 def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None): 2654 """ 2655 Compute the `Volume` object whose voxels contains the signed distance from 2656 the mesh. 2657 2658 Arguments: 2659 bounds : (list) 2660 bounds of the output volume 2661 dims : (list) 2662 dimensions (nr. of voxels) of the output volume 2663 invert : (bool) 2664 flip the sign 2665 2666 Examples: 2667 - [volumeFromMesh.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/volumeFromMesh.py) 2668 """ 2669 if maxradius is not None: 2670 vedo.logger.warning( 2671 "in signedDistance(maxradius=...) is ignored. (Only valid for pointclouds)." 2672 ) 2673 if bounds is None: 2674 bounds = self.bounds() 2675 sx = (bounds[1] - bounds[0]) / dims[0] 2676 sy = (bounds[3] - bounds[2]) / dims[1] 2677 sz = (bounds[5] - bounds[4]) / dims[2] 2678 2679 img = vtk.vtkImageData() 2680 img.SetDimensions(dims) 2681 img.SetSpacing(sx, sy, sz) 2682 img.SetOrigin(bounds[0], bounds[2], bounds[4]) 2683 img.AllocateScalars(vtk.VTK_FLOAT, 1) 2684 2685 imp = vtk.vtkImplicitPolyDataDistance() 2686 imp.SetInput(self.polydata()) 2687 b2 = bounds[2] 2688 b4 = bounds[4] 2689 d0, d1, d2 = dims 2690 2691 for i in range(d0): 2692 x = i * sx + bounds[0] 2693 for j in range(d1): 2694 y = j * sy + b2 2695 for k in range(d2): 2696 v = imp.EvaluateFunction((x, y, k * sz + b4)) 2697 if invert: 2698 v = -v 2699 img.SetScalarComponentFromFloat(i, j, k, 0, v) 2700 2701 vol = vedo.Volume(img) 2702 vol.name = "SignedVolume" 2703 2704 vol.pipeline = OperationNode( 2705 "signed_distance", 2706 parents=[self], 2707 comment=f"dim = {tuple(vol.dimensions())}", 2708 c="#e9c46a:#0096c7", 2709 ) 2710 return vol
Compute the Volume
object whose voxels contains the signed distance from
the mesh.
Arguments:
- bounds : (list) bounds of the output volume
- dims : (list) dimensions (nr. of voxels) of the output volume
- invert : (bool) flip the sign
Examples:
2712 def tetralize( 2713 self, side=0.02, nmax=300_000, gap=None, subsample=False, uniform=True, seed=0, debug=False 2714 ): 2715 """ 2716 Tetralize a closed polygonal mesh. Return a `TetMesh`. 2717 2718 Arguments: 2719 side : (float) 2720 desired side of the single tetras as fraction of the bounding box diagonal. 2721 Typical values are in the range (0.01 - 0.03) 2722 nmax : (int) 2723 maximum random numbers to be sampled in the bounding box 2724 gap : (float) 2725 keep this minimum distance from the surface, 2726 if None an automatic choice is made. 2727 subsample : (bool) 2728 subsample input surface, the geometry might be affected 2729 (the number of original faces reduceed), but higher tet quality might be obtained. 2730 uniform : (bool) 2731 generate tets more uniformly packed in the interior of the mesh 2732 seed : (int) 2733 random number generator seed 2734 debug : (bool) 2735 show an intermediate plot with sampled points 2736 2737 Examples: 2738 - [tetralize_surface.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/tetralize_surface.py) 2739 2740 ![](https://vedo.embl.es/images/volumetric/tetralize_surface.jpg) 2741 """ 2742 surf = self.clone().clean().compute_normals() 2743 d = surf.diagonal_size() 2744 if gap is None: 2745 gap = side * d * np.sqrt(2 / 3) 2746 n = int(min((1 / side) ** 3, nmax)) 2747 2748 # fill the space w/ points 2749 x0, x1, y0, y1, z0, z1 = surf.bounds() 2750 2751 if uniform: 2752 pts = vedo.utils.pack_spheres([x0, x1, y0, y1, z0, z1], side * d * 1.42) 2753 pts += np.random.randn(len(pts), 3) * side * d * 1.42 / 100 # some small jitter 2754 else: 2755 disp = np.array([x0 + x1, y0 + y1, z0 + z1]) / 2 2756 np.random.seed(seed) 2757 pts = (np.random.rand(n, 3) - 0.5) * np.array([x1 - x0, y1 - y0, z1 - z0]) + disp 2758 2759 normals = surf.celldata["Normals"] 2760 cc = surf.cell_centers() 2761 subpts = cc - normals * gap * 1.05 2762 pts = pts.tolist() + subpts.tolist() 2763 2764 if debug: 2765 print(".. tetralize(): subsampling and cleaning") 2766 2767 fillpts = surf.inside_points(pts) 2768 fillpts.subsample(side) 2769 2770 if gap: 2771 fillpts.distance_to(surf) 2772 fillpts.threshold("Distance", above=gap) 2773 2774 if subsample: 2775 surf.subsample(side) 2776 2777 tmesh = vedo.tetmesh.delaunay3d(vedo.merge(fillpts, surf)) 2778 tcenters = tmesh.cell_centers() 2779 2780 ids = surf.inside_points(tcenters, return_ids=True) 2781 ins = np.zeros(tmesh.ncells) 2782 ins[ids] = 1 2783 2784 if debug: 2785 # vedo.pyplot.histogram(fillpts.pointdata["Distance"], xtitle=f"gap={gap}").show().close() 2786 edges = self.edges() 2787 points = self.points() 2788 elen = mag(points[edges][:, 0, :] - points[edges][:, 1, :]) 2789 histo = vedo.pyplot.histogram(elen, xtitle="edge length", xlim=(0, 3 * side * d)) 2790 print(".. edges min, max", elen.min(), elen.max()) 2791 fillpts.cmap("bone") 2792 vedo.show( 2793 [ 2794 [ 2795 f"This is a debug plot.\n\nGenerated points: {n}\ngap: {gap}", 2796 surf.wireframe().alpha(0.2), 2797 vedo.addons.Axes(surf), 2798 fillpts, 2799 Points(subpts).c("r4").ps(3), 2800 ], 2801 [f"Edges mean length: {np.mean(elen)}\n\nPress q to continue", histo], 2802 ], 2803 N=2, 2804 sharecam=False, 2805 new=True, 2806 ).close() 2807 print(".. thresholding") 2808 2809 tmesh.celldata["inside"] = ins.astype(np.uint8) 2810 tmesh.threshold("inside", above=0.9) 2811 tmesh.celldata.remove("inside") 2812 2813 if debug: 2814 print(f".. tetralize() completed, ntets = {tmesh.ncells}") 2815 2816 tmesh.pipeline = OperationNode( 2817 "tetralize", parents=[self], comment=f"#tets = {tmesh.ncells}", c="#e9c46a:#9e2a2b" 2818 ) 2819 return tmesh
Tetralize a closed polygonal mesh. Return a TetMesh
.
Arguments:
- side : (float) desired side of the single tetras as fraction of the bounding box diagonal. Typical values are in the range (0.01 - 0.03)
- nmax : (int) maximum random numbers to be sampled in the bounding box
- gap : (float) keep this minimum distance from the surface, if None an automatic choice is made.
- subsample : (bool) subsample input surface, the geometry might be affected (the number of original faces reduceed), but higher tet quality might be obtained.
- uniform : (bool) generate tets more uniformly packed in the interior of the mesh
- seed : (int) random number generator seed
- debug : (bool) show an intermediate plot with sampled points
Examples:
Inherited Members
- vedo.pointcloud.Points
- polydata
- clone
- clone2d
- add_trail
- update_trail
- add_shadow
- update_shadows
- delete_cells_by_point_index
- compute_normals_with_pca
- compute_acoplanarity
- distance_to
- alpha
- opacity
- force_opaque
- force_translucent
- point_size
- ps
- render_points_as_spheres
- color
- clean
- subsample
- threshold
- quantize
- average_size
- center_of_mass
- normal_at
- normals
- labels
- labels2d
- legend
- flagpole
- flagpost
- align_to
- transform_with_landmarks
- apply_transform
- normalize
- mirror
- shear
- flip_normals
- cmap
- cell_individual_colors
- cellcolors
- pointcolors
- interpolate_data_from
- add_gaussian_noise
- closest_point
- hausdorff_distance
- chamfer_distance
- remove_outliers
- smooth_mls_1d
- smooth_mls_2d
- smooth_lloyd_2d
- project_on_plane
- warp
- cut_with_plane
- cut_with_planes
- cut_with_box
- cut_with_line
- cut_with_cylinder
- cut_with_sphere
- cut_with_mesh
- cut_with_point_loop
- cut_with_scalar
- implicit_modeller
- generate_mesh
- reconstruct_surface
- compute_clustering
- compute_connections
- compute_camera_distance
- density
- densify
- tovolume
- generate_random_data
- vedo.base.BaseActor
- mapper
- inputdata
- modified
- npoints
- ncells
- points
- cell_centers
- delete_cells
- mark_boundaries
- find_cells_in
- count_vertices
- lighting
- print_histogram
- c
- pointdata
- celldata
- metadata
- map_cells_to_points
- map_points_to_cells
- resample_data_from
- add_ids
- gradient
- divergence
- vorticity
- add_scalarbar
- add_scalarbar3d
- write