vedo.tetmesh

Work with tetrahedral meshes.

  1#!/usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3
  4try:
  5    import vedo.vtkclasses as vtk
  6except ImportError:
  7    import vtkmodules.all as vtk
  8
  9import numpy as np
 10import vedo
 11from vedo import utils
 12from vedo.base import BaseGrid
 13from vedo.mesh import Mesh
 14from vedo.io import download, loadUnStructuredGrid
 15
 16
 17__docformat__ = "google"
 18
 19__doc__ = """
 20Work with tetrahedral meshes.
 21
 22![](https://vedo.embl.es/images/volumetric/82767107-2631d500-9e25-11ea-967c-42558f98f721.jpg)
 23"""
 24
 25__all__ = ["TetMesh", "delaunay3d"]
 26
 27
 28##########################################################################
 29def delaunay3d(mesh, radius=0, tol=None):
 30    """
 31    Create 3D Delaunay triangulation of input points.
 32
 33    Arguments:
 34    
 35        radius : (float)
 36            specify distance (or "alpha") value to control output.
 37            For a non-zero values, only tetra contained within the circumsphere
 38            will be output.
 39
 40        tol : (float)
 41            Specify a tolerance to control discarding of closely spaced points.
 42            This tolerance is specified as a fraction of the diagonal length of
 43            the bounding box of the points.
 44    """
 45    deln = vtk.vtkDelaunay3D()
 46    if utils.is_sequence(mesh):
 47        pd = vtk.vtkPolyData()
 48        vpts = vtk.vtkPoints()
 49        vpts.SetData(utils.numpy2vtk(mesh, dtype=np.float32))
 50        pd.SetPoints(vpts)
 51        deln.SetInputData(pd)
 52    else:
 53        deln.SetInputData(mesh.GetMapper().GetInput())
 54    deln.SetAlpha(radius)
 55    deln.AlphaTetsOn()
 56    deln.AlphaTrisOff()
 57    deln.AlphaLinesOff()
 58    deln.AlphaVertsOff()
 59    deln.BoundingTriangulationOff()
 60    if tol:
 61        deln.SetTolerance(tol)
 62    deln.Update()
 63    m = TetMesh(deln.GetOutput())
 64    m.pipeline = utils.OperationNode(
 65        "delaunay3d", c="#e9c46a:#edabab", parents=[mesh],
 66    )
 67    return m
 68
 69
 70def _buildtetugrid(points, cells):
 71    ug = vtk.vtkUnstructuredGrid()
 72
 73    if len(points) == 0:
 74        return ug
 75    if not utils.is_sequence(points[0]):
 76        return ug
 77
 78    if len(cells) == 0:
 79        return ug
 80
 81    if not utils.is_sequence(cells[0]):
 82        tets = []
 83        nf = cells[0] + 1
 84        for i, cl in enumerate(cells):
 85            if i in (nf, 0):
 86                k = i + 1
 87                nf = cl + k
 88                cell = [cells[j + k] for j in range(cl)]
 89                tets.append(cell)
 90        cells = tets
 91
 92    source_points = vtk.vtkPoints()
 93    varr = utils.numpy2vtk(points, dtype=np.float32)
 94    source_points.SetData(varr)
 95    ug.SetPoints(source_points)
 96
 97    source_tets = vtk.vtkCellArray()
 98    for f in cells:
 99        ele = vtk.vtkTetra()
100        pid = ele.GetPointIds()
101        for i, fi in enumerate(f):
102            pid.SetId(i, fi)
103        source_tets.InsertNextCell(ele)
104    ug.SetCells(vtk.VTK_TETRA, source_tets)
105    return ug
106
107
108##########################################################################
109class TetMesh(BaseGrid, vtk.vtkVolume):
110    """The class describing tetrahedral meshes."""
111
112    def __init__(
113        self,
114        inputobj=None,
115        c=("r", "y", "lg", "lb", "b"),  # ('b','lb','lg','y','r')
116        alpha=(0.5, 1),
117        alpha_unit=1,
118        mapper="tetra",
119    ):
120        """
121        Arguments:
122            inputobj : (vtkDataSet, list, str)
123                list of points and tet indices, or filename
124            alpha_unit : (float)
125                opacity scale
126            mapper : (str)
127                choose a visualization style in `['tetra', 'raycast', 'zsweep']`
128        """
129        vtk.vtkVolume.__init__(self)
130        BaseGrid.__init__(self)
131
132        self.useArray = 0
133
134        # inputtype = str(type(inputobj))
135        # print('TetMesh inputtype', inputtype)
136
137        ###################
138        if inputobj is None:
139            self._data = vtk.vtkUnstructuredGrid()
140
141        elif isinstance(inputobj, vtk.vtkUnstructuredGrid):
142            self._data = inputobj
143
144        elif isinstance(inputobj, vtk.vtkRectilinearGrid):
145            r2t = vtk.vtkRectilinearGridToTetrahedra()
146            r2t.SetInputData(inputobj)
147            r2t.RememberVoxelIdOn()
148            r2t.SetTetraPerCellTo6()
149            r2t.Update()
150            self._data = r2t.GetOutput()
151
152        elif isinstance(inputobj, vtk.vtkDataSet):
153            r2t = vtk.vtkDataSetTriangleFilter()
154            r2t.SetInputData(inputobj)
155            # r2t.TetrahedraOnlyOn()
156            r2t.Update()
157            self._data = r2t.GetOutput()
158
159        elif isinstance(inputobj, str):
160            if "https://" in inputobj:
161                inputobj = download(inputobj, verbose=False)
162            ug = loadUnStructuredGrid(inputobj)
163            tt = vtk.vtkDataSetTriangleFilter()
164            tt.SetInputData(ug)
165            tt.SetTetrahedraOnly(True)
166            tt.Update()
167            self._data = tt.GetOutput()
168
169        elif utils.is_sequence(inputobj):
170            # if "ndarray" not in inputtype:
171            #     inputobj = np.array(inputobj)
172            self._data = _buildtetugrid(inputobj[0], inputobj[1])
173
174        ###################
175        if "tetra" in mapper:
176            self._mapper = vtk.vtkProjectedTetrahedraMapper()
177        elif "ray" in mapper:
178            self._mapper = vtk.vtkUnstructuredGridVolumeRayCastMapper()
179        elif "zs" in mapper:
180            self._mapper = vtk.vtkUnstructuredGridVolumeZSweepMapper()
181        elif isinstance(mapper, vtk.vtkMapper):
182            self._mapper = mapper
183        else:
184            vedo.logger.error(f"Unknown mapper type {type(mapper)}")
185            raise RuntimeError()
186
187        self._mapper.SetInputData(self._data)
188        self.SetMapper(self._mapper)
189        self.color(c).alpha(alpha)
190        if alpha_unit:
191            self.GetProperty().SetScalarOpacityUnitDistance(alpha_unit)
192
193        # remember stuff:
194        self._color = c
195        self._alpha = alpha
196        self._alpha_unit = alpha_unit
197
198        self.pipeline = utils.OperationNode(
199            self, comment=f"#tets {self._data.GetNumberOfCells()}",
200            c="#9e2a2b",
201        )
202        # -----------------------------------------------------------
203
204    def _repr_html_(self):
205        """
206        HTML representation of the TetMesh object for Jupyter Notebooks.
207        
208        Returns:
209            HTML text with the image and some properties.
210        """
211        import io
212        import base64
213        from PIL import Image
214
215        library_name = "vedo.tetmesh.TetMesh"
216        help_url = "https://vedo.embl.es/docs/vedo/tetmesh.html"
217
218        arr = self.thumbnail()
219        im = Image.fromarray(arr)
220        buffered = io.BytesIO()
221        im.save(buffered, format="PNG", quality=100)
222        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
223        url = "data:image/png;base64," + encoded
224        image = f"<img src='{url}'></img>"
225
226        bounds = "<br/>".join(
227            [
228                utils.precision(min_x,4) + " ... " + utils.precision(max_x,4)
229                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
230            ]
231        )
232
233        help_text = ""
234        if self.name:
235            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
236        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 
237        if self.filename:
238            dots = ""
239            if len(self.filename) > 30:
240                dots = "..."
241            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
242        
243        pdata = ""
244        if self._data.GetPointData().GetScalars():
245            if self._data.GetPointData().GetScalars().GetName():
246                name = self._data.GetPointData().GetScalars().GetName()
247                pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>"
248
249        cdata = ""
250        if self._data.GetCellData().GetScalars():
251            if self._data.GetCellData().GetScalars().GetName():
252                name = self._data.GetCellData().GetScalars().GetName()
253                cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>"
254
255        pts = self.points()
256        cm = np.mean(pts, axis=0)
257
258        all = [
259            "<table>",
260            "<tr>", 
261            "<td>", image, "</td>",
262            "<td style='text-align: center; vertical-align: center;'><br/>", help_text,
263            "<table>",
264            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
265            "<tr><td><b> center of mass </b></td><td>" + utils.precision(cm,3) + "</td></tr>",
266            # "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>",
267            "<tr><td><b> nr. points&nbsp/&nbsptets </b></td><td>" 
268            + str(self.npoints) + "&nbsp/&nbsp" + str(self.ncells) + "</td></tr>",
269            pdata,
270            cdata,
271            "</table>",
272            "</table>",
273        ]
274        return "\n".join(all)
275
276
277    def _update(self, data):
278        self._data = data
279        self.mapper().SetInputData(data)
280        self.mapper().Modified()
281        return self
282
283    def clone(self):
284        """Clone the `TetMesh` object to yield an exact copy."""
285        ugCopy = vtk.vtkUnstructuredGrid()
286        ugCopy.DeepCopy(self._data)
287
288        cloned = TetMesh(ugCopy)
289        pr = vtk.vtkVolumeProperty()
290        pr.DeepCopy(self.GetProperty())
291        cloned.SetProperty(pr)
292
293        # assign the same transformation to the copy
294        cloned.SetOrigin(self.GetOrigin())
295        cloned.SetScale(self.GetScale())
296        cloned.SetOrientation(self.GetOrientation())
297        cloned.SetPosition(self.GetPosition())
298
299        cloned.mapper().SetScalarMode(self.mapper().GetScalarMode())
300        cloned.name = self.name
301
302        cloned.pipeline = utils.OperationNode(
303            "clone", c="#edabab", shape="diamond", parents=[self],
304        )
305        return cloned
306
307    def compute_quality(self, metric=7):
308        """
309        Calculate functions of quality for the elements of a triangular mesh.
310        This method adds to the mesh a cell array named "Quality".
311
312        Arguments:
313            metric : (int)
314                type of estimators:
315                    - EDGE RATIO, 0
316                    - ASPECT RATIO, 1
317                    - RADIUS RATIO, 2
318                    - ASPECT FROBENIUS, 3
319                    - MIN_ANGLE, 4
320                    - COLLAPSE RATIO, 5
321                    - ASPECT GAMMA, 6
322                    - VOLUME, 7
323                    - ...
324
325        See class [vtkMeshQuality](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html)
326        for an explanation of the meaning of each metric..
327        """
328        qf = vtk.vtkMeshQuality()
329        qf.SetInputData(self._data)
330        qf.SetTetQualityMeasure(metric)
331        qf.SaveCellQualityOn()
332        qf.Update()
333        self._update(qf.GetOutput())
334        return utils.vtk2numpy(qf.GetOutput().GetCellData().GetArray("Quality"))
335
336    def compute_tets_volume(self):
337        """Add to this mesh a cell data array containing the tetrahedron volume."""
338        csf = vtk.vtkCellSizeFilter()
339        csf.SetInputData(self._data)
340        csf.SetComputeArea(False)
341        csf.SetComputeVolume(True)
342        csf.SetComputeLength(False)
343        csf.SetComputeVertexCount(False)
344        csf.SetVolumeArrayName("TetVolume")
345        csf.Update()
346        self._update(csf.GetOutput())
347        return utils.vtk2numpy(csf.GetOutput().GetCellData().GetArray("TetVolume"))
348
349    def check_validity(self, tol=0):
350        """
351        Return an array of possible problematic tets following this convention:
352        ```python
353        Valid               =  0
354        WrongNumberOfPoints = 01
355        IntersectingEdges   = 02
356        IntersectingFaces   = 04
357        NoncontiguousEdges  = 08
358        Nonconvex           = 10
359        OrientedIncorrectly = 20
360        ```
361
362        Arguments:
363            tol : (float)
364                This value is used as an epsilon for floating point
365                equality checks throughout the cell checking process.
366        """
367        vald = vtk.vtkCellValidator()
368        if tol:
369            vald.SetTolerance(tol)
370        vald.SetInputData(self._data)
371        vald.Update()
372        varr = vald.GetOutput().GetCellData().GetArray("ValidityState")
373        return utils.vtk2numpy(varr)
374
375    def threshold(self, name=None, above=None, below=None, on="cells"):
376        """
377        Threshold the tetrahedral mesh by a cell scalar value.
378        Reduce to only tets which satisfy the threshold limits.
379
380        - if `above = below` will only select tets with that specific value.
381        - if `above > below` selection range is flipped.
382
383        Set keyword "on" to either "cells" or "points".
384        """
385        th = vtk.vtkThreshold()
386        th.SetInputData(self._data)
387
388        if name is None:
389            if self.celldata.keys():
390                name = self.celldata.keys()[0]
391                th.SetInputArrayToProcess(0, 0, 0, 1, name)
392            elif self.pointdata.keys():
393                name = self.pointdata.keys()[0]
394                th.SetInputArrayToProcess(0, 0, 0, 0, name)
395            if name is None:
396                vedo.logger.warning("cannot find active array. Skip.")
397                return self
398        else:
399            if on.startswith("c"):
400                th.SetInputArrayToProcess(0, 0, 0, 1, name)
401            else:
402                th.SetInputArrayToProcess(0, 0, 0, 0, name)
403
404        if above is not None and below is not None:
405            if above > below:
406                if vedo.vtk_version[0] >= 9:
407                    th.SetInvert(True)
408                    th.ThresholdBetween(below, above)
409                else:
410                    vedo.logger.error("in vtk<9, above cannot be larger than below. Skip")
411                    return self
412            else:
413                th.ThresholdBetween(above, below)
414
415        elif above is not None:
416            th.ThresholdByUpper(above)
417
418        elif below is not None:
419            th.ThresholdByLower(below)
420
421        th.Update()
422        return self._update(th.GetOutput())
423
424    def decimate(self, scalars_name, fraction=0.5, n=0):
425        """
426        Downsample the number of tets in a TetMesh to a specified fraction.
427        Either `fraction` or `n` must be set.
428
429        Arguments:
430            fraction : (float)
431                the desired final fraction of the total.
432            n : (int)
433                the desired number of final tets
434
435        .. note:: setting `fraction=0.1` leaves 10% of the original nr of tets.
436        """
437        decimate = vtk.vtkUnstructuredGridQuadricDecimation()
438        decimate.SetInputData(self._data)
439        decimate.SetScalarsName(scalars_name)
440
441        if n:  # n = desired number of points
442            decimate.SetNumberOfTetsOutput(n)
443        else:
444            decimate.SetTargetReduction(1 - fraction)
445        decimate.Update()
446        self._update(decimate.GetOutput())
447        self.pipeline = utils.OperationNode(
448            "decimate", comment=f"array: {scalars_name}",
449            c="#edabab", parents=[self],
450        )
451        return self
452
453    def subdvide(self):
454        """
455        Increase the number of tets of a `TetMesh`.
456        Subdivide one tetrahedron into twelve for every tetra.
457        """
458        sd = vtk.vtkSubdivideTetra()
459        sd.SetInputData(self._data)
460        sd.Update()
461        self._update(sd.GetOutput())
462        self.pipeline = utils.OperationNode(
463            "subdvide", c="#edabab", parents=[self],
464        )
465        return self
466
467    def isosurface(self, value=True):
468        """
469        Return a `vedo.Mesh` isosurface.
470
471        Set `value` to a single value or list of values to compute the isosurface(s).
472        """
473        if not self._data.GetPointData().GetScalars():
474            self.map_cells_to_points()
475        scrange = self._data.GetPointData().GetScalars().GetRange()
476        cf = vtk.vtkContourFilter()  # vtk.vtkContourGrid()
477        cf.SetInputData(self._data)
478
479        if utils.is_sequence(value):
480            cf.SetNumberOfContours(len(value))
481            for i, t in enumerate(value):
482                cf.SetValue(i, t)
483            cf.Update()
484        else:
485            if value is True:
486                value = (2 * scrange[0] + scrange[1]) / 3.0
487            cf.SetValue(0, value)
488            cf.Update()
489
490        clp = vtk.vtkCleanPolyData()
491        clp.SetInputData(cf.GetOutput())
492        clp.Update()
493        msh = Mesh(clp.GetOutput(), c=None).phong()
494        msh.mapper().SetLookupTable(utils.ctf2lut(self))
495        msh.pipeline = utils.OperationNode("isosurface", c="#edabab", parents=[self])
496        return msh
497
498    def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)):
499        """
500        Return a 2D slice of the mesh by a plane passing through origin and assigned normal.
501        """
502        strn = str(normal)
503        if strn   ==  "x": normal = (1, 0, 0)
504        elif strn ==  "y": normal = (0, 1, 0)
505        elif strn ==  "z": normal = (0, 0, 1)
506        elif strn == "-x": normal = (-1, 0, 0)
507        elif strn == "-y": normal = (0, -1, 0)
508        elif strn == "-z": normal = (0, 0, -1)
509        plane = vtk.vtkPlane()
510        plane.SetOrigin(origin)
511        plane.SetNormal(normal)
512
513        cc = vtk.vtkCutter()
514        cc.SetInputData(self._data)
515        cc.SetCutFunction(plane)
516        cc.Update()
517        msh = Mesh(cc.GetOutput()).flat().lighting("ambient")
518        msh.mapper().SetLookupTable(utils.ctf2lut(self))
519        msh.pipeline = utils.OperationNode("slice", c="#edabab", parents=[self])
520        return msh
class TetMesh(vedo.base.BaseGrid, vtkmodules.vtkRenderingCore.vtkVolume):
110class TetMesh(BaseGrid, vtk.vtkVolume):
111    """The class describing tetrahedral meshes."""
112
113    def __init__(
114        self,
115        inputobj=None,
116        c=("r", "y", "lg", "lb", "b"),  # ('b','lb','lg','y','r')
117        alpha=(0.5, 1),
118        alpha_unit=1,
119        mapper="tetra",
120    ):
121        """
122        Arguments:
123            inputobj : (vtkDataSet, list, str)
124                list of points and tet indices, or filename
125            alpha_unit : (float)
126                opacity scale
127            mapper : (str)
128                choose a visualization style in `['tetra', 'raycast', 'zsweep']`
129        """
130        vtk.vtkVolume.__init__(self)
131        BaseGrid.__init__(self)
132
133        self.useArray = 0
134
135        # inputtype = str(type(inputobj))
136        # print('TetMesh inputtype', inputtype)
137
138        ###################
139        if inputobj is None:
140            self._data = vtk.vtkUnstructuredGrid()
141
142        elif isinstance(inputobj, vtk.vtkUnstructuredGrid):
143            self._data = inputobj
144
145        elif isinstance(inputobj, vtk.vtkRectilinearGrid):
146            r2t = vtk.vtkRectilinearGridToTetrahedra()
147            r2t.SetInputData(inputobj)
148            r2t.RememberVoxelIdOn()
149            r2t.SetTetraPerCellTo6()
150            r2t.Update()
151            self._data = r2t.GetOutput()
152
153        elif isinstance(inputobj, vtk.vtkDataSet):
154            r2t = vtk.vtkDataSetTriangleFilter()
155            r2t.SetInputData(inputobj)
156            # r2t.TetrahedraOnlyOn()
157            r2t.Update()
158            self._data = r2t.GetOutput()
159
160        elif isinstance(inputobj, str):
161            if "https://" in inputobj:
162                inputobj = download(inputobj, verbose=False)
163            ug = loadUnStructuredGrid(inputobj)
164            tt = vtk.vtkDataSetTriangleFilter()
165            tt.SetInputData(ug)
166            tt.SetTetrahedraOnly(True)
167            tt.Update()
168            self._data = tt.GetOutput()
169
170        elif utils.is_sequence(inputobj):
171            # if "ndarray" not in inputtype:
172            #     inputobj = np.array(inputobj)
173            self._data = _buildtetugrid(inputobj[0], inputobj[1])
174
175        ###################
176        if "tetra" in mapper:
177            self._mapper = vtk.vtkProjectedTetrahedraMapper()
178        elif "ray" in mapper:
179            self._mapper = vtk.vtkUnstructuredGridVolumeRayCastMapper()
180        elif "zs" in mapper:
181            self._mapper = vtk.vtkUnstructuredGridVolumeZSweepMapper()
182        elif isinstance(mapper, vtk.vtkMapper):
183            self._mapper = mapper
184        else:
185            vedo.logger.error(f"Unknown mapper type {type(mapper)}")
186            raise RuntimeError()
187
188        self._mapper.SetInputData(self._data)
189        self.SetMapper(self._mapper)
190        self.color(c).alpha(alpha)
191        if alpha_unit:
192            self.GetProperty().SetScalarOpacityUnitDistance(alpha_unit)
193
194        # remember stuff:
195        self._color = c
196        self._alpha = alpha
197        self._alpha_unit = alpha_unit
198
199        self.pipeline = utils.OperationNode(
200            self, comment=f"#tets {self._data.GetNumberOfCells()}",
201            c="#9e2a2b",
202        )
203        # -----------------------------------------------------------
204
205    def _repr_html_(self):
206        """
207        HTML representation of the TetMesh object for Jupyter Notebooks.
208        
209        Returns:
210            HTML text with the image and some properties.
211        """
212        import io
213        import base64
214        from PIL import Image
215
216        library_name = "vedo.tetmesh.TetMesh"
217        help_url = "https://vedo.embl.es/docs/vedo/tetmesh.html"
218
219        arr = self.thumbnail()
220        im = Image.fromarray(arr)
221        buffered = io.BytesIO()
222        im.save(buffered, format="PNG", quality=100)
223        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
224        url = "data:image/png;base64," + encoded
225        image = f"<img src='{url}'></img>"
226
227        bounds = "<br/>".join(
228            [
229                utils.precision(min_x,4) + " ... " + utils.precision(max_x,4)
230                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
231            ]
232        )
233
234        help_text = ""
235        if self.name:
236            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
237        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 
238        if self.filename:
239            dots = ""
240            if len(self.filename) > 30:
241                dots = "..."
242            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
243        
244        pdata = ""
245        if self._data.GetPointData().GetScalars():
246            if self._data.GetPointData().GetScalars().GetName():
247                name = self._data.GetPointData().GetScalars().GetName()
248                pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>"
249
250        cdata = ""
251        if self._data.GetCellData().GetScalars():
252            if self._data.GetCellData().GetScalars().GetName():
253                name = self._data.GetCellData().GetScalars().GetName()
254                cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>"
255
256        pts = self.points()
257        cm = np.mean(pts, axis=0)
258
259        all = [
260            "<table>",
261            "<tr>", 
262            "<td>", image, "</td>",
263            "<td style='text-align: center; vertical-align: center;'><br/>", help_text,
264            "<table>",
265            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
266            "<tr><td><b> center of mass </b></td><td>" + utils.precision(cm,3) + "</td></tr>",
267            # "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>",
268            "<tr><td><b> nr. points&nbsp/&nbsptets </b></td><td>" 
269            + str(self.npoints) + "&nbsp/&nbsp" + str(self.ncells) + "</td></tr>",
270            pdata,
271            cdata,
272            "</table>",
273            "</table>",
274        ]
275        return "\n".join(all)
276
277
278    def _update(self, data):
279        self._data = data
280        self.mapper().SetInputData(data)
281        self.mapper().Modified()
282        return self
283
284    def clone(self):
285        """Clone the `TetMesh` object to yield an exact copy."""
286        ugCopy = vtk.vtkUnstructuredGrid()
287        ugCopy.DeepCopy(self._data)
288
289        cloned = TetMesh(ugCopy)
290        pr = vtk.vtkVolumeProperty()
291        pr.DeepCopy(self.GetProperty())
292        cloned.SetProperty(pr)
293
294        # assign the same transformation to the copy
295        cloned.SetOrigin(self.GetOrigin())
296        cloned.SetScale(self.GetScale())
297        cloned.SetOrientation(self.GetOrientation())
298        cloned.SetPosition(self.GetPosition())
299
300        cloned.mapper().SetScalarMode(self.mapper().GetScalarMode())
301        cloned.name = self.name
302
303        cloned.pipeline = utils.OperationNode(
304            "clone", c="#edabab", shape="diamond", parents=[self],
305        )
306        return cloned
307
308    def compute_quality(self, metric=7):
309        """
310        Calculate functions of quality for the elements of a triangular mesh.
311        This method adds to the mesh a cell array named "Quality".
312
313        Arguments:
314            metric : (int)
315                type of estimators:
316                    - EDGE RATIO, 0
317                    - ASPECT RATIO, 1
318                    - RADIUS RATIO, 2
319                    - ASPECT FROBENIUS, 3
320                    - MIN_ANGLE, 4
321                    - COLLAPSE RATIO, 5
322                    - ASPECT GAMMA, 6
323                    - VOLUME, 7
324                    - ...
325
326        See class [vtkMeshQuality](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html)
327        for an explanation of the meaning of each metric..
328        """
329        qf = vtk.vtkMeshQuality()
330        qf.SetInputData(self._data)
331        qf.SetTetQualityMeasure(metric)
332        qf.SaveCellQualityOn()
333        qf.Update()
334        self._update(qf.GetOutput())
335        return utils.vtk2numpy(qf.GetOutput().GetCellData().GetArray("Quality"))
336
337    def compute_tets_volume(self):
338        """Add to this mesh a cell data array containing the tetrahedron volume."""
339        csf = vtk.vtkCellSizeFilter()
340        csf.SetInputData(self._data)
341        csf.SetComputeArea(False)
342        csf.SetComputeVolume(True)
343        csf.SetComputeLength(False)
344        csf.SetComputeVertexCount(False)
345        csf.SetVolumeArrayName("TetVolume")
346        csf.Update()
347        self._update(csf.GetOutput())
348        return utils.vtk2numpy(csf.GetOutput().GetCellData().GetArray("TetVolume"))
349
350    def check_validity(self, tol=0):
351        """
352        Return an array of possible problematic tets following this convention:
353        ```python
354        Valid               =  0
355        WrongNumberOfPoints = 01
356        IntersectingEdges   = 02
357        IntersectingFaces   = 04
358        NoncontiguousEdges  = 08
359        Nonconvex           = 10
360        OrientedIncorrectly = 20
361        ```
362
363        Arguments:
364            tol : (float)
365                This value is used as an epsilon for floating point
366                equality checks throughout the cell checking process.
367        """
368        vald = vtk.vtkCellValidator()
369        if tol:
370            vald.SetTolerance(tol)
371        vald.SetInputData(self._data)
372        vald.Update()
373        varr = vald.GetOutput().GetCellData().GetArray("ValidityState")
374        return utils.vtk2numpy(varr)
375
376    def threshold(self, name=None, above=None, below=None, on="cells"):
377        """
378        Threshold the tetrahedral mesh by a cell scalar value.
379        Reduce to only tets which satisfy the threshold limits.
380
381        - if `above = below` will only select tets with that specific value.
382        - if `above > below` selection range is flipped.
383
384        Set keyword "on" to either "cells" or "points".
385        """
386        th = vtk.vtkThreshold()
387        th.SetInputData(self._data)
388
389        if name is None:
390            if self.celldata.keys():
391                name = self.celldata.keys()[0]
392                th.SetInputArrayToProcess(0, 0, 0, 1, name)
393            elif self.pointdata.keys():
394                name = self.pointdata.keys()[0]
395                th.SetInputArrayToProcess(0, 0, 0, 0, name)
396            if name is None:
397                vedo.logger.warning("cannot find active array. Skip.")
398                return self
399        else:
400            if on.startswith("c"):
401                th.SetInputArrayToProcess(0, 0, 0, 1, name)
402            else:
403                th.SetInputArrayToProcess(0, 0, 0, 0, name)
404
405        if above is not None and below is not None:
406            if above > below:
407                if vedo.vtk_version[0] >= 9:
408                    th.SetInvert(True)
409                    th.ThresholdBetween(below, above)
410                else:
411                    vedo.logger.error("in vtk<9, above cannot be larger than below. Skip")
412                    return self
413            else:
414                th.ThresholdBetween(above, below)
415
416        elif above is not None:
417            th.ThresholdByUpper(above)
418
419        elif below is not None:
420            th.ThresholdByLower(below)
421
422        th.Update()
423        return self._update(th.GetOutput())
424
425    def decimate(self, scalars_name, fraction=0.5, n=0):
426        """
427        Downsample the number of tets in a TetMesh to a specified fraction.
428        Either `fraction` or `n` must be set.
429
430        Arguments:
431            fraction : (float)
432                the desired final fraction of the total.
433            n : (int)
434                the desired number of final tets
435
436        .. note:: setting `fraction=0.1` leaves 10% of the original nr of tets.
437        """
438        decimate = vtk.vtkUnstructuredGridQuadricDecimation()
439        decimate.SetInputData(self._data)
440        decimate.SetScalarsName(scalars_name)
441
442        if n:  # n = desired number of points
443            decimate.SetNumberOfTetsOutput(n)
444        else:
445            decimate.SetTargetReduction(1 - fraction)
446        decimate.Update()
447        self._update(decimate.GetOutput())
448        self.pipeline = utils.OperationNode(
449            "decimate", comment=f"array: {scalars_name}",
450            c="#edabab", parents=[self],
451        )
452        return self
453
454    def subdvide(self):
455        """
456        Increase the number of tets of a `TetMesh`.
457        Subdivide one tetrahedron into twelve for every tetra.
458        """
459        sd = vtk.vtkSubdivideTetra()
460        sd.SetInputData(self._data)
461        sd.Update()
462        self._update(sd.GetOutput())
463        self.pipeline = utils.OperationNode(
464            "subdvide", c="#edabab", parents=[self],
465        )
466        return self
467
468    def isosurface(self, value=True):
469        """
470        Return a `vedo.Mesh` isosurface.
471
472        Set `value` to a single value or list of values to compute the isosurface(s).
473        """
474        if not self._data.GetPointData().GetScalars():
475            self.map_cells_to_points()
476        scrange = self._data.GetPointData().GetScalars().GetRange()
477        cf = vtk.vtkContourFilter()  # vtk.vtkContourGrid()
478        cf.SetInputData(self._data)
479
480        if utils.is_sequence(value):
481            cf.SetNumberOfContours(len(value))
482            for i, t in enumerate(value):
483                cf.SetValue(i, t)
484            cf.Update()
485        else:
486            if value is True:
487                value = (2 * scrange[0] + scrange[1]) / 3.0
488            cf.SetValue(0, value)
489            cf.Update()
490
491        clp = vtk.vtkCleanPolyData()
492        clp.SetInputData(cf.GetOutput())
493        clp.Update()
494        msh = Mesh(clp.GetOutput(), c=None).phong()
495        msh.mapper().SetLookupTable(utils.ctf2lut(self))
496        msh.pipeline = utils.OperationNode("isosurface", c="#edabab", parents=[self])
497        return msh
498
499    def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)):
500        """
501        Return a 2D slice of the mesh by a plane passing through origin and assigned normal.
502        """
503        strn = str(normal)
504        if strn   ==  "x": normal = (1, 0, 0)
505        elif strn ==  "y": normal = (0, 1, 0)
506        elif strn ==  "z": normal = (0, 0, 1)
507        elif strn == "-x": normal = (-1, 0, 0)
508        elif strn == "-y": normal = (0, -1, 0)
509        elif strn == "-z": normal = (0, 0, -1)
510        plane = vtk.vtkPlane()
511        plane.SetOrigin(origin)
512        plane.SetNormal(normal)
513
514        cc = vtk.vtkCutter()
515        cc.SetInputData(self._data)
516        cc.SetCutFunction(plane)
517        cc.Update()
518        msh = Mesh(cc.GetOutput()).flat().lighting("ambient")
519        msh.mapper().SetLookupTable(utils.ctf2lut(self))
520        msh.pipeline = utils.OperationNode("slice", c="#edabab", parents=[self])
521        return msh

The class describing tetrahedral meshes.

TetMesh( inputobj=None, c=('r', 'y', 'lg', 'lb', 'b'), alpha=(0.5, 1), alpha_unit=1, mapper='tetra')
113    def __init__(
114        self,
115        inputobj=None,
116        c=("r", "y", "lg", "lb", "b"),  # ('b','lb','lg','y','r')
117        alpha=(0.5, 1),
118        alpha_unit=1,
119        mapper="tetra",
120    ):
121        """
122        Arguments:
123            inputobj : (vtkDataSet, list, str)
124                list of points and tet indices, or filename
125            alpha_unit : (float)
126                opacity scale
127            mapper : (str)
128                choose a visualization style in `['tetra', 'raycast', 'zsweep']`
129        """
130        vtk.vtkVolume.__init__(self)
131        BaseGrid.__init__(self)
132
133        self.useArray = 0
134
135        # inputtype = str(type(inputobj))
136        # print('TetMesh inputtype', inputtype)
137
138        ###################
139        if inputobj is None:
140            self._data = vtk.vtkUnstructuredGrid()
141
142        elif isinstance(inputobj, vtk.vtkUnstructuredGrid):
143            self._data = inputobj
144
145        elif isinstance(inputobj, vtk.vtkRectilinearGrid):
146            r2t = vtk.vtkRectilinearGridToTetrahedra()
147            r2t.SetInputData(inputobj)
148            r2t.RememberVoxelIdOn()
149            r2t.SetTetraPerCellTo6()
150            r2t.Update()
151            self._data = r2t.GetOutput()
152
153        elif isinstance(inputobj, vtk.vtkDataSet):
154            r2t = vtk.vtkDataSetTriangleFilter()
155            r2t.SetInputData(inputobj)
156            # r2t.TetrahedraOnlyOn()
157            r2t.Update()
158            self._data = r2t.GetOutput()
159
160        elif isinstance(inputobj, str):
161            if "https://" in inputobj:
162                inputobj = download(inputobj, verbose=False)
163            ug = loadUnStructuredGrid(inputobj)
164            tt = vtk.vtkDataSetTriangleFilter()
165            tt.SetInputData(ug)
166            tt.SetTetrahedraOnly(True)
167            tt.Update()
168            self._data = tt.GetOutput()
169
170        elif utils.is_sequence(inputobj):
171            # if "ndarray" not in inputtype:
172            #     inputobj = np.array(inputobj)
173            self._data = _buildtetugrid(inputobj[0], inputobj[1])
174
175        ###################
176        if "tetra" in mapper:
177            self._mapper = vtk.vtkProjectedTetrahedraMapper()
178        elif "ray" in mapper:
179            self._mapper = vtk.vtkUnstructuredGridVolumeRayCastMapper()
180        elif "zs" in mapper:
181            self._mapper = vtk.vtkUnstructuredGridVolumeZSweepMapper()
182        elif isinstance(mapper, vtk.vtkMapper):
183            self._mapper = mapper
184        else:
185            vedo.logger.error(f"Unknown mapper type {type(mapper)}")
186            raise RuntimeError()
187
188        self._mapper.SetInputData(self._data)
189        self.SetMapper(self._mapper)
190        self.color(c).alpha(alpha)
191        if alpha_unit:
192            self.GetProperty().SetScalarOpacityUnitDistance(alpha_unit)
193
194        # remember stuff:
195        self._color = c
196        self._alpha = alpha
197        self._alpha_unit = alpha_unit
198
199        self.pipeline = utils.OperationNode(
200            self, comment=f"#tets {self._data.GetNumberOfCells()}",
201            c="#9e2a2b",
202        )
203        # -----------------------------------------------------------
Arguments:
  • inputobj : (vtkDataSet, list, str) list of points and tet indices, or filename
  • alpha_unit : (float) opacity scale
  • mapper : (str) choose a visualization style in ['tetra', 'raycast', 'zsweep']
def clone(self):
284    def clone(self):
285        """Clone the `TetMesh` object to yield an exact copy."""
286        ugCopy = vtk.vtkUnstructuredGrid()
287        ugCopy.DeepCopy(self._data)
288
289        cloned = TetMesh(ugCopy)
290        pr = vtk.vtkVolumeProperty()
291        pr.DeepCopy(self.GetProperty())
292        cloned.SetProperty(pr)
293
294        # assign the same transformation to the copy
295        cloned.SetOrigin(self.GetOrigin())
296        cloned.SetScale(self.GetScale())
297        cloned.SetOrientation(self.GetOrientation())
298        cloned.SetPosition(self.GetPosition())
299
300        cloned.mapper().SetScalarMode(self.mapper().GetScalarMode())
301        cloned.name = self.name
302
303        cloned.pipeline = utils.OperationNode(
304            "clone", c="#edabab", shape="diamond", parents=[self],
305        )
306        return cloned

Clone the TetMesh object to yield an exact copy.

def compute_quality(self, metric=7):
308    def compute_quality(self, metric=7):
309        """
310        Calculate functions of quality for the elements of a triangular mesh.
311        This method adds to the mesh a cell array named "Quality".
312
313        Arguments:
314            metric : (int)
315                type of estimators:
316                    - EDGE RATIO, 0
317                    - ASPECT RATIO, 1
318                    - RADIUS RATIO, 2
319                    - ASPECT FROBENIUS, 3
320                    - MIN_ANGLE, 4
321                    - COLLAPSE RATIO, 5
322                    - ASPECT GAMMA, 6
323                    - VOLUME, 7
324                    - ...
325
326        See class [vtkMeshQuality](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html)
327        for an explanation of the meaning of each metric..
328        """
329        qf = vtk.vtkMeshQuality()
330        qf.SetInputData(self._data)
331        qf.SetTetQualityMeasure(metric)
332        qf.SaveCellQualityOn()
333        qf.Update()
334        self._update(qf.GetOutput())
335        return utils.vtk2numpy(qf.GetOutput().GetCellData().GetArray("Quality"))

Calculate functions of quality for the elements of a triangular mesh. This method adds to the mesh a cell array named "Quality".

Arguments:
  • metric : (int) type of estimators: - EDGE RATIO, 0 - ASPECT RATIO, 1 - RADIUS RATIO, 2 - ASPECT FROBENIUS, 3 - MIN_ANGLE, 4 - COLLAPSE RATIO, 5 - ASPECT GAMMA, 6 - VOLUME, 7 - ...

See class vtkMeshQuality for an explanation of the meaning of each metric..

def compute_tets_volume(self):
337    def compute_tets_volume(self):
338        """Add to this mesh a cell data array containing the tetrahedron volume."""
339        csf = vtk.vtkCellSizeFilter()
340        csf.SetInputData(self._data)
341        csf.SetComputeArea(False)
342        csf.SetComputeVolume(True)
343        csf.SetComputeLength(False)
344        csf.SetComputeVertexCount(False)
345        csf.SetVolumeArrayName("TetVolume")
346        csf.Update()
347        self._update(csf.GetOutput())
348        return utils.vtk2numpy(csf.GetOutput().GetCellData().GetArray("TetVolume"))

Add to this mesh a cell data array containing the tetrahedron volume.

def check_validity(self, tol=0):
350    def check_validity(self, tol=0):
351        """
352        Return an array of possible problematic tets following this convention:
353        ```python
354        Valid               =  0
355        WrongNumberOfPoints = 01
356        IntersectingEdges   = 02
357        IntersectingFaces   = 04
358        NoncontiguousEdges  = 08
359        Nonconvex           = 10
360        OrientedIncorrectly = 20
361        ```
362
363        Arguments:
364            tol : (float)
365                This value is used as an epsilon for floating point
366                equality checks throughout the cell checking process.
367        """
368        vald = vtk.vtkCellValidator()
369        if tol:
370            vald.SetTolerance(tol)
371        vald.SetInputData(self._data)
372        vald.Update()
373        varr = vald.GetOutput().GetCellData().GetArray("ValidityState")
374        return utils.vtk2numpy(varr)

Return an array of possible problematic tets following this convention:

Valid               =  0
WrongNumberOfPoints = 01
IntersectingEdges   = 02
IntersectingFaces   = 04
NoncontiguousEdges  = 08
Nonconvex           = 10
OrientedIncorrectly = 20
Arguments:
  • tol : (float) This value is used as an epsilon for floating point equality checks throughout the cell checking process.
def threshold(self, name=None, above=None, below=None, on='cells'):
376    def threshold(self, name=None, above=None, below=None, on="cells"):
377        """
378        Threshold the tetrahedral mesh by a cell scalar value.
379        Reduce to only tets which satisfy the threshold limits.
380
381        - if `above = below` will only select tets with that specific value.
382        - if `above > below` selection range is flipped.
383
384        Set keyword "on" to either "cells" or "points".
385        """
386        th = vtk.vtkThreshold()
387        th.SetInputData(self._data)
388
389        if name is None:
390            if self.celldata.keys():
391                name = self.celldata.keys()[0]
392                th.SetInputArrayToProcess(0, 0, 0, 1, name)
393            elif self.pointdata.keys():
394                name = self.pointdata.keys()[0]
395                th.SetInputArrayToProcess(0, 0, 0, 0, name)
396            if name is None:
397                vedo.logger.warning("cannot find active array. Skip.")
398                return self
399        else:
400            if on.startswith("c"):
401                th.SetInputArrayToProcess(0, 0, 0, 1, name)
402            else:
403                th.SetInputArrayToProcess(0, 0, 0, 0, name)
404
405        if above is not None and below is not None:
406            if above > below:
407                if vedo.vtk_version[0] >= 9:
408                    th.SetInvert(True)
409                    th.ThresholdBetween(below, above)
410                else:
411                    vedo.logger.error("in vtk<9, above cannot be larger than below. Skip")
412                    return self
413            else:
414                th.ThresholdBetween(above, below)
415
416        elif above is not None:
417            th.ThresholdByUpper(above)
418
419        elif below is not None:
420            th.ThresholdByLower(below)
421
422        th.Update()
423        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".

def decimate(self, scalars_name, fraction=0.5, n=0):
425    def decimate(self, scalars_name, fraction=0.5, n=0):
426        """
427        Downsample the number of tets in a TetMesh to a specified fraction.
428        Either `fraction` or `n` must be set.
429
430        Arguments:
431            fraction : (float)
432                the desired final fraction of the total.
433            n : (int)
434                the desired number of final tets
435
436        .. note:: setting `fraction=0.1` leaves 10% of the original nr of tets.
437        """
438        decimate = vtk.vtkUnstructuredGridQuadricDecimation()
439        decimate.SetInputData(self._data)
440        decimate.SetScalarsName(scalars_name)
441
442        if n:  # n = desired number of points
443            decimate.SetNumberOfTetsOutput(n)
444        else:
445            decimate.SetTargetReduction(1 - fraction)
446        decimate.Update()
447        self._update(decimate.GetOutput())
448        self.pipeline = utils.OperationNode(
449            "decimate", comment=f"array: {scalars_name}",
450            c="#edabab", parents=[self],
451        )
452        return self

Downsample the number of tets in a TetMesh to a specified fraction. Either fraction or n must be set.

Arguments:
  • fraction : (float) the desired final fraction of the total.
  • n : (int) the desired number of final tets
setting fraction=0.1 leaves 10% of the original nr of tets.
def subdvide(self):
454    def subdvide(self):
455        """
456        Increase the number of tets of a `TetMesh`.
457        Subdivide one tetrahedron into twelve for every tetra.
458        """
459        sd = vtk.vtkSubdivideTetra()
460        sd.SetInputData(self._data)
461        sd.Update()
462        self._update(sd.GetOutput())
463        self.pipeline = utils.OperationNode(
464            "subdvide", c="#edabab", parents=[self],
465        )
466        return self

Increase the number of tets of a TetMesh. Subdivide one tetrahedron into twelve for every tetra.

def isosurface(self, value=True):
468    def isosurface(self, value=True):
469        """
470        Return a `vedo.Mesh` isosurface.
471
472        Set `value` to a single value or list of values to compute the isosurface(s).
473        """
474        if not self._data.GetPointData().GetScalars():
475            self.map_cells_to_points()
476        scrange = self._data.GetPointData().GetScalars().GetRange()
477        cf = vtk.vtkContourFilter()  # vtk.vtkContourGrid()
478        cf.SetInputData(self._data)
479
480        if utils.is_sequence(value):
481            cf.SetNumberOfContours(len(value))
482            for i, t in enumerate(value):
483                cf.SetValue(i, t)
484            cf.Update()
485        else:
486            if value is True:
487                value = (2 * scrange[0] + scrange[1]) / 3.0
488            cf.SetValue(0, value)
489            cf.Update()
490
491        clp = vtk.vtkCleanPolyData()
492        clp.SetInputData(cf.GetOutput())
493        clp.Update()
494        msh = Mesh(clp.GetOutput(), c=None).phong()
495        msh.mapper().SetLookupTable(utils.ctf2lut(self))
496        msh.pipeline = utils.OperationNode("isosurface", c="#edabab", parents=[self])
497        return msh

Return a vedo.Mesh isosurface.

Set value to a single value or list of values to compute the isosurface(s).

def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)):
499    def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)):
500        """
501        Return a 2D slice of the mesh by a plane passing through origin and assigned normal.
502        """
503        strn = str(normal)
504        if strn   ==  "x": normal = (1, 0, 0)
505        elif strn ==  "y": normal = (0, 1, 0)
506        elif strn ==  "z": normal = (0, 0, 1)
507        elif strn == "-x": normal = (-1, 0, 0)
508        elif strn == "-y": normal = (0, -1, 0)
509        elif strn == "-z": normal = (0, 0, -1)
510        plane = vtk.vtkPlane()
511        plane.SetOrigin(origin)
512        plane.SetNormal(normal)
513
514        cc = vtk.vtkCutter()
515        cc.SetInputData(self._data)
516        cc.SetCutFunction(plane)
517        cc.Update()
518        msh = Mesh(cc.GetOutput()).flat().lighting("ambient")
519        msh.mapper().SetLookupTable(utils.ctf2lut(self))
520        msh.pipeline = utils.OperationNode("slice", c="#edabab", parents=[self])
521        return msh

Return a 2D slice of the mesh by a plane passing through origin and assigned normal.

def delaunay3d(mesh, radius=0, tol=None):
30def delaunay3d(mesh, radius=0, tol=None):
31    """
32    Create 3D Delaunay triangulation of input points.
33
34    Arguments:
35    
36        radius : (float)
37            specify distance (or "alpha") value to control output.
38            For a non-zero values, only tetra contained within the circumsphere
39            will be output.
40
41        tol : (float)
42            Specify a tolerance to control discarding of closely spaced points.
43            This tolerance is specified as a fraction of the diagonal length of
44            the bounding box of the points.
45    """
46    deln = vtk.vtkDelaunay3D()
47    if utils.is_sequence(mesh):
48        pd = vtk.vtkPolyData()
49        vpts = vtk.vtkPoints()
50        vpts.SetData(utils.numpy2vtk(mesh, dtype=np.float32))
51        pd.SetPoints(vpts)
52        deln.SetInputData(pd)
53    else:
54        deln.SetInputData(mesh.GetMapper().GetInput())
55    deln.SetAlpha(radius)
56    deln.AlphaTetsOn()
57    deln.AlphaTrisOff()
58    deln.AlphaLinesOff()
59    deln.AlphaVertsOff()
60    deln.BoundingTriangulationOff()
61    if tol:
62        deln.SetTolerance(tol)
63    deln.Update()
64    m = TetMesh(deln.GetOutput())
65    m.pipeline = utils.OperationNode(
66        "delaunay3d", c="#e9c46a:#edabab", parents=[mesh],
67    )
68    return m

Create 3D Delaunay triangulation of input points.

Arguments:
  • radius : (float) specify distance (or "alpha") value to control output. For a non-zero values, only tetra contained within the circumsphere will be output.
  • tol : (float) Specify a tolerance to control discarding of closely spaced points. This tolerance is specified as a fraction of the diagonal length of the bounding box of the points.