vedo.grids
Work with tetrahedral meshes.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import os 4import time 5from weakref import ref as weak_ref_to 6from typing import Any 7from typing_extensions import Self 8import numpy as np 9 10import vedo.vtkclasses as vtki # a wrapper for lazy imports 11 12import vedo 13from vedo import utils 14from vedo.core import PointAlgorithms 15from vedo.mesh import Mesh 16from vedo.file_io import download 17from vedo.visual import MeshVisual 18from vedo.transformations import LinearTransform 19 20__docformat__ = "google" 21 22__doc__ = """ 23Work with tetrahedral meshes. 24 25![](https://vedo.embl.es/images/volumetric/82767107-2631d500-9e25-11ea-967c-42558f98f721.jpg) 26""" 27 28__all__ = [ 29 "UnstructuredGrid", 30 "TetMesh", 31 "RectilinearGrid", 32 "StructuredGrid", 33] 34 35 36######################################################################### 37class UnstructuredGrid(PointAlgorithms, MeshVisual): 38 """Support for UnstructuredGrid objects.""" 39 40 def __init__(self, inputobj=None): 41 """ 42 Support for UnstructuredGrid objects. 43 44 Arguments: 45 inputobj : (list, vtkUnstructuredGrid, str) 46 A list in the form `[points, cells, celltypes]`, 47 or a vtkUnstructuredGrid object, or a filename 48 49 Celltypes are identified by the following 50 [convention](https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html). 51 """ 52 super().__init__() 53 54 self.dataset = None 55 56 self.mapper = vtki.new("PolyDataMapper") 57 self._actor = vtki.vtkActor() 58 self._actor.retrieve_object = weak_ref_to(self) 59 self._actor.SetMapper(self.mapper) 60 self.properties = self._actor.GetProperty() 61 62 self.transform = LinearTransform() 63 self.point_locator = None 64 self.cell_locator = None 65 self.line_locator = None 66 67 self.name = "UnstructuredGrid" 68 self.filename = "" 69 self.file_size = "" 70 71 self.info = {} 72 self.time = time.time() 73 self.rendered_at = set() 74 75 ###################################### 76 inputtype = str(type(inputobj)) 77 78 if inputobj is None: 79 self.dataset = vtki.vtkUnstructuredGrid() 80 81 elif utils.is_sequence(inputobj): 82 83 pts, cells, celltypes = inputobj 84 assert len(cells) == len(celltypes) 85 86 self.dataset = vtki.vtkUnstructuredGrid() 87 88 if not utils.is_sequence(cells[0]): 89 tets = [] 90 nf = cells[0] + 1 91 for i, cl in enumerate(cells): 92 if i in (nf, 0): 93 k = i + 1 94 nf = cl + k 95 cell = [cells[j + k] for j in range(cl)] 96 tets.append(cell) 97 cells = tets 98 99 # This would fill the points and use those to define orientation 100 vpts = utils.numpy2vtk(pts, dtype=np.float32) 101 points = vtki.vtkPoints() 102 points.SetData(vpts) 103 self.dataset.SetPoints(points) 104 105 # Fill cells 106 # https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html 107 for i, ct in enumerate(celltypes): 108 if ct == vtki.cell_types["VERTEX"]: 109 cell = vtki.vtkVertex() 110 elif ct == vtki.cell_types["POLY_VERTEX"]: 111 cell = vtki.vtkPolyVertex() 112 elif ct == vtki.cell_types["TETRA"]: 113 cell = vtki.vtkTetra() 114 elif ct == vtki.cell_types["WEDGE"]: 115 cell = vtki.vtkWedge() 116 elif ct == vtki.cell_types["LINE"]: 117 cell = vtki.vtkLine() 118 elif ct == vtki.cell_types["POLY_LINE"]: 119 cell = vtki.vtkPolyLine() 120 elif ct == vtki.cell_types["TRIANGLE"]: 121 cell = vtki.vtkTriangle() 122 elif ct == vtki.cell_types["TRIANGLE_STRIP"]: 123 cell = vtki.vtkTriangleStrip() 124 elif ct == vtki.cell_types["POLYGON"]: 125 cell = vtki.vtkPolygon() 126 elif ct == vtki.cell_types["PIXEL"]: 127 cell = vtki.vtkPixel() 128 elif ct == vtki.cell_types["QUAD"]: 129 cell = vtki.vtkQuad() 130 elif ct == vtki.cell_types["VOXEL"]: 131 cell = vtki.vtkVoxel() 132 elif ct == vtki.cell_types["PYRAMID"]: 133 cell = vtki.vtkPyramid() 134 elif ct == vtki.cell_types["HEXAHEDRON"]: 135 cell = vtki.vtkHexahedron() 136 elif ct == vtki.cell_types["HEXAGONAL_PRISM"]: 137 cell = vtki.vtkHexagonalPrism() 138 elif ct == vtki.cell_types["PENTAGONAL_PRISM"]: 139 cell = vtki.vtkPentagonalPrism() 140 elif ct == vtki.cell_types["QUADRATIC_TETRA"]: 141 from vtkmodules.vtkCommonDataModel import vtkQuadraticTetra 142 cell = vtkQuadraticTetra() 143 elif ct == vtki.cell_types["QUADRATIC_HEXAHEDRON"]: 144 from vtkmodules.vtkCommonDataModel import vtkQuadraticHexahedron 145 cell = vtkQuadraticHexahedron() 146 elif ct == vtki.cell_types["QUADRATIC_WEDGE"]: 147 from vtkmodules.vtkCommonDataModel import vtkQuadraticWedge 148 cell = vtkQuadraticWedge() 149 elif ct == vtki.cell_types["QUADRATIC_PYRAMID"]: 150 from vtkmodules.vtkCommonDataModel import vtkQuadraticPyramid 151 cell = vtkQuadraticPyramid() 152 elif ct == vtki.cell_types["QUADRATIC_LINEAR_QUAD"]: 153 from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearQuad 154 cell = vtkQuadraticLinearQuad() 155 elif ct == vtki.cell_types["QUADRATIC_LINEAR_WEDGE"]: 156 from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearWedge 157 cell = vtkQuadraticLinearWedge() 158 elif ct == vtki.cell_types["BIQUADRATIC_QUADRATIC_WEDGE"]: 159 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticQuadraticWedge 160 cell = vtkBiQuadraticQuadraticWedge() 161 elif ct == vtki.cell_types["BIQUADRATIC_QUADRATIC_HEXAHEDRON"]: 162 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticQuadraticHexahedron 163 cell = vtkBiQuadraticQuadraticHexahedron() 164 elif ct == vtki.cell_types["BIQUADRATIC_TRIANGLE"]: 165 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticTriangle 166 cell = vtkBiQuadraticTriangle() 167 elif ct == vtki.cell_types["CUBIC_LINE"]: 168 from vtkmodules.vtkCommonDataModel import vtkCubicLine 169 cell = vtkCubicLine() 170 elif ct == vtki.cell_types["CONVEX_POINT_SET"]: 171 from vtkmodules.vtkCommonDataModel import vtkConvexPointSet 172 cell = vtkConvexPointSet() 173 elif ct == vtki.cell_types["POLYHEDRON"]: 174 from vtkmodules.vtkCommonDataModel import vtkPolyhedron 175 cell = vtkPolyhedron() 176 elif ct == vtki.cell_types["HIGHER_ORDER_TRIANGLE"]: 177 from vtkmodules.vtkCommonDataModel import vtkHigherOrderTriangle 178 cell = vtkHigherOrderTriangle() 179 elif ct == vtki.cell_types["HIGHER_ORDER_QUAD"]: 180 from vtkmodules.vtkCommonDataModel import vtkHigherOrderQuadrilateral 181 cell = vtkHigherOrderQuadrilateral() 182 elif ct == vtki.cell_types["HIGHER_ORDER_TETRAHEDRON"]: 183 from vtkmodules.vtkCommonDataModel import vtkHigherOrderTetra 184 cell = vtkHigherOrderTetra() 185 elif ct == vtki.cell_types["HIGHER_ORDER_WEDGE"]: 186 from vtkmodules.vtkCommonDataModel import vtkHigherOrderWedge 187 cell = vtkHigherOrderWedge() 188 elif ct == vtki.cell_types["HIGHER_ORDER_HEXAHEDRON"]: 189 from vtkmodules.vtkCommonDataModel import vtkHigherOrderHexahedron 190 cell = vtkHigherOrderHexahedron() 191 elif ct == vtki.cell_types["LAGRANGE_CURVE"]: 192 from vtkmodules.vtkCommonDataModel import vtkLagrangeCurve 193 cell = vtkLagrangeCurve() 194 elif ct == vtki.cell_types["LAGRANGE_TRIANGLE"]: 195 from vtkmodules.vtkCommonDataModel import vtkLagrangeTriangle 196 cell = vtkLagrangeTriangle() 197 elif ct == vtki.cell_types["LAGRANGE_QUADRILATERAL"]: 198 from vtkmodules.vtkCommonDataModel import vtkLagrangeQuadrilateral 199 cell = vtkLagrangeQuadrilateral() 200 elif ct == vtki.cell_types["LAGRANGE_TETRAHEDRON"]: 201 from vtkmodules.vtkCommonDataModel import vtkLagrangeTetra 202 cell = vtkLagrangeTetra() 203 elif ct == vtki.cell_types["LAGRANGE_HEXAHEDRON"]: 204 from vtkmodules.vtkCommonDataModel import vtkLagrangeHexahedron 205 cell = vtkLagrangeHexahedron() 206 elif ct == vtki.cell_types["LAGRANGE_WEDGE"]: 207 from vtkmodules.vtkCommonDataModel import vtkLagrangeWedge 208 cell = vtkLagrangeWedge() 209 elif ct == vtki.cell_types["BEZIER_CURVE"]: 210 from vtkmodules.vtkCommonDataModel import vtkBezierCurve 211 cell = vtkBezierCurve() 212 elif ct == vtki.cell_types["BEZIER_TRIANGLE"]: 213 from vtkmodules.vtkCommonDataModel import vtkBezierTriangle 214 cell = vtkBezierTriangle() 215 elif ct == vtki.cell_types["BEZIER_QUADRILATERAL"]: 216 from vtkmodules.vtkCommonDataModel import vtkBezierQuadrilateral 217 cell = vtkBezierQuadrilateral() 218 elif ct == vtki.cell_types["BEZIER_TETRAHEDRON"]: 219 from vtkmodules.vtkCommonDataModel import vtkBezierTetra 220 cell = vtkBezierTetra() 221 elif ct == vtki.cell_types["BEZIER_HEXAHEDRON"]: 222 from vtkmodules.vtkCommonDataModel import vtkBezierHexahedron 223 cell = vtkBezierHexahedron() 224 elif ct == vtki.cell_types["BEZIER_WEDGE"]: 225 from vtkmodules.vtkCommonDataModel import vtkBezierWedge 226 cell = vtkBezierWedge() 227 else: 228 vedo.logger.error( 229 f"UnstructuredGrid: cell type {ct} not supported. Skip.") 230 continue 231 232 cpids = cell.GetPointIds() 233 cell_conn = cells[i] 234 for j, pid in enumerate(cell_conn): 235 cpids.SetId(j, pid) 236 self.dataset.InsertNextCell(ct, cpids) 237 238 elif "UnstructuredGrid" in inputtype: 239 self.dataset = inputobj 240 241 elif isinstance(inputobj, str): 242 if "https://" in inputobj: 243 inputobj = download(inputobj, verbose=False) 244 self.filename = inputobj 245 if inputobj.endswith(".vtu"): 246 reader = vtki.new("XMLUnstructuredGridReader") 247 else: 248 reader = vtki.new("UnstructuredGridReader") 249 self.filename = inputobj 250 reader.SetFileName(inputobj) 251 reader.Update() 252 self.dataset = reader.GetOutput() 253 254 else: 255 # this converts other types of vtk objects to UnstructuredGrid 256 apf = vtki.new("AppendFilter") 257 try: 258 apf.AddInputData(inputobj) 259 except TypeError: 260 apf.AddInputData(inputobj.dataset) 261 apf.Update() 262 self.dataset = apf.GetOutput() 263 264 self.properties.SetColor(0.89, 0.455, 0.671) # pink7 265 266 self.pipeline = utils.OperationNode( 267 self, comment=f"#cells {self.dataset.GetNumberOfCells()}", c="#4cc9f0" 268 ) 269 270 # ------------------------------------------------------------------ 271 def __str__(self): 272 """Print a string summary of the `UnstructuredGrid` object.""" 273 module = self.__class__.__module__ 274 name = self.__class__.__name__ 275 out = vedo.printc( 276 f"{module}.{name} at ({hex(self.memory_address())})".ljust(75), 277 c="m", bold=True, invert=True, return_string=True, 278 ) 279 out += "\x1b[0m\u001b[35m" 280 281 out += "nr. of verts".ljust(14) + ": " + str(self.npoints) + "\n" 282 out += "nr. of cells".ljust(14) + ": " + str(self.ncells) + "\n" 283 ct_arr = np.unique(self.cell_types_array) 284 cnames = [k for k, v in vtki.cell_types.items() if v in ct_arr] 285 out += "cell types".ljust(14) + ": " + str(cnames) + "\n" 286 287 if self.npoints: 288 out+="size".ljust(14)+ ": average=" + utils.precision(self.average_size(),6) 289 out+=", diagonal="+ utils.precision(self.diagonal_size(), 6)+ "\n" 290 out+="center of mass".ljust(14) + ": " + utils.precision(self.center_of_mass(),6)+"\n" 291 292 bnds = self.bounds() 293 bx1, bx2 = utils.precision(bnds[0], 3), utils.precision(bnds[1], 3) 294 by1, by2 = utils.precision(bnds[2], 3), utils.precision(bnds[3], 3) 295 bz1, bz2 = utils.precision(bnds[4], 3), utils.precision(bnds[5], 3) 296 out += "bounds".ljust(14) + ":" 297 out += " x=(" + bx1 + ", " + bx2 + ")," 298 out += " y=(" + by1 + ", " + by2 + ")," 299 out += " z=(" + bz1 + ", " + bz2 + ")\n" 300 301 for key in self.pointdata.keys(): 302 arr = self.pointdata[key] 303 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 304 mark_active = "pointdata" 305 a_scalars = self.dataset.GetPointData().GetScalars() 306 a_vectors = self.dataset.GetPointData().GetVectors() 307 a_tensors = self.dataset.GetPointData().GetTensors() 308 if a_scalars and a_scalars.GetName() == key: 309 mark_active += " *" 310 elif a_vectors and a_vectors.GetName() == key: 311 mark_active += " **" 312 elif a_tensors and a_tensors.GetName() == key: 313 mark_active += " ***" 314 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 315 out += f", range=({rng})\n" 316 317 for key in self.celldata.keys(): 318 arr = self.celldata[key] 319 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 320 mark_active = "celldata" 321 a_scalars = self.dataset.GetCellData().GetScalars() 322 a_vectors = self.dataset.GetCellData().GetVectors() 323 a_tensors = self.dataset.GetCellData().GetTensors() 324 if a_scalars and a_scalars.GetName() == key: 325 mark_active += " *" 326 elif a_vectors and a_vectors.GetName() == key: 327 mark_active += " **" 328 elif a_tensors and a_tensors.GetName() == key: 329 mark_active += " ***" 330 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 331 out += f", range=({rng})\n" 332 333 for key in self.metadata.keys(): 334 arr = self.metadata[key] 335 out += "metadata".ljust(14) + ": " + f'"{key}" ({len(arr)} values)\n' 336 337 return out.rstrip() + "\x1b[0m" 338 339 def _repr_html_(self): 340 """ 341 HTML representation of the UnstructuredGrid object for Jupyter Notebooks. 342 343 Returns: 344 HTML text with the image and some properties. 345 """ 346 import io 347 import base64 348 from PIL import Image 349 350 library_name = "vedo.grids.UnstructuredGrid" 351 help_url = "https://vedo.embl.es/docs/vedo/grids.html#UnstructuredGrid" 352 353 arr = self.thumbnail() 354 im = Image.fromarray(arr) 355 buffered = io.BytesIO() 356 im.save(buffered, format="PNG", quality=100) 357 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 358 url = "data:image/png;base64," + encoded 359 image = f"<img src='{url}'></img>" 360 361 bounds = "<br/>".join( 362 [ 363 utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4) 364 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 365 ] 366 ) 367 368 help_text = "" 369 if self.name: 370 help_text += f"<b> {self.name}:   </b>" 371 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 372 if self.filename: 373 dots = "" 374 if len(self.filename) > 30: 375 dots = "..." 376 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 377 378 pdata = "" 379 if self.dataset.GetPointData().GetScalars(): 380 if self.dataset.GetPointData().GetScalars().GetName(): 381 name = self.dataset.GetPointData().GetScalars().GetName() 382 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 383 384 cdata = "" 385 if self.dataset.GetCellData().GetScalars(): 386 if self.dataset.GetCellData().GetScalars().GetName(): 387 name = self.dataset.GetCellData().GetScalars().GetName() 388 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 389 390 pts = self.vertices 391 cm = np.mean(pts, axis=0) 392 393 all = [ 394 "<table>", 395 "<tr>", 396 "<td>", image, "</td>", 397 "<td style='text-align: center; vertical-align: center;'><br/>", help_text, 398 "<table>", 399 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 400 "<tr><td><b> center of mass </b></td><td>" + utils.precision(cm,3) + "</td></tr>", 401 # "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>", 402 "<tr><td><b> nr. points / cells </b></td><td>" 403 + str(self.npoints) + " / " + str(self.ncells) + "</td></tr>", 404 pdata, 405 cdata, 406 "</table>", 407 "</table>", 408 ] 409 return "\n".join(all) 410 411 @property 412 def actor(self): 413 """Return the `vtkActor` of the object.""" 414 # print("building actor") 415 gf = vtki.new("GeometryFilter") 416 gf.SetInputData(self.dataset) 417 gf.Update() 418 out = gf.GetOutput() 419 self.mapper.SetInputData(out) 420 self.mapper.Modified() 421 return self._actor 422 423 @actor.setter 424 def actor(self, _): 425 pass 426 427 def _update(self, data, reset_locators=False): 428 self.dataset = data 429 if reset_locators: 430 self.cell_locator = None 431 self.point_locator = None 432 return self 433 434 def merge(self, *others) -> Self: 435 """ 436 Merge multiple datasets into one single `UnstrcturedGrid`. 437 """ 438 apf = vtki.new("AppendFilter") 439 for o in others: 440 if isinstance(o, UnstructuredGrid): 441 apf.AddInputData(o.dataset) 442 elif isinstance(o, vtki.vtkUnstructuredGrid): 443 apf.AddInputData(o) 444 else: 445 vedo.printc("Error: cannot merge type", type(o), c="r") 446 apf.Update() 447 self._update(apf.GetOutput()) 448 self.pipeline = utils.OperationNode( 449 "merge", parents=[self, *others], c="#9e2a2b" 450 ) 451 return self 452 453 def copy(self, deep=True) -> "UnstructuredGrid": 454 """Return a copy of the object. Alias of `clone()`.""" 455 return self.clone(deep=deep) 456 457 def clone(self, deep=True) -> "UnstructuredGrid": 458 """Clone the UnstructuredGrid object to yield an exact copy.""" 459 ug = vtki.vtkUnstructuredGrid() 460 if deep: 461 ug.DeepCopy(self.dataset) 462 else: 463 ug.ShallowCopy(self.dataset) 464 if isinstance(self, vedo.UnstructuredGrid): 465 cloned = vedo.UnstructuredGrid(ug) 466 else: 467 cloned = vedo.TetMesh(ug) 468 469 cloned.copy_properties_from(self) 470 471 cloned.pipeline = utils.OperationNode( 472 "clone", parents=[self], shape="diamond", c="#bbe1ed" 473 ) 474 return cloned 475 476 def bounds(self) -> np.ndarray: 477 """ 478 Get the object bounds. 479 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 480 """ 481 # OVERRIDE CommonAlgorithms.bounds() which is too slow 482 return np.array(self.dataset.GetBounds()) 483 484 def threshold(self, name=None, above=None, below=None, on="cells") -> Self: 485 """ 486 Threshold the tetrahedral mesh by a cell scalar value. 487 Reduce to only tets which satisfy the threshold limits. 488 489 - if `above = below` will only select tets with that specific value. 490 - if `above > below` selection range is flipped. 491 492 Set keyword "on" to either "cells" or "points". 493 """ 494 th = vtki.new("Threshold") 495 th.SetInputData(self.dataset) 496 497 if name is None: 498 if self.celldata.keys(): 499 name = self.celldata.keys()[0] 500 th.SetInputArrayToProcess(0, 0, 0, 1, name) 501 elif self.pointdata.keys(): 502 name = self.pointdata.keys()[0] 503 th.SetInputArrayToProcess(0, 0, 0, 0, name) 504 if name is None: 505 vedo.logger.warning("cannot find active array. Skip.") 506 return self 507 else: 508 if on.startswith("c"): 509 th.SetInputArrayToProcess(0, 0, 0, 1, name) 510 else: 511 th.SetInputArrayToProcess(0, 0, 0, 0, name) 512 513 if above is not None: 514 th.SetLowerThreshold(above) 515 516 if below is not None: 517 th.SetUpperThreshold(below) 518 519 th.Update() 520 return self._update(th.GetOutput()) 521 522 def isosurface(self, value=None, flying_edges=False) -> "vedo.mesh.Mesh": 523 """ 524 Return an `Mesh` isosurface extracted from the `Volume` object. 525 526 Set `value` as single float or list of values to draw the isosurface(s). 527 Use flying_edges for faster results (but sometimes can interfere with `smooth()`). 528 529 Examples: 530 - [isosurfaces1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces1.py) 531 532 ![](https://vedo.embl.es/images/volumetric/isosurfaces.png) 533 """ 534 scrange = self.dataset.GetScalarRange() 535 536 if flying_edges: 537 cf = vtki.new("FlyingEdges3D") 538 cf.InterpolateAttributesOn() 539 else: 540 cf = vtki.new("ContourFilter") 541 cf.UseScalarTreeOn() 542 543 cf.SetInputData(self.dataset) 544 cf.ComputeNormalsOn() 545 546 if utils.is_sequence(value): 547 cf.SetNumberOfContours(len(value)) 548 for i, t in enumerate(value): 549 cf.SetValue(i, t) 550 else: 551 if value is None: 552 value = (2 * scrange[0] + scrange[1]) / 3.0 553 # print("automatic isosurface value =", value) 554 cf.SetValue(0, value) 555 556 cf.Update() 557 poly = cf.GetOutput() 558 559 out = vedo.mesh.Mesh(poly, c=None).flat() 560 out.mapper.SetScalarRange(scrange[0], scrange[1]) 561 562 out.pipeline = utils.OperationNode( 563 "isosurface", 564 parents=[self], 565 comment=f"#pts {out.dataset.GetNumberOfPoints()}", 566 c="#4cc9f0:#e9c46a", 567 ) 568 return out 569 570 def shrink(self, fraction=0.8) -> Self: 571 """ 572 Shrink the individual cells. 573 574 ![](https://vedo.embl.es/images/feats/shrink_hex.png) 575 """ 576 sf = vtki.new("ShrinkFilter") 577 sf.SetInputData(self.dataset) 578 sf.SetShrinkFactor(fraction) 579 sf.Update() 580 out = sf.GetOutput() 581 self._update(out) 582 self.pipeline = utils.OperationNode( 583 "shrink", comment=f"by {fraction}", parents=[self], c="#9e2a2b" 584 ) 585 return self 586 587 def tomesh(self, fill=False, shrink=1.0) -> "vedo.mesh.Mesh": 588 """ 589 Build a polygonal `Mesh` from the current object. 590 591 If `fill=True`, the interior faces of all the cells are created. 592 (setting a `shrink` value slightly smaller than the default 1.0 593 can avoid flickering due to internal adjacent faces). 594 595 If `fill=False`, only the boundary faces will be generated. 596 """ 597 gf = vtki.new("GeometryFilter") 598 if fill: 599 sf = vtki.new("ShrinkFilter") 600 sf.SetInputData(self.dataset) 601 sf.SetShrinkFactor(shrink) 602 sf.Update() 603 gf.SetInputData(sf.GetOutput()) 604 gf.Update() 605 poly = gf.GetOutput() 606 else: 607 gf.SetInputData(self.dataset) 608 gf.Update() 609 poly = gf.GetOutput() 610 611 msh = vedo.mesh.Mesh(poly) 612 msh.copy_properties_from(self) 613 msh.pipeline = utils.OperationNode( 614 "tomesh", parents=[self], comment=f"fill={fill}", c="#9e2a2b:#e9c46a" 615 ) 616 return msh 617 618 @property 619 def cell_types_array(self): 620 """Return the list of cell types in the dataset.""" 621 uarr = self.dataset.GetCellTypesArray() 622 return utils.vtk2numpy(uarr) 623 624 def extract_cells_by_type(self, ctype) -> "UnstructuredGrid": 625 """Extract a specific cell type and return a new `UnstructuredGrid`.""" 626 if isinstance(ctype, str): 627 try: 628 ctype = vtki.cell_types[ctype.upper()] 629 except KeyError: 630 vedo.logger.error(f"extract_cells_by_type: cell type {ctype} does not exist. Skip.") 631 return self 632 uarr = self.dataset.GetCellTypesArray() 633 ctarrtyp = np.where(utils.vtk2numpy(uarr) == ctype)[0] 634 uarrtyp = utils.numpy2vtk(ctarrtyp, deep=False, dtype="id") 635 selection_node = vtki.new("SelectionNode") 636 selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL) 637 selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES) 638 selection_node.SetSelectionList(uarrtyp) 639 selection = vtki.new("Selection") 640 selection.AddNode(selection_node) 641 es = vtki.new("ExtractSelection") 642 es.SetInputData(0, self.dataset) 643 es.SetInputData(1, selection) 644 es.Update() 645 646 ug = UnstructuredGrid(es.GetOutput()) 647 ug.pipeline = utils.OperationNode( 648 "extract_cell_type", comment=f"type {ctype}", c="#edabab", parents=[self] 649 ) 650 return ug 651 652 def extract_cells_by_id(self, idlist, use_point_ids=False) -> "UnstructuredGrid": 653 """Return a new `UnstructuredGrid` composed of the specified subset of indices.""" 654 selection_node = vtki.new("SelectionNode") 655 if use_point_ids: 656 selection_node.SetFieldType(vtki.get_class("SelectionNode").POINT) 657 contcells = vtki.get_class("SelectionNode").CONTAINING_CELLS() 658 selection_node.GetProperties().Set(contcells, 1) 659 else: 660 selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL) 661 selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES) 662 vidlist = utils.numpy2vtk(idlist, dtype="id") 663 selection_node.SetSelectionList(vidlist) 664 selection = vtki.new("Selection") 665 selection.AddNode(selection_node) 666 es = vtki.new("ExtractSelection") 667 es.SetInputData(0, self) 668 es.SetInputData(1, selection) 669 es.Update() 670 671 ug = UnstructuredGrid(es.GetOutput()) 672 pr = vtki.vtkProperty() 673 pr.DeepCopy(self.properties) 674 ug.actor.SetProperty(pr) 675 ug.properties = pr 676 677 ug.mapper.SetLookupTable(utils.ctf2lut(self)) 678 ug.pipeline = utils.OperationNode( 679 "extract_cells_by_id", 680 parents=[self], 681 comment=f"#cells {self.dataset.GetNumberOfCells()}", 682 c="#9e2a2b", 683 ) 684 return ug 685 686 def find_cell(self, p: list) -> int: 687 """Locate the cell that contains a point and return the cell ID.""" 688 if self.cell_locator is None: 689 self.cell_locator = vtki.new("CellLocator") 690 self.cell_locator.SetDataSet(self.dataset) 691 self.cell_locator.BuildLocator() 692 cid = self.cell_locator.FindCell(p) 693 return cid 694 695 def clean(self) -> Self: 696 """ 697 Cleanup unused points and empty cells 698 """ 699 cl = vtki.new("StaticCleanUnstructuredGrid") 700 cl.SetInputData(self.dataset) 701 cl.RemoveUnusedPointsOn() 702 cl.ProduceMergeMapOff() 703 cl.AveragePointDataOff() 704 cl.Update() 705 706 self._update(cl.GetOutput()) 707 self.pipeline = utils.OperationNode( 708 "clean", 709 parents=[self], 710 comment=f"#cells {self.dataset.GetNumberOfCells()}", 711 c="#9e2a2b", 712 ) 713 return self 714 715 def extract_cells_on_plane(self, origin: tuple, normal: tuple) -> Self: 716 """ 717 Extract cells that are lying of the specified surface. 718 """ 719 bf = vtki.new("3DLinearGridCrinkleExtractor") 720 bf.SetInputData(self.dataset) 721 bf.CopyPointDataOn() 722 bf.CopyCellDataOn() 723 bf.RemoveUnusedPointsOff() 724 725 plane = vtki.new("Plane") 726 plane.SetOrigin(origin) 727 plane.SetNormal(normal) 728 bf.SetImplicitFunction(plane) 729 bf.Update() 730 731 self._update(bf.GetOutput(), reset_locators=False) 732 self.pipeline = utils.OperationNode( 733 "extract_cells_on_plane", 734 parents=[self], 735 comment=f"#cells {self.dataset.GetNumberOfCells()}", 736 c="#9e2a2b", 737 ) 738 return self 739 740 def extract_cells_on_sphere(self, center: tuple, radius: tuple) -> Self: 741 """ 742 Extract cells that are lying of the specified surface. 743 """ 744 bf = vtki.new("3DLinearGridCrinkleExtractor") 745 bf.SetInputData(self.dataset) 746 bf.CopyPointDataOn() 747 bf.CopyCellDataOn() 748 bf.RemoveUnusedPointsOff() 749 750 sph = vtki.new("Sphere") 751 sph.SetRadius(radius) 752 sph.SetCenter(center) 753 bf.SetImplicitFunction(sph) 754 bf.Update() 755 756 self._update(bf.GetOutput()) 757 self.pipeline = utils.OperationNode( 758 "extract_cells_on_sphere", 759 parents=[self], 760 comment=f"#cells {self.dataset.GetNumberOfCells()}", 761 c="#9e2a2b", 762 ) 763 return self 764 765 def extract_cells_on_cylinder(self, center: tuple, axis: tuple, radius: float) -> Self: 766 """ 767 Extract cells that are lying of the specified surface. 768 """ 769 bf = vtki.new("3DLinearGridCrinkleExtractor") 770 bf.SetInputData(self.dataset) 771 bf.CopyPointDataOn() 772 bf.CopyCellDataOn() 773 bf.RemoveUnusedPointsOff() 774 775 cyl = vtki.new("Cylinder") 776 cyl.SetRadius(radius) 777 cyl.SetCenter(center) 778 cyl.SetAxis(axis) 779 bf.SetImplicitFunction(cyl) 780 bf.Update() 781 782 self.pipeline = utils.OperationNode( 783 "extract_cells_on_cylinder", 784 parents=[self], 785 comment=f"#cells {self.dataset.GetNumberOfCells()}", 786 c="#9e2a2b", 787 ) 788 self._update(bf.GetOutput()) 789 return self 790 791 def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> "UnstructuredGrid": 792 """ 793 Cut the object with the plane defined by a point and a normal. 794 795 Arguments: 796 origin : (list) 797 the cutting plane goes through this point 798 normal : (list, str) 799 normal vector to the cutting plane 800 """ 801 # if isinstance(self, vedo.Volume): 802 # raise RuntimeError("cut_with_plane() is not applicable to Volume objects.") 803 804 strn = str(normal) 805 if strn == "x": normal = (1, 0, 0) 806 elif strn == "y": normal = (0, 1, 0) 807 elif strn == "z": normal = (0, 0, 1) 808 elif strn == "-x": normal = (-1, 0, 0) 809 elif strn == "-y": normal = (0, -1, 0) 810 elif strn == "-z": normal = (0, 0, -1) 811 plane = vtki.new("Plane") 812 plane.SetOrigin(origin) 813 plane.SetNormal(normal) 814 clipper = vtki.new("ClipDataSet") 815 clipper.SetInputData(self.dataset) 816 clipper.SetClipFunction(plane) 817 clipper.GenerateClipScalarsOff() 818 clipper.GenerateClippedOutputOff() 819 clipper.SetValue(0) 820 clipper.Update() 821 cout = clipper.GetOutput() 822 823 if isinstance(cout, vtki.vtkUnstructuredGrid): 824 ug = vedo.UnstructuredGrid(cout) 825 if isinstance(self, vedo.UnstructuredGrid): 826 self._update(cout) 827 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 828 return self 829 ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 830 return ug 831 832 else: 833 self._update(cout) 834 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 835 return self 836 837 def cut_with_box(self, box: Any) -> "UnstructuredGrid": 838 """ 839 Cut the grid with the specified bounding box. 840 841 Parameter box has format [xmin, xmax, ymin, ymax, zmin, zmax]. 842 If an object is passed, its bounding box are used. 843 844 This method always returns a TetMesh object. 845 846 Example: 847 ```python 848 from vedo import * 849 tmesh = TetMesh(dataurl+'limb_ugrid.vtk') 850 tmesh.color('rainbow') 851 cu = Cube(side=500).x(500) # any Mesh works 852 tmesh.cut_with_box(cu).show(axes=1) 853 ``` 854 855 ![](https://vedo.embl.es/images/feats/tet_cut_box.png) 856 """ 857 bc = vtki.new("BoxClipDataSet") 858 bc.SetInputData(self.dataset) 859 try: 860 boxb = box.bounds() 861 except AttributeError: 862 boxb = box 863 864 bc.SetBoxClip(*boxb) 865 bc.Update() 866 cout = bc.GetOutput() 867 868 # output of vtkBoxClipDataSet is always tetrahedrons 869 tm = vedo.TetMesh(cout) 870 tm.pipeline = utils.OperationNode("cut_with_box", parents=[self], c="#9e2a2b") 871 return tm 872 873 def cut_with_mesh(self, mesh: Mesh, invert=False, whole_cells=False, on_boundary=False) -> "UnstructuredGrid": 874 """ 875 Cut a `UnstructuredGrid` or `TetMesh` with a `Mesh`. 876 877 Use `invert` to return cut off part of the input object. 878 """ 879 ug = self.dataset 880 881 ippd = vtki.new("ImplicitPolyDataDistance") 882 ippd.SetInput(mesh.dataset) 883 884 if whole_cells or on_boundary: 885 clipper = vtki.new("ExtractGeometry") 886 clipper.SetInputData(ug) 887 clipper.SetImplicitFunction(ippd) 888 clipper.SetExtractInside(not invert) 889 clipper.SetExtractBoundaryCells(False) 890 if on_boundary: 891 clipper.SetExtractBoundaryCells(True) 892 clipper.SetExtractOnlyBoundaryCells(True) 893 else: 894 signed_dists = vtki.vtkFloatArray() 895 signed_dists.SetNumberOfComponents(1) 896 signed_dists.SetName("SignedDistance") 897 for pointId in range(ug.GetNumberOfPoints()): 898 p = ug.GetPoint(pointId) 899 signed_dist = ippd.EvaluateFunction(p) 900 signed_dists.InsertNextValue(signed_dist) 901 ug.GetPointData().AddArray(signed_dists) 902 ug.GetPointData().SetActiveScalars("SignedDistance") # NEEDED 903 clipper = vtki.new("ClipDataSet") 904 clipper.SetInputData(ug) 905 clipper.SetInsideOut(not invert) 906 clipper.SetValue(0.0) 907 908 clipper.Update() 909 910 out = vedo.UnstructuredGrid(clipper.GetOutput()) 911 out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b") 912 return out 913 914 915########################################################################## 916class TetMesh(UnstructuredGrid): 917 """The class describing tetrahedral meshes.""" 918 919 def __init__(self, inputobj=None): 920 """ 921 Arguments: 922 inputobj : (vtkUnstructuredGrid, list, str, tetgenpy.TetgenIO) 923 list of points and tet indices, or filename 924 """ 925 super().__init__() 926 927 self.dataset = None 928 929 self.mapper = vtki.new("PolyDataMapper") 930 self._actor = vtki.vtkActor() 931 self._actor.retrieve_object = weak_ref_to(self) 932 self._actor.SetMapper(self.mapper) 933 self.properties = self._actor.GetProperty() 934 935 self.name = "TetMesh" 936 937 # print('TetMesh inputtype', type(inputobj)) 938 939 ################### 940 if inputobj is None: 941 self.dataset = vtki.vtkUnstructuredGrid() 942 943 elif isinstance(inputobj, vtki.vtkUnstructuredGrid): 944 self.dataset = inputobj 945 946 elif isinstance(inputobj, UnstructuredGrid): 947 self.dataset = inputobj.dataset 948 949 elif "TetgenIO" in str(type(inputobj)): # tetgenpy object 950 inputobj = [inputobj.points(), inputobj.tetrahedra()] 951 952 elif isinstance(inputobj, vtki.vtkRectilinearGrid): 953 r2t = vtki.new("RectilinearGridToTetrahedra") 954 r2t.SetInputData(inputobj) 955 r2t.RememberVoxelIdOn() 956 r2t.SetTetraPerCellTo6() 957 r2t.Update() 958 self.dataset = r2t.GetOutput() 959 960 elif isinstance(inputobj, vtki.vtkDataSet): 961 r2t = vtki.new("DataSetTriangleFilter") 962 r2t.SetInputData(inputobj) 963 r2t.TetrahedraOnlyOn() 964 r2t.Update() 965 self.dataset = r2t.GetOutput() 966 967 elif isinstance(inputobj, str): 968 if "https://" in inputobj: 969 inputobj = download(inputobj, verbose=False) 970 if inputobj.endswith(".vtu"): 971 reader = vtki.new("XMLUnstructuredGridReader") 972 else: 973 reader = vtki.new("UnstructuredGridReader") 974 975 if not os.path.isfile(inputobj): 976 # for some reason vtk Reader does not complain 977 vedo.logger.error(f"file {inputobj} not found") 978 raise FileNotFoundError 979 980 self.filename = inputobj 981 reader.SetFileName(inputobj) 982 reader.Update() 983 ug = reader.GetOutput() 984 985 tt = vtki.new("DataSetTriangleFilter") 986 tt.SetInputData(ug) 987 tt.SetTetrahedraOnly(True) 988 tt.Update() 989 self.dataset = tt.GetOutput() 990 991 ############################### 992 if utils.is_sequence(inputobj): 993 self.dataset = vtki.vtkUnstructuredGrid() 994 995 points, cells = inputobj 996 if len(points) == 0: 997 return 998 if not utils.is_sequence(points[0]): 999 return 1000 if len(cells) == 0: 1001 return 1002 1003 if not utils.is_sequence(cells[0]): 1004 tets = [] 1005 nf = cells[0] + 1 1006 for i, cl in enumerate(cells): 1007 if i in (nf, 0): 1008 k = i + 1 1009 nf = cl + k 1010 cell = [cells[j + k] for j in range(cl)] 1011 tets.append(cell) 1012 cells = tets 1013 1014 source_points = vtki.vtkPoints() 1015 varr = utils.numpy2vtk(points, dtype=np.float32) 1016 source_points.SetData(varr) 1017 self.dataset.SetPoints(source_points) 1018 1019 source_tets = vtki.vtkCellArray() 1020 for f in cells: 1021 ele = vtki.vtkTetra() 1022 pid = ele.GetPointIds() 1023 for i, fi in enumerate(f): 1024 pid.SetId(i, fi) 1025 source_tets.InsertNextCell(ele) 1026 self.dataset.SetCells(vtki.cell_types["TETRA"], source_tets) 1027 1028 if not self.dataset: 1029 vedo.logger.error(f"cannot understand input type {type(inputobj)}") 1030 return 1031 1032 self.properties.SetColor(0.352, 0.612, 0.996) # blue7 1033 1034 self.pipeline = utils.OperationNode( 1035 self, comment=f"#tets {self.dataset.GetNumberOfCells()}", c="#9e2a2b" 1036 ) 1037 1038 ################################################################## 1039 def __str__(self): 1040 """Print a string summary of the `TetMesh` object.""" 1041 module = self.__class__.__module__ 1042 name = self.__class__.__name__ 1043 out = vedo.printc( 1044 f"{module}.{name} at ({hex(self.memory_address())})".ljust(75), 1045 c="c", bold=True, invert=True, return_string=True, 1046 ) 1047 out += "\x1b[0m\u001b[36m" 1048 1049 out += "nr. of verts".ljust(14) + ": " + str(self.npoints) + "\n" 1050 out += "nr. of tetras".ljust(14) + ": " + str(self.ncells) + "\n" 1051 1052 if self.npoints: 1053 out+="size".ljust(14)+ ": average=" + utils.precision(self.average_size(),6) 1054 out+=", diagonal="+ utils.precision(self.diagonal_size(), 6)+ "\n" 1055 out+="center of mass".ljust(14) + ": " + utils.precision(self.center_of_mass(),6)+"\n" 1056 1057 bnds = self.bounds() 1058 bx1, bx2 = utils.precision(bnds[0], 3), utils.precision(bnds[1], 3) 1059 by1, by2 = utils.precision(bnds[2], 3), utils.precision(bnds[3], 3) 1060 bz1, bz2 = utils.precision(bnds[4], 3), utils.precision(bnds[5], 3) 1061 out += "bounds".ljust(14) + ":" 1062 out += " x=(" + bx1 + ", " + bx2 + ")," 1063 out += " y=(" + by1 + ", " + by2 + ")," 1064 out += " z=(" + bz1 + ", " + bz2 + ")\n" 1065 1066 for key in self.pointdata.keys(): 1067 arr = self.pointdata[key] 1068 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 1069 mark_active = "pointdata" 1070 a_scalars = self.dataset.GetPointData().GetScalars() 1071 a_vectors = self.dataset.GetPointData().GetVectors() 1072 a_tensors = self.dataset.GetPointData().GetTensors() 1073 if a_scalars and a_scalars.GetName() == key: 1074 mark_active += " *" 1075 elif a_vectors and a_vectors.GetName() == key: 1076 mark_active += " **" 1077 elif a_tensors and a_tensors.GetName() == key: 1078 mark_active += " ***" 1079 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 1080 out += f", range=({rng})\n" 1081 1082 for key in self.celldata.keys(): 1083 arr = self.celldata[key] 1084 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 1085 mark_active = "celldata" 1086 a_scalars = self.dataset.GetCellData().GetScalars() 1087 a_vectors = self.dataset.GetCellData().GetVectors() 1088 a_tensors = self.dataset.GetCellData().GetTensors() 1089 if a_scalars and a_scalars.GetName() == key: 1090 mark_active += " *" 1091 elif a_vectors and a_vectors.GetName() == key: 1092 mark_active += " **" 1093 elif a_tensors and a_tensors.GetName() == key: 1094 mark_active += " ***" 1095 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 1096 out += f", range=({rng})\n" 1097 1098 for key in self.metadata.keys(): 1099 arr = self.metadata[key] 1100 out += "metadata".ljust(14) + ": " + f'"{key}" ({len(arr)} values)\n' 1101 1102 return out.rstrip() + "\x1b[0m" 1103 1104 def _repr_html_(self): 1105 """ 1106 HTML representation of the TetMesh object for Jupyter Notebooks. 1107 1108 Returns: 1109 HTML text with the image and some properties. 1110 """ 1111 import io 1112 import base64 1113 from PIL import Image 1114 1115 library_name = "vedo.grids.TetMesh" 1116 help_url = "https://vedo.embl.es/docs/vedo/grids.html#TetMesh" 1117 1118 arr = self.thumbnail() 1119 im = Image.fromarray(arr) 1120 buffered = io.BytesIO() 1121 im.save(buffered, format="PNG", quality=100) 1122 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 1123 url = "data:image/png;base64," + encoded 1124 image = f"<img src='{url}'></img>" 1125 1126 bounds = "<br/>".join( 1127 [ 1128 utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4) 1129 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 1130 ] 1131 ) 1132 1133 help_text = "" 1134 if self.name: 1135 help_text += f"<b> {self.name}:   </b>" 1136 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 1137 if self.filename: 1138 dots = "" 1139 if len(self.filename) > 30: 1140 dots = "..." 1141 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 1142 1143 pdata = "" 1144 if self.dataset.GetPointData().GetScalars(): 1145 if self.dataset.GetPointData().GetScalars().GetName(): 1146 name = self.dataset.GetPointData().GetScalars().GetName() 1147 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 1148 1149 cdata = "" 1150 if self.dataset.GetCellData().GetScalars(): 1151 if self.dataset.GetCellData().GetScalars().GetName(): 1152 name = self.dataset.GetCellData().GetScalars().GetName() 1153 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 1154 1155 pts = self.vertices 1156 cm = np.mean(pts, axis=0) 1157 1158 allt = [ 1159 "<table>", 1160 "<tr>", 1161 "<td>", image, "</td>", 1162 "<td style='text-align: center; vertical-align: center;'><br/>", help_text, 1163 "<table>", 1164 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 1165 "<tr><td><b> center of mass </b></td><td>" + utils.precision(cm,3) + "</td></tr>", 1166 "<tr><td><b> nr. points / tets </b></td><td>" 1167 + str(self.npoints) + " / " + str(self.ncells) + "</td></tr>", 1168 pdata, 1169 cdata, 1170 "</table>", 1171 "</table>", 1172 ] 1173 return "\n".join(allt) 1174 1175 def compute_quality(self, metric=7) -> np.ndarray: 1176 """ 1177 Calculate functions of quality for the elements of a tetrahedral mesh. 1178 This method adds to the mesh a cell array named "Quality". 1179 1180 Arguments: 1181 metric : (int) 1182 type of estimators: 1183 - EDGE RATIO, 0 1184 - ASPECT RATIO, 1 1185 - RADIUS RATIO, 2 1186 - ASPECT FROBENIUS, 3 1187 - MIN_ANGLE, 4 1188 - COLLAPSE RATIO, 5 1189 - ASPECT GAMMA, 6 1190 - VOLUME, 7 1191 - ... 1192 1193 See class [vtkMeshQuality](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html) 1194 for an explanation of the meaning of each metric.. 1195 """ 1196 qf = vtki.new("MeshQuality") 1197 qf.SetInputData(self.dataset) 1198 qf.SetTetQualityMeasure(metric) 1199 qf.SaveCellQualityOn() 1200 qf.Update() 1201 self._update(qf.GetOutput()) 1202 return utils.vtk2numpy(qf.GetOutput().GetCellData().GetArray("Quality")) 1203 1204 def check_validity(self, tol=0) -> np.ndarray: 1205 """ 1206 Return an array of possible problematic tets following this convention: 1207 ```python 1208 Valid = 0 1209 WrongNumberOfPoints = 01 1210 IntersectingEdges = 02 1211 IntersectingFaces = 04 1212 NoncontiguousEdges = 08 1213 Nonconvex = 10 1214 OrientedIncorrectly = 20 1215 ``` 1216 1217 Arguments: 1218 tol : (float) 1219 This value is used as an epsilon for floating point 1220 equality checks throughout the cell checking process. 1221 """ 1222 vald = vtki.new("CellValidator") 1223 if tol: 1224 vald.SetTolerance(tol) 1225 vald.SetInputData(self.dataset) 1226 vald.Update() 1227 varr = vald.GetOutput().GetCellData().GetArray("ValidityState") 1228 return utils.vtk2numpy(varr) 1229 1230 def decimate(self, scalars_name: str, fraction=0.5, n=0) -> Self: 1231 """ 1232 Downsample the number of tets in a TetMesh to a specified fraction. 1233 Either `fraction` or `n` must be set. 1234 1235 Arguments: 1236 fraction : (float) 1237 the desired final fraction of the total. 1238 n : (int) 1239 the desired number of final tets 1240 1241 .. note:: setting `fraction=0.1` leaves 10% of the original nr of tets. 1242 """ 1243 decimate = vtki.new("UnstructuredGridQuadricDecimation") 1244 decimate.SetInputData(self.dataset) 1245 decimate.SetScalarsName(scalars_name) 1246 1247 if n: # n = desired number of points 1248 decimate.SetNumberOfTetsOutput(n) 1249 else: 1250 decimate.SetTargetReduction(1 - fraction) 1251 decimate.Update() 1252 self._update(decimate.GetOutput()) 1253 self.pipeline = utils.OperationNode( 1254 "decimate", comment=f"array: {scalars_name}", c="#edabab", parents=[self] 1255 ) 1256 return self 1257 1258 def subdivide(self) -> Self: 1259 """ 1260 Increase the number of tetrahedrons of a `TetMesh`. 1261 Subdivides each tetrahedron into twelve smaller tetras. 1262 """ 1263 sd = vtki.new("SubdivideTetra") 1264 sd.SetInputData(self.dataset) 1265 sd.Update() 1266 self._update(sd.GetOutput()) 1267 self.pipeline = utils.OperationNode("subdivide", c="#edabab", parents=[self]) 1268 return self 1269 1270 def generate_random_points(self, n, min_radius=0) -> "vedo.Points": 1271 """ 1272 Generate `n` uniformly distributed random points 1273 inside the tetrahedral mesh. 1274 1275 A new point data array is added to the output points 1276 called "OriginalCellID" which contains the index of 1277 the cell ID in which the point was generated. 1278 1279 Arguments: 1280 n : (int) 1281 number of points to generate. 1282 min_radius: (float) 1283 impose a minimum distance between points. 1284 If `min_radius` is set to 0, the points are 1285 generated uniformly at random inside the mesh. 1286 If `min_radius` is set to a positive value, 1287 the points are generated uniformly at random 1288 inside the mesh, but points closer than `min_radius` 1289 to any other point are discarded. 1290 1291 Returns a `vedo.Points` object. 1292 1293 Note: 1294 Consider using `points.probe(msh)` to interpolate 1295 any existing mesh data onto the points. 1296 1297 Example: 1298 ```python 1299 from vedo import * 1300 tmesh = TetMesh(dataurl + "limb.vtu").alpha(0.2) 1301 pts = tmesh.generate_random_points(20000, min_radius=10) 1302 print(pts.pointdata["OriginalCellID"]) 1303 show(pts, tmesh, axes=1).close() 1304 ``` 1305 """ 1306 cmesh = self.compute_cell_size() 1307 tets = cmesh.cells 1308 verts = cmesh.vertices 1309 cumul = np.cumsum(np.abs(cmesh.celldata["Volume"])) 1310 1311 out_pts = [] 1312 orig_cell = [] 1313 for _ in range(n): 1314 random_area = np.random.random() * cumul[-1] 1315 it = np.searchsorted(cumul, random_area) 1316 A, B, C, D = verts[tets[it]] 1317 r1, r2, r3 = sorted(np.random.random(3)) 1318 p = r1 * A + (r2 - r1) * B + (r3 - r2) * C + (1 - r3) * D 1319 out_pts.append(p) 1320 orig_cell.append(it) 1321 orig_cellnp = np.array(orig_cell, dtype=np.uint32) 1322 1323 vpts = vedo.pointcloud.Points(out_pts) 1324 vpts.pointdata["OriginalCellID"] = orig_cellnp 1325 1326 if min_radius > 0: 1327 vpts.subsample(min_radius, absolute=True) 1328 1329 vpts.point_size(5).color("k1") 1330 vpts.name = "RandomPoints" 1331 vpts.pipeline = utils.OperationNode( 1332 "generate_random_points", c="#edabab", parents=[self]) 1333 return vpts 1334 1335 def isosurface(self, value=True, flying_edges=None) -> "vedo.Mesh": 1336 """ 1337 Return a `vedo.Mesh` isosurface. 1338 The "isosurface" is the surface of the region of points 1339 whose values equal to `value`. 1340 1341 Set `value` to a single value or list of values to compute the isosurface(s). 1342 1343 Note that flying_edges option is not available for `TetMesh`. 1344 """ 1345 if flying_edges is not None: 1346 vedo.logger.warning("flying_edges option is not available for TetMesh.") 1347 1348 if not self.dataset.GetPointData().GetScalars(): 1349 vedo.logger.warning( 1350 "in isosurface() no scalar pointdata found. " 1351 "Mappping cells to points." 1352 ) 1353 self.map_cells_to_points() 1354 scrange = self.dataset.GetPointData().GetScalars().GetRange() 1355 cf = vtki.new("ContourFilter") # vtki.new("ContourGrid") 1356 cf.SetInputData(self.dataset) 1357 1358 if utils.is_sequence(value): 1359 cf.SetNumberOfContours(len(value)) 1360 for i, t in enumerate(value): 1361 cf.SetValue(i, t) 1362 cf.Update() 1363 else: 1364 if value is True: 1365 value = (2 * scrange[0] + scrange[1]) / 3.0 1366 cf.SetValue(0, value) 1367 cf.Update() 1368 1369 msh = Mesh(cf.GetOutput(), c=None) 1370 msh.copy_properties_from(self) 1371 msh.pipeline = utils.OperationNode("isosurface", c="#edabab", parents=[self]) 1372 return msh 1373 1374 def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)) -> "vedo.Mesh": 1375 """ 1376 Return a 2D slice of the mesh by a plane passing through origin and assigned normal. 1377 """ 1378 strn = str(normal) 1379 if strn == "x": normal = (1, 0, 0) 1380 elif strn == "y": normal = (0, 1, 0) 1381 elif strn == "z": normal = (0, 0, 1) 1382 elif strn == "-x": normal = (-1, 0, 0) 1383 elif strn == "-y": normal = (0, -1, 0) 1384 elif strn == "-z": normal = (0, 0, -1) 1385 plane = vtki.new("Plane") 1386 plane.SetOrigin(origin) 1387 plane.SetNormal(normal) 1388 1389 cc = vtki.new("Cutter") 1390 cc.SetInputData(self.dataset) 1391 cc.SetCutFunction(plane) 1392 cc.Update() 1393 msh = Mesh(cc.GetOutput()).flat().lighting("ambient") 1394 msh.copy_properties_from(self) 1395 msh.pipeline = utils.OperationNode("slice", c="#edabab", parents=[self]) 1396 return msh 1397 1398 1399########################################################################## 1400class RectilinearGrid(PointAlgorithms, MeshVisual): 1401 """ 1402 Build a rectilinear grid. 1403 """ 1404 1405 def __init__(self, inputobj=None): 1406 """ 1407 A RectilinearGrid is a dataset where edges are parallel to the coordinate axes. 1408 It can be thought of as a tessellation of a box in 3D space, similar to a `Volume` 1409 except that the cells are not necessarily cubes, but they can have different lengths 1410 along each axis. 1411 This can be useful to describe a volume with variable resolution where one needs 1412 to represent a region with higher detail with respect to another region. 1413 1414 Arguments: 1415 inputobj : (vtkRectilinearGrid, list, str) 1416 list of points and tet indices, or filename 1417 1418 Example: 1419 ```python 1420 from vedo import RectilinearGrid, show 1421 1422 xcoords = 7 + np.sqrt(np.arange(0,2500,25)) 1423 ycoords = np.arange(0, 20) 1424 zcoords = np.arange(0, 20) 1425 1426 rgrid = RectilinearGrid([xcoords, ycoords, zcoords]) 1427 1428 print(rgrid) 1429 print(rgrid.x_coordinates().shape) 1430 print(rgrid.compute_structured_coords([20,10,11])) 1431 1432 msh = rgrid.tomesh().lw(1) 1433 1434 show(msh, axes=1, viewup="z") 1435 ``` 1436 """ 1437 1438 super().__init__() 1439 1440 self.dataset = None 1441 1442 self.mapper = vtki.new("PolyDataMapper") 1443 self._actor = vtki.vtkActor() 1444 self._actor.retrieve_object = weak_ref_to(self) 1445 self._actor.SetMapper(self.mapper) 1446 self.properties = self._actor.GetProperty() 1447 1448 self.transform = LinearTransform() 1449 self.point_locator = None 1450 self.cell_locator = None 1451 self.line_locator = None 1452 1453 self.name = "RectilinearGrid" 1454 self.filename = "" 1455 1456 self.info = {} 1457 self.time = time.time() 1458 1459 ############################### 1460 if inputobj is None: 1461 self.dataset = vtki.vtkRectilinearGrid() 1462 1463 elif isinstance(inputobj, vtki.vtkRectilinearGrid): 1464 self.dataset = inputobj 1465 1466 elif isinstance(inputobj, RectilinearGrid): 1467 self.dataset = inputobj.dataset 1468 1469 elif isinstance(inputobj, str): 1470 if "https://" in inputobj: 1471 inputobj = download(inputobj, verbose=False) 1472 if inputobj.endswith(".vtr"): 1473 reader = vtki.new("XMLRectilinearGridReader") 1474 else: 1475 reader = vtki.new("RectilinearGridReader") 1476 self.filename = inputobj 1477 reader.SetFileName(inputobj) 1478 reader.Update() 1479 self.dataset = reader.GetOutput() 1480 1481 elif utils.is_sequence(inputobj): 1482 self.dataset = vtki.vtkRectilinearGrid() 1483 xcoords, ycoords, zcoords = inputobj 1484 nx, ny, nz = len(xcoords), len(ycoords), len(zcoords) 1485 self.dataset.SetDimensions(nx, ny, nz) 1486 self.dataset.SetXCoordinates(utils.numpy2vtk(xcoords)) 1487 self.dataset.SetYCoordinates(utils.numpy2vtk(ycoords)) 1488 self.dataset.SetZCoordinates(utils.numpy2vtk(zcoords)) 1489 1490 ############################### 1491 1492 if not self.dataset: 1493 vedo.logger.error(f"RectilinearGrid: cannot understand input type {type(inputobj)}") 1494 return 1495 1496 self.properties.SetColor(0.352, 0.612, 0.996) # blue7 1497 1498 self.pipeline = utils.OperationNode( 1499 self, comment=f"#tets {self.dataset.GetNumberOfCells()}", c="#9e2a2b" 1500 ) 1501 1502 @property 1503 def actor(self): 1504 """Return the `vtkActor` of the object.""" 1505 gf = vtki.new("GeometryFilter") 1506 gf.SetInputData(self.dataset) 1507 gf.Update() 1508 self.mapper.SetInputData(gf.GetOutput()) 1509 self.mapper.Modified() 1510 return self._actor 1511 1512 @actor.setter 1513 def actor(self, _): 1514 pass 1515 1516 def _update(self, data, reset_locators=False): 1517 self.dataset = data 1518 if reset_locators: 1519 self.cell_locator = None 1520 self.point_locator = None 1521 return self 1522 1523 ################################################################## 1524 def __str__(self): 1525 """Print a summary for the `RectilinearGrid` object.""" 1526 module = self.__class__.__module__ 1527 name = self.__class__.__name__ 1528 out = vedo.printc( 1529 f"{module}.{name} at ({hex(self.memory_address())})".ljust(75), 1530 c="c", bold=True, invert=True, return_string=True, 1531 ) 1532 out += "\x1b[0m\x1b[36;1m" 1533 1534 out += "name".ljust(14) + ": " + str(self.name) + "\n" 1535 if self.filename: 1536 out += "filename".ljust(14) + ": " + str(self.filename) + "\n" 1537 1538 out += "dimensions".ljust(14) + ": " + str(self.dataset.GetDimensions()) + "\n" 1539 1540 out += "center".ljust(14) + ": " 1541 out += utils.precision(self.dataset.GetCenter(), 6) + "\n" 1542 1543 bnds = self.bounds() 1544 bx1, bx2 = utils.precision(bnds[0], 3), utils.precision(bnds[1], 3) 1545 by1, by2 = utils.precision(bnds[2], 3), utils.precision(bnds[3], 3) 1546 bz1, bz2 = utils.precision(bnds[4], 3), utils.precision(bnds[5], 3) 1547 out += "bounds".ljust(14) + ":" 1548 out += " x=(" + bx1 + ", " + bx2 + ")," 1549 out += " y=(" + by1 + ", " + by2 + ")," 1550 out += " z=(" + bz1 + ", " + bz2 + ")\n" 1551 1552 out += "memory size".ljust(14) + ": " 1553 out += utils.precision(self.dataset.GetActualMemorySize() / 1024, 2) + " MB\n" 1554 1555 for key in self.pointdata.keys(): 1556 arr = self.pointdata[key] 1557 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 1558 mark_active = "pointdata" 1559 a_scalars = self.dataset.GetPointData().GetScalars() 1560 a_vectors = self.dataset.GetPointData().GetVectors() 1561 a_tensors = self.dataset.GetPointData().GetTensors() 1562 if a_scalars and a_scalars.GetName() == key: 1563 mark_active += " *" 1564 elif a_vectors and a_vectors.GetName() == key: 1565 mark_active += " **" 1566 elif a_tensors and a_tensors.GetName() == key: 1567 mark_active += " ***" 1568 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 1569 out += f", range=({rng})\n" 1570 1571 for key in self.celldata.keys(): 1572 arr = self.celldata[key] 1573 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 1574 mark_active = "celldata" 1575 a_scalars = self.dataset.GetCellData().GetScalars() 1576 a_vectors = self.dataset.GetCellData().GetVectors() 1577 a_tensors = self.dataset.GetCellData().GetTensors() 1578 if a_scalars and a_scalars.GetName() == key: 1579 mark_active += " *" 1580 elif a_vectors and a_vectors.GetName() == key: 1581 mark_active += " **" 1582 elif a_tensors and a_tensors.GetName() == key: 1583 mark_active += " ***" 1584 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 1585 out += f", range=({rng})\n" 1586 1587 for key in self.metadata.keys(): 1588 arr = self.metadata[key] 1589 out += "metadata".ljust(14) + ": " + f'"{key}" ({len(arr)} values)\n' 1590 1591 return out.rstrip() + "\x1b[0m" 1592 1593 def _repr_html_(self): 1594 """ 1595 HTML representation of the RectilinearGrid object for Jupyter Notebooks. 1596 1597 Returns: 1598 HTML text with the image and some properties. 1599 """ 1600 import io 1601 import base64 1602 from PIL import Image 1603 1604 library_name = "vedo.grids.RectilinearGrid" 1605 help_url = "https://vedo.embl.es/docs/vedo/grids.html#RectilinearGrid" 1606 1607 m = self.tomesh().linewidth(1).lighting("off") 1608 arr= m.thumbnail(zoom=1, elevation=-30, azimuth=-30) 1609 1610 im = Image.fromarray(arr) 1611 buffered = io.BytesIO() 1612 im.save(buffered, format="PNG", quality=100) 1613 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 1614 url = "data:image/png;base64," + encoded 1615 image = f"<img src='{url}'></img>" 1616 1617 bounds = "<br/>".join( 1618 [ 1619 utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4) 1620 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 1621 ] 1622 ) 1623 1624 help_text = "" 1625 if self.name: 1626 help_text += f"<b> {self.name}:   </b>" 1627 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 1628 if self.filename: 1629 dots = "" 1630 if len(self.filename) > 30: 1631 dots = "..." 1632 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 1633 1634 pdata = "" 1635 if self.dataset.GetPointData().GetScalars(): 1636 if self.dataset.GetPointData().GetScalars().GetName(): 1637 name = self.dataset.GetPointData().GetScalars().GetName() 1638 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 1639 1640 cdata = "" 1641 if self.dataset.GetCellData().GetScalars(): 1642 if self.dataset.GetCellData().GetScalars().GetName(): 1643 name = self.dataset.GetCellData().GetScalars().GetName() 1644 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 1645 1646 pts = self.vertices 1647 cm = np.mean(pts, axis=0) 1648 1649 all = [ 1650 "<table>", 1651 "<tr>", 1652 "<td>", image, "</td>", 1653 "<td style='text-align: center; vertical-align: center;'><br/>", help_text, 1654 "<table>", 1655 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 1656 "<tr><td><b> center of mass </b></td><td>" + utils.precision(cm,3) + "</td></tr>", 1657 "<tr><td><b> nr. points / cells </b></td><td>" 1658 + str(self.npoints) + " / " + str(self.ncells) + "</td></tr>", 1659 pdata, 1660 cdata, 1661 "</table>", 1662 "</table>", 1663 ] 1664 return "\n".join(all) 1665 1666 def dimensions(self) -> np.ndarray: 1667 """Return the number of points in the x, y and z directions.""" 1668 return np.array(self.dataset.GetDimensions()) 1669 1670 def x_coordinates(self) -> np.ndarray: 1671 """Return the x-coordinates of the grid.""" 1672 return utils.vtk2numpy(self.dataset.GetXCoordinates()) 1673 1674 def y_coordinates(self) -> np.ndarray: 1675 """Return the y-coordinates of the grid.""" 1676 return utils.vtk2numpy(self.dataset.GetYCoordinates()) 1677 1678 def z_coordinates(self) -> np.ndarray: 1679 """Return the z-coordinates of the grid.""" 1680 return utils.vtk2numpy(self.dataset.GetZCoordinates()) 1681 1682 def is_point_visible(self, pid: int) -> bool: 1683 """Return True if point `pid` is visible.""" 1684 return self.dataset.IsPointVisible(pid) 1685 1686 def is_cell_visible(self, cid: int) -> bool: 1687 """Return True if cell `cid` is visible.""" 1688 return self.dataset.IsCellVisible(cid) 1689 1690 def has_blank_points(self) -> bool: 1691 """Return True if the grid has blank points.""" 1692 return self.dataset.HasAnyBlankPoints() 1693 1694 def has_blank_cells(self) -> bool: 1695 """Return True if the grid has blank cells.""" 1696 return self.dataset.HasAnyBlankCells() 1697 1698 def compute_structured_coords(self, x: list) -> dict: 1699 """ 1700 Convenience function computes the structured coordinates for a point `x`. 1701 1702 This method returns a dictionary with keys `ijk`, `pcoords` and `inside`. 1703 The cell is specified by the array `ijk`. 1704 and the parametric coordinates in the cell are specified with `pcoords`. 1705 Value of `inside` is False if the point x is outside of the grid. 1706 """ 1707 ijk = [0, 0, 0] 1708 pcoords = [0., 0., 0.] 1709 inout = self.dataset.ComputeStructuredCoordinates(x, ijk, pcoords) 1710 return {"ijk": np.array(ijk), "pcoords": np.array(pcoords), "inside": bool(inout)} 1711 1712 def compute_pointid(self, ijk: int) -> int: 1713 """Given a location in structured coordinates (i-j-k), return the point id.""" 1714 return self.dataset.ComputePointId(ijk) 1715 1716 def compute_cellid(self, ijk: int) -> int: 1717 """Given a location in structured coordinates (i-j-k), return the cell id.""" 1718 return self.dataset.ComputeCellId(ijk) 1719 1720 def find_point(self, x: list) -> int: 1721 """Given a position `x`, return the id of the closest point.""" 1722 return self.dataset.FindPoint(x) 1723 1724 def find_cell(self, x: list) -> dict: 1725 """Given a position `x`, return the id of the closest cell.""" 1726 cell = vtki.vtkHexagonalPrism() 1727 cellid = vtki.mutable(0) 1728 tol2 = 0.001 # vtki.mutable(0) 1729 subid = vtki.mutable(0) 1730 pcoords = [0.0, 0.0, 0.0] 1731 weights = [0.0, 0.0, 0.0] 1732 res = self.dataset.FindCell(x, cell, cellid, tol2, subid, pcoords, weights) 1733 result = {} 1734 result["cellid"] = cellid 1735 result["subid"] = subid 1736 result["pcoords"] = pcoords 1737 result["weights"] = weights 1738 result["status"] = res 1739 return result 1740 1741 def clone(self, deep=True) -> "RectilinearGrid": 1742 """Return a clone copy of the RectilinearGrid. Alias of `copy()`.""" 1743 if deep: 1744 newrg = vtki.vtkRectilinearGrid() 1745 newrg.CopyStructure(self.dataset) 1746 newrg.CopyAttributes(self.dataset) 1747 newvol = RectilinearGrid(newrg) 1748 else: 1749 newvol = RectilinearGrid(self.dataset) 1750 1751 prop = vtki.vtkProperty() 1752 prop.DeepCopy(self.properties) 1753 newvol.actor.SetProperty(prop) 1754 newvol.properties = prop 1755 newvol.pipeline = utils.OperationNode("clone", parents=[self], c="#bbd0ff", shape="diamond") 1756 return newvol 1757 1758 def bounds(self) -> np.ndarray: 1759 """ 1760 Get the object bounds. 1761 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 1762 """ 1763 # OVERRIDE CommonAlgorithms.bounds() which is too slow 1764 return np.array(self.dataset.GetBounds()) 1765 1766 def isosurface(self, value=None) -> "vedo.Mesh": 1767 """ 1768 Return a `Mesh` isosurface extracted from the object. 1769 1770 Set `value` as single float or list of values to draw the isosurface(s). 1771 """ 1772 scrange = self.dataset.GetScalarRange() 1773 1774 cf = vtki.new("ContourFilter") 1775 cf.UseScalarTreeOn() 1776 cf.SetInputData(self.dataset) 1777 cf.ComputeNormalsOn() 1778 1779 if value is None: 1780 value = (2 * scrange[0] + scrange[1]) / 3.0 1781 # print("automatic isosurface value =", value) 1782 cf.SetValue(0, value) 1783 else: 1784 if utils.is_sequence(value): 1785 cf.SetNumberOfContours(len(value)) 1786 for i, t in enumerate(value): 1787 cf.SetValue(i, t) 1788 else: 1789 cf.SetValue(0, value) 1790 1791 cf.Update() 1792 poly = cf.GetOutput() 1793 1794 out = vedo.mesh.Mesh(poly, c=None).phong() 1795 out.mapper.SetScalarRange(scrange[0], scrange[1]) 1796 1797 out.pipeline = utils.OperationNode( 1798 "isosurface", 1799 parents=[self], 1800 comment=f"#pts {out.dataset.GetNumberOfPoints()}", 1801 c="#4cc9f0:#e9c46a", 1802 ) 1803 return out 1804 1805 def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> "UnstructuredGrid": 1806 """ 1807 Cut the object with the plane defined by a point and a normal. 1808 1809 Arguments: 1810 origin : (list) 1811 the cutting plane goes through this point 1812 normal : (list, str) 1813 normal vector to the cutting plane 1814 """ 1815 strn = str(normal) 1816 if strn == "x": normal = (1, 0, 0) 1817 elif strn == "y": normal = (0, 1, 0) 1818 elif strn == "z": normal = (0, 0, 1) 1819 elif strn == "-x": normal = (-1, 0, 0) 1820 elif strn == "-y": normal = (0, -1, 0) 1821 elif strn == "-z": normal = (0, 0, -1) 1822 plane = vtki.new("Plane") 1823 plane.SetOrigin(origin) 1824 plane.SetNormal(normal) 1825 clipper = vtki.new("ClipDataSet") 1826 clipper.SetInputData(self.dataset) 1827 clipper.SetClipFunction(plane) 1828 clipper.GenerateClipScalarsOff() 1829 clipper.GenerateClippedOutputOff() 1830 clipper.SetValue(0) 1831 clipper.Update() 1832 cout = clipper.GetOutput() 1833 ug = vedo.UnstructuredGrid(cout) 1834 if isinstance(self, UnstructuredGrid): 1835 self._update(cout) 1836 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 1837 return self 1838 ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 1839 return ug 1840 1841 def cut_with_mesh(self, mesh, invert=False, whole_cells=False, on_boundary=False) -> "UnstructuredGrid": 1842 """ 1843 Cut a `RectilinearGrid` with a `Mesh`. 1844 1845 Use `invert` to return cut off part of the input object. 1846 """ 1847 ug = self.dataset 1848 1849 ippd = vtki.new("ImplicitPolyDataDistance") 1850 ippd.SetInput(mesh.dataset) 1851 1852 if whole_cells or on_boundary: 1853 clipper = vtki.new("ExtractGeometry") 1854 clipper.SetInputData(ug) 1855 clipper.SetImplicitFunction(ippd) 1856 clipper.SetExtractInside(not invert) 1857 clipper.SetExtractBoundaryCells(False) 1858 if on_boundary: 1859 clipper.SetExtractBoundaryCells(True) 1860 clipper.SetExtractOnlyBoundaryCells(True) 1861 else: 1862 signed_dists = vtki.vtkFloatArray() 1863 signed_dists.SetNumberOfComponents(1) 1864 signed_dists.SetName("SignedDistance") 1865 for pointId in range(ug.GetNumberOfPoints()): 1866 p = ug.GetPoint(pointId) 1867 signed_dist = ippd.EvaluateFunction(p) 1868 signed_dists.InsertNextValue(signed_dist) 1869 ug.GetPointData().AddArray(signed_dists) 1870 ug.GetPointData().SetActiveScalars("SignedDistance") # NEEDED 1871 clipper = vtki.new("ClipDataSet") 1872 clipper.SetInputData(ug) 1873 clipper.SetInsideOut(not invert) 1874 clipper.SetValue(0.0) 1875 1876 clipper.Update() 1877 1878 out = UnstructuredGrid(clipper.GetOutput()) 1879 out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b") 1880 return out 1881 1882 1883########################################################################## 1884class StructuredGrid(PointAlgorithms, MeshVisual): 1885 """ 1886 Build a structured grid. 1887 """ 1888 1889 def __init__(self, inputobj=None): 1890 """ 1891 A StructuredGrid is a dataset where edges of the hexahedrons are 1892 not necessarily parallel to the coordinate axes. 1893 It can be thought of as a tessellation of a block of 3D space, 1894 similar to a `RectilinearGrid` 1895 except that the cells are not necessarily cubes, they can have different 1896 orientations but are connected in the same way as a `RectilinearGrid`. 1897 1898 Arguments: 1899 inputobj : (vtkStructuredGrid, list, str) 1900 list of points and tet indices, or filename 1901 1902 Example: 1903 ```python 1904 from vedo import * 1905 sgrid = StructuredGrid(dataurl+"structgrid.vts") 1906 print(sgrid) 1907 msh = sgrid.tomesh().lw(1) 1908 show(msh, axes=1, viewup="z") 1909 ``` 1910 1911 ```python 1912 from vedo import * 1913 1914 cx = np.sqrt(np.linspace(100, 400, 10)) 1915 cy = np.linspace(30, 40, 20) 1916 cz = np.linspace(40, 50, 30) 1917 x, y, z = np.meshgrid(cx, cy, cz) 1918 1919 sgrid1 = StructuredGrid([x, y, z]) 1920 sgrid1.cmap("viridis", sgrid1.vertices[:, 0]) 1921 print(sgrid1) 1922 1923 sgrid2 = sgrid1.clone().cut_with_plane(normal=(-1,1,1), origin=[14,34,44]) 1924 msh2 = sgrid2.tomesh(shrink=0.9).lw(1).cmap("viridis") 1925 1926 show( 1927 [["StructuredGrid", sgrid1], ["Shrinked Mesh", msh2]], 1928 N=2, axes=1, viewup="z", 1929 ) 1930 ``` 1931 """ 1932 1933 super().__init__() 1934 1935 self.dataset = None 1936 1937 self.mapper = vtki.new("PolyDataMapper") 1938 self._actor = vtki.vtkActor() 1939 self._actor.retrieve_object = weak_ref_to(self) 1940 self._actor.SetMapper(self.mapper) 1941 self.properties = self._actor.GetProperty() 1942 1943 self.transform = LinearTransform() 1944 self.point_locator = None 1945 self.cell_locator = None 1946 self.line_locator = None 1947 1948 self.name = "StructuredGrid" 1949 self.filename = "" 1950 1951 self.info = {} 1952 self.time = time.time() 1953 1954 ############################### 1955 if inputobj is None: 1956 self.dataset = vtki.vtkStructuredGrid() 1957 1958 elif isinstance(inputobj, vtki.vtkStructuredGrid): 1959 self.dataset = inputobj 1960 1961 elif isinstance(inputobj, StructuredGrid): 1962 self.dataset = inputobj.dataset 1963 1964 elif isinstance(inputobj, str): 1965 if "https://" in inputobj: 1966 inputobj = download(inputobj, verbose=False) 1967 if inputobj.endswith(".vts"): 1968 reader = vtki.new("XMLStructuredGridReader") 1969 else: 1970 reader = vtki.new("StructuredGridReader") 1971 self.filename = inputobj 1972 reader.SetFileName(inputobj) 1973 reader.Update() 1974 self.dataset = reader.GetOutput() 1975 1976 elif utils.is_sequence(inputobj): 1977 self.dataset = vtki.vtkStructuredGrid() 1978 x, y, z = inputobj 1979 xyz = np.vstack(( 1980 x.flatten(order="F"), 1981 y.flatten(order="F"), 1982 z.flatten(order="F")) 1983 ).T 1984 dims = x.shape 1985 self.dataset.SetDimensions(dims) 1986 # self.dataset.SetDimensions(dims[1], dims[0], dims[2]) 1987 vpoints = vtki.vtkPoints() 1988 vpoints.SetData(utils.numpy2vtk(xyz)) 1989 self.dataset.SetPoints(vpoints) 1990 1991 1992 ############################### 1993 if not self.dataset: 1994 vedo.logger.error(f"StructuredGrid: cannot understand input type {type(inputobj)}") 1995 return 1996 1997 self.properties.SetColor(0.352, 0.612, 0.996) # blue7 1998 1999 self.pipeline = utils.OperationNode( 2000 self, comment=f"#tets {self.dataset.GetNumberOfCells()}", c="#9e2a2b" 2001 ) 2002 2003 @property 2004 def actor(self): 2005 """Return the `vtkActor` of the object.""" 2006 gf = vtki.new("GeometryFilter") 2007 gf.SetInputData(self.dataset) 2008 gf.Update() 2009 self.mapper.SetInputData(gf.GetOutput()) 2010 self.mapper.Modified() 2011 return self._actor 2012 2013 @actor.setter 2014 def actor(self, _): 2015 pass 2016 2017 def _update(self, data, reset_locators=False): 2018 self.dataset = data 2019 if reset_locators: 2020 self.cell_locator = None 2021 self.point_locator = None 2022 return self 2023 2024 ################################################################## 2025 def __str__(self): 2026 """Print a summary for the `StructuredGrid` object.""" 2027 module = self.__class__.__module__ 2028 name = self.__class__.__name__ 2029 out = vedo.printc( 2030 f"{module}.{name} at ({hex(self.memory_address())})".ljust(75), 2031 c="c", bold=True, invert=True, return_string=True, 2032 ) 2033 out += "\x1b[0m\x1b[36;1m" 2034 2035 out += "name".ljust(14) + ": " + str(self.name) + "\n" 2036 if self.filename: 2037 out += "filename".ljust(14) + ": " + str(self.filename) + "\n" 2038 2039 out += "dimensions".ljust(14) + ": " + str(self.dataset.GetDimensions()) + "\n" 2040 2041 out += "center".ljust(14) + ": " 2042 out += utils.precision(self.dataset.GetCenter(), 6) + "\n" 2043 2044 bnds = self.bounds() 2045 bx1, bx2 = utils.precision(bnds[0], 3), utils.precision(bnds[1], 3) 2046 by1, by2 = utils.precision(bnds[2], 3), utils.precision(bnds[3], 3) 2047 bz1, bz2 = utils.precision(bnds[4], 3), utils.precision(bnds[5], 3) 2048 out += "bounds".ljust(14) + ":" 2049 out += " x=(" + bx1 + ", " + bx2 + ")," 2050 out += " y=(" + by1 + ", " + by2 + ")," 2051 out += " z=(" + bz1 + ", " + bz2 + ")\n" 2052 2053 out += "memory size".ljust(14) + ": " 2054 out += utils.precision(self.dataset.GetActualMemorySize() / 1024, 2) + " MB\n" 2055 2056 for key in self.pointdata.keys(): 2057 arr = self.pointdata[key] 2058 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 2059 mark_active = "pointdata" 2060 a_scalars = self.dataset.GetPointData().GetScalars() 2061 a_vectors = self.dataset.GetPointData().GetVectors() 2062 a_tensors = self.dataset.GetPointData().GetTensors() 2063 if a_scalars and a_scalars.GetName() == key: 2064 mark_active += " *" 2065 elif a_vectors and a_vectors.GetName() == key: 2066 mark_active += " **" 2067 elif a_tensors and a_tensors.GetName() == key: 2068 mark_active += " ***" 2069 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 2070 out += f", range=({rng})\n" 2071 2072 for key in self.celldata.keys(): 2073 arr = self.celldata[key] 2074 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 2075 mark_active = "celldata" 2076 a_scalars = self.dataset.GetCellData().GetScalars() 2077 a_vectors = self.dataset.GetCellData().GetVectors() 2078 a_tensors = self.dataset.GetCellData().GetTensors() 2079 if a_scalars and a_scalars.GetName() == key: 2080 mark_active += " *" 2081 elif a_vectors and a_vectors.GetName() == key: 2082 mark_active += " **" 2083 elif a_tensors and a_tensors.GetName() == key: 2084 mark_active += " ***" 2085 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 2086 out += f", range=({rng})\n" 2087 2088 for key in self.metadata.keys(): 2089 arr = self.metadata[key] 2090 out += "metadata".ljust(14) + ": " + f'"{key}" ({len(arr)} values)\n' 2091 2092 return out.rstrip() + "\x1b[0m" 2093 2094 def _repr_html_(self): 2095 """ 2096 HTML representation of the StructuredGrid object for Jupyter Notebooks. 2097 2098 Returns: 2099 HTML text with the image and some properties. 2100 """ 2101 import io 2102 import base64 2103 from PIL import Image 2104 2105 library_name = "vedo.grids.StructuredGrid" 2106 help_url = "https://vedo.embl.es/docs/vedo/grids.html#StructuredGrid" 2107 2108 m = self.tomesh().linewidth(1).lighting("off") 2109 arr= m.thumbnail(zoom=1, elevation=-30, azimuth=-30) 2110 2111 im = Image.fromarray(arr) 2112 buffered = io.BytesIO() 2113 im.save(buffered, format="PNG", quality=100) 2114 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 2115 url = "data:image/png;base64," + encoded 2116 image = f"<img src='{url}'></img>" 2117 2118 bounds = "<br/>".join( 2119 [ 2120 utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4) 2121 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 2122 ] 2123 ) 2124 2125 help_text = "" 2126 if self.name: 2127 help_text += f"<b> {self.name}:   </b>" 2128 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 2129 if self.filename: 2130 dots = "" 2131 if len(self.filename) > 30: 2132 dots = "..." 2133 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 2134 2135 pdata = "" 2136 if self.dataset.GetPointData().GetScalars(): 2137 if self.dataset.GetPointData().GetScalars().GetName(): 2138 name = self.dataset.GetPointData().GetScalars().GetName() 2139 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 2140 2141 cdata = "" 2142 if self.dataset.GetCellData().GetScalars(): 2143 if self.dataset.GetCellData().GetScalars().GetName(): 2144 name = self.dataset.GetCellData().GetScalars().GetName() 2145 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 2146 2147 pts = self.vertices 2148 cm = np.mean(pts, axis=0) 2149 2150 all = [ 2151 "<table>", 2152 "<tr>", 2153 "<td>", image, "</td>", 2154 "<td style='text-align: center; vertical-align: center;'><br/>", help_text, 2155 "<table>", 2156 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 2157 "<tr><td><b> center of mass </b></td><td>" + utils.precision(cm,3) + "</td></tr>", 2158 "<tr><td><b> nr. points / cells </b></td><td>" 2159 + str(self.npoints) + " / " + str(self.ncells) + "</td></tr>", 2160 pdata, 2161 cdata, 2162 "</table>", 2163 "</table>", 2164 ] 2165 return "\n".join(all) 2166 2167 def dimensions(self) -> np.ndarray: 2168 """Return the number of points in the x, y and z directions.""" 2169 return np.array(self.dataset.GetDimensions()) 2170 2171 def clone(self, deep=True) -> "StructuredGrid": 2172 """Return a clone copy of the StructuredGrid. Alias of `copy()`.""" 2173 if deep: 2174 newrg = vtki.vtkStructuredGrid() 2175 newrg.CopyStructure(self.dataset) 2176 newrg.CopyAttributes(self.dataset) 2177 newvol = StructuredGrid(newrg) 2178 else: 2179 newvol = StructuredGrid(self.dataset) 2180 2181 prop = vtki.vtkProperty() 2182 prop.DeepCopy(self.properties) 2183 newvol.actor.SetProperty(prop) 2184 newvol.properties = prop 2185 newvol.pipeline = utils.OperationNode("clone", parents=[self], c="#bbd0ff", shape="diamond") 2186 return newvol 2187 2188 def find_point(self, x: list) -> int: 2189 """Given a position `x`, return the id of the closest point.""" 2190 return self.dataset.FindPoint(x) 2191 2192 def find_cell(self, x: list) -> dict: 2193 """Given a position `x`, return the id of the closest cell.""" 2194 cell = vtki.vtkHexagonalPrism() 2195 cellid = vtki.mutable(0) 2196 tol2 = 0.001 # vtki.mutable(0) 2197 subid = vtki.mutable(0) 2198 pcoords = [0.0, 0.0, 0.0] 2199 weights = [0.0, 0.0, 0.0] 2200 res = self.dataset.FindCell(x, cell, cellid, tol2, subid, pcoords, weights) 2201 result = {} 2202 result["cellid"] = cellid 2203 result["subid"] = subid 2204 result["pcoords"] = pcoords 2205 result["weights"] = weights 2206 result["status"] = res 2207 return result 2208 2209 def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> "vedo.UnstructuredGrid": 2210 """ 2211 Cut the object with the plane defined by a point and a normal. 2212 2213 Arguments: 2214 origin : (list) 2215 the cutting plane goes through this point 2216 normal : (list, str) 2217 normal vector to the cutting plane 2218 2219 Returns an `UnstructuredGrid` object. 2220 """ 2221 strn = str(normal) 2222 if strn == "x": normal = (1, 0, 0) 2223 elif strn == "y": normal = (0, 1, 0) 2224 elif strn == "z": normal = (0, 0, 1) 2225 elif strn == "-x": normal = (-1, 0, 0) 2226 elif strn == "-y": normal = (0, -1, 0) 2227 elif strn == "-z": normal = (0, 0, -1) 2228 plane = vtki.new("Plane") 2229 plane.SetOrigin(origin) 2230 plane.SetNormal(normal) 2231 clipper = vtki.new("ClipDataSet") 2232 clipper.SetInputData(self.dataset) 2233 clipper.SetClipFunction(plane) 2234 clipper.GenerateClipScalarsOff() 2235 clipper.GenerateClippedOutputOff() 2236 clipper.SetValue(0) 2237 clipper.Update() 2238 cout = clipper.GetOutput() 2239 ug = vedo.UnstructuredGrid(cout) 2240 if isinstance(self, vedo.UnstructuredGrid): 2241 self._update(cout) 2242 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 2243 return self 2244 ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 2245 return ug 2246 2247 def cut_with_mesh(self, mesh: Mesh, invert=False, whole_cells=False, on_boundary=False) -> "UnstructuredGrid": 2248 """ 2249 Cut a `RectilinearGrid` with a `Mesh`. 2250 2251 Use `invert` to return cut off part of the input object. 2252 2253 Returns an `UnstructuredGrid` object. 2254 """ 2255 ug = self.dataset 2256 2257 ippd = vtki.new("ImplicitPolyDataDistance") 2258 ippd.SetInput(mesh.dataset) 2259 2260 if whole_cells or on_boundary: 2261 clipper = vtki.new("ExtractGeometry") 2262 clipper.SetInputData(ug) 2263 clipper.SetImplicitFunction(ippd) 2264 clipper.SetExtractInside(not invert) 2265 clipper.SetExtractBoundaryCells(False) 2266 if on_boundary: 2267 clipper.SetExtractBoundaryCells(True) 2268 clipper.SetExtractOnlyBoundaryCells(True) 2269 else: 2270 signed_dists = vtki.vtkFloatArray() 2271 signed_dists.SetNumberOfComponents(1) 2272 signed_dists.SetName("SignedDistance") 2273 for pointId in range(ug.GetNumberOfPoints()): 2274 p = ug.GetPoint(pointId) 2275 signed_dist = ippd.EvaluateFunction(p) 2276 signed_dists.InsertNextValue(signed_dist) 2277 ug.GetPointData().AddArray(signed_dists) 2278 ug.GetPointData().SetActiveScalars("SignedDistance") # NEEDED 2279 clipper = vtki.new("ClipDataSet") 2280 clipper.SetInputData(ug) 2281 clipper.SetInsideOut(not invert) 2282 clipper.SetValue(0.0) 2283 2284 clipper.Update() 2285 2286 out = UnstructuredGrid(clipper.GetOutput()) 2287 out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b") 2288 return out 2289 2290 def isosurface(self, value=None) -> "vedo.Mesh": 2291 """ 2292 Return a `Mesh` isosurface extracted from the object. 2293 2294 Set `value` as single float or list of values to draw the isosurface(s). 2295 """ 2296 scrange = self.dataset.GetScalarRange() 2297 2298 cf = vtki.new("ContourFilter") 2299 cf.UseScalarTreeOn() 2300 cf.SetInputData(self.dataset) 2301 cf.ComputeNormalsOn() 2302 2303 if value is None: 2304 value = (2 * scrange[0] + scrange[1]) / 3.0 2305 # print("automatic isosurface value =", value) 2306 cf.SetValue(0, value) 2307 else: 2308 if utils.is_sequence(value): 2309 cf.SetNumberOfContours(len(value)) 2310 for i, t in enumerate(value): 2311 cf.SetValue(i, t) 2312 else: 2313 cf.SetValue(0, value) 2314 2315 cf.Update() 2316 poly = cf.GetOutput() 2317 2318 out = vedo.mesh.Mesh(poly, c=None).phong() 2319 out.mapper.SetScalarRange(scrange[0], scrange[1]) 2320 2321 out.pipeline = utils.OperationNode( 2322 "isosurface", 2323 parents=[self], 2324 comment=f"#pts {out.dataset.GetNumberOfPoints()}", 2325 c="#4cc9f0:#e9c46a", 2326 ) 2327 return out
38class UnstructuredGrid(PointAlgorithms, MeshVisual): 39 """Support for UnstructuredGrid objects.""" 40 41 def __init__(self, inputobj=None): 42 """ 43 Support for UnstructuredGrid objects. 44 45 Arguments: 46 inputobj : (list, vtkUnstructuredGrid, str) 47 A list in the form `[points, cells, celltypes]`, 48 or a vtkUnstructuredGrid object, or a filename 49 50 Celltypes are identified by the following 51 [convention](https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html). 52 """ 53 super().__init__() 54 55 self.dataset = None 56 57 self.mapper = vtki.new("PolyDataMapper") 58 self._actor = vtki.vtkActor() 59 self._actor.retrieve_object = weak_ref_to(self) 60 self._actor.SetMapper(self.mapper) 61 self.properties = self._actor.GetProperty() 62 63 self.transform = LinearTransform() 64 self.point_locator = None 65 self.cell_locator = None 66 self.line_locator = None 67 68 self.name = "UnstructuredGrid" 69 self.filename = "" 70 self.file_size = "" 71 72 self.info = {} 73 self.time = time.time() 74 self.rendered_at = set() 75 76 ###################################### 77 inputtype = str(type(inputobj)) 78 79 if inputobj is None: 80 self.dataset = vtki.vtkUnstructuredGrid() 81 82 elif utils.is_sequence(inputobj): 83 84 pts, cells, celltypes = inputobj 85 assert len(cells) == len(celltypes) 86 87 self.dataset = vtki.vtkUnstructuredGrid() 88 89 if not utils.is_sequence(cells[0]): 90 tets = [] 91 nf = cells[0] + 1 92 for i, cl in enumerate(cells): 93 if i in (nf, 0): 94 k = i + 1 95 nf = cl + k 96 cell = [cells[j + k] for j in range(cl)] 97 tets.append(cell) 98 cells = tets 99 100 # This would fill the points and use those to define orientation 101 vpts = utils.numpy2vtk(pts, dtype=np.float32) 102 points = vtki.vtkPoints() 103 points.SetData(vpts) 104 self.dataset.SetPoints(points) 105 106 # Fill cells 107 # https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html 108 for i, ct in enumerate(celltypes): 109 if ct == vtki.cell_types["VERTEX"]: 110 cell = vtki.vtkVertex() 111 elif ct == vtki.cell_types["POLY_VERTEX"]: 112 cell = vtki.vtkPolyVertex() 113 elif ct == vtki.cell_types["TETRA"]: 114 cell = vtki.vtkTetra() 115 elif ct == vtki.cell_types["WEDGE"]: 116 cell = vtki.vtkWedge() 117 elif ct == vtki.cell_types["LINE"]: 118 cell = vtki.vtkLine() 119 elif ct == vtki.cell_types["POLY_LINE"]: 120 cell = vtki.vtkPolyLine() 121 elif ct == vtki.cell_types["TRIANGLE"]: 122 cell = vtki.vtkTriangle() 123 elif ct == vtki.cell_types["TRIANGLE_STRIP"]: 124 cell = vtki.vtkTriangleStrip() 125 elif ct == vtki.cell_types["POLYGON"]: 126 cell = vtki.vtkPolygon() 127 elif ct == vtki.cell_types["PIXEL"]: 128 cell = vtki.vtkPixel() 129 elif ct == vtki.cell_types["QUAD"]: 130 cell = vtki.vtkQuad() 131 elif ct == vtki.cell_types["VOXEL"]: 132 cell = vtki.vtkVoxel() 133 elif ct == vtki.cell_types["PYRAMID"]: 134 cell = vtki.vtkPyramid() 135 elif ct == vtki.cell_types["HEXAHEDRON"]: 136 cell = vtki.vtkHexahedron() 137 elif ct == vtki.cell_types["HEXAGONAL_PRISM"]: 138 cell = vtki.vtkHexagonalPrism() 139 elif ct == vtki.cell_types["PENTAGONAL_PRISM"]: 140 cell = vtki.vtkPentagonalPrism() 141 elif ct == vtki.cell_types["QUADRATIC_TETRA"]: 142 from vtkmodules.vtkCommonDataModel import vtkQuadraticTetra 143 cell = vtkQuadraticTetra() 144 elif ct == vtki.cell_types["QUADRATIC_HEXAHEDRON"]: 145 from vtkmodules.vtkCommonDataModel import vtkQuadraticHexahedron 146 cell = vtkQuadraticHexahedron() 147 elif ct == vtki.cell_types["QUADRATIC_WEDGE"]: 148 from vtkmodules.vtkCommonDataModel import vtkQuadraticWedge 149 cell = vtkQuadraticWedge() 150 elif ct == vtki.cell_types["QUADRATIC_PYRAMID"]: 151 from vtkmodules.vtkCommonDataModel import vtkQuadraticPyramid 152 cell = vtkQuadraticPyramid() 153 elif ct == vtki.cell_types["QUADRATIC_LINEAR_QUAD"]: 154 from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearQuad 155 cell = vtkQuadraticLinearQuad() 156 elif ct == vtki.cell_types["QUADRATIC_LINEAR_WEDGE"]: 157 from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearWedge 158 cell = vtkQuadraticLinearWedge() 159 elif ct == vtki.cell_types["BIQUADRATIC_QUADRATIC_WEDGE"]: 160 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticQuadraticWedge 161 cell = vtkBiQuadraticQuadraticWedge() 162 elif ct == vtki.cell_types["BIQUADRATIC_QUADRATIC_HEXAHEDRON"]: 163 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticQuadraticHexahedron 164 cell = vtkBiQuadraticQuadraticHexahedron() 165 elif ct == vtki.cell_types["BIQUADRATIC_TRIANGLE"]: 166 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticTriangle 167 cell = vtkBiQuadraticTriangle() 168 elif ct == vtki.cell_types["CUBIC_LINE"]: 169 from vtkmodules.vtkCommonDataModel import vtkCubicLine 170 cell = vtkCubicLine() 171 elif ct == vtki.cell_types["CONVEX_POINT_SET"]: 172 from vtkmodules.vtkCommonDataModel import vtkConvexPointSet 173 cell = vtkConvexPointSet() 174 elif ct == vtki.cell_types["POLYHEDRON"]: 175 from vtkmodules.vtkCommonDataModel import vtkPolyhedron 176 cell = vtkPolyhedron() 177 elif ct == vtki.cell_types["HIGHER_ORDER_TRIANGLE"]: 178 from vtkmodules.vtkCommonDataModel import vtkHigherOrderTriangle 179 cell = vtkHigherOrderTriangle() 180 elif ct == vtki.cell_types["HIGHER_ORDER_QUAD"]: 181 from vtkmodules.vtkCommonDataModel import vtkHigherOrderQuadrilateral 182 cell = vtkHigherOrderQuadrilateral() 183 elif ct == vtki.cell_types["HIGHER_ORDER_TETRAHEDRON"]: 184 from vtkmodules.vtkCommonDataModel import vtkHigherOrderTetra 185 cell = vtkHigherOrderTetra() 186 elif ct == vtki.cell_types["HIGHER_ORDER_WEDGE"]: 187 from vtkmodules.vtkCommonDataModel import vtkHigherOrderWedge 188 cell = vtkHigherOrderWedge() 189 elif ct == vtki.cell_types["HIGHER_ORDER_HEXAHEDRON"]: 190 from vtkmodules.vtkCommonDataModel import vtkHigherOrderHexahedron 191 cell = vtkHigherOrderHexahedron() 192 elif ct == vtki.cell_types["LAGRANGE_CURVE"]: 193 from vtkmodules.vtkCommonDataModel import vtkLagrangeCurve 194 cell = vtkLagrangeCurve() 195 elif ct == vtki.cell_types["LAGRANGE_TRIANGLE"]: 196 from vtkmodules.vtkCommonDataModel import vtkLagrangeTriangle 197 cell = vtkLagrangeTriangle() 198 elif ct == vtki.cell_types["LAGRANGE_QUADRILATERAL"]: 199 from vtkmodules.vtkCommonDataModel import vtkLagrangeQuadrilateral 200 cell = vtkLagrangeQuadrilateral() 201 elif ct == vtki.cell_types["LAGRANGE_TETRAHEDRON"]: 202 from vtkmodules.vtkCommonDataModel import vtkLagrangeTetra 203 cell = vtkLagrangeTetra() 204 elif ct == vtki.cell_types["LAGRANGE_HEXAHEDRON"]: 205 from vtkmodules.vtkCommonDataModel import vtkLagrangeHexahedron 206 cell = vtkLagrangeHexahedron() 207 elif ct == vtki.cell_types["LAGRANGE_WEDGE"]: 208 from vtkmodules.vtkCommonDataModel import vtkLagrangeWedge 209 cell = vtkLagrangeWedge() 210 elif ct == vtki.cell_types["BEZIER_CURVE"]: 211 from vtkmodules.vtkCommonDataModel import vtkBezierCurve 212 cell = vtkBezierCurve() 213 elif ct == vtki.cell_types["BEZIER_TRIANGLE"]: 214 from vtkmodules.vtkCommonDataModel import vtkBezierTriangle 215 cell = vtkBezierTriangle() 216 elif ct == vtki.cell_types["BEZIER_QUADRILATERAL"]: 217 from vtkmodules.vtkCommonDataModel import vtkBezierQuadrilateral 218 cell = vtkBezierQuadrilateral() 219 elif ct == vtki.cell_types["BEZIER_TETRAHEDRON"]: 220 from vtkmodules.vtkCommonDataModel import vtkBezierTetra 221 cell = vtkBezierTetra() 222 elif ct == vtki.cell_types["BEZIER_HEXAHEDRON"]: 223 from vtkmodules.vtkCommonDataModel import vtkBezierHexahedron 224 cell = vtkBezierHexahedron() 225 elif ct == vtki.cell_types["BEZIER_WEDGE"]: 226 from vtkmodules.vtkCommonDataModel import vtkBezierWedge 227 cell = vtkBezierWedge() 228 else: 229 vedo.logger.error( 230 f"UnstructuredGrid: cell type {ct} not supported. Skip.") 231 continue 232 233 cpids = cell.GetPointIds() 234 cell_conn = cells[i] 235 for j, pid in enumerate(cell_conn): 236 cpids.SetId(j, pid) 237 self.dataset.InsertNextCell(ct, cpids) 238 239 elif "UnstructuredGrid" in inputtype: 240 self.dataset = inputobj 241 242 elif isinstance(inputobj, str): 243 if "https://" in inputobj: 244 inputobj = download(inputobj, verbose=False) 245 self.filename = inputobj 246 if inputobj.endswith(".vtu"): 247 reader = vtki.new("XMLUnstructuredGridReader") 248 else: 249 reader = vtki.new("UnstructuredGridReader") 250 self.filename = inputobj 251 reader.SetFileName(inputobj) 252 reader.Update() 253 self.dataset = reader.GetOutput() 254 255 else: 256 # this converts other types of vtk objects to UnstructuredGrid 257 apf = vtki.new("AppendFilter") 258 try: 259 apf.AddInputData(inputobj) 260 except TypeError: 261 apf.AddInputData(inputobj.dataset) 262 apf.Update() 263 self.dataset = apf.GetOutput() 264 265 self.properties.SetColor(0.89, 0.455, 0.671) # pink7 266 267 self.pipeline = utils.OperationNode( 268 self, comment=f"#cells {self.dataset.GetNumberOfCells()}", c="#4cc9f0" 269 ) 270 271 # ------------------------------------------------------------------ 272 def __str__(self): 273 """Print a string summary of the `UnstructuredGrid` object.""" 274 module = self.__class__.__module__ 275 name = self.__class__.__name__ 276 out = vedo.printc( 277 f"{module}.{name} at ({hex(self.memory_address())})".ljust(75), 278 c="m", bold=True, invert=True, return_string=True, 279 ) 280 out += "\x1b[0m\u001b[35m" 281 282 out += "nr. of verts".ljust(14) + ": " + str(self.npoints) + "\n" 283 out += "nr. of cells".ljust(14) + ": " + str(self.ncells) + "\n" 284 ct_arr = np.unique(self.cell_types_array) 285 cnames = [k for k, v in vtki.cell_types.items() if v in ct_arr] 286 out += "cell types".ljust(14) + ": " + str(cnames) + "\n" 287 288 if self.npoints: 289 out+="size".ljust(14)+ ": average=" + utils.precision(self.average_size(),6) 290 out+=", diagonal="+ utils.precision(self.diagonal_size(), 6)+ "\n" 291 out+="center of mass".ljust(14) + ": " + utils.precision(self.center_of_mass(),6)+"\n" 292 293 bnds = self.bounds() 294 bx1, bx2 = utils.precision(bnds[0], 3), utils.precision(bnds[1], 3) 295 by1, by2 = utils.precision(bnds[2], 3), utils.precision(bnds[3], 3) 296 bz1, bz2 = utils.precision(bnds[4], 3), utils.precision(bnds[5], 3) 297 out += "bounds".ljust(14) + ":" 298 out += " x=(" + bx1 + ", " + bx2 + ")," 299 out += " y=(" + by1 + ", " + by2 + ")," 300 out += " z=(" + bz1 + ", " + bz2 + ")\n" 301 302 for key in self.pointdata.keys(): 303 arr = self.pointdata[key] 304 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 305 mark_active = "pointdata" 306 a_scalars = self.dataset.GetPointData().GetScalars() 307 a_vectors = self.dataset.GetPointData().GetVectors() 308 a_tensors = self.dataset.GetPointData().GetTensors() 309 if a_scalars and a_scalars.GetName() == key: 310 mark_active += " *" 311 elif a_vectors and a_vectors.GetName() == key: 312 mark_active += " **" 313 elif a_tensors and a_tensors.GetName() == key: 314 mark_active += " ***" 315 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 316 out += f", range=({rng})\n" 317 318 for key in self.celldata.keys(): 319 arr = self.celldata[key] 320 rng = utils.precision(arr.min(), 3) + ", " + utils.precision(arr.max(), 3) 321 mark_active = "celldata" 322 a_scalars = self.dataset.GetCellData().GetScalars() 323 a_vectors = self.dataset.GetCellData().GetVectors() 324 a_tensors = self.dataset.GetCellData().GetTensors() 325 if a_scalars and a_scalars.GetName() == key: 326 mark_active += " *" 327 elif a_vectors and a_vectors.GetName() == key: 328 mark_active += " **" 329 elif a_tensors and a_tensors.GetName() == key: 330 mark_active += " ***" 331 out += mark_active.ljust(14) + f': "{key}" ({arr.dtype}), ndim={arr.ndim}' 332 out += f", range=({rng})\n" 333 334 for key in self.metadata.keys(): 335 arr = self.metadata[key] 336 out += "metadata".ljust(14) + ": " + f'"{key}" ({len(arr)} values)\n' 337 338 return out.rstrip() + "\x1b[0m" 339 340 def _repr_html_(self): 341 """ 342 HTML representation of the UnstructuredGrid object for Jupyter Notebooks. 343 344 Returns: 345 HTML text with the image and some properties. 346 """ 347 import io 348 import base64 349 from PIL import Image 350 351 library_name = "vedo.grids.UnstructuredGrid" 352 help_url = "https://vedo.embl.es/docs/vedo/grids.html#UnstructuredGrid" 353 354 arr = self.thumbnail() 355 im = Image.fromarray(arr) 356 buffered = io.BytesIO() 357 im.save(buffered, format="PNG", quality=100) 358 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 359 url = "data:image/png;base64," + encoded 360 image = f"<img src='{url}'></img>" 361 362 bounds = "<br/>".join( 363 [ 364 utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4) 365 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 366 ] 367 ) 368 369 help_text = "" 370 if self.name: 371 help_text += f"<b> {self.name}:   </b>" 372 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 373 if self.filename: 374 dots = "" 375 if len(self.filename) > 30: 376 dots = "..." 377 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 378 379 pdata = "" 380 if self.dataset.GetPointData().GetScalars(): 381 if self.dataset.GetPointData().GetScalars().GetName(): 382 name = self.dataset.GetPointData().GetScalars().GetName() 383 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 384 385 cdata = "" 386 if self.dataset.GetCellData().GetScalars(): 387 if self.dataset.GetCellData().GetScalars().GetName(): 388 name = self.dataset.GetCellData().GetScalars().GetName() 389 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 390 391 pts = self.vertices 392 cm = np.mean(pts, axis=0) 393 394 all = [ 395 "<table>", 396 "<tr>", 397 "<td>", image, "</td>", 398 "<td style='text-align: center; vertical-align: center;'><br/>", help_text, 399 "<table>", 400 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 401 "<tr><td><b> center of mass </b></td><td>" + utils.precision(cm,3) + "</td></tr>", 402 # "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>", 403 "<tr><td><b> nr. points / cells </b></td><td>" 404 + str(self.npoints) + " / " + str(self.ncells) + "</td></tr>", 405 pdata, 406 cdata, 407 "</table>", 408 "</table>", 409 ] 410 return "\n".join(all) 411 412 @property 413 def actor(self): 414 """Return the `vtkActor` of the object.""" 415 # print("building actor") 416 gf = vtki.new("GeometryFilter") 417 gf.SetInputData(self.dataset) 418 gf.Update() 419 out = gf.GetOutput() 420 self.mapper.SetInputData(out) 421 self.mapper.Modified() 422 return self._actor 423 424 @actor.setter 425 def actor(self, _): 426 pass 427 428 def _update(self, data, reset_locators=False): 429 self.dataset = data 430 if reset_locators: 431 self.cell_locator = None 432 self.point_locator = None 433 return self 434 435 def merge(self, *others) -> Self: 436 """ 437 Merge multiple datasets into one single `UnstrcturedGrid`. 438 """ 439 apf = vtki.new("AppendFilter") 440 for o in others: 441 if isinstance(o, UnstructuredGrid): 442 apf.AddInputData(o.dataset) 443 elif isinstance(o, vtki.vtkUnstructuredGrid): 444 apf.AddInputData(o) 445 else: 446 vedo.printc("Error: cannot merge type", type(o), c="r") 447 apf.Update() 448 self._update(apf.GetOutput()) 449 self.pipeline = utils.OperationNode( 450 "merge", parents=[self, *others], c="#9e2a2b" 451 ) 452 return self 453 454 def copy(self, deep=True) -> "UnstructuredGrid": 455 """Return a copy of the object. Alias of `clone()`.""" 456 return self.clone(deep=deep) 457 458 def clone(self, deep=True) -> "UnstructuredGrid": 459 """Clone the UnstructuredGrid object to yield an exact copy.""" 460 ug = vtki.vtkUnstructuredGrid() 461 if deep: 462 ug.DeepCopy(self.dataset) 463 else: 464 ug.ShallowCopy(self.dataset) 465 if isinstance(self, vedo.UnstructuredGrid): 466 cloned = vedo.UnstructuredGrid(ug) 467 else: 468 cloned = vedo.TetMesh(ug) 469 470 cloned.copy_properties_from(self) 471 472 cloned.pipeline = utils.OperationNode( 473 "clone", parents=[self], shape="diamond", c="#bbe1ed" 474 ) 475 return cloned 476 477 def bounds(self) -> np.ndarray: 478 """ 479 Get the object bounds. 480 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 481 """ 482 # OVERRIDE CommonAlgorithms.bounds() which is too slow 483 return np.array(self.dataset.GetBounds()) 484 485 def threshold(self, name=None, above=None, below=None, on="cells") -> Self: 486 """ 487 Threshold the tetrahedral mesh by a cell scalar value. 488 Reduce to only tets which satisfy the threshold limits. 489 490 - if `above = below` will only select tets with that specific value. 491 - if `above > below` selection range is flipped. 492 493 Set keyword "on" to either "cells" or "points". 494 """ 495 th = vtki.new("Threshold") 496 th.SetInputData(self.dataset) 497 498 if name is None: 499 if self.celldata.keys(): 500 name = self.celldata.keys()[0] 501 th.SetInputArrayToProcess(0, 0, 0, 1, name) 502 elif self.pointdata.keys(): 503 name = self.pointdata.keys()[0] 504 th.SetInputArrayToProcess(0, 0, 0, 0, name) 505 if name is None: 506 vedo.logger.warning("cannot find active array. Skip.") 507 return self 508 else: 509 if on.startswith("c"): 510 th.SetInputArrayToProcess(0, 0, 0, 1, name) 511 else: 512 th.SetInputArrayToProcess(0, 0, 0, 0, name) 513 514 if above is not None: 515 th.SetLowerThreshold(above) 516 517 if below is not None: 518 th.SetUpperThreshold(below) 519 520 th.Update() 521 return self._update(th.GetOutput()) 522 523 def isosurface(self, value=None, flying_edges=False) -> "vedo.mesh.Mesh": 524 """ 525 Return an `Mesh` isosurface extracted from the `Volume` object. 526 527 Set `value` as single float or list of values to draw the isosurface(s). 528 Use flying_edges for faster results (but sometimes can interfere with `smooth()`). 529 530 Examples: 531 - [isosurfaces1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces1.py) 532 533 ![](https://vedo.embl.es/images/volumetric/isosurfaces.png) 534 """ 535 scrange = self.dataset.GetScalarRange() 536 537 if flying_edges: 538 cf = vtki.new("FlyingEdges3D") 539 cf.InterpolateAttributesOn() 540 else: 541 cf = vtki.new("ContourFilter") 542 cf.UseScalarTreeOn() 543 544 cf.SetInputData(self.dataset) 545 cf.ComputeNormalsOn() 546 547 if utils.is_sequence(value): 548 cf.SetNumberOfContours(len(value)) 549 for i, t in enumerate(value): 550 cf.SetValue(i, t) 551 else: 552 if value is None: 553 value = (2 * scrange[0] + scrange[1]) / 3.0 554 # print("automatic isosurface value =", value) 555 cf.SetValue(0, value) 556 557 cf.Update() 558 poly = cf.GetOutput() 559 560 out = vedo.mesh.Mesh(poly, c=None).flat() 561 out.mapper.SetScalarRange(scrange[0], scrange[1]) 562 563 out.pipeline = utils.OperationNode( 564 "isosurface", 565 parents=[self], 566 comment=f"#pts {out.dataset.GetNumberOfPoints()}", 567 c="#4cc9f0:#e9c46a", 568 ) 569 return out 570 571 def shrink(self, fraction=0.8) -> Self: 572 """ 573 Shrink the individual cells. 574 575 ![](https://vedo.embl.es/images/feats/shrink_hex.png) 576 """ 577 sf = vtki.new("ShrinkFilter") 578 sf.SetInputData(self.dataset) 579 sf.SetShrinkFactor(fraction) 580 sf.Update() 581 out = sf.GetOutput() 582 self._update(out) 583 self.pipeline = utils.OperationNode( 584 "shrink", comment=f"by {fraction}", parents=[self], c="#9e2a2b" 585 ) 586 return self 587 588 def tomesh(self, fill=False, shrink=1.0) -> "vedo.mesh.Mesh": 589 """ 590 Build a polygonal `Mesh` from the current object. 591 592 If `fill=True`, the interior faces of all the cells are created. 593 (setting a `shrink` value slightly smaller than the default 1.0 594 can avoid flickering due to internal adjacent faces). 595 596 If `fill=False`, only the boundary faces will be generated. 597 """ 598 gf = vtki.new("GeometryFilter") 599 if fill: 600 sf = vtki.new("ShrinkFilter") 601 sf.SetInputData(self.dataset) 602 sf.SetShrinkFactor(shrink) 603 sf.Update() 604 gf.SetInputData(sf.GetOutput()) 605 gf.Update() 606 poly = gf.GetOutput() 607 else: 608 gf.SetInputData(self.dataset) 609 gf.Update() 610 poly = gf.GetOutput() 611 612 msh = vedo.mesh.Mesh(poly) 613 msh.copy_properties_from(self) 614 msh.pipeline = utils.OperationNode( 615 "tomesh", parents=[self], comment=f"fill={fill}", c="#9e2a2b:#e9c46a" 616 ) 617 return msh 618 619 @property 620 def cell_types_array(self): 621 """Return the list of cell types in the dataset.""" 622 uarr = self.dataset.GetCellTypesArray() 623 return utils.vtk2numpy(uarr) 624 625 def extract_cells_by_type(self, ctype) -> "UnstructuredGrid": 626 """Extract a specific cell type and return a new `UnstructuredGrid`.""" 627 if isinstance(ctype, str): 628 try: 629 ctype = vtki.cell_types[ctype.upper()] 630 except KeyError: 631 vedo.logger.error(f"extract_cells_by_type: cell type {ctype} does not exist. Skip.") 632 return self 633 uarr = self.dataset.GetCellTypesArray() 634 ctarrtyp = np.where(utils.vtk2numpy(uarr) == ctype)[0] 635 uarrtyp = utils.numpy2vtk(ctarrtyp, deep=False, dtype="id") 636 selection_node = vtki.new("SelectionNode") 637 selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL) 638 selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES) 639 selection_node.SetSelectionList(uarrtyp) 640 selection = vtki.new("Selection") 641 selection.AddNode(selection_node) 642 es = vtki.new("ExtractSelection") 643 es.SetInputData(0, self.dataset) 644 es.SetInputData(1, selection) 645 es.Update() 646 647 ug = UnstructuredGrid(es.GetOutput()) 648 ug.pipeline = utils.OperationNode( 649 "extract_cell_type", comment=f"type {ctype}", c="#edabab", parents=[self] 650 ) 651 return ug 652 653 def extract_cells_by_id(self, idlist, use_point_ids=False) -> "UnstructuredGrid": 654 """Return a new `UnstructuredGrid` composed of the specified subset of indices.""" 655 selection_node = vtki.new("SelectionNode") 656 if use_point_ids: 657 selection_node.SetFieldType(vtki.get_class("SelectionNode").POINT) 658 contcells = vtki.get_class("SelectionNode").CONTAINING_CELLS() 659 selection_node.GetProperties().Set(contcells, 1) 660 else: 661 selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL) 662 selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES) 663 vidlist = utils.numpy2vtk(idlist, dtype="id") 664 selection_node.SetSelectionList(vidlist) 665 selection = vtki.new("Selection") 666 selection.AddNode(selection_node) 667 es = vtki.new("ExtractSelection") 668 es.SetInputData(0, self) 669 es.SetInputData(1, selection) 670 es.Update() 671 672 ug = UnstructuredGrid(es.GetOutput()) 673 pr = vtki.vtkProperty() 674 pr.DeepCopy(self.properties) 675 ug.actor.SetProperty(pr) 676 ug.properties = pr 677 678 ug.mapper.SetLookupTable(utils.ctf2lut(self)) 679 ug.pipeline = utils.OperationNode( 680 "extract_cells_by_id", 681 parents=[self], 682 comment=f"#cells {self.dataset.GetNumberOfCells()}", 683 c="#9e2a2b", 684 ) 685 return ug 686 687 def find_cell(self, p: list) -> int: 688 """Locate the cell that contains a point and return the cell ID.""" 689 if self.cell_locator is None: 690 self.cell_locator = vtki.new("CellLocator") 691 self.cell_locator.SetDataSet(self.dataset) 692 self.cell_locator.BuildLocator() 693 cid = self.cell_locator.FindCell(p) 694 return cid 695 696 def clean(self) -> Self: 697 """ 698 Cleanup unused points and empty cells 699 """ 700 cl = vtki.new("StaticCleanUnstructuredGrid") 701 cl.SetInputData(self.dataset) 702 cl.RemoveUnusedPointsOn() 703 cl.ProduceMergeMapOff() 704 cl.AveragePointDataOff() 705 cl.Update() 706 707 self._update(cl.GetOutput()) 708 self.pipeline = utils.OperationNode( 709 "clean", 710 parents=[self], 711 comment=f"#cells {self.dataset.GetNumberOfCells()}", 712 c="#9e2a2b", 713 ) 714 return self 715 716 def extract_cells_on_plane(self, origin: tuple, normal: tuple) -> Self: 717 """ 718 Extract cells that are lying of the specified surface. 719 """ 720 bf = vtki.new("3DLinearGridCrinkleExtractor") 721 bf.SetInputData(self.dataset) 722 bf.CopyPointDataOn() 723 bf.CopyCellDataOn() 724 bf.RemoveUnusedPointsOff() 725 726 plane = vtki.new("Plane") 727 plane.SetOrigin(origin) 728 plane.SetNormal(normal) 729 bf.SetImplicitFunction(plane) 730 bf.Update() 731 732 self._update(bf.GetOutput(), reset_locators=False) 733 self.pipeline = utils.OperationNode( 734 "extract_cells_on_plane", 735 parents=[self], 736 comment=f"#cells {self.dataset.GetNumberOfCells()}", 737 c="#9e2a2b", 738 ) 739 return self 740 741 def extract_cells_on_sphere(self, center: tuple, radius: tuple) -> Self: 742 """ 743 Extract cells that are lying of the specified surface. 744 """ 745 bf = vtki.new("3DLinearGridCrinkleExtractor") 746 bf.SetInputData(self.dataset) 747 bf.CopyPointDataOn() 748 bf.CopyCellDataOn() 749 bf.RemoveUnusedPointsOff() 750 751 sph = vtki.new("Sphere") 752 sph.SetRadius(radius) 753 sph.SetCenter(center) 754 bf.SetImplicitFunction(sph) 755 bf.Update() 756 757 self._update(bf.GetOutput()) 758 self.pipeline = utils.OperationNode( 759 "extract_cells_on_sphere", 760 parents=[self], 761 comment=f"#cells {self.dataset.GetNumberOfCells()}", 762 c="#9e2a2b", 763 ) 764 return self 765 766 def extract_cells_on_cylinder(self, center: tuple, axis: tuple, radius: float) -> Self: 767 """ 768 Extract cells that are lying of the specified surface. 769 """ 770 bf = vtki.new("3DLinearGridCrinkleExtractor") 771 bf.SetInputData(self.dataset) 772 bf.CopyPointDataOn() 773 bf.CopyCellDataOn() 774 bf.RemoveUnusedPointsOff() 775 776 cyl = vtki.new("Cylinder") 777 cyl.SetRadius(radius) 778 cyl.SetCenter(center) 779 cyl.SetAxis(axis) 780 bf.SetImplicitFunction(cyl) 781 bf.Update() 782 783 self.pipeline = utils.OperationNode( 784 "extract_cells_on_cylinder", 785 parents=[self], 786 comment=f"#cells {self.dataset.GetNumberOfCells()}", 787 c="#9e2a2b", 788 ) 789 self._update(bf.GetOutput()) 790 return self 791 792 def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> "UnstructuredGrid": 793 """ 794 Cut the object with the plane defined by a point and a normal. 795 796 Arguments: 797 origin : (list) 798 the cutting plane goes through this point 799 normal : (list, str) 800 normal vector to the cutting plane 801 """ 802 # if isinstance(self, vedo.Volume): 803 # raise RuntimeError("cut_with_plane() is not applicable to Volume objects.") 804 805 strn = str(normal) 806 if strn == "x": normal = (1, 0, 0) 807 elif strn == "y": normal = (0, 1, 0) 808 elif strn == "z": normal = (0, 0, 1) 809 elif strn == "-x": normal = (-1, 0, 0) 810 elif strn == "-y": normal = (0, -1, 0) 811 elif strn == "-z": normal = (0, 0, -1) 812 plane = vtki.new("Plane") 813 plane.SetOrigin(origin) 814 plane.SetNormal(normal) 815 clipper = vtki.new("ClipDataSet") 816 clipper.SetInputData(self.dataset) 817 clipper.SetClipFunction(plane) 818 clipper.GenerateClipScalarsOff() 819 clipper.GenerateClippedOutputOff() 820 clipper.SetValue(0) 821 clipper.Update() 822 cout = clipper.GetOutput() 823 824 if isinstance(cout, vtki.vtkUnstructuredGrid): 825 ug = vedo.UnstructuredGrid(cout) 826 if isinstance(self, vedo.UnstructuredGrid): 827 self._update(cout) 828 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 829 return self 830 ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 831 return ug 832 833 else: 834 self._update(cout) 835 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b") 836 return self 837 838 def cut_with_box(self, box: Any) -> "UnstructuredGrid": 839 """ 840 Cut the grid with the specified bounding box. 841 842 Parameter box has format [xmin, xmax, ymin, ymax, zmin, zmax]. 843 If an object is passed, its bounding box are used. 844 845 This method always returns a TetMesh object. 846 847 Example: 848 ```python 849 from vedo import * 850 tmesh = TetMesh(dataurl+'limb_ugrid.vtk') 851 tmesh.color('rainbow') 852 cu = Cube(side=500).x(500) # any Mesh works 853 tmesh.cut_with_box(cu).show(axes=1) 854 ``` 855 856 ![](https://vedo.embl.es/images/feats/tet_cut_box.png) 857 """ 858 bc = vtki.new("BoxClipDataSet") 859 bc.SetInputData(self.dataset) 860 try: 861 boxb = box.bounds() 862 except AttributeError: 863 boxb = box 864 865 bc.SetBoxClip(*boxb) 866 bc.Update() 867 cout = bc.GetOutput() 868 869 # output of vtkBoxClipDataSet is always tetrahedrons 870 tm = vedo.TetMesh(cout) 871 tm.pipeline = utils.OperationNode("cut_with_box", parents=[self], c="#9e2a2b") 872 return tm 873 874 def cut_with_mesh(self, mesh: Mesh, invert=False, whole_cells=False, on_boundary=False) -> "UnstructuredGrid": 875 """ 876 Cut a `UnstructuredGrid` or `TetMesh` with a `Mesh`. 877 878 Use `invert` to return cut off part of the input object. 879 """ 880 ug = self.dataset 881 882 ippd = vtki.new("ImplicitPolyDataDistance") 883 ippd.SetInput(mesh.dataset) 884 885 if whole_cells or on_boundary: 886 clipper = vtki.new("ExtractGeometry") 887 clipper.SetInputData(ug) 888 clipper.SetImplicitFunction(ippd) 889 clipper.SetExtractInside(not invert) 890 clipper.SetExtractBoundaryCells(False) 891 if on_boundary: 892 clipper.SetExtractBoundaryCells(True) 893 clipper.SetExtractOnlyBoundaryCells(True) 894 else: 895 signed_dists = vtki.vtkFloatArray() 896 signed_dists.SetNumberOfComponents(1) 897 signed_dists.SetName("SignedDistance") 898 for pointId in range(ug.GetNumberOfPoints()): 899 p = ug.GetPoint(pointId) 900 signed_dist = ippd.EvaluateFunction(p) 901 signed_dists.InsertNextValue(signed_dist) 902 ug.GetPointData().AddArray(signed_dists) 903 ug.GetPointData().SetActiveScalars("SignedDistance") # NEEDED 904 clipper = vtki.new("ClipDataSet") 905 clipper.SetInputData(ug) 906 clipper.SetInsideOut(not invert) 907 clipper.SetValue(0.0) 908 909 clipper.Update() 910 911 out = vedo.UnstructuredGrid(clipper.GetOutput()) 912 out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b") 913 return out
Support for UnstructuredGrid objects.
41 def __init__(self, inputobj=None): 42 """ 43 Support for UnstructuredGrid objects. 44 45 Arguments: 46 inputobj : (list, vtkUnstructuredGrid, str) 47 A list in the form `[points, cells, celltypes]`, 48 or a vtkUnstructuredGrid object, or a filename 49 50 Celltypes are identified by the following 51 [convention](https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html). 52 """ 53 super().__init__() 54 55 self.dataset = None 56 57 self.mapper = vtki.new("PolyDataMapper") 58 self._actor = vtki.vtkActor() 59 self._actor.retrieve_object = weak_ref_to(self) 60 self._actor.SetMapper(self.mapper) 61 self.properties = self._actor.GetProperty() 62 63 self.transform = LinearTransform() 64 self.point_locator = None 65 self.cell_locator = None 66 self.line_locator = None 67 68 self.name = "UnstructuredGrid" 69 self.filename = "" 70 self.file_size = "" 71 72 self.info = {} 73 self.time = time.time() 74 self.rendered_at = set() 75 76 ###################################### 77 inputtype = str(type(inputobj)) 78 79 if inputobj is None: 80 self.dataset = vtki.vtkUnstructuredGrid() 81 82 elif utils.is_sequence(inputobj): 83 84 pts, cells, celltypes = inputobj 85 assert len(cells) == len(celltypes) 86 87 self.dataset = vtki.vtkUnstructuredGrid() 88 89 if not utils.is_sequence(cells[0]): 90 tets = [] 91 nf = cells[0] + 1 92 for i, cl in enumerate(cells): 93 if i in (nf, 0): 94 k = i + 1 95 nf = cl + k 96 cell = [cells[j + k] for j in range(cl)] 97 tets.append(cell) 98 cells = tets 99 100 # This would fill the points and use those to define orientation 101 vpts = utils.numpy2vtk(pts, dtype=np.float32) 102 points = vtki.vtkPoints() 103 points.SetData(vpts) 104 self.dataset.SetPoints(points) 105 106 # Fill cells 107 # https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html 108 for i, ct in enumerate(celltypes): 109 if ct == vtki.cell_types["VERTEX"]: 110 cell = vtki.vtkVertex() 111 elif ct == vtki.cell_types["POLY_VERTEX"]: 112 cell = vtki.vtkPolyVertex() 113 elif ct == vtki.cell_types["TETRA"]: 114 cell = vtki.vtkTetra() 115 elif ct == vtki.cell_types["WEDGE"]: 116 cell = vtki.vtkWedge() 117 elif ct == vtki.cell_types["LINE"]: 118 cell = vtki.vtkLine() 119 elif ct == vtki.cell_types["POLY_LINE"]: 120 cell = vtki.vtkPolyLine() 121 elif ct == vtki.cell_types["TRIANGLE"]: 122 cell = vtki.vtkTriangle() 123 elif ct == vtki.cell_types["TRIANGLE_STRIP"]: 124 cell = vtki.vtkTriangleStrip() 125 elif ct == vtki.cell_types["POLYGON"]: 126 cell = vtki.vtkPolygon() 127 elif ct == vtki.cell_types["PIXEL"]: 128 cell = vtki.vtkPixel() 129 elif ct == vtki.cell_types["QUAD"]: 130 cell = vtki.vtkQuad() 131 elif ct == vtki.cell_types["VOXEL"]: 132 cell = vtki.vtkVoxel() 133 elif ct == vtki.cell_types["PYRAMID"]: 134 cell = vtki.vtkPyramid() 135 elif ct == vtki.cell_types["HEXAHEDRON"]: 136 cell = vtki.vtkHexahedron() 137 elif ct == vtki.cell_types["HEXAGONAL_PRISM"]: 138 cell = vtki.vtkHexagonalPrism() 139 elif ct == vtki.cell_types["PENTAGONAL_PRISM"]: 140 cell = vtki.vtkPentagonalPrism() 141 elif ct == vtki.cell_types["QUADRATIC_TETRA"]: 142 from vtkmodules.vtkCommonDataModel import vtkQuadraticTetra 143 cell = vtkQuadraticTetra() 144 elif ct == vtki.cell_types["QUADRATIC_HEXAHEDRON"]: 145 from vtkmodules.vtkCommonDataModel import vtkQuadraticHexahedron 146 cell = vtkQuadraticHexahedron() 147 elif ct == vtki.cell_types["QUADRATIC_WEDGE"]: 148 from vtkmodules.vtkCommonDataModel import vtkQuadraticWedge 149 cell = vtkQuadraticWedge() 150 elif ct == vtki.cell_types["QUADRATIC_PYRAMID"]: 151 from vtkmodules.vtkCommonDataModel import vtkQuadraticPyramid 152 cell = vtkQuadraticPyramid() 153 elif ct == vtki.cell_types["QUADRATIC_LINEAR_QUAD"]: 154 from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearQuad 155 cell = vtkQuadraticLinearQuad() 156 elif ct == vtki.cell_types["QUADRATIC_LINEAR_WEDGE"]: 157 from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearWedge 158 cell = vtkQuadraticLinearWedge() 159 elif ct == vtki.cell_types["BIQUADRATIC_QUADRATIC_WEDGE"]: 160 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticQuadraticWedge 161 cell = vtkBiQuadraticQuadraticWedge() 162 elif ct == vtki.cell_types["BIQUADRATIC_QUADRATIC_HEXAHEDRON"]: 163 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticQuadraticHexahedron 164 cell = vtkBiQuadraticQuadraticHexahedron() 165 elif ct == vtki.cell_types["BIQUADRATIC_TRIANGLE"]: 166 from vtkmodules.vtkCommonDataModel import vtkBiQuadraticTriangle 167 cell = vtkBiQuadraticTriangle() 168 elif ct == vtki.cell_types["CUBIC_LINE"]: 169 from vtkmodules.vtkCommonDataModel import vtkCubicLine 170 cell = vtkCubicLine() 171 elif ct == vtki.cell_types["CONVEX_POINT_SET"]: 172 from vtkmodules.vtkCommonDataModel import vtkConvexPointSet 173 cell = vtkConvexPointSet() 174 elif ct == vtki.cell_types["POLYHEDRON"]: 175 from vtkmodules.vtkCommonDataModel import vtkPolyhedron 176 cell = vtkPolyhedron() 177 elif ct == vtki.cell_types["HIGHER_ORDER_TRIANGLE"]: 178 from vtkmodules.vtkCommonDataModel import vtkHigherOrderTriangle 179 cell = vtkHigherOrderTriangle() 180 elif ct == vtki.cell_types["HIGHER_ORDER_QUAD"]: 181 from vtkmodules.vtkCommonDataModel import vtkHigherOrderQuadrilateral 182 cell = vtkHigherOrderQuadrilateral() 183 elif ct == vtki.cell_types["HIGHER_ORDER_TETRAHEDRON"]: 184 from vtkmodules.vtkCommonDataModel import vtkHigherOrderTetra 185 cell = vtkHigherOrderTetra() 186 elif ct == vtki.cell_types["HIGHER_ORDER_WEDGE"]: 187 from vtkmodules.vtkCommonDataModel import vtkHigherOrderWedge 188 cell = vtkHigherOrderWedge() 189 elif ct == vtki.cell_types["HIGHER_ORDER_HEXAHEDRON"]: 190 from vtkmodules.vtkCommonDataModel import vtkHigherOrderHexahedron 191 cell = vtkHigherOrderHexahedron() 192 elif ct == vtki.cell_types["LAGRANGE_CURVE"]: 193 from vtkmodules.vtkCommonDataModel import vtkLagrangeCurve 194 cell = vtkLagrangeCurve() 195 elif ct == vtki.cell_types["LAGRANGE_TRIANGLE"]: 196 from vtkmodules.vtkCommonDataModel import vtkLagrangeTriangle 197 cell = vtkLagrangeTriangle() 198 elif ct == vtki.cell_types["LAGRANGE_QUADRILATERAL"]: 199 from vtkmodules.vtkCommonDataModel import vtkLagrangeQuadrilateral 200 cell = vtkLagrangeQuadrilateral() 201 elif ct == vtki.cell_types["LAGRANGE_TETRAHEDRON"]: 202 from vtkmodules.vtkCommonDataModel import vtkLagrangeTetra 203 cell = vtkLagrangeTetra() 204 elif ct == vtki.cell_types["LAGRANGE_HEXAHEDRON"]: 205 from vtkmodules.vtkCommonDataModel import vtkLagrangeHexahedron 206 cell = vtkLagrangeHexahedron() 207 elif ct == vtki.cell_types["LAGRANGE_WEDGE"]: 208 from vtkmodules.vtkCommonDataModel import vtkLagrangeWedge 209 cell = vtkLagrangeWedge() 210 elif ct == vtki.cell_types["BEZIER_CURVE"]: 211 from vtkmodules.vtkCommonDataModel import vtkBezierCurve 212 cell = vtkBezierCurve() 213 elif ct == vtki.cell_types["BEZIER_TRIANGLE"]: 214 from vtkmodules.vtkCommonDataModel import vtkBezierTriangle 215 cell = vtkBezierTriangle() 216 elif ct == vtki.cell_types["BEZIER_QUADRILATERAL"]: 217 from vtkmodules.vtkCommonDataModel import vtkBezierQuadrilateral 218 cell = vtkBezierQuadrilateral() 219 elif ct == vtki.cell_types["BEZIER_TETRAHEDRON"]: 220 from vtkmodules.vtkCommonDataModel import vtkBezierTetra 221 cell = vtkBezierTetra() 222 elif ct == vtki.cell_types["BEZIER_HEXAHEDRON"]: 223 from vtkmodules.vtkCommonDataModel import vtkBezierHexahedron 224 cell = vtkBezierHexahedron() 225 elif ct == vtki.cell_types["BEZIER_WEDGE"]: 226 from vtkmodules.vtkCommonDataModel import vtkBezierWedge 227 cell = vtkBezierWedge() 228 else: 229 vedo.logger.error( 230 f"UnstructuredGrid: cell type {ct} not supported. Skip.") 231 continue 232 233 cpids = cell.GetPointIds() 234 cell_conn = cells[i] 235 for j, pid in enumerate(cell_conn): 236 cpids.SetId(j, pid) 237 self.dataset.InsertNextCell(ct, cpids) 238 239 elif "UnstructuredGrid" in inputtype: 240 self.dataset = inputobj 241 242 elif isinstance(inputobj, str): 243 if "https://" in inputobj: 244 inputobj = download(inputobj, verbose=False) 245 self.filename = inputobj 246 if inputobj.endswith(".vtu"): 247 reader = vtki.new("XMLUnstructuredGridReader") 248 else: 249 reader = vtki.new("UnstructuredGridReader") 250 self.filename = inputobj 251 reader.SetFileName(inputobj) 252 reader.Update() 253 self.dataset = reader.GetOutput() 254 255 else: 256 # this converts other types of vtk objects to UnstructuredGrid 257 apf = vtki.new("AppendFilter") 258 try: 259 apf.AddInputData(inputobj) 260 except TypeError: 261 apf.AddInputData(inputobj.dataset) 262 apf.Update() 263 self.dataset = apf.GetOutput() 264 265 self.properties.SetColor(0.89, 0.455, 0.671) # pink7 266 267 self.pipeline = utils.OperationNode( 268 self, comment=f"#cells {self.dataset.GetNumberOfCells()}", c="#4cc9f0" 269 )
Support for UnstructuredGrid objects.
Arguments:
- inputobj : (list, vtkUnstructuredGrid, str)
A list in the form
[points, cells, celltypes]
, or a vtkUnstructuredGrid object, or a filename
Celltypes are identified by the following convention.
412 @property 413 def actor(self): 414 """Return the `vtkActor` of the object.""" 415 # print("building actor") 416 gf = vtki.new("GeometryFilter") 417 gf.SetInputData(self.dataset) 418 gf.Update() 419 out = gf.GetOutput() 420 self.mapper.SetInputData(out) 421 self.mapper.Modified() 422 return self._actor
Return the vtkActor
of the object.
435 def merge(self, *others) -> Self: 436 """ 437 Merge multiple datasets into one single `UnstrcturedGrid`. 438 """ 439 apf = vtki.new("AppendFilter") 440 for o in others: 441 if isinstance(o, UnstructuredGrid): 442 apf.AddInputData(o.dataset) 443 elif isinstance(o, vtki.vtkUnstructuredGrid): 444 apf.AddInputData(o) 445 else: 446 vedo.printc("Error: cannot merge type", type(o), c="r") 447 apf.Update() 448 self._update(apf.GetOutput()) 449 self.pipeline = utils.OperationNode( 450 "merge", parents=[self, *others], c="#9e2a2b" 451 ) 452 return self
Merge multiple datasets into one single UnstrcturedGrid
.
454 def copy(self, deep=True) -> "UnstructuredGrid": 455 """Return a copy of the object. Alias of `clone()`.""" 456 return self.clone(deep=deep)
Return a copy of the object. Alias of clone()
.
458 def clone(self, deep=True) -> "UnstructuredGrid": 459 """Clone the UnstructuredGrid object to yield an exact copy.""" 460 ug = vtki.vtkUnstructuredGrid() 461 if deep: 462 ug.DeepCopy(self.dataset) 463 else: 464 ug.ShallowCopy(self.dataset) 465 if isinstance(self, vedo.UnstructuredGrid): 466 cloned = vedo.UnstructuredGrid(ug) 467 else: 468 cloned = vedo.TetMesh(ug) 469 470 cloned.copy_properties_from(self) 471 472 cloned.pipeline = utils.OperationNode( 473 "clone", parents=[self], shape="diamond", c="#bbe1ed" 474 ) 475 return cloned
Clone the UnstructuredGrid object to yield an exact copy.
477 def bounds(self) -> np.ndarray: 478 """ 479 Get the object bounds. 480 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 481 """ 482 # OVERRIDE CommonAlgorithms.bounds() which is too slow 483 return np.array(self.dataset.GetBounds())
Get the object bounds.
Returns a list in format [xmin,xmax, ymin,ymax, zmin,zmax]
.
485 def threshold(self, name=None, above=None, below=None, on="cells") -> Self: 486 """ 487 Threshold the tetrahedral mesh by a cell scalar value. 488 Reduce to only tets which satisfy the threshold limits. 489 490 - if `above = below` will only select tets with that specific value. 491 - if `above > below` selection range is flipped. 492 493 Set keyword "on" to either "cells" or "points". 494 """ 495 th = vtki.new("Threshold") 496 th.SetInputData(self.dataset) 497 498 if name is None: 499 if self.celldata.keys(): 500 name = self.celldata.keys()[0] 501 th.SetInputArrayToProcess(0, 0, 0, 1, name) 502 elif self.pointdata.keys(): 503 name = self.pointdata.keys()[0] 504 th.SetInputArrayToProcess(0, 0, 0, 0, name) 505 if name is None: 506 vedo.logger.warning("cannot find active array. Skip.") 507 return self 508 else: 509 if on.startswith("c"): 510 th.SetInputArrayToProcess(0, 0, 0, 1, name) 511 else: 512 th.SetInputArrayToProcess(0, 0, 0, 0, name) 513 514 if above is not None: 515 th.SetLowerThreshold(above) 516 517 if below is not None: 518 th.SetUpperThreshold(below) 519 520 th.Update() 521 return self._update(th.GetOutput())
Threshold the tetrahedral mesh by a cell scalar value. Reduce to only tets which satisfy the threshold limits.
- if
above = below
will only select tets with that specific value. - if
above > below
selection range is flipped.
Set keyword "on" to either "cells" or "points".
523 def isosurface(self, value=None, flying_edges=False) -> "vedo.mesh.Mesh": 524 """ 525 Return an `Mesh` isosurface extracted from the `Volume` object. 526 527 Set `value` as single float or list of values to draw the isosurface(s). 528 Use flying_edges for faster results (but sometimes can interfere with `smooth()`). 529 530 Examples: 531 - [isosurfaces1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces1.py) 532 533 ![](https://vedo.embl.es/images/volumetric/isosurfaces.png) 534 """ 535 scrange = self.dataset.GetScalarRange() 536 537 if flying_edges: 538 cf = vtki.new("FlyingEdges3D") 539 cf.InterpolateAttributesOn() 540 else: 541 cf = vtki.new("ContourFilter") 542 cf.UseScalarTreeOn() 543 544 cf.SetInputData(self.dataset) 545 cf.ComputeNormalsOn() 546 547 if utils.is_sequence(value): 548 cf.SetNumberOfContours(len(value)) 549 for i, t in enumerate(value): 550 cf.SetValue(i, t) 551 else: 552 if value is None: 553 value = (2 * scrange[0] + scrange[1]) / 3.0 554 # print("automatic isosurface value =", value) 555 cf.SetValue(0, value) 556 557 cf.Update() 558 poly = cf.GetOutput() 559 560 out = vedo.mesh.Mesh(poly, c=None).flat() 561 out.mapper.SetScalarRange(scrange[0], scrange[1]) 562 563 out.pipeline = utils.OperationNode( 564 "isosurface", 565 parents=[self], 566 comment=f"#pts {out.dataset.GetNumberOfPoints()}", 567 c="#4cc9f0:#e9c46a", 568 ) 569 return out
Return an Mesh
isosurface extracted from the Volume
object.
Set value
as single float or list of values to draw the isosurface(s).
Use flying_edges for faster results (but sometimes can interfere with smooth()
).
Examples:
571 def shrink(self, fraction=0.8) -> Self: 572 """ 573 Shrink the individual cells. 574 575 ![](https://vedo.embl.es/images/feats/shrink_hex.png) 576 """ 577 sf = vtki.new("ShrinkFilter") 578 sf.SetInputData(self.dataset) 579 sf.SetShrinkFactor(fraction) 580 sf.Update() 581 out = sf.GetOutput() 582 self._update(out) 583 self.pipeline = utils.OperationNode( 584 "shrink", comment=f"by {fraction}", parents=[self], c="#9e2a2b" 585 ) 586 return self
Shrink the individual cells.
588 def tomesh(self, fill=False, shrink=1.0) -> "vedo.mesh.Mesh": 589 """ 590 Build a polygonal `Mesh` from the current object. 591 592 If `fill=True`, the interior faces of all the cells are created. 593 (setting a `shrink` value slightly smaller than the default 1.0 594 can avoid flickering due to internal adjacent faces). 595 596 If `fill=False`, only the boundary faces will be generated. 597 """ 598 gf = vtki.new("GeometryFilter") 599 if fill: 600 sf = vtki.new("ShrinkFilter") 601 sf.SetInputData(self.dataset) 602 sf.SetShrinkFactor(shrink) 603 sf.Update() 604 gf.SetInputData(sf.GetOutput()) 605 gf.Update() 606 poly = gf.GetOutput() 607 else: 608 gf.SetInputData(self.dataset) 609 gf.Update() 610 poly = gf.GetOutput() 611 612 msh = vedo.mesh.Mesh(poly) 613 msh.copy_properties_from(self) 614 msh.pipeline = utils.OperationNode( 615 "tomesh", parents=[self], comment=f"fill={fill}", c="#9e2a2b:#e9c46a" 616 ) 617 return msh
Build a polygonal Mesh
from the current object.
If fill=True
, the interior faces of all the cells are created.
(setting a shrink
value slightly smaller than the default 1.0
can avoid flickering due to internal adjacent faces).
If fill=False
, only the boundary faces will be generated.
619 @property 620 def cell_types_array(self): 621 """Return the list of cell types in the dataset.""" 622 uarr = self.dataset.GetCellTypesArray() 623 return utils.vtk2numpy(uarr)
Return the list of cell types in the dataset.
625 def extract_cells_by_type(self, ctype) -> "UnstructuredGrid": 626 """Extract a specific cell type and return a new `UnstructuredGrid`.""" 627 if isinstance(ctype, str): 628 try: 629 ctype = vtki.cell_types[ctype.upper()] 630 except KeyError: 631 vedo.logger.error(f"extract_cells_by_type: cell type {ctype} does not exist. Skip.") 632 return self 633 uarr = self.dataset.GetCellTypesArray() 634 ctarrtyp = np.where(utils.vtk2numpy(uarr) == ctype)[0] 635 uarrtyp = utils.numpy2vtk(ctarrtyp, deep=False, dtype="id") 636 selection_node = vtki.new("SelectionNode") 637 selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL) 638 selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES) 639 selection_node.SetSelectionList(uarrtyp) 640 selection = vtki.new("Selection") 641 selection.AddNode(selection_node) 642 es = vtki.new("ExtractSelection") 643 es.SetInputData(0, self.dataset) 644 es.SetInputData(1, selection) 645 es.Update() 646 647 ug = UnstructuredGrid(es.GetOutput()) 648 ug.pipeline = utils.OperationNode( 649 "extract_cell_type", comment=f"type {ctype}", c="#edabab", parents=[self] 650 ) 651 return ug
Extract a specific cell type and return a new UnstructuredGrid
.
653 def extract_cells_by_id(self, idlist, use_point_ids=False) -> "UnstructuredGrid": 654 """Return a new `UnstructuredGrid` composed of the specified subset of indices.""" 655 selection_node = vtki.new("SelectionNode") 656 if use_point_ids: 657 selection_node.SetFieldType(vtki.get_class("SelectionNode").POINT) 658 contcells = vtki.get_class("SelectionNode").CONTAINING_CELLS() 659 selection_node.GetProperties().Set(contcells, 1) 660 else: 661 selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL) 662 selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES) 663 vidlist = utils.numpy2vtk(idlist, dtype="id") 664 selection_node.SetSelectionList(vidlist) 665 selection = vtki.new("Selection") 666 selection.AddNode(selection_node) 667 es = vtki.new("ExtractSelection") 668 es.SetInputData(0, self) 669 es.SetInputData(1, selection) 670 es.Update() 671 672 ug = UnstructuredGrid(es.GetOutput()) 673 pr = vtki.vtkProperty() 674 pr.DeepCopy(self.properties) 675 ug.actor.SetProperty(pr) 676 ug.properties = pr 677 678 ug.mapper.SetLookupTable(utils.ctf2lut(self)) 679 ug.pipeline = utils.OperationNode( 680 "extract_cells_by_id", 681 parents=[self], 682 comment=f"#cells {self.dataset.GetNumberOfCells()}", 683 c="#9e2a2b", 684 ) 685 return ug
Return a new UnstructuredGrid
composed of the specified subset of indices.
687 def find_cell(self, p: list) -> int: 688 """Locate the cell that contains a point and return the cell ID.""" 689 if self.cell_locator is None: 690 self.cell_locator = vtki.new("CellLocator") 691 self.cell_locator.SetDataSet(self.dataset) 692 self.cell_locator.BuildLocator() 693 cid = self.cell_locator.FindCell(p) 694 return cid
Locate the cell that contains a point and return the cell ID.