Skip to content

vedo.grids

grids

unstructured

UnstructuredGrid

Bases: PointAlgorithms, MeshVisual

Support for UnstructuredGrid objects.

Source code in vedo/grids/unstructured.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
class UnstructuredGrid(PointAlgorithms, MeshVisual):
    """Support for UnstructuredGrid objects."""

    def __init__(self, inputobj=None):
        """
        Support for UnstructuredGrid objects.

        Args:
            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](https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html).
        """
        super().__init__()

        self.dataset = None

        self.mapper = vtki.new("PolyDataMapper")
        self._actor = vtki.vtkActor()
        self._actor.retrieve_object = weak_ref_to(self)
        self._actor.SetMapper(self.mapper)
        self.properties = self._actor.GetProperty()

        self.transform = LinearTransform()
        self.point_locator = None
        self.cell_locator = None
        self.line_locator = None

        self.name = "UnstructuredGrid"
        self.filename = ""
        self.file_size = ""

        self.info = {}
        self.time = time.time()
        self.rendered_at = set()

        ######################################
        inputtype = str(type(inputobj))

        if inputobj is None:
            self.dataset = vtki.vtkUnstructuredGrid()

        elif utils.is_sequence(inputobj):
            pts, cells, celltypes = inputobj
            if len(cells) != len(celltypes):
                raise ValueError(
                    f"UnstructuredGrid: mismatch between cells ({len(cells)}) "
                    f"and celltypes ({len(celltypes)})"
                )

            self.dataset = vtki.vtkUnstructuredGrid()

            if not utils.is_sequence(cells[0]):
                tets = []
                nf = cells[0] + 1
                for i, cl in enumerate(cells):
                    if i in (nf, 0):
                        k = i + 1
                        nf = cl + k
                        cell = [cells[j + k] for j in range(cl)]
                        tets.append(cell)
                cells = tets

            # This would fill the points and use those to define orientation
            vpts = utils.numpy2vtk(pts, dtype=np.float32)
            points = vtki.vtkPoints()
            points.SetData(vpts)
            self.dataset.SetPoints(points)

            # Fill cells
            # https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html
            for i, ct in enumerate(celltypes):
                if ct == vtki.cell_types["VERTEX"]:
                    cell = vtki.vtkVertex()
                elif ct == vtki.cell_types["POLY_VERTEX"]:
                    cell = vtki.vtkPolyVertex()
                elif ct == vtki.cell_types["TETRA"]:
                    cell = vtki.vtkTetra()
                elif ct == vtki.cell_types["WEDGE"]:
                    cell = vtki.vtkWedge()
                elif ct == vtki.cell_types["LINE"]:
                    cell = vtki.vtkLine()
                elif ct == vtki.cell_types["POLY_LINE"]:
                    cell = vtki.vtkPolyLine()
                elif ct == vtki.cell_types["TRIANGLE"]:
                    cell = vtki.vtkTriangle()
                elif ct == vtki.cell_types["TRIANGLE_STRIP"]:
                    cell = vtki.vtkTriangleStrip()
                elif ct == vtki.cell_types["POLYGON"]:
                    cell = vtki.vtkPolygon()
                elif ct == vtki.cell_types["PIXEL"]:
                    cell = vtki.vtkPixel()
                elif ct == vtki.cell_types["QUAD"]:
                    cell = vtki.vtkQuad()
                elif ct == vtki.cell_types["VOXEL"]:
                    cell = vtki.vtkVoxel()
                elif ct == vtki.cell_types["PYRAMID"]:
                    cell = vtki.vtkPyramid()
                elif ct == vtki.cell_types["HEXAHEDRON"]:
                    cell = vtki.vtkHexahedron()
                elif ct == vtki.cell_types["HEXAGONAL_PRISM"]:
                    cell = vtki.vtkHexagonalPrism()
                elif ct == vtki.cell_types["PENTAGONAL_PRISM"]:
                    cell = vtki.vtkPentagonalPrism()
                elif ct == vtki.cell_types["QUADRATIC_TETRA"]:
                    from vtkmodules.vtkCommonDataModel import vtkQuadraticTetra

                    cell = vtkQuadraticTetra()
                elif ct == vtki.cell_types["QUADRATIC_HEXAHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import vtkQuadraticHexahedron

                    cell = vtkQuadraticHexahedron()
                elif ct == vtki.cell_types["QUADRATIC_WEDGE"]:
                    from vtkmodules.vtkCommonDataModel import vtkQuadraticWedge

                    cell = vtkQuadraticWedge()
                elif ct == vtki.cell_types["QUADRATIC_PYRAMID"]:
                    from vtkmodules.vtkCommonDataModel import vtkQuadraticPyramid

                    cell = vtkQuadraticPyramid()
                elif ct == vtki.cell_types["QUADRATIC_LINEAR_QUAD"]:
                    from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearQuad

                    cell = vtkQuadraticLinearQuad()
                elif ct == vtki.cell_types["QUADRATIC_LINEAR_WEDGE"]:
                    from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearWedge

                    cell = vtkQuadraticLinearWedge()
                elif ct == vtki.cell_types["BIQUADRATIC_QUADRATIC_WEDGE"]:
                    from vtkmodules.vtkCommonDataModel import (
                        vtkBiQuadraticQuadraticWedge,
                    )

                    cell = vtkBiQuadraticQuadraticWedge()
                elif ct == vtki.cell_types["BIQUADRATIC_QUADRATIC_HEXAHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import (
                        vtkBiQuadraticQuadraticHexahedron,
                    )

                    cell = vtkBiQuadraticQuadraticHexahedron()
                elif ct == vtki.cell_types["BIQUADRATIC_TRIANGLE"]:
                    from vtkmodules.vtkCommonDataModel import vtkBiQuadraticTriangle

                    cell = vtkBiQuadraticTriangle()
                elif ct == vtki.cell_types["CUBIC_LINE"]:
                    from vtkmodules.vtkCommonDataModel import vtkCubicLine

                    cell = vtkCubicLine()
                elif ct == vtki.cell_types["CONVEX_POINT_SET"]:
                    from vtkmodules.vtkCommonDataModel import vtkConvexPointSet

                    cell = vtkConvexPointSet()
                elif ct == vtki.cell_types["POLYHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import vtkPolyhedron

                    cell = vtkPolyhedron()
                elif ct == vtki.cell_types["HIGHER_ORDER_TRIANGLE"]:
                    from vtkmodules.vtkCommonDataModel import vtkHigherOrderTriangle

                    cell = vtkHigherOrderTriangle()
                elif ct == vtki.cell_types["HIGHER_ORDER_QUAD"]:
                    from vtkmodules.vtkCommonDataModel import (
                        vtkHigherOrderQuadrilateral,
                    )

                    cell = vtkHigherOrderQuadrilateral()
                elif ct == vtki.cell_types["HIGHER_ORDER_TETRAHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import vtkHigherOrderTetra

                    cell = vtkHigherOrderTetra()
                elif ct == vtki.cell_types["HIGHER_ORDER_WEDGE"]:
                    from vtkmodules.vtkCommonDataModel import vtkHigherOrderWedge

                    cell = vtkHigherOrderWedge()
                elif ct == vtki.cell_types["HIGHER_ORDER_HEXAHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import vtkHigherOrderHexahedron

                    cell = vtkHigherOrderHexahedron()
                elif ct == vtki.cell_types["LAGRANGE_CURVE"]:
                    from vtkmodules.vtkCommonDataModel import vtkLagrangeCurve

                    cell = vtkLagrangeCurve()
                elif ct == vtki.cell_types["LAGRANGE_TRIANGLE"]:
                    from vtkmodules.vtkCommonDataModel import vtkLagrangeTriangle

                    cell = vtkLagrangeTriangle()
                elif ct == vtki.cell_types["LAGRANGE_QUADRILATERAL"]:
                    from vtkmodules.vtkCommonDataModel import vtkLagrangeQuadrilateral

                    cell = vtkLagrangeQuadrilateral()
                elif ct == vtki.cell_types["LAGRANGE_TETRAHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import vtkLagrangeTetra

                    cell = vtkLagrangeTetra()
                elif ct == vtki.cell_types["LAGRANGE_HEXAHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import vtkLagrangeHexahedron

                    cell = vtkLagrangeHexahedron()
                elif ct == vtki.cell_types["LAGRANGE_WEDGE"]:
                    from vtkmodules.vtkCommonDataModel import vtkLagrangeWedge

                    cell = vtkLagrangeWedge()
                elif ct == vtki.cell_types["BEZIER_CURVE"]:
                    from vtkmodules.vtkCommonDataModel import vtkBezierCurve

                    cell = vtkBezierCurve()
                elif ct == vtki.cell_types["BEZIER_TRIANGLE"]:
                    from vtkmodules.vtkCommonDataModel import vtkBezierTriangle

                    cell = vtkBezierTriangle()
                elif ct == vtki.cell_types["BEZIER_QUADRILATERAL"]:
                    from vtkmodules.vtkCommonDataModel import vtkBezierQuadrilateral

                    cell = vtkBezierQuadrilateral()
                elif ct == vtki.cell_types["BEZIER_TETRAHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import vtkBezierTetra

                    cell = vtkBezierTetra()
                elif ct == vtki.cell_types["BEZIER_HEXAHEDRON"]:
                    from vtkmodules.vtkCommonDataModel import vtkBezierHexahedron

                    cell = vtkBezierHexahedron()
                elif ct == vtki.cell_types["BEZIER_WEDGE"]:
                    from vtkmodules.vtkCommonDataModel import vtkBezierWedge

                    cell = vtkBezierWedge()
                else:
                    vedo.logger.error(
                        f"UnstructuredGrid: cell type {ct} not supported. Skip."
                    )
                    continue

                cpids = cell.GetPointIds()
                cell_conn = cells[i]
                for j, pid in enumerate(cell_conn):
                    cpids.SetId(j, pid)
                self.dataset.InsertNextCell(ct, cpids)

        elif "UnstructuredGrid" in inputtype:
            self.dataset = inputobj

        elif isinstance(inputobj, str):
            if "https://" in inputobj:
                inputobj = download(inputobj, verbose=False)
            self.filename = inputobj
            if inputobj.endswith(".vtu"):
                reader = vtki.new("XMLUnstructuredGridReader")
            else:
                reader = vtki.new("UnstructuredGridReader")
            self.filename = inputobj
            reader.SetFileName(inputobj)
            reader.Update()
            self.dataset = reader.GetOutput()

        else:
            # this converts other types of vtk objects to UnstructuredGrid
            apf = vtki.new("AppendFilter")
            try:
                apf.AddInputData(inputobj)
            except TypeError:
                apf.AddInputData(inputobj.dataset)
            apf.Update()
            self.dataset = apf.GetOutput()

        self.properties.SetColor(0.89, 0.455, 0.671)  # pink7

        self.pipeline = utils.OperationNode(
            self, comment=f"#cells {self.dataset.GetNumberOfCells()}", c="#4cc9f0"
        )

    # ------------------------------------------------------------------
    def __str__(self):
        return summary_string(self, self._summary_rows(), color="magenta")

    def __repr__(self):
        return self.__str__()

    def __rich__(self):
        return summary_panel(self, self._summary_rows(), color="magenta")

    def _summary_rows(self):
        ct_arr = np.unique(self.cell_types_array)
        cnames = [k for k, v in vtki.cell_types.items() if v in ct_arr]
        rows = [
            ("nr. of verts", str(self.npoints)),
            ("nr. of cells", str(self.ncells)),
            ("cell types", str(cnames)),
        ]

        if self.npoints:
            rows.append(
                (
                    "size",
                    "average="
                    + utils.precision(self.average_size(), 6)
                    + ", diagonal="
                    + utils.precision(self.diagonal_size(), 6),
                )
            )
            rows.append(("center of mass", utils.precision(self.center_of_mass(), 6)))

        rows.append(("bounds", format_bounds(self.bounds(), utils.precision)))

        for key in self.pointdata.keys():
            arr = self.pointdata[key]
            label = active_array_label(self.dataset, "point", key, "pointdata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(arr, utils.precision, dim_label="ndim"),
                )
            )

        for key in self.celldata.keys():
            arr = self.celldata[key]
            label = active_array_label(self.dataset, "cell", key, "celldata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(arr, utils.precision, dim_label="ndim"),
                )
            )

        for key in self.metadata.keys():
            arr = self.metadata[key]
            rows.append(("metadata", f'"{key}" ({len(arr)} values)'))

        return rows

    def _repr_html_(self):
        """
        HTML representation of the UnstructuredGrid object for Jupyter Notebooks.

        Returns:
            HTML text with the image and some properties.
        """
        import io
        import base64
        from PIL import Image

        library_name = "vedo.grids.UnstructuredGrid"
        help_url = "https://vedo.embl.es/docs/vedo/grids.html#UnstructuredGrid"

        arr = self.thumbnail()
        im = Image.fromarray(arr)
        buffered = io.BytesIO()
        im.save(buffered, format="PNG", quality=100)
        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
        url = "data:image/png;base64," + encoded
        image = f"<img src='{url}'></img>"

        bounds = "<br/>".join(
            [
                utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4)
                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
            ]
        )

        help_text = ""
        if self.name:
            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
        help_text += (
            '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
        )
        if self.filename:
            dots = ""
            if len(self.filename) > 30:
                dots = "..."
            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"

        pdata = ""
        if self.dataset.GetPointData().GetScalars():
            if self.dataset.GetPointData().GetScalars().GetName():
                name = self.dataset.GetPointData().GetScalars().GetName()
                pdata = (
                    "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>"
                )

        cdata = ""
        if self.dataset.GetCellData().GetScalars():
            if self.dataset.GetCellData().GetScalars().GetName():
                name = self.dataset.GetCellData().GetScalars().GetName()
                cdata = (
                    "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>"
                )

        pts = self.coordinates
        cm = np.mean(pts, axis=0)

        _all = [
            "<table>",
            "<tr>",
            "<td>",
            image,
            "</td>",
            "<td style='text-align: center; vertical-align: center;'><br/>",
            help_text,
            "<table>",
            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>"
            + str(bounds)
            + "</td></tr>",
            "<tr><td><b> center of mass </b></td><td>"
            + utils.precision(cm, 3)
            + "</td></tr>",
            # "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>",
            "<tr><td><b> nr. points&nbsp/&nbspcells </b></td><td>"
            + str(self.npoints)
            + "&nbsp/&nbsp"
            + str(self.ncells)
            + "</td></tr>",
            pdata,
            cdata,
            "</table>",
            "</table>",
        ]
        return "\n".join(_all)

    @property
    def actor(self):
        """Return the `vtkActor` of the object."""
        # print("building actor")
        gf = vtki.new("GeometryFilter")
        gf.SetInputData(self.dataset)
        gf.Update()
        out = gf.GetOutput()
        self.mapper.SetInputData(out)
        self.mapper.Modified()
        return self._actor

    @actor.setter
    def actor(self, _):
        pass

    def _update(self, data, reset_locators=False):
        self.dataset = data
        if reset_locators:
            self.cell_locator = None
            self.point_locator = None
        return self

    def merge(self, *others) -> Self:
        """
        Merge multiple datasets into one single `UnstrcturedGrid`.
        """
        apf = vtki.new("AppendFilter")
        for o in others:
            if isinstance(o, UnstructuredGrid):
                apf.AddInputData(o.dataset)
            elif isinstance(o, vtki.vtkUnstructuredGrid):
                apf.AddInputData(o)
            else:
                vedo.logger.error(f"Error: cannot merge type {type(o)}")
        apf.Update()
        self._update(apf.GetOutput())
        self.pipeline = utils.OperationNode(
            "merge", parents=[self, *others], c="#9e2a2b"
        )
        return self

    def copy(self, deep=True) -> UnstructuredGrid:
        """Return a copy of the object. Alias of `clone()`."""
        return self.clone(deep=deep)

    def clone(self, deep=True) -> UnstructuredGrid:
        """Clone the UnstructuredGrid object to yield an exact copy."""
        ug = vtki.vtkUnstructuredGrid()
        if deep:
            ug.DeepCopy(self.dataset)
        else:
            ug.ShallowCopy(self.dataset)
        if isinstance(self, vedo.UnstructuredGrid):
            cloned = vedo.UnstructuredGrid(ug)
        else:
            cloned = vedo.TetMesh(ug)

        cloned.copy_properties_from(self)

        cloned.pipeline = utils.OperationNode(
            "clone", parents=[self], shape="diamond", c="#bbe1ed"
        )
        return cloned

    def bounds(self) -> np.ndarray:
        """
        Get the object bounds.
        Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`.
        """
        # OVERRIDE CommonAlgorithms.bounds() which is too slow
        return np.array(self.dataset.GetBounds())

    def threshold(self, name=None, above=None, below=None, on="cells") -> Self:
        """
        Threshold the tetrahedral mesh by a cell scalar value.
        Reduce to only cells which satisfy the threshold limits.

        - if `above = below` will only select cells with that specific value.
        - if `above > below` selection range is flipped.

        Set keyword "on" to either "cells" or "points".
        """
        th = vtki.new("Threshold")
        th.SetInputData(self.dataset)

        if name is None:
            if self.celldata.keys():
                name = self.celldata.keys()[0]
                th.SetInputArrayToProcess(0, 0, 0, 1, name)
            elif self.pointdata.keys():
                name = self.pointdata.keys()[0]
                th.SetInputArrayToProcess(0, 0, 0, 0, name)
            if name is None:
                vedo.logger.warning("cannot find active array. Skip.")
                return self
        else:
            if on.startswith("c"):
                th.SetInputArrayToProcess(0, 0, 0, 1, name)
            else:
                th.SetInputArrayToProcess(0, 0, 0, 0, name)

        if above is not None:
            th.SetLowerThreshold(above)

        if below is not None:
            th.SetUpperThreshold(below)

        th.Update()
        return self._update(th.GetOutput())

    def isosurface(self, value=None, flying_edges=False) -> vedo.mesh.Mesh:
        """
        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:
            - [isosurfaces1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces1.py)

                ![](https://vedo.embl.es/images/volumetric/isosurfaces.png)
        """
        scrange = self.dataset.GetScalarRange()

        if flying_edges:
            cf = vtki.new("FlyingEdges3D")
            cf.InterpolateAttributesOn()
        else:
            cf = vtki.new("ContourFilter")
            cf.UseScalarTreeOn()

        cf.SetInputData(self.dataset)
        cf.ComputeNormalsOn()

        if utils.is_sequence(value):
            cf.SetNumberOfContours(len(value))
            for i, t in enumerate(value):
                cf.SetValue(i, t)
        else:
            if value is None:
                value = (2 * scrange[0] + scrange[1]) / 3.0
                # print("automatic isosurface value =", value)
            cf.SetValue(0, value)

        cf.Update()
        poly = cf.GetOutput()

        out = vedo.mesh.Mesh(poly, c=None).flat()
        out.mapper.SetScalarRange(scrange[0], scrange[1])

        out.pipeline = utils.OperationNode(
            "isosurface",
            parents=[self],
            comment=f"#pts {out.dataset.GetNumberOfPoints()}",
            c="#4cc9f0:#e9c46a",
        )
        return out

    def shrink(self, fraction=0.8) -> Self:
        """
        Shrink the individual cells.

        ![](https://vedo.embl.es/images/feats/shrink_hex.png)
        """
        sf = vtki.new("ShrinkFilter")
        sf.SetInputData(self.dataset)
        sf.SetShrinkFactor(fraction)
        sf.Update()
        out = sf.GetOutput()
        self._update(out)
        self.pipeline = utils.OperationNode(
            "shrink", comment=f"by {fraction}", parents=[self], c="#9e2a2b"
        )
        return self

    def tomesh(self, fill=False, shrink=1.0) -> vedo.mesh.Mesh:
        """
        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.
        """
        gf = vtki.new("GeometryFilter")
        if fill:
            sf = vtki.new("ShrinkFilter")
            sf.SetInputData(self.dataset)
            sf.SetShrinkFactor(shrink)
            sf.Update()
            gf.SetInputData(sf.GetOutput())
            gf.Update()
            poly = gf.GetOutput()
        else:
            gf.SetInputData(self.dataset)
            gf.Update()
            poly = gf.GetOutput()

        msh = vedo.mesh.Mesh(poly)
        msh.copy_properties_from(self)
        msh.pipeline = utils.OperationNode(
            "tomesh", parents=[self], comment=f"fill={fill}", c="#9e2a2b:#e9c46a"
        )
        return msh

    @property
    def cell_types_array(self):
        """Return the list of cell types in the dataset."""
        uarr = self.dataset.GetCellTypes()
        return utils.vtk2numpy(uarr)

    def extract_cells_by_type(self, ctype) -> UnstructuredGrid:
        """Extract a specific cell type and return a new `UnstructuredGrid`."""
        if isinstance(ctype, str):
            try:
                ctype = vtki.cell_types[ctype.upper()]
            except KeyError:
                vedo.logger.error(
                    f"extract_cells_by_type: cell type {ctype} does not exist. Skip."
                )
                return self
        uarr = self.dataset.GetCellTypes()
        ctarrtyp = np.where(utils.vtk2numpy(uarr) == ctype)[0]
        uarrtyp = utils.numpy2vtk(ctarrtyp, deep=False, dtype="id")
        selection_node = vtki.new("SelectionNode")
        selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL)
        selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES)
        selection_node.SetSelectionList(uarrtyp)
        selection = vtki.new("Selection")
        selection.AddNode(selection_node)
        es = vtki.new("ExtractSelection")
        es.SetInputData(0, self.dataset)
        es.SetInputData(1, selection)
        es.Update()

        ug = UnstructuredGrid(es.GetOutput())
        ug.pipeline = utils.OperationNode(
            "extract_cell_type", comment=f"type {ctype}", c="#edabab", parents=[self]
        )
        return ug

    def extract_cells_by_id(self, idlist, use_point_ids=False) -> UnstructuredGrid:
        """Return a new `UnstructuredGrid` composed of the specified subset of indices."""
        selection_node = vtki.new("SelectionNode")
        if use_point_ids:
            selection_node.SetFieldType(vtki.get_class("SelectionNode").POINT)
            contcells = vtki.get_class("SelectionNode").CONTAINING_CELLS()
            selection_node.GetProperties().Set(contcells, 1)
        else:
            selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL)
        selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES)
        vidlist = utils.numpy2vtk(idlist, dtype="id")
        selection_node.SetSelectionList(vidlist)
        selection = vtki.new("Selection")
        selection.AddNode(selection_node)
        es = vtki.new("ExtractSelection")
        es.SetInputData(0, self)
        es.SetInputData(1, selection)
        es.Update()

        ug = UnstructuredGrid(es.GetOutput())
        pr = vtki.vtkProperty()
        pr.DeepCopy(self.properties)
        ug.actor.SetProperty(pr)
        ug.properties = pr

        ug.mapper.SetLookupTable(utils.ctf2lut(self))
        ug.pipeline = utils.OperationNode(
            "extract_cells_by_id",
            parents=[self],
            comment=f"#cells {self.dataset.GetNumberOfCells()}",
            c="#9e2a2b",
        )
        return ug

    def find_cell(self, p: list) -> int:
        """Locate the cell that contains a point and return the cell ID."""
        if self.cell_locator is None:
            self.cell_locator = vtki.new("CellLocator")
            self.cell_locator.SetDataSet(self.dataset)
            self.cell_locator.BuildLocator()
        cid = self.cell_locator.FindCell(p)
        return cid

    def clean(self) -> Self:
        """
        Cleanup unused points and empty cells
        """
        cl = vtki.new("StaticCleanUnstructuredGrid")
        cl.SetInputData(self.dataset)
        cl.RemoveUnusedPointsOn()
        cl.ProduceMergeMapOff()
        cl.AveragePointDataOff()
        cl.Update()

        self._update(cl.GetOutput())
        self.pipeline = utils.OperationNode(
            "clean",
            parents=[self],
            comment=f"#cells {self.dataset.GetNumberOfCells()}",
            c="#9e2a2b",
        )
        return self

    def extract_cells_on_plane(self, origin: tuple, normal: tuple) -> Self:
        """
        Extract cells that are lying of the specified surface.
        """
        bf = vtki.new("3DLinearGridCrinkleExtractor")
        bf.SetInputData(self.dataset)
        bf.CopyPointDataOn()
        bf.CopyCellDataOn()
        bf.RemoveUnusedPointsOff()

        plane = vtki.new("Plane")
        plane.SetOrigin(origin)
        plane.SetNormal(normal)
        bf.SetImplicitFunction(plane)
        bf.Update()

        self._update(bf.GetOutput(), reset_locators=False)
        self.pipeline = utils.OperationNode(
            "extract_cells_on_plane",
            parents=[self],
            comment=f"#cells {self.dataset.GetNumberOfCells()}",
            c="#9e2a2b",
        )
        return self

    def extract_cells_on_sphere(self, center: tuple, radius: tuple) -> Self:
        """
        Extract cells that are lying of the specified surface.
        """
        bf = vtki.new("3DLinearGridCrinkleExtractor")
        bf.SetInputData(self.dataset)
        bf.CopyPointDataOn()
        bf.CopyCellDataOn()
        bf.RemoveUnusedPointsOff()

        sph = vtki.new("Sphere")
        sph.SetRadius(radius)
        sph.SetCenter(center)
        bf.SetImplicitFunction(sph)
        bf.Update()

        self._update(bf.GetOutput())
        self.pipeline = utils.OperationNode(
            "extract_cells_on_sphere",
            parents=[self],
            comment=f"#cells {self.dataset.GetNumberOfCells()}",
            c="#9e2a2b",
        )
        return self

    def extract_cells_on_cylinder(
        self, center: tuple, axis: tuple, radius: float
    ) -> Self:
        """
        Extract cells that are lying of the specified surface.
        """
        bf = vtki.new("3DLinearGridCrinkleExtractor")
        bf.SetInputData(self.dataset)
        bf.CopyPointDataOn()
        bf.CopyCellDataOn()
        bf.RemoveUnusedPointsOff()

        cyl = vtki.new("Cylinder")
        cyl.SetRadius(radius)
        cyl.SetCenter(center)
        cyl.SetAxis(axis)
        bf.SetImplicitFunction(cyl)
        bf.Update()

        self.pipeline = utils.OperationNode(
            "extract_cells_on_cylinder",
            parents=[self],
            comment=f"#cells {self.dataset.GetNumberOfCells()}",
            c="#9e2a2b",
        )
        self._update(bf.GetOutput())
        return self

    def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> UnstructuredGrid:
        """
        Cut the object with the plane defined by a point and a normal.

        Args:
            origin (list):
                the cutting plane goes through this point
            normal (list, str):
                normal vector to the cutting plane
        """
        # if isinstance(self, vedo.Volume):
        #     raise RuntimeError("cut_with_plane() is not applicable to Volume objects.")

        strn = str(normal)
        if strn == "x":
            normal = (1, 0, 0)
        elif strn == "y":
            normal = (0, 1, 0)
        elif strn == "z":
            normal = (0, 0, 1)
        elif strn == "-x":
            normal = (-1, 0, 0)
        elif strn == "-y":
            normal = (0, -1, 0)
        elif strn == "-z":
            normal = (0, 0, -1)
        plane = vtki.new("Plane")
        plane.SetOrigin(origin)
        plane.SetNormal(normal)
        clipper = vtki.new("ClipDataSet")
        clipper.SetInputData(self.dataset)
        clipper.SetClipFunction(plane)
        clipper.GenerateClipScalarsOff()
        clipper.GenerateClippedOutputOff()
        clipper.SetValue(0)
        clipper.Update()
        cout = clipper.GetOutput()

        if isinstance(cout, vtki.vtkUnstructuredGrid):
            ug = vedo.UnstructuredGrid(cout)
            if isinstance(self, vedo.UnstructuredGrid):
                self._update(cout)
                self.pipeline = utils.OperationNode(
                    "cut_with_plane", parents=[self], c="#9e2a2b"
                )
                return self
            ug.pipeline = utils.OperationNode(
                "cut_with_plane", parents=[self], c="#9e2a2b"
            )
            return ug

        else:
            self._update(cout)
            self.pipeline = utils.OperationNode(
                "cut_with_plane", parents=[self], c="#9e2a2b"
            )
            return self

    def cut_with_box(self, box: Any) -> UnstructuredGrid:
        """
        Cut the grid with the specified bounding box.

        Parameter box has format [xmin, xmax, ymin, ymax, zmin, zmax].
        If an object is passed, its bounding box are used.

        This method always returns a TetMesh object.

        Examples:
        ```python
        from vedo import *
        tmesh = TetMesh(dataurl+'limb_ugrid.vtk')
        tmesh.color('rainbow')
        cu = Cube(side=500).x(500) # any Mesh works
        tmesh.cut_with_box(cu).show(axes=1)
        ```

        ![](https://vedo.embl.es/images/feats/tet_cut_box.png)
        """
        bc = vtki.new("BoxClipDataSet")
        bc.SetInputData(self.dataset)
        try:
            boxb = box.bounds()
        except AttributeError:
            boxb = box

        bc.SetBoxClip(*boxb)
        bc.Update()
        cout = bc.GetOutput()

        # output of vtkBoxClipDataSet is always tetrahedrons
        tm = vedo.TetMesh(cout)
        tm.pipeline = utils.OperationNode("cut_with_box", parents=[self], c="#9e2a2b")
        return tm

    def cut_with_mesh(
        self, mesh: Mesh, invert=False, whole_cells=False, on_boundary=False
    ) -> UnstructuredGrid:
        """
        Cut a `UnstructuredGrid` or `TetMesh` with a `Mesh`.

        Use `invert` to return cut off part of the input object.
        """
        ug = self.dataset

        ippd = vtki.new("ImplicitPolyDataDistance")
        ippd.SetInput(mesh.dataset)

        if whole_cells or on_boundary:
            clipper = vtki.new("ExtractGeometry")
            clipper.SetInputData(ug)
            clipper.SetImplicitFunction(ippd)
            clipper.SetExtractInside(not invert)
            clipper.SetExtractBoundaryCells(False)
            if on_boundary:
                clipper.SetExtractBoundaryCells(True)
                clipper.SetExtractOnlyBoundaryCells(True)
        else:
            signed_dists = vtki.vtkFloatArray()
            signed_dists.SetNumberOfComponents(1)
            signed_dists.SetName("SignedDistance")
            for pointId in range(ug.GetNumberOfPoints()):
                p = ug.GetPoint(pointId)
                signed_dist = ippd.EvaluateFunction(p)
                signed_dists.InsertNextValue(signed_dist)
            ug.GetPointData().AddArray(signed_dists)
            ug.GetPointData().SetActiveScalars("SignedDistance")  # NEEDED
            clipper = vtki.new("ClipDataSet")
            clipper.SetInputData(ug)
            clipper.SetInsideOut(not invert)
            clipper.SetValue(0.0)

        clipper.Update()

        out = vedo.UnstructuredGrid(clipper.GetOutput())
        out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b")
        return out

actor property writable

Return the vtkActor of the object.

cell_types_array property

Return the list of cell types in the dataset.

bounds()

Get the object bounds. Returns a list in format [xmin,xmax, ymin,ymax, zmin,zmax].

Source code in vedo/grids/unstructured.py
515
516
517
518
519
520
521
def bounds(self) -> np.ndarray:
    """
    Get the object bounds.
    Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`.
    """
    # OVERRIDE CommonAlgorithms.bounds() which is too slow
    return np.array(self.dataset.GetBounds())

clean()

Cleanup unused points and empty cells

Source code in vedo/grids/unstructured.py
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
def clean(self) -> Self:
    """
    Cleanup unused points and empty cells
    """
    cl = vtki.new("StaticCleanUnstructuredGrid")
    cl.SetInputData(self.dataset)
    cl.RemoveUnusedPointsOn()
    cl.ProduceMergeMapOff()
    cl.AveragePointDataOff()
    cl.Update()

    self._update(cl.GetOutput())
    self.pipeline = utils.OperationNode(
        "clean",
        parents=[self],
        comment=f"#cells {self.dataset.GetNumberOfCells()}",
        c="#9e2a2b",
    )
    return self

clone(deep=True)

Clone the UnstructuredGrid object to yield an exact copy.

Source code in vedo/grids/unstructured.py
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
def clone(self, deep=True) -> UnstructuredGrid:
    """Clone the UnstructuredGrid object to yield an exact copy."""
    ug = vtki.vtkUnstructuredGrid()
    if deep:
        ug.DeepCopy(self.dataset)
    else:
        ug.ShallowCopy(self.dataset)
    if isinstance(self, vedo.UnstructuredGrid):
        cloned = vedo.UnstructuredGrid(ug)
    else:
        cloned = vedo.TetMesh(ug)

    cloned.copy_properties_from(self)

    cloned.pipeline = utils.OperationNode(
        "clone", parents=[self], shape="diamond", c="#bbe1ed"
    )
    return cloned

copy(deep=True)

Return a copy of the object. Alias of clone().

Source code in vedo/grids/unstructured.py
492
493
494
def copy(self, deep=True) -> UnstructuredGrid:
    """Return a copy of the object. Alias of `clone()`."""
    return self.clone(deep=deep)

cut_with_box(box)

Cut the grid with the specified bounding box.

Parameter box has format [xmin, xmax, ymin, ymax, zmin, zmax]. If an object is passed, its bounding box are used.

This method always returns a TetMesh object.

Examples:

from vedo import *
tmesh = TetMesh(dataurl+'limb_ugrid.vtk')
tmesh.color('rainbow')
cu = Cube(side=500).x(500) # any Mesh works
tmesh.cut_with_box(cu).show(axes=1)

Source code in vedo/grids/unstructured.py
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
def cut_with_box(self, box: Any) -> UnstructuredGrid:
    """
    Cut the grid with the specified bounding box.

    Parameter box has format [xmin, xmax, ymin, ymax, zmin, zmax].
    If an object is passed, its bounding box are used.

    This method always returns a TetMesh object.

    Examples:
    ```python
    from vedo import *
    tmesh = TetMesh(dataurl+'limb_ugrid.vtk')
    tmesh.color('rainbow')
    cu = Cube(side=500).x(500) # any Mesh works
    tmesh.cut_with_box(cu).show(axes=1)
    ```

    ![](https://vedo.embl.es/images/feats/tet_cut_box.png)
    """
    bc = vtki.new("BoxClipDataSet")
    bc.SetInputData(self.dataset)
    try:
        boxb = box.bounds()
    except AttributeError:
        boxb = box

    bc.SetBoxClip(*boxb)
    bc.Update()
    cout = bc.GetOutput()

    # output of vtkBoxClipDataSet is always tetrahedrons
    tm = vedo.TetMesh(cout)
    tm.pipeline = utils.OperationNode("cut_with_box", parents=[self], c="#9e2a2b")
    return tm

cut_with_mesh(mesh, invert=False, whole_cells=False, on_boundary=False)

Cut a UnstructuredGrid or TetMesh with a Mesh.

Use invert to return cut off part of the input object.

Source code in vedo/grids/unstructured.py
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
def cut_with_mesh(
    self, mesh: Mesh, invert=False, whole_cells=False, on_boundary=False
) -> UnstructuredGrid:
    """
    Cut a `UnstructuredGrid` or `TetMesh` with a `Mesh`.

    Use `invert` to return cut off part of the input object.
    """
    ug = self.dataset

    ippd = vtki.new("ImplicitPolyDataDistance")
    ippd.SetInput(mesh.dataset)

    if whole_cells or on_boundary:
        clipper = vtki.new("ExtractGeometry")
        clipper.SetInputData(ug)
        clipper.SetImplicitFunction(ippd)
        clipper.SetExtractInside(not invert)
        clipper.SetExtractBoundaryCells(False)
        if on_boundary:
            clipper.SetExtractBoundaryCells(True)
            clipper.SetExtractOnlyBoundaryCells(True)
    else:
        signed_dists = vtki.vtkFloatArray()
        signed_dists.SetNumberOfComponents(1)
        signed_dists.SetName("SignedDistance")
        for pointId in range(ug.GetNumberOfPoints()):
            p = ug.GetPoint(pointId)
            signed_dist = ippd.EvaluateFunction(p)
            signed_dists.InsertNextValue(signed_dist)
        ug.GetPointData().AddArray(signed_dists)
        ug.GetPointData().SetActiveScalars("SignedDistance")  # NEEDED
        clipper = vtki.new("ClipDataSet")
        clipper.SetInputData(ug)
        clipper.SetInsideOut(not invert)
        clipper.SetValue(0.0)

    clipper.Update()

    out = vedo.UnstructuredGrid(clipper.GetOutput())
    out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b")
    return out

cut_with_plane(origin=(0, 0, 0), normal='x')

Cut the object with the plane defined by a point and a normal.

Parameters:

Name Type Description Default
origin list

the cutting plane goes through this point

(0, 0, 0)
normal (list, str)

normal vector to the cutting plane

'x'
Source code in vedo/grids/unstructured.py
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> UnstructuredGrid:
    """
    Cut the object with the plane defined by a point and a normal.

    Args:
        origin (list):
            the cutting plane goes through this point
        normal (list, str):
            normal vector to the cutting plane
    """
    # if isinstance(self, vedo.Volume):
    #     raise RuntimeError("cut_with_plane() is not applicable to Volume objects.")

    strn = str(normal)
    if strn == "x":
        normal = (1, 0, 0)
    elif strn == "y":
        normal = (0, 1, 0)
    elif strn == "z":
        normal = (0, 0, 1)
    elif strn == "-x":
        normal = (-1, 0, 0)
    elif strn == "-y":
        normal = (0, -1, 0)
    elif strn == "-z":
        normal = (0, 0, -1)
    plane = vtki.new("Plane")
    plane.SetOrigin(origin)
    plane.SetNormal(normal)
    clipper = vtki.new("ClipDataSet")
    clipper.SetInputData(self.dataset)
    clipper.SetClipFunction(plane)
    clipper.GenerateClipScalarsOff()
    clipper.GenerateClippedOutputOff()
    clipper.SetValue(0)
    clipper.Update()
    cout = clipper.GetOutput()

    if isinstance(cout, vtki.vtkUnstructuredGrid):
        ug = vedo.UnstructuredGrid(cout)
        if isinstance(self, vedo.UnstructuredGrid):
            self._update(cout)
            self.pipeline = utils.OperationNode(
                "cut_with_plane", parents=[self], c="#9e2a2b"
            )
            return self
        ug.pipeline = utils.OperationNode(
            "cut_with_plane", parents=[self], c="#9e2a2b"
        )
        return ug

    else:
        self._update(cout)
        self.pipeline = utils.OperationNode(
            "cut_with_plane", parents=[self], c="#9e2a2b"
        )
        return self

extract_cells_by_id(idlist, use_point_ids=False)

Return a new UnstructuredGrid composed of the specified subset of indices.

Source code in vedo/grids/unstructured.py
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
def extract_cells_by_id(self, idlist, use_point_ids=False) -> UnstructuredGrid:
    """Return a new `UnstructuredGrid` composed of the specified subset of indices."""
    selection_node = vtki.new("SelectionNode")
    if use_point_ids:
        selection_node.SetFieldType(vtki.get_class("SelectionNode").POINT)
        contcells = vtki.get_class("SelectionNode").CONTAINING_CELLS()
        selection_node.GetProperties().Set(contcells, 1)
    else:
        selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL)
    selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES)
    vidlist = utils.numpy2vtk(idlist, dtype="id")
    selection_node.SetSelectionList(vidlist)
    selection = vtki.new("Selection")
    selection.AddNode(selection_node)
    es = vtki.new("ExtractSelection")
    es.SetInputData(0, self)
    es.SetInputData(1, selection)
    es.Update()

    ug = UnstructuredGrid(es.GetOutput())
    pr = vtki.vtkProperty()
    pr.DeepCopy(self.properties)
    ug.actor.SetProperty(pr)
    ug.properties = pr

    ug.mapper.SetLookupTable(utils.ctf2lut(self))
    ug.pipeline = utils.OperationNode(
        "extract_cells_by_id",
        parents=[self],
        comment=f"#cells {self.dataset.GetNumberOfCells()}",
        c="#9e2a2b",
    )
    return ug

extract_cells_by_type(ctype)

Extract a specific cell type and return a new UnstructuredGrid.

Source code in vedo/grids/unstructured.py
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
def extract_cells_by_type(self, ctype) -> UnstructuredGrid:
    """Extract a specific cell type and return a new `UnstructuredGrid`."""
    if isinstance(ctype, str):
        try:
            ctype = vtki.cell_types[ctype.upper()]
        except KeyError:
            vedo.logger.error(
                f"extract_cells_by_type: cell type {ctype} does not exist. Skip."
            )
            return self
    uarr = self.dataset.GetCellTypes()
    ctarrtyp = np.where(utils.vtk2numpy(uarr) == ctype)[0]
    uarrtyp = utils.numpy2vtk(ctarrtyp, deep=False, dtype="id")
    selection_node = vtki.new("SelectionNode")
    selection_node.SetFieldType(vtki.get_class("SelectionNode").CELL)
    selection_node.SetContentType(vtki.get_class("SelectionNode").INDICES)
    selection_node.SetSelectionList(uarrtyp)
    selection = vtki.new("Selection")
    selection.AddNode(selection_node)
    es = vtki.new("ExtractSelection")
    es.SetInputData(0, self.dataset)
    es.SetInputData(1, selection)
    es.Update()

    ug = UnstructuredGrid(es.GetOutput())
    ug.pipeline = utils.OperationNode(
        "extract_cell_type", comment=f"type {ctype}", c="#edabab", parents=[self]
    )
    return ug

extract_cells_on_cylinder(center, axis, radius)

Extract cells that are lying of the specified surface.

Source code in vedo/grids/unstructured.py
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
def extract_cells_on_cylinder(
    self, center: tuple, axis: tuple, radius: float
) -> Self:
    """
    Extract cells that are lying of the specified surface.
    """
    bf = vtki.new("3DLinearGridCrinkleExtractor")
    bf.SetInputData(self.dataset)
    bf.CopyPointDataOn()
    bf.CopyCellDataOn()
    bf.RemoveUnusedPointsOff()

    cyl = vtki.new("Cylinder")
    cyl.SetRadius(radius)
    cyl.SetCenter(center)
    cyl.SetAxis(axis)
    bf.SetImplicitFunction(cyl)
    bf.Update()

    self.pipeline = utils.OperationNode(
        "extract_cells_on_cylinder",
        parents=[self],
        comment=f"#cells {self.dataset.GetNumberOfCells()}",
        c="#9e2a2b",
    )
    self._update(bf.GetOutput())
    return self

extract_cells_on_plane(origin, normal)

Extract cells that are lying of the specified surface.

Source code in vedo/grids/unstructured.py
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
def extract_cells_on_plane(self, origin: tuple, normal: tuple) -> Self:
    """
    Extract cells that are lying of the specified surface.
    """
    bf = vtki.new("3DLinearGridCrinkleExtractor")
    bf.SetInputData(self.dataset)
    bf.CopyPointDataOn()
    bf.CopyCellDataOn()
    bf.RemoveUnusedPointsOff()

    plane = vtki.new("Plane")
    plane.SetOrigin(origin)
    plane.SetNormal(normal)
    bf.SetImplicitFunction(plane)
    bf.Update()

    self._update(bf.GetOutput(), reset_locators=False)
    self.pipeline = utils.OperationNode(
        "extract_cells_on_plane",
        parents=[self],
        comment=f"#cells {self.dataset.GetNumberOfCells()}",
        c="#9e2a2b",
    )
    return self

extract_cells_on_sphere(center, radius)

Extract cells that are lying of the specified surface.

Source code in vedo/grids/unstructured.py
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
def extract_cells_on_sphere(self, center: tuple, radius: tuple) -> Self:
    """
    Extract cells that are lying of the specified surface.
    """
    bf = vtki.new("3DLinearGridCrinkleExtractor")
    bf.SetInputData(self.dataset)
    bf.CopyPointDataOn()
    bf.CopyCellDataOn()
    bf.RemoveUnusedPointsOff()

    sph = vtki.new("Sphere")
    sph.SetRadius(radius)
    sph.SetCenter(center)
    bf.SetImplicitFunction(sph)
    bf.Update()

    self._update(bf.GetOutput())
    self.pipeline = utils.OperationNode(
        "extract_cells_on_sphere",
        parents=[self],
        comment=f"#cells {self.dataset.GetNumberOfCells()}",
        c="#9e2a2b",
    )
    return self

find_cell(p)

Locate the cell that contains a point and return the cell ID.

Source code in vedo/grids/unstructured.py
727
728
729
730
731
732
733
734
def find_cell(self, p: list) -> int:
    """Locate the cell that contains a point and return the cell ID."""
    if self.cell_locator is None:
        self.cell_locator = vtki.new("CellLocator")
        self.cell_locator.SetDataSet(self.dataset)
        self.cell_locator.BuildLocator()
    cid = self.cell_locator.FindCell(p)
    return cid

isosurface(value=None, flying_edges=False)

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:

Source code in vedo/grids/unstructured.py
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
def isosurface(self, value=None, flying_edges=False) -> vedo.mesh.Mesh:
    """
    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:
        - [isosurfaces1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces1.py)

            ![](https://vedo.embl.es/images/volumetric/isosurfaces.png)
    """
    scrange = self.dataset.GetScalarRange()

    if flying_edges:
        cf = vtki.new("FlyingEdges3D")
        cf.InterpolateAttributesOn()
    else:
        cf = vtki.new("ContourFilter")
        cf.UseScalarTreeOn()

    cf.SetInputData(self.dataset)
    cf.ComputeNormalsOn()

    if utils.is_sequence(value):
        cf.SetNumberOfContours(len(value))
        for i, t in enumerate(value):
            cf.SetValue(i, t)
    else:
        if value is None:
            value = (2 * scrange[0] + scrange[1]) / 3.0
            # print("automatic isosurface value =", value)
        cf.SetValue(0, value)

    cf.Update()
    poly = cf.GetOutput()

    out = vedo.mesh.Mesh(poly, c=None).flat()
    out.mapper.SetScalarRange(scrange[0], scrange[1])

    out.pipeline = utils.OperationNode(
        "isosurface",
        parents=[self],
        comment=f"#pts {out.dataset.GetNumberOfPoints()}",
        c="#4cc9f0:#e9c46a",
    )
    return out

merge(*others)

Merge multiple datasets into one single UnstrcturedGrid.

Source code in vedo/grids/unstructured.py
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
def merge(self, *others) -> Self:
    """
    Merge multiple datasets into one single `UnstrcturedGrid`.
    """
    apf = vtki.new("AppendFilter")
    for o in others:
        if isinstance(o, UnstructuredGrid):
            apf.AddInputData(o.dataset)
        elif isinstance(o, vtki.vtkUnstructuredGrid):
            apf.AddInputData(o)
        else:
            vedo.logger.error(f"Error: cannot merge type {type(o)}")
    apf.Update()
    self._update(apf.GetOutput())
    self.pipeline = utils.OperationNode(
        "merge", parents=[self, *others], c="#9e2a2b"
    )
    return self

shrink(fraction=0.8)

Shrink the individual cells.

Source code in vedo/grids/unstructured.py
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
def shrink(self, fraction=0.8) -> Self:
    """
    Shrink the individual cells.

    ![](https://vedo.embl.es/images/feats/shrink_hex.png)
    """
    sf = vtki.new("ShrinkFilter")
    sf.SetInputData(self.dataset)
    sf.SetShrinkFactor(fraction)
    sf.Update()
    out = sf.GetOutput()
    self._update(out)
    self.pipeline = utils.OperationNode(
        "shrink", comment=f"by {fraction}", parents=[self], c="#9e2a2b"
    )
    return self

threshold(name=None, above=None, below=None, on='cells')

Threshold the tetrahedral mesh by a cell scalar value. Reduce to only cells which satisfy the threshold limits.

  • if above = below will only select cells with that specific value.
  • if above > below selection range is flipped.

Set keyword "on" to either "cells" or "points".

Source code in vedo/grids/unstructured.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
def threshold(self, name=None, above=None, below=None, on="cells") -> Self:
    """
    Threshold the tetrahedral mesh by a cell scalar value.
    Reduce to only cells which satisfy the threshold limits.

    - if `above = below` will only select cells with that specific value.
    - if `above > below` selection range is flipped.

    Set keyword "on" to either "cells" or "points".
    """
    th = vtki.new("Threshold")
    th.SetInputData(self.dataset)

    if name is None:
        if self.celldata.keys():
            name = self.celldata.keys()[0]
            th.SetInputArrayToProcess(0, 0, 0, 1, name)
        elif self.pointdata.keys():
            name = self.pointdata.keys()[0]
            th.SetInputArrayToProcess(0, 0, 0, 0, name)
        if name is None:
            vedo.logger.warning("cannot find active array. Skip.")
            return self
    else:
        if on.startswith("c"):
            th.SetInputArrayToProcess(0, 0, 0, 1, name)
        else:
            th.SetInputArrayToProcess(0, 0, 0, 0, name)

    if above is not None:
        th.SetLowerThreshold(above)

    if below is not None:
        th.SetUpperThreshold(below)

    th.Update()
    return self._update(th.GetOutput())

tomesh(fill=False, shrink=1.0)

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.

Source code in vedo/grids/unstructured.py
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
def tomesh(self, fill=False, shrink=1.0) -> vedo.mesh.Mesh:
    """
    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.
    """
    gf = vtki.new("GeometryFilter")
    if fill:
        sf = vtki.new("ShrinkFilter")
        sf.SetInputData(self.dataset)
        sf.SetShrinkFactor(shrink)
        sf.Update()
        gf.SetInputData(sf.GetOutput())
        gf.Update()
        poly = gf.GetOutput()
    else:
        gf.SetInputData(self.dataset)
        gf.Update()
        poly = gf.GetOutput()

    msh = vedo.mesh.Mesh(poly)
    msh.copy_properties_from(self)
    msh.pipeline = utils.OperationNode(
        "tomesh", parents=[self], comment=f"fill={fill}", c="#9e2a2b:#e9c46a"
    )
    return msh

tetmesh

TetMesh

Bases: UnstructuredGrid

The class describing tetrahedral meshes.

Source code in vedo/grids/tetmesh.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
class TetMesh(UnstructuredGrid):
    """The class describing tetrahedral meshes."""

    def __init__(self, inputobj=None):
        """
        Args:
            inputobj (vtkUnstructuredGrid, list, str, tetgenpy.TetgenIO):
                list of points and tet indices, or filename
        """
        super().__init__()

        self.dataset = None

        self.mapper = vtki.new("PolyDataMapper")
        self._actor = vtki.vtkActor()
        self._actor.retrieve_object = weak_ref_to(self)
        self._actor.SetMapper(self.mapper)
        self.properties = self._actor.GetProperty()

        self.name = "TetMesh"

        # print('TetMesh inputtype', type(inputobj))

        ###################
        if inputobj is None:
            self.dataset = vtki.vtkUnstructuredGrid()

        elif isinstance(inputobj, vtki.vtkUnstructuredGrid):
            self.dataset = inputobj

        elif isinstance(inputobj, UnstructuredGrid):
            self.dataset = inputobj.dataset

        elif "TetgenIO" in str(type(inputobj)):  # tetgenpy object
            inputobj = [inputobj.points(), inputobj.tetrahedra()]

        elif isinstance(inputobj, vtki.vtkRectilinearGrid):
            r2t = vtki.new("RectilinearGridToTetrahedra")
            r2t.SetInputData(inputobj)
            r2t.RememberVoxelIdOn()
            r2t.SetTetraPerCellTo6()
            r2t.Update()
            self.dataset = r2t.GetOutput()

        elif isinstance(inputobj, vtki.vtkDataSet) or (
            hasattr(inputobj, "dataset") and inputobj.dataset
        ):
            r2t = vtki.new("DataSetTriangleFilter")
            try:
                r2t.SetInputData(inputobj)
            except TypeError:
                r2t.SetInputData(inputobj.dataset)

            r2t.TetrahedraOnlyOn()
            r2t.Update()
            self.dataset = r2t.GetOutput()

        elif isinstance(inputobj, str):
            if "https://" in inputobj:
                inputobj = download(inputobj, verbose=False)
            if inputobj.endswith(".vtu"):
                reader = vtki.new("XMLUnstructuredGridReader")
            else:
                reader = vtki.new("UnstructuredGridReader")

            if not os.path.isfile(inputobj):
                # for some reason vtk Reader does not complain
                vedo.logger.error(f"file {inputobj} not found")
                raise FileNotFoundError

            self.filename = inputobj
            reader.SetFileName(inputobj)
            reader.Update()
            ug = reader.GetOutput()

            tt = vtki.new("DataSetTriangleFilter")
            tt.SetInputData(ug)
            tt.SetTetrahedraOnly(True)
            tt.Update()
            self.dataset = tt.GetOutput()

        ###############################
        if utils.is_sequence(inputobj):
            self.dataset = vtki.vtkUnstructuredGrid()

            points, cells = inputobj
            if len(points) == 0:
                return
            if not utils.is_sequence(points[0]):
                return
            if len(cells) == 0:
                return

            if not utils.is_sequence(cells[0]):
                tets = []
                nf = cells[0] + 1
                for i, cl in enumerate(cells):
                    if i in (nf, 0):
                        k = i + 1
                        nf = cl + k
                        cell = [cells[j + k] for j in range(cl)]
                        tets.append(cell)
                cells = tets

            source_points = vtki.vtkPoints()
            varr = utils.numpy2vtk(points, dtype=np.float32)
            source_points.SetData(varr)
            self.dataset.SetPoints(source_points)

            for f in cells:
                ele = vtki.vtkTetra()
                pid = ele.GetPointIds()
                for i, fi in enumerate(f):
                    pid.SetId(i, fi)
                self.dataset.InsertNextCell(vtki.cell_types["TETRA"], pid)

        if not self.dataset:
            vedo.logger.error(f"cannot understand input type {type(inputobj)}")
            return

        self.properties.SetColor(0.352, 0.612, 0.996)  # blue7

        self.pipeline = utils.OperationNode(
            self, comment=f"#tets {self.dataset.GetNumberOfCells()}", c="#9e2a2b"
        )

    ##################################################################
    def __str__(self):
        return summary_string(self, self._summary_rows(), color="cyan")

    def __repr__(self):
        return self.__str__()

    def __rich__(self):
        return summary_panel(self, self._summary_rows(), color="cyan")

    def _summary_rows(self):
        rows = [
            ("nr. of verts", str(self.npoints)),
            ("nr. of tetras", str(self.ncells)),
        ]

        if self.npoints:
            rows.append(
                (
                    "size",
                    "average="
                    + utils.precision(self.average_size(), 6)
                    + ", diagonal="
                    + utils.precision(self.diagonal_size(), 6),
                )
            )
            rows.append(("center of mass", utils.precision(self.center_of_mass(), 6)))

        rows.append(("bounds", format_bounds(self.bounds(), utils.precision)))

        for key in self.pointdata.keys():
            arr = self.pointdata[key]
            label = active_array_label(self.dataset, "point", key, "pointdata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(arr, utils.precision, dim_label="ndim"),
                )
            )

        for key in self.celldata.keys():
            arr = self.celldata[key]
            label = active_array_label(self.dataset, "cell", key, "celldata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(arr, utils.precision, dim_label="ndim"),
                )
            )

        for key in self.metadata.keys():
            arr = self.metadata[key]
            rows.append(("metadata", f'"{key}" ({len(arr)} values)'))

        return rows

    def _repr_html_(self):
        """
        HTML representation of the TetMesh object for Jupyter Notebooks.

        Returns:
            HTML text with the image and some properties.
        """
        import io
        import base64
        from PIL import Image

        library_name = "vedo.grids.TetMesh"
        help_url = "https://vedo.embl.es/docs/vedo/grids.html#TetMesh"

        arr = self.thumbnail()
        im = Image.fromarray(arr)
        buffered = io.BytesIO()
        im.save(buffered, format="PNG", quality=100)
        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
        url = "data:image/png;base64," + encoded
        image = f"<img src='{url}'></img>"

        bounds = "<br/>".join(
            [
                utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4)
                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
            ]
        )

        help_text = ""
        if self.name:
            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
        help_text += (
            '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
        )
        if self.filename:
            dots = ""
            if len(self.filename) > 30:
                dots = "..."
            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"

        pdata = ""
        if self.dataset.GetPointData().GetScalars():
            if self.dataset.GetPointData().GetScalars().GetName():
                name = self.dataset.GetPointData().GetScalars().GetName()
                pdata = (
                    "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>"
                )

        cdata = ""
        if self.dataset.GetCellData().GetScalars():
            if self.dataset.GetCellData().GetScalars().GetName():
                name = self.dataset.GetCellData().GetScalars().GetName()
                cdata = (
                    "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>"
                )

        pts = self.coordinates
        cm = np.mean(pts, axis=0)

        allt = [
            "<table>",
            "<tr>",
            "<td>",
            image,
            "</td>",
            "<td style='text-align: center; vertical-align: center;'><br/>",
            help_text,
            "<table>",
            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>"
            + str(bounds)
            + "</td></tr>",
            "<tr><td><b> center of mass </b></td><td>"
            + utils.precision(cm, 3)
            + "</td></tr>",
            "<tr><td><b> nr. points&nbsp/&nbsptets </b></td><td>"
            + str(self.npoints)
            + "&nbsp/&nbsp"
            + str(self.ncells)
            + "</td></tr>",
            pdata,
            cdata,
            "</table>",
            "</table>",
        ]
        return "\n".join(allt)

    def compute_quality(self, metric=7) -> np.ndarray:
        """
        Calculate functions of quality for the elements of a tetrahedral mesh.
        This method adds to the mesh a cell array named "Quality".

        Args:
            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](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html)
        for an explanation of the meaning of each metric..
        """
        qf = vtki.new("MeshQuality")
        qf.SetInputData(self.dataset)
        qf.SetTetQualityMeasure(metric)
        qf.SaveCellQualityOn()
        qf.Update()
        self._update(qf.GetOutput())
        return utils.vtk2numpy(qf.GetOutput().GetCellData().GetArray("Quality"))

    def check_validity(self, tol=0) -> np.ndarray:
        """
        Return an array of possible problematic tets following this convention:
        ```python
        Valid               =  0
        WrongNumberOfPoints = 01
        IntersectingEdges   = 02
        IntersectingFaces   = 04
        NoncontiguousEdges  = 08
        Nonconvex           = 10
        OrientedIncorrectly = 20
        ```

        Args:
            tol (float):
                This value is used as an epsilon for floating point
                equality checks throughout the cell checking process.
        """
        vald = vtki.new("CellValidator")
        if tol:
            vald.SetTolerance(tol)
        vald.SetInputData(self.dataset)
        vald.Update()
        varr = vald.GetOutput().GetCellData().GetArray("ValidityState")
        return utils.vtk2numpy(varr)

    def decimate(self, scalars_name: str, fraction=0.5, n=0) -> Self:
        """
        Downsample the number of tets in a TetMesh to a specified fraction.
        Either `fraction` or `n` must be set.

        Args:
            fraction (float):
                the desired final fraction of the total.
            n (int):
                the desired number of final tets

        .. note:: setting `fraction=0.1` leaves 10% of the original nr of tets.
        """
        decimate = vtki.new("UnstructuredGridQuadricDecimation")
        decimate.SetInputData(self.dataset)
        decimate.SetScalarsName(scalars_name)

        if n:  # n = desired number of points
            decimate.SetNumberOfTetsOutput(n)
        else:
            decimate.SetTargetReduction(1 - fraction)
        decimate.Update()
        self._update(decimate.GetOutput())
        self.pipeline = utils.OperationNode(
            "decimate", comment=f"array: {scalars_name}", c="#edabab", parents=[self]
        )
        return self

    def subdivide(self) -> Self:
        """
        Increase the number of tetrahedrons of a `TetMesh`.
        Subdivides each tetrahedron into twelve smaller tetras.
        """
        sd = vtki.new("SubdivideTetra")
        sd.SetInputData(self.dataset)
        sd.Update()
        self._update(sd.GetOutput())
        self.pipeline = utils.OperationNode("subdivide", c="#edabab", parents=[self])
        return self

    def generate_random_points(self, n, min_radius=0) -> vedo.Points:
        """
        Generate `n` uniformly distributed random points
        inside the tetrahedral mesh.

        A new point data array is added to the output points
        called "OriginalCellID" which contains the index of
        the cell ID in which the point was generated.

        Args:
            n (int):
                number of points to generate.
            min_radius (float):
                impose a minimum distance between points.
                If `min_radius` is set to 0, the points are
                generated uniformly at random inside the mesh.
                If `min_radius` is set to a positive value,
                the points are generated uniformly at random
                inside the mesh, but points closer than `min_radius`
                to any other point are discarded.

        Returns a `vedo.Points` object.

        Note:
            Consider using `points.probe(msh)` to interpolate
            any existing mesh data onto the points.

        Examples:
        ```python
        from vedo import *
        tmesh = TetMesh(dataurl + "limb.vtu").alpha(0.2)
        pts = tmesh.generate_random_points(20000, min_radius=10)
        print(pts.pointdata["OriginalCellID"])
        show(pts, tmesh, axes=1).close()
        ```
        """
        cmesh = self.compute_cell_size()
        tets = cmesh.cells
        verts = cmesh.coordinates
        cumul = np.cumsum(np.abs(cmesh.celldata["Volume"]))

        out_pts = []
        orig_cell = []
        for _ in range(n):
            random_area = np.random.random() * cumul[-1]
            it = np.searchsorted(cumul, random_area)
            A, B, C, D = verts[tets[it]]
            r1, r2, r3 = sorted(np.random.random(3))
            p = r1 * A + (r2 - r1) * B + (r3 - r2) * C + (1 - r3) * D
            out_pts.append(p)
            orig_cell.append(it)
        orig_cellnp = np.array(orig_cell, dtype=np.uint32)

        vpts = vedo.pointcloud.Points(out_pts)
        vpts.pointdata["OriginalCellID"] = orig_cellnp

        if min_radius > 0:
            vpts.subsample(min_radius, absolute=True)

        vpts.point_size(5).color("k1")
        vpts.name = "RandomPoints"
        vpts.pipeline = utils.OperationNode(
            "generate_random_points", c="#edabab", parents=[self]
        )
        return vpts

    def isosurface(self, value=True, flying_edges=None) -> vedo.Mesh:
        """
        Return a `vedo.Mesh` isosurface.
        The "isosurface" is the surface of the region of points
        whose values equal to `value`.

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

        Note that flying_edges option is not available for `TetMesh`.
        """
        if flying_edges is not None:
            vedo.logger.warning("flying_edges option is not available for TetMesh.")

        if not self.dataset.GetPointData().GetScalars():
            vedo.logger.warning(
                "in isosurface() no scalar pointdata found. Mappping cells to points."
            )
            self.map_cells_to_points()
        scrange = self.dataset.GetPointData().GetScalars().GetRange()
        cf = vtki.new("ContourFilter")  # vtki.new("ContourGrid")
        cf.SetInputData(self.dataset)

        if utils.is_sequence(value):
            cf.SetNumberOfContours(len(value))
            for i, t in enumerate(value):
                cf.SetValue(i, t)
            cf.Update()
        else:
            if value is True:
                value = (2 * scrange[0] + scrange[1]) / 3.0
            cf.SetValue(0, value)
            cf.Update()

        msh = Mesh(cf.GetOutput(), c=None)
        msh.copy_properties_from(self)
        msh.pipeline = utils.OperationNode("isosurface", c="#edabab", parents=[self])
        return msh

    def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)) -> vedo.Mesh:
        """
        Return a 2D slice of the mesh by a plane passing through origin and assigned normal.
        """
        strn = str(normal)
        if strn == "x":
            normal = (1, 0, 0)
        elif strn == "y":
            normal = (0, 1, 0)
        elif strn == "z":
            normal = (0, 0, 1)
        elif strn == "-x":
            normal = (-1, 0, 0)
        elif strn == "-y":
            normal = (0, -1, 0)
        elif strn == "-z":
            normal = (0, 0, -1)
        plane = vtki.new("Plane")
        plane.SetOrigin(origin)
        plane.SetNormal(normal)

        cc = vtki.new("Cutter")
        cc.SetInputData(self.dataset)
        cc.SetCutFunction(plane)
        cc.Update()
        msh = Mesh(cc.GetOutput()).flat().lighting("ambient")
        msh.copy_properties_from(self)
        msh.pipeline = utils.OperationNode("slice", c="#edabab", parents=[self])
        return msh

check_validity(tol=0)

Return an array of possible problematic tets following this convention:

Valid               =  0
WrongNumberOfPoints = 01
IntersectingEdges   = 02
IntersectingFaces   = 04
NoncontiguousEdges  = 08
Nonconvex           = 10
OrientedIncorrectly = 20

Parameters:

Name Type Description Default
tol float

This value is used as an epsilon for floating point equality checks throughout the cell checking process.

0
Source code in vedo/grids/tetmesh.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
def check_validity(self, tol=0) -> np.ndarray:
    """
    Return an array of possible problematic tets following this convention:
    ```python
    Valid               =  0
    WrongNumberOfPoints = 01
    IntersectingEdges   = 02
    IntersectingFaces   = 04
    NoncontiguousEdges  = 08
    Nonconvex           = 10
    OrientedIncorrectly = 20
    ```

    Args:
        tol (float):
            This value is used as an epsilon for floating point
            equality checks throughout the cell checking process.
    """
    vald = vtki.new("CellValidator")
    if tol:
        vald.SetTolerance(tol)
    vald.SetInputData(self.dataset)
    vald.Update()
    varr = vald.GetOutput().GetCellData().GetArray("ValidityState")
    return utils.vtk2numpy(varr)

compute_quality(metric=7)

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

Parameters:

Name Type Description Default
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

7

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

Source code in vedo/grids/tetmesh.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
def compute_quality(self, metric=7) -> np.ndarray:
    """
    Calculate functions of quality for the elements of a tetrahedral mesh.
    This method adds to the mesh a cell array named "Quality".

    Args:
        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](https://vtk.org/doc/nightly/html/classvtkMeshQuality.html)
    for an explanation of the meaning of each metric..
    """
    qf = vtki.new("MeshQuality")
    qf.SetInputData(self.dataset)
    qf.SetTetQualityMeasure(metric)
    qf.SaveCellQualityOn()
    qf.Update()
    self._update(qf.GetOutput())
    return utils.vtk2numpy(qf.GetOutput().GetCellData().GetArray("Quality"))

decimate(scalars_name, fraction=0.5, n=0)

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

Parameters:

Name Type Description Default
fraction float

the desired final fraction of the total.

0.5
n int

the desired number of final tets

0

.. note:: setting fraction=0.1 leaves 10% of the original nr of tets.

Source code in vedo/grids/tetmesh.py
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
def decimate(self, scalars_name: str, fraction=0.5, n=0) -> Self:
    """
    Downsample the number of tets in a TetMesh to a specified fraction.
    Either `fraction` or `n` must be set.

    Args:
        fraction (float):
            the desired final fraction of the total.
        n (int):
            the desired number of final tets

    .. note:: setting `fraction=0.1` leaves 10% of the original nr of tets.
    """
    decimate = vtki.new("UnstructuredGridQuadricDecimation")
    decimate.SetInputData(self.dataset)
    decimate.SetScalarsName(scalars_name)

    if n:  # n = desired number of points
        decimate.SetNumberOfTetsOutput(n)
    else:
        decimate.SetTargetReduction(1 - fraction)
    decimate.Update()
    self._update(decimate.GetOutput())
    self.pipeline = utils.OperationNode(
        "decimate", comment=f"array: {scalars_name}", c="#edabab", parents=[self]
    )
    return self

generate_random_points(n, min_radius=0)

Generate n uniformly distributed random points inside the tetrahedral mesh.

A new point data array is added to the output points called "OriginalCellID" which contains the index of the cell ID in which the point was generated.

Parameters:

Name Type Description Default
n int

number of points to generate.

required
min_radius float

impose a minimum distance between points. If min_radius is set to 0, the points are generated uniformly at random inside the mesh. If min_radius is set to a positive value, the points are generated uniformly at random inside the mesh, but points closer than min_radius to any other point are discarded.

0

Returns a vedo.Points object.

Note

Consider using points.probe(msh) to interpolate any existing mesh data onto the points.

Examples:

from vedo import *
tmesh = TetMesh(dataurl + "limb.vtu").alpha(0.2)
pts = tmesh.generate_random_points(20000, min_radius=10)
print(pts.pointdata["OriginalCellID"])
show(pts, tmesh, axes=1).close()

Source code in vedo/grids/tetmesh.py
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
def generate_random_points(self, n, min_radius=0) -> vedo.Points:
    """
    Generate `n` uniformly distributed random points
    inside the tetrahedral mesh.

    A new point data array is added to the output points
    called "OriginalCellID" which contains the index of
    the cell ID in which the point was generated.

    Args:
        n (int):
            number of points to generate.
        min_radius (float):
            impose a minimum distance between points.
            If `min_radius` is set to 0, the points are
            generated uniformly at random inside the mesh.
            If `min_radius` is set to a positive value,
            the points are generated uniformly at random
            inside the mesh, but points closer than `min_radius`
            to any other point are discarded.

    Returns a `vedo.Points` object.

    Note:
        Consider using `points.probe(msh)` to interpolate
        any existing mesh data onto the points.

    Examples:
    ```python
    from vedo import *
    tmesh = TetMesh(dataurl + "limb.vtu").alpha(0.2)
    pts = tmesh.generate_random_points(20000, min_radius=10)
    print(pts.pointdata["OriginalCellID"])
    show(pts, tmesh, axes=1).close()
    ```
    """
    cmesh = self.compute_cell_size()
    tets = cmesh.cells
    verts = cmesh.coordinates
    cumul = np.cumsum(np.abs(cmesh.celldata["Volume"]))

    out_pts = []
    orig_cell = []
    for _ in range(n):
        random_area = np.random.random() * cumul[-1]
        it = np.searchsorted(cumul, random_area)
        A, B, C, D = verts[tets[it]]
        r1, r2, r3 = sorted(np.random.random(3))
        p = r1 * A + (r2 - r1) * B + (r3 - r2) * C + (1 - r3) * D
        out_pts.append(p)
        orig_cell.append(it)
    orig_cellnp = np.array(orig_cell, dtype=np.uint32)

    vpts = vedo.pointcloud.Points(out_pts)
    vpts.pointdata["OriginalCellID"] = orig_cellnp

    if min_radius > 0:
        vpts.subsample(min_radius, absolute=True)

    vpts.point_size(5).color("k1")
    vpts.name = "RandomPoints"
    vpts.pipeline = utils.OperationNode(
        "generate_random_points", c="#edabab", parents=[self]
    )
    return vpts

isosurface(value=True, flying_edges=None)

Return a vedo.Mesh isosurface. The "isosurface" is the surface of the region of points whose values equal to value.

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

Note that flying_edges option is not available for TetMesh.

Source code in vedo/grids/tetmesh.py
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
def isosurface(self, value=True, flying_edges=None) -> vedo.Mesh:
    """
    Return a `vedo.Mesh` isosurface.
    The "isosurface" is the surface of the region of points
    whose values equal to `value`.

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

    Note that flying_edges option is not available for `TetMesh`.
    """
    if flying_edges is not None:
        vedo.logger.warning("flying_edges option is not available for TetMesh.")

    if not self.dataset.GetPointData().GetScalars():
        vedo.logger.warning(
            "in isosurface() no scalar pointdata found. Mappping cells to points."
        )
        self.map_cells_to_points()
    scrange = self.dataset.GetPointData().GetScalars().GetRange()
    cf = vtki.new("ContourFilter")  # vtki.new("ContourGrid")
    cf.SetInputData(self.dataset)

    if utils.is_sequence(value):
        cf.SetNumberOfContours(len(value))
        for i, t in enumerate(value):
            cf.SetValue(i, t)
        cf.Update()
    else:
        if value is True:
            value = (2 * scrange[0] + scrange[1]) / 3.0
        cf.SetValue(0, value)
        cf.Update()

    msh = Mesh(cf.GetOutput(), c=None)
    msh.copy_properties_from(self)
    msh.pipeline = utils.OperationNode("isosurface", c="#edabab", parents=[self])
    return msh

slice(origin=(0, 0, 0), normal=(1, 0, 0))

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

Source code in vedo/grids/tetmesh.py
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def slice(self, origin=(0, 0, 0), normal=(1, 0, 0)) -> vedo.Mesh:
    """
    Return a 2D slice of the mesh by a plane passing through origin and assigned normal.
    """
    strn = str(normal)
    if strn == "x":
        normal = (1, 0, 0)
    elif strn == "y":
        normal = (0, 1, 0)
    elif strn == "z":
        normal = (0, 0, 1)
    elif strn == "-x":
        normal = (-1, 0, 0)
    elif strn == "-y":
        normal = (0, -1, 0)
    elif strn == "-z":
        normal = (0, 0, -1)
    plane = vtki.new("Plane")
    plane.SetOrigin(origin)
    plane.SetNormal(normal)

    cc = vtki.new("Cutter")
    cc.SetInputData(self.dataset)
    cc.SetCutFunction(plane)
    cc.Update()
    msh = Mesh(cc.GetOutput()).flat().lighting("ambient")
    msh.copy_properties_from(self)
    msh.pipeline = utils.OperationNode("slice", c="#edabab", parents=[self])
    return msh

subdivide()

Increase the number of tetrahedrons of a TetMesh. Subdivides each tetrahedron into twelve smaller tetras.

Source code in vedo/grids/tetmesh.py
383
384
385
386
387
388
389
390
391
392
393
def subdivide(self) -> Self:
    """
    Increase the number of tetrahedrons of a `TetMesh`.
    Subdivides each tetrahedron into twelve smaller tetras.
    """
    sd = vtki.new("SubdivideTetra")
    sd.SetInputData(self.dataset)
    sd.Update()
    self._update(sd.GetOutput())
    self.pipeline = utils.OperationNode("subdivide", c="#edabab", parents=[self])
    return self

rectilinear

RectilinearGrid

Bases: PointAlgorithms, MeshVisual

Build a rectilinear grid.

Source code in vedo/grids/rectilinear.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
class RectilinearGrid(PointAlgorithms, MeshVisual):
    """
    Build a rectilinear grid.
    """

    def __init__(self, inputobj=None):
        """
        A RectilinearGrid is a dataset where edges are parallel to the coordinate axes.
        It can be thought of as a tessellation of a box in 3D space, similar to a `Volume`
        except that the cells are not necessarily cubes, but they can have different lengths
        along each axis.
        This can be useful to describe a volume with variable resolution where one needs
        to represent a region with higher detail with respect to another region.

        Args:
            inputobj (vtkRectilinearGrid, list, str):
                list of points and indices, or filename

        Examples:
            ```python
            from vedo import RectilinearGrid, show

            xcoords = 7 + np.sqrt(np.arange(0,2500,25))
            ycoords = np.arange(0, 20)
            zcoords = np.arange(0, 20)

            rgrid = RectilinearGrid([xcoords, ycoords, zcoords])

            print(rgrid)
            print(rgrid.x_coordinates().shape)
            print(rgrid.compute_structured_coords([20,10,11]))

            msh = rgrid.tomesh().lw(1)

            show(msh, axes=1, viewup="z")
            ```
        """

        super().__init__()

        self.dataset = None

        self.mapper = vtki.new("PolyDataMapper")
        self._actor = vtki.vtkActor()
        self._actor.retrieve_object = weak_ref_to(self)
        self._actor.SetMapper(self.mapper)
        self.properties = self._actor.GetProperty()

        self.transform = LinearTransform()
        self.point_locator = None
        self.cell_locator = None
        self.line_locator = None

        self.name = "RectilinearGrid"
        self.filename = ""

        self.info = {}
        self.time = time.time()

        ###############################
        if inputobj is None:
            self.dataset = vtki.vtkRectilinearGrid()

        elif isinstance(inputobj, vtki.vtkRectilinearGrid):
            self.dataset = inputobj

        elif isinstance(inputobj, RectilinearGrid):
            self.dataset = inputobj.dataset

        elif isinstance(inputobj, str):
            if "https://" in inputobj:
                inputobj = download(inputobj, verbose=False)
            if inputobj.endswith(".vtr"):
                reader = vtki.new("XMLRectilinearGridReader")
            else:
                reader = vtki.new("RectilinearGridReader")
            self.filename = inputobj
            reader.SetFileName(inputobj)
            reader.Update()
            self.dataset = reader.GetOutput()

        elif utils.is_sequence(inputobj):
            self.dataset = vtki.vtkRectilinearGrid()
            xcoords, ycoords, zcoords = inputobj
            nx, ny, nz = len(xcoords), len(ycoords), len(zcoords)
            self.dataset.SetDimensions(nx, ny, nz)
            self.dataset.SetXCoordinates(utils.numpy2vtk(xcoords))
            self.dataset.SetYCoordinates(utils.numpy2vtk(ycoords))
            self.dataset.SetZCoordinates(utils.numpy2vtk(zcoords))

        ###############################

        if not self.dataset:
            vedo.logger.error(
                f"RectilinearGrid: cannot understand input type {type(inputobj)}"
            )
            return

        self.properties.SetColor(0.352, 0.612, 0.996)  # blue7

        self.pipeline = utils.OperationNode(
            self, comment=f"#tets {self.dataset.GetNumberOfCells()}", c="#9e2a2b"
        )

    @property
    def actor(self):
        """Return the `vtkActor` of the object."""
        gf = vtki.new("GeometryFilter")
        gf.SetInputData(self.dataset)
        gf.Update()
        self.mapper.SetInputData(gf.GetOutput())
        self.mapper.Modified()
        return self._actor

    @actor.setter
    def actor(self, _):
        pass

    def _update(self, data, reset_locators=False):
        self.dataset = data
        if reset_locators:
            self.cell_locator = None
            self.point_locator = None
        return self

    ##################################################################
    def __str__(self):
        return summary_string(self, self._summary_rows(), color="cyan")

    def __repr__(self):
        return self.__str__()

    def __rich__(self):
        return summary_panel(self, self._summary_rows(), color="cyan")

    def _summary_rows(self):
        rows = [("name", str(self.name))]
        if self.filename:
            rows.append(("filename", str(self.filename)))
        rows.append(("dimensions", str(self.dataset.GetDimensions())))
        rows.append(("center", utils.precision(self.dataset.GetCenter(), 6)))
        rows.append(("bounds", format_bounds(self.bounds(), utils.precision)))
        rows.append(
            (
                "memory size",
                utils.precision(self.dataset.GetActualMemorySize() / 1024, 2) + " MB",
            )
        )

        for key in self.pointdata.keys():
            arr = self.pointdata[key]
            label = active_array_label(self.dataset, "point", key, "pointdata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(arr, utils.precision, dim_label="ndim"),
                )
            )

        for key in self.celldata.keys():
            arr = self.celldata[key]
            label = active_array_label(self.dataset, "cell", key, "celldata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(arr, utils.precision, dim_label="ndim"),
                )
            )

        for key in self.metadata.keys():
            arr = self.metadata[key]
            rows.append(("metadata", f'"{key}" ({len(arr)} values)'))

        return rows

    def _repr_html_(self):
        """
        HTML representation of the RectilinearGrid object for Jupyter Notebooks.

        Returns:
            HTML text with the image and some properties.
        """
        import io
        import base64
        from PIL import Image

        library_name = "vedo.grids.RectilinearGrid"
        help_url = "https://vedo.embl.es/docs/vedo/grids.html#RectilinearGrid"

        m = self.tomesh().linewidth(1).lighting("off")
        arr = m.thumbnail(zoom=1, elevation=-30, azimuth=-30)

        im = Image.fromarray(arr)
        buffered = io.BytesIO()
        im.save(buffered, format="PNG", quality=100)
        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
        url = "data:image/png;base64," + encoded
        image = f"<img src='{url}'></img>"

        bounds = "<br/>".join(
            [
                utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4)
                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
            ]
        )

        help_text = ""
        if self.name:
            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
        help_text += (
            '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
        )
        if self.filename:
            dots = ""
            if len(self.filename) > 30:
                dots = "..."
            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"

        pdata = ""
        if self.dataset.GetPointData().GetScalars():
            if self.dataset.GetPointData().GetScalars().GetName():
                name = self.dataset.GetPointData().GetScalars().GetName()
                pdata = (
                    "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>"
                )

        cdata = ""
        if self.dataset.GetCellData().GetScalars():
            if self.dataset.GetCellData().GetScalars().GetName():
                name = self.dataset.GetCellData().GetScalars().GetName()
                cdata = (
                    "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>"
                )

        pts = self.coordinates
        cm = np.mean(pts, axis=0)

        _all = [
            "<table>",
            "<tr>",
            "<td>",
            image,
            "</td>",
            "<td style='text-align: center; vertical-align: center;'><br/>",
            help_text,
            "<table>",
            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>"
            + str(bounds)
            + "</td></tr>",
            "<tr><td><b> center of mass </b></td><td>"
            + utils.precision(cm, 3)
            + "</td></tr>",
            "<tr><td><b> nr. points&nbsp/&nbspcells </b></td><td>"
            + str(self.npoints)
            + "&nbsp/&nbsp"
            + str(self.ncells)
            + "</td></tr>",
            pdata,
            cdata,
            "</table>",
            "</table>",
        ]
        return "\n".join(_all)

    def dimensions(self) -> np.ndarray:
        """Return the number of points in the x, y and z directions."""
        return np.array(self.dataset.GetDimensions())

    def x_coordinates(self) -> np.ndarray:
        """Return the x-coordinates of the grid."""
        return utils.vtk2numpy(self.dataset.GetXCoordinates())

    def y_coordinates(self) -> np.ndarray:
        """Return the y-coordinates of the grid."""
        return utils.vtk2numpy(self.dataset.GetYCoordinates())

    def z_coordinates(self) -> np.ndarray:
        """Return the z-coordinates of the grid."""
        return utils.vtk2numpy(self.dataset.GetZCoordinates())

    def is_point_visible(self, pid: int) -> bool:
        """Return True if point `pid` is visible."""
        return self.dataset.IsPointVisible(pid)

    def is_cell_visible(self, cid: int) -> bool:
        """Return True if cell `cid` is visible."""
        return self.dataset.IsCellVisible(cid)

    def has_blank_points(self) -> bool:
        """Return True if the grid has blank points."""
        return self.dataset.HasAnyBlankPoints()

    def has_blank_cells(self) -> bool:
        """Return True if the grid has blank cells."""
        return self.dataset.HasAnyBlankCells()

    def compute_structured_coords(self, x: list) -> dict:
        """
        Convenience function computes the structured coordinates for a point `x`.

        This method returns a dictionary with keys `ijk`, `pcoords` and `inside`.
        The cell is specified by the array `ijk`.
        and the parametric coordinates in the cell are specified with `pcoords`.
        Value of `inside` is False if the point x is outside of the grid.
        """
        ijk = [0, 0, 0]
        pcoords = [0.0, 0.0, 0.0]
        inout = self.dataset.ComputeStructuredCoordinates(x, ijk, pcoords)
        return {
            "ijk": np.array(ijk),
            "pcoords": np.array(pcoords),
            "inside": bool(inout),
        }

    def compute_pointid(self, ijk: int) -> int:
        """Given a location in structured coordinates (i-j-k), return the point id."""
        return self.dataset.ComputePointId(ijk)

    def compute_cellid(self, ijk: int) -> int:
        """Given a location in structured coordinates (i-j-k), return the cell id."""
        return self.dataset.ComputeCellId(ijk)

    def find_point(self, x: list) -> int:
        """Given a position `x`, return the id of the closest point."""
        return self.dataset.FindPoint(x)

    def find_cell(self, x: list) -> dict:
        """Given a position `x`, return the id of the closest cell."""
        cell = vtki.vtkHexagonalPrism()
        cellid = vtki.mutable(0)
        tol2 = 0.001  # vtki.mutable(0)
        subid = vtki.mutable(0)
        pcoords = [0.0, 0.0, 0.0]
        weights = [0.0, 0.0, 0.0]
        res = self.dataset.FindCell(x, cell, cellid, tol2, subid, pcoords, weights)
        result = {}
        result["cellid"] = cellid
        result["subid"] = subid
        result["pcoords"] = pcoords
        result["weights"] = weights
        result["status"] = res
        return result

    def clone(self, deep=True) -> RectilinearGrid:
        """Return a clone copy of the RectilinearGrid. Alias of `copy()`."""
        if deep:
            newrg = vtki.vtkRectilinearGrid()
            newrg.CopyStructure(self.dataset)
            newrg.CopyAttributes(self.dataset)
            newvol = RectilinearGrid(newrg)
        else:
            newvol = RectilinearGrid(self.dataset)

        prop = vtki.vtkProperty()
        prop.DeepCopy(self.properties)
        newvol.actor.SetProperty(prop)
        newvol.properties = prop
        newvol.pipeline = utils.OperationNode(
            "clone", parents=[self], c="#bbd0ff", shape="diamond"
        )
        return newvol

    def bounds(self) -> np.ndarray:
        """
        Get the object bounds.
        Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`.
        """
        # OVERRIDE CommonAlgorithms.bounds() which is too slow
        return np.array(self.dataset.GetBounds())

    def isosurface(self, value=None) -> vedo.Mesh:
        """
        Return a `Mesh` isosurface extracted from the object.

        Set `value` as single float or list of values to draw the isosurface(s).
        """
        scrange = self.dataset.GetScalarRange()

        cf = vtki.new("ContourFilter")
        cf.UseScalarTreeOn()
        cf.SetInputData(self.dataset)
        cf.ComputeNormalsOn()

        if value is None:
            value = (2 * scrange[0] + scrange[1]) / 3.0
            # print("automatic isosurface value =", value)
            cf.SetValue(0, value)
        else:
            if utils.is_sequence(value):
                cf.SetNumberOfContours(len(value))
                for i, t in enumerate(value):
                    cf.SetValue(i, t)
            else:
                cf.SetValue(0, value)

        cf.Update()
        poly = cf.GetOutput()

        out = vedo.mesh.Mesh(poly, c=None).phong()
        out.mapper.SetScalarRange(scrange[0], scrange[1])

        out.pipeline = utils.OperationNode(
            "isosurface",
            parents=[self],
            comment=f"#pts {out.dataset.GetNumberOfPoints()}",
            c="#4cc9f0:#e9c46a",
        )
        return out

    def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> UnstructuredGrid:
        """
        Cut the object with the plane defined by a point and a normal.

        Args:
            origin (list):
                the cutting plane goes through this point
            normal (list, str):
                normal vector to the cutting plane
        """
        strn = str(normal)
        if strn == "x":
            normal = (1, 0, 0)
        elif strn == "y":
            normal = (0, 1, 0)
        elif strn == "z":
            normal = (0, 0, 1)
        elif strn == "-x":
            normal = (-1, 0, 0)
        elif strn == "-y":
            normal = (0, -1, 0)
        elif strn == "-z":
            normal = (0, 0, -1)
        plane = vtki.new("Plane")
        plane.SetOrigin(origin)
        plane.SetNormal(normal)
        clipper = vtki.new("ClipDataSet")
        clipper.SetInputData(self.dataset)
        clipper.SetClipFunction(plane)
        clipper.GenerateClipScalarsOff()
        clipper.GenerateClippedOutputOff()
        clipper.SetValue(0)
        clipper.Update()
        cout = clipper.GetOutput()
        ug = vedo.UnstructuredGrid(cout)
        if isinstance(self, UnstructuredGrid):
            self._update(cout)
            self.pipeline = utils.OperationNode(
                "cut_with_plane", parents=[self], c="#9e2a2b"
            )
            return self
        ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b")
        return ug

    def cut_with_mesh(
        self, mesh, invert=False, whole_cells=False, on_boundary=False
    ) -> UnstructuredGrid:
        """
        Cut a `RectilinearGrid` with a `Mesh`.

        Use `invert` to return cut off part of the input object.
        """
        ug = self.dataset

        ippd = vtki.new("ImplicitPolyDataDistance")
        ippd.SetInput(mesh.dataset)

        if whole_cells or on_boundary:
            clipper = vtki.new("ExtractGeometry")
            clipper.SetInputData(ug)
            clipper.SetImplicitFunction(ippd)
            clipper.SetExtractInside(not invert)
            clipper.SetExtractBoundaryCells(False)
            if on_boundary:
                clipper.SetExtractBoundaryCells(True)
                clipper.SetExtractOnlyBoundaryCells(True)
        else:
            signed_dists = vtki.vtkFloatArray()
            signed_dists.SetNumberOfComponents(1)
            signed_dists.SetName("SignedDistance")
            for pointId in range(ug.GetNumberOfPoints()):
                p = ug.GetPoint(pointId)
                signed_dist = ippd.EvaluateFunction(p)
                signed_dists.InsertNextValue(signed_dist)
            ug.GetPointData().AddArray(signed_dists)
            ug.GetPointData().SetActiveScalars("SignedDistance")  # NEEDED
            clipper = vtki.new("ClipDataSet")
            clipper.SetInputData(ug)
            clipper.SetInsideOut(not invert)
            clipper.SetValue(0.0)

        clipper.Update()

        out = UnstructuredGrid(clipper.GetOutput())
        out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b")
        return out

actor property writable

Return the vtkActor of the object.

bounds()

Get the object bounds. Returns a list in format [xmin,xmax, ymin,ymax, zmin,zmax].

Source code in vedo/grids/rectilinear.py
394
395
396
397
398
399
400
def bounds(self) -> np.ndarray:
    """
    Get the object bounds.
    Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`.
    """
    # OVERRIDE CommonAlgorithms.bounds() which is too slow
    return np.array(self.dataset.GetBounds())

clone(deep=True)

Return a clone copy of the RectilinearGrid. Alias of copy().

Source code in vedo/grids/rectilinear.py
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
def clone(self, deep=True) -> RectilinearGrid:
    """Return a clone copy of the RectilinearGrid. Alias of `copy()`."""
    if deep:
        newrg = vtki.vtkRectilinearGrid()
        newrg.CopyStructure(self.dataset)
        newrg.CopyAttributes(self.dataset)
        newvol = RectilinearGrid(newrg)
    else:
        newvol = RectilinearGrid(self.dataset)

    prop = vtki.vtkProperty()
    prop.DeepCopy(self.properties)
    newvol.actor.SetProperty(prop)
    newvol.properties = prop
    newvol.pipeline = utils.OperationNode(
        "clone", parents=[self], c="#bbd0ff", shape="diamond"
    )
    return newvol

compute_cellid(ijk)

Given a location in structured coordinates (i-j-k), return the cell id.

Source code in vedo/grids/rectilinear.py
350
351
352
def compute_cellid(self, ijk: int) -> int:
    """Given a location in structured coordinates (i-j-k), return the cell id."""
    return self.dataset.ComputeCellId(ijk)

compute_pointid(ijk)

Given a location in structured coordinates (i-j-k), return the point id.

Source code in vedo/grids/rectilinear.py
346
347
348
def compute_pointid(self, ijk: int) -> int:
    """Given a location in structured coordinates (i-j-k), return the point id."""
    return self.dataset.ComputePointId(ijk)

compute_structured_coords(x)

Convenience function computes the structured coordinates for a point x.

This method returns a dictionary with keys ijk, pcoords and inside. The cell is specified by the array ijk. and the parametric coordinates in the cell are specified with pcoords. Value of inside is False if the point x is outside of the grid.

Source code in vedo/grids/rectilinear.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
def compute_structured_coords(self, x: list) -> dict:
    """
    Convenience function computes the structured coordinates for a point `x`.

    This method returns a dictionary with keys `ijk`, `pcoords` and `inside`.
    The cell is specified by the array `ijk`.
    and the parametric coordinates in the cell are specified with `pcoords`.
    Value of `inside` is False if the point x is outside of the grid.
    """
    ijk = [0, 0, 0]
    pcoords = [0.0, 0.0, 0.0]
    inout = self.dataset.ComputeStructuredCoordinates(x, ijk, pcoords)
    return {
        "ijk": np.array(ijk),
        "pcoords": np.array(pcoords),
        "inside": bool(inout),
    }

cut_with_mesh(mesh, invert=False, whole_cells=False, on_boundary=False)

Cut a RectilinearGrid with a Mesh.

Use invert to return cut off part of the input object.

Source code in vedo/grids/rectilinear.py
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
def cut_with_mesh(
    self, mesh, invert=False, whole_cells=False, on_boundary=False
) -> UnstructuredGrid:
    """
    Cut a `RectilinearGrid` with a `Mesh`.

    Use `invert` to return cut off part of the input object.
    """
    ug = self.dataset

    ippd = vtki.new("ImplicitPolyDataDistance")
    ippd.SetInput(mesh.dataset)

    if whole_cells or on_boundary:
        clipper = vtki.new("ExtractGeometry")
        clipper.SetInputData(ug)
        clipper.SetImplicitFunction(ippd)
        clipper.SetExtractInside(not invert)
        clipper.SetExtractBoundaryCells(False)
        if on_boundary:
            clipper.SetExtractBoundaryCells(True)
            clipper.SetExtractOnlyBoundaryCells(True)
    else:
        signed_dists = vtki.vtkFloatArray()
        signed_dists.SetNumberOfComponents(1)
        signed_dists.SetName("SignedDistance")
        for pointId in range(ug.GetNumberOfPoints()):
            p = ug.GetPoint(pointId)
            signed_dist = ippd.EvaluateFunction(p)
            signed_dists.InsertNextValue(signed_dist)
        ug.GetPointData().AddArray(signed_dists)
        ug.GetPointData().SetActiveScalars("SignedDistance")  # NEEDED
        clipper = vtki.new("ClipDataSet")
        clipper.SetInputData(ug)
        clipper.SetInsideOut(not invert)
        clipper.SetValue(0.0)

    clipper.Update()

    out = UnstructuredGrid(clipper.GetOutput())
    out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b")
    return out

cut_with_plane(origin=(0, 0, 0), normal='x')

Cut the object with the plane defined by a point and a normal.

Parameters:

Name Type Description Default
origin list

the cutting plane goes through this point

(0, 0, 0)
normal (list, str)

normal vector to the cutting plane

'x'
Source code in vedo/grids/rectilinear.py
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> UnstructuredGrid:
    """
    Cut the object with the plane defined by a point and a normal.

    Args:
        origin (list):
            the cutting plane goes through this point
        normal (list, str):
            normal vector to the cutting plane
    """
    strn = str(normal)
    if strn == "x":
        normal = (1, 0, 0)
    elif strn == "y":
        normal = (0, 1, 0)
    elif strn == "z":
        normal = (0, 0, 1)
    elif strn == "-x":
        normal = (-1, 0, 0)
    elif strn == "-y":
        normal = (0, -1, 0)
    elif strn == "-z":
        normal = (0, 0, -1)
    plane = vtki.new("Plane")
    plane.SetOrigin(origin)
    plane.SetNormal(normal)
    clipper = vtki.new("ClipDataSet")
    clipper.SetInputData(self.dataset)
    clipper.SetClipFunction(plane)
    clipper.GenerateClipScalarsOff()
    clipper.GenerateClippedOutputOff()
    clipper.SetValue(0)
    clipper.Update()
    cout = clipper.GetOutput()
    ug = vedo.UnstructuredGrid(cout)
    if isinstance(self, UnstructuredGrid):
        self._update(cout)
        self.pipeline = utils.OperationNode(
            "cut_with_plane", parents=[self], c="#9e2a2b"
        )
        return self
    ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b")
    return ug

dimensions()

Return the number of points in the x, y and z directions.

Source code in vedo/grids/rectilinear.py
296
297
298
def dimensions(self) -> np.ndarray:
    """Return the number of points in the x, y and z directions."""
    return np.array(self.dataset.GetDimensions())

find_cell(x)

Given a position x, return the id of the closest cell.

Source code in vedo/grids/rectilinear.py
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
def find_cell(self, x: list) -> dict:
    """Given a position `x`, return the id of the closest cell."""
    cell = vtki.vtkHexagonalPrism()
    cellid = vtki.mutable(0)
    tol2 = 0.001  # vtki.mutable(0)
    subid = vtki.mutable(0)
    pcoords = [0.0, 0.0, 0.0]
    weights = [0.0, 0.0, 0.0]
    res = self.dataset.FindCell(x, cell, cellid, tol2, subid, pcoords, weights)
    result = {}
    result["cellid"] = cellid
    result["subid"] = subid
    result["pcoords"] = pcoords
    result["weights"] = weights
    result["status"] = res
    return result

find_point(x)

Given a position x, return the id of the closest point.

Source code in vedo/grids/rectilinear.py
354
355
356
def find_point(self, x: list) -> int:
    """Given a position `x`, return the id of the closest point."""
    return self.dataset.FindPoint(x)

has_blank_cells()

Return True if the grid has blank cells.

Source code in vedo/grids/rectilinear.py
324
325
326
def has_blank_cells(self) -> bool:
    """Return True if the grid has blank cells."""
    return self.dataset.HasAnyBlankCells()

has_blank_points()

Return True if the grid has blank points.

Source code in vedo/grids/rectilinear.py
320
321
322
def has_blank_points(self) -> bool:
    """Return True if the grid has blank points."""
    return self.dataset.HasAnyBlankPoints()

is_cell_visible(cid)

Return True if cell cid is visible.

Source code in vedo/grids/rectilinear.py
316
317
318
def is_cell_visible(self, cid: int) -> bool:
    """Return True if cell `cid` is visible."""
    return self.dataset.IsCellVisible(cid)

is_point_visible(pid)

Return True if point pid is visible.

Source code in vedo/grids/rectilinear.py
312
313
314
def is_point_visible(self, pid: int) -> bool:
    """Return True if point `pid` is visible."""
    return self.dataset.IsPointVisible(pid)

isosurface(value=None)

Return a Mesh isosurface extracted from the object.

Set value as single float or list of values to draw the isosurface(s).

Source code in vedo/grids/rectilinear.py
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
def isosurface(self, value=None) -> vedo.Mesh:
    """
    Return a `Mesh` isosurface extracted from the object.

    Set `value` as single float or list of values to draw the isosurface(s).
    """
    scrange = self.dataset.GetScalarRange()

    cf = vtki.new("ContourFilter")
    cf.UseScalarTreeOn()
    cf.SetInputData(self.dataset)
    cf.ComputeNormalsOn()

    if value is None:
        value = (2 * scrange[0] + scrange[1]) / 3.0
        # print("automatic isosurface value =", value)
        cf.SetValue(0, value)
    else:
        if utils.is_sequence(value):
            cf.SetNumberOfContours(len(value))
            for i, t in enumerate(value):
                cf.SetValue(i, t)
        else:
            cf.SetValue(0, value)

    cf.Update()
    poly = cf.GetOutput()

    out = vedo.mesh.Mesh(poly, c=None).phong()
    out.mapper.SetScalarRange(scrange[0], scrange[1])

    out.pipeline = utils.OperationNode(
        "isosurface",
        parents=[self],
        comment=f"#pts {out.dataset.GetNumberOfPoints()}",
        c="#4cc9f0:#e9c46a",
    )
    return out

x_coordinates()

Return the x-coordinates of the grid.

Source code in vedo/grids/rectilinear.py
300
301
302
def x_coordinates(self) -> np.ndarray:
    """Return the x-coordinates of the grid."""
    return utils.vtk2numpy(self.dataset.GetXCoordinates())

y_coordinates()

Return the y-coordinates of the grid.

Source code in vedo/grids/rectilinear.py
304
305
306
def y_coordinates(self) -> np.ndarray:
    """Return the y-coordinates of the grid."""
    return utils.vtk2numpy(self.dataset.GetYCoordinates())

z_coordinates()

Return the z-coordinates of the grid.

Source code in vedo/grids/rectilinear.py
308
309
310
def z_coordinates(self) -> np.ndarray:
    """Return the z-coordinates of the grid."""
    return utils.vtk2numpy(self.dataset.GetZCoordinates())

structured

StructuredGrid

Bases: PointAlgorithms, MeshVisual

Build a structured grid.

Source code in vedo/grids/structured.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
class StructuredGrid(PointAlgorithms, MeshVisual):
    """
    Build a structured grid.
    """

    def __init__(self, inputobj=None):
        """
        A StructuredGrid is a dataset where edges of the hexahedrons are
        not necessarily parallel to the coordinate axes.
        It can be thought of as a tessellation of a block of 3D space,
        similar to a `RectilinearGrid`
        except that the cells are not necessarily cubes, they can have different
        orientations but are connected in the same way as a `RectilinearGrid`.

        Args:
            inputobj (vtkStructuredGrid, list, str):
                list of points and indices, or filename

        Examples:
            ```python
            from vedo import *
            sgrid = StructuredGrid(dataurl+"structgrid.vts")
            print(sgrid)
            msh = sgrid.tomesh().lw(1)
            show(msh, axes=1, viewup="z")
            ```

            ```python
            from vedo import *

            cx = np.sqrt(np.linspace(100, 400, 10))
            cy = np.linspace(30, 40, 20)
            cz = np.linspace(40, 50, 30)
            x, y, z = np.meshgrid(cx, cy, cz)

            sgrid1 = StructuredGrid([x, y, z])
            sgrid1.cmap("viridis", sgrid1.coordinates[:, 0])
            print(sgrid1)

            sgrid2 = sgrid1.clone().cut_with_plane(normal=(-1,1,1), origin=[14,34,44])
            msh2 = sgrid2.tomesh(shrink=0.9).lw(1).cmap("viridis")

            show(
                [["StructuredGrid", sgrid1], ["Shrinked Mesh", msh2]],
                N=2, axes=1, viewup="z",
            )
            ```
        """

        super().__init__()

        self.dataset = None

        self.mapper = vtki.new("PolyDataMapper")
        self._actor = vtki.vtkActor()
        self._actor.retrieve_object = weak_ref_to(self)
        self._actor.SetMapper(self.mapper)
        self.properties = self._actor.GetProperty()

        self.transform = LinearTransform()
        self.point_locator = None
        self.cell_locator = None
        self.line_locator = None

        self.name = "StructuredGrid"
        self.filename = ""

        self.info = {}
        self.time = time.time()

        ###############################
        if inputobj is None:
            self.dataset = vtki.vtkStructuredGrid()

        elif isinstance(inputobj, vtki.vtkStructuredGrid):
            self.dataset = inputobj

        elif isinstance(inputobj, StructuredGrid):
            self.dataset = inputobj.dataset

        elif isinstance(inputobj, str):
            if "https://" in inputobj:
                inputobj = download(inputobj, verbose=False)
            if inputobj.endswith(".vts"):
                reader = vtki.new("XMLStructuredGridReader")
            else:
                reader = vtki.new("StructuredGridReader")
            self.filename = inputobj
            reader.SetFileName(inputobj)
            reader.Update()
            self.dataset = reader.GetOutput()

        elif utils.is_sequence(inputobj):
            self.dataset = vtki.vtkStructuredGrid()
            x, y, z = inputobj
            xyz = np.vstack(
                (x.flatten(order="F"), y.flatten(order="F"), z.flatten(order="F"))
            ).T
            dims = x.shape
            self.dataset.SetDimensions(dims)
            # self.dataset.SetDimensions(dims[1], dims[0], dims[2])
            vpoints = vtki.vtkPoints()
            vpoints.SetData(utils.numpy2vtk(xyz))
            self.dataset.SetPoints(vpoints)

        ###############################
        if not self.dataset:
            vedo.logger.error(
                f"StructuredGrid: cannot understand input type {type(inputobj)}"
            )
            return

        self.properties.SetColor(0.352, 0.612, 0.996)  # blue7

        self.pipeline = utils.OperationNode(
            self, comment=f"#tets {self.dataset.GetNumberOfCells()}", c="#9e2a2b"
        )

    @property
    def actor(self):
        """Return the `vtkActor` of the object."""
        gf = vtki.new("GeometryFilter")
        gf.SetInputData(self.dataset)
        gf.Update()
        self.mapper.SetInputData(gf.GetOutput())
        self.mapper.Modified()
        return self._actor

    @actor.setter
    def actor(self, _):
        pass

    def _update(self, data, reset_locators=False):
        self.dataset = data
        if reset_locators:
            self.cell_locator = None
            self.point_locator = None
        return self

    ##################################################################
    def __str__(self):
        return summary_string(self, self._summary_rows(), color="cyan")

    def __repr__(self):
        return self.__str__()

    def __rich__(self):
        return summary_panel(self, self._summary_rows(), color="cyan")

    def _summary_rows(self):
        rows = [("name", str(self.name))]
        if self.filename:
            rows.append(("filename", str(self.filename)))
        rows.append(("dimensions", str(self.dimensions())))
        rows.append(("center", utils.precision(self.dataset.GetCenter(), 6)))
        rows.append(("bounds", format_bounds(self.bounds(), utils.precision)))
        rows.append(
            (
                "memory size",
                utils.precision(self.dataset.GetActualMemorySize() / 1024, 2) + " MB",
            )
        )

        for key in self.pointdata.keys():
            arr = self.pointdata[key]
            label = active_array_label(self.dataset, "point", key, "pointdata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(arr, utils.precision, dim_label="ndim"),
                )
            )

        for key in self.celldata.keys():
            arr = self.celldata[key]
            label = active_array_label(self.dataset, "cell", key, "celldata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(arr, utils.precision, dim_label="ndim"),
                )
            )

        for key in self.metadata.keys():
            arr = self.metadata[key]
            rows.append(("metadata", f'"{key}" ({len(arr)} values)'))

        return rows

    def _repr_html_(self):
        """
        HTML representation of the StructuredGrid object for Jupyter Notebooks.

        Returns:
            HTML text with the image and some properties.
        """
        import io
        import base64
        from PIL import Image

        library_name = "vedo.grids.StructuredGrid"
        help_url = "https://vedo.embl.es/docs/vedo/grids.html#StructuredGrid"

        m = self.tomesh().linewidth(1).lighting("off")
        arr = m.thumbnail(zoom=1, elevation=-30, azimuth=-30)

        im = Image.fromarray(arr)
        buffered = io.BytesIO()
        im.save(buffered, format="PNG", quality=100)
        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
        url = "data:image/png;base64," + encoded
        image = f"<img src='{url}'></img>"

        bounds = "<br/>".join(
            [
                utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4)
                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
            ]
        )

        help_text = ""
        if self.name:
            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
        help_text += (
            '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
        )
        if self.filename:
            dots = ""
            if len(self.filename) > 30:
                dots = "..."
            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"

        pdata = ""
        if self.dataset.GetPointData().GetScalars():
            if self.dataset.GetPointData().GetScalars().GetName():
                name = self.dataset.GetPointData().GetScalars().GetName()
                pdata = (
                    "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>"
                )

        cdata = ""
        if self.dataset.GetCellData().GetScalars():
            if self.dataset.GetCellData().GetScalars().GetName():
                name = self.dataset.GetCellData().GetScalars().GetName()
                cdata = (
                    "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>"
                )

        pts = self.coordinates
        cm = np.mean(pts, axis=0)

        _all = [
            "<table>",
            "<tr>",
            "<td>",
            image,
            "</td>",
            "<td style='text-align: center; vertical-align: center;'><br/>",
            help_text,
            "<table>",
            "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>"
            + str(bounds)
            + "</td></tr>",
            "<tr><td><b> center of mass </b></td><td>"
            + utils.precision(cm, 3)
            + "</td></tr>",
            "<tr><td><b> nr. points&nbsp/&nbspcells </b></td><td>"
            + str(self.npoints)
            + "&nbsp/&nbsp"
            + str(self.ncells)
            + "</td></tr>",
            pdata,
            cdata,
            "</table>",
            "</table>",
        ]
        return "\n".join(_all)

    def dimensions(self) -> np.ndarray:
        """Return the number of points in the x, y and z directions."""
        try:
            dims = [0, 0, 0]
            self.dataset.GetDimensions(dims)
        except Exception:
            dims = self.dataset.GetDimensions()
        return np.array(dims)

    def clone(self, deep=True) -> StructuredGrid:
        """Return a clone copy of the StructuredGrid. Alias of `copy()`."""
        if deep:
            newrg = vtki.vtkStructuredGrid()
            newrg.CopyStructure(self.dataset)
            newrg.CopyAttributes(self.dataset)
            newvol = StructuredGrid(newrg)
        else:
            newvol = StructuredGrid(self.dataset)

        prop = vtki.vtkProperty()
        prop.DeepCopy(self.properties)
        newvol.actor.SetProperty(prop)
        newvol.properties = prop
        newvol.pipeline = utils.OperationNode(
            "clone", parents=[self], c="#bbd0ff", shape="diamond"
        )
        return newvol

    def find_point(self, x: list) -> int:
        """Given a position `x`, return the id of the closest point."""
        return self.dataset.FindPoint(x)

    def find_cell(self, x: list) -> dict:
        """Given a position `x`, return the id of the closest cell."""
        cell = vtki.vtkHexagonalPrism()
        cellid = vtki.mutable(0)
        tol2 = 0.001  # vtki.mutable(0)
        subid = vtki.mutable(0)
        pcoords = [0.0, 0.0, 0.0]
        weights = [0.0, 0.0, 0.0]
        res = self.dataset.FindCell(x, cell, cellid, tol2, subid, pcoords, weights)
        result = {}
        result["cellid"] = cellid
        result["subid"] = subid
        result["pcoords"] = pcoords
        result["weights"] = weights
        result["status"] = res
        return result

    def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> vedo.UnstructuredGrid:
        """
        Cut the object with the plane defined by a point and a normal.

        Args:
            origin (list):
                the cutting plane goes through this point
            normal (list, str):
                normal vector to the cutting plane

        Returns an `UnstructuredGrid` object.
        """
        strn = str(normal)
        if strn == "x":
            normal = (1, 0, 0)
        elif strn == "y":
            normal = (0, 1, 0)
        elif strn == "z":
            normal = (0, 0, 1)
        elif strn == "-x":
            normal = (-1, 0, 0)
        elif strn == "-y":
            normal = (0, -1, 0)
        elif strn == "-z":
            normal = (0, 0, -1)
        plane = vtki.new("Plane")
        plane.SetOrigin(origin)
        plane.SetNormal(normal)
        clipper = vtki.new("ClipDataSet")
        clipper.SetInputData(self.dataset)
        clipper.SetClipFunction(plane)
        clipper.GenerateClipScalarsOff()
        clipper.GenerateClippedOutputOff()
        clipper.SetValue(0)
        clipper.Update()
        cout = clipper.GetOutput()
        ug = vedo.UnstructuredGrid(cout)
        if isinstance(self, vedo.UnstructuredGrid):
            self._update(cout)
            self.pipeline = utils.OperationNode(
                "cut_with_plane", parents=[self], c="#9e2a2b"
            )
            return self
        ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b")
        return ug

    def cut_with_mesh(
        self, mesh: Mesh, invert=False, whole_cells=False, on_boundary=False
    ) -> UnstructuredGrid:
        """
        Cut a `RectilinearGrid` with a `Mesh`.

        Use `invert` to return cut off part of the input object.

        Returns an `UnstructuredGrid` object.
        """
        ug = self.dataset

        ippd = vtki.new("ImplicitPolyDataDistance")
        ippd.SetInput(mesh.dataset)

        if whole_cells or on_boundary:
            clipper = vtki.new("ExtractGeometry")
            clipper.SetInputData(ug)
            clipper.SetImplicitFunction(ippd)
            clipper.SetExtractInside(not invert)
            clipper.SetExtractBoundaryCells(False)
            if on_boundary:
                clipper.SetExtractBoundaryCells(True)
                clipper.SetExtractOnlyBoundaryCells(True)
        else:
            signed_dists = vtki.vtkFloatArray()
            signed_dists.SetNumberOfComponents(1)
            signed_dists.SetName("SignedDistance")
            for pointId in range(ug.GetNumberOfPoints()):
                p = ug.GetPoint(pointId)
                signed_dist = ippd.EvaluateFunction(p)
                signed_dists.InsertNextValue(signed_dist)
            ug.GetPointData().AddArray(signed_dists)
            ug.GetPointData().SetActiveScalars("SignedDistance")  # NEEDED
            clipper = vtki.new("ClipDataSet")
            clipper.SetInputData(ug)
            clipper.SetInsideOut(not invert)
            clipper.SetValue(0.0)

        clipper.Update()

        out = UnstructuredGrid(clipper.GetOutput())
        out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b")
        return out

    def isosurface(self, value=None) -> vedo.Mesh:
        """
        Return a `Mesh` isosurface extracted from the object.

        Set `value` as single float or list of values to draw the isosurface(s).
        """
        scrange = self.dataset.GetScalarRange()

        cf = vtki.new("ContourFilter")
        cf.UseScalarTreeOn()
        cf.SetInputData(self.dataset)
        cf.ComputeNormalsOn()

        if value is None:
            value = (2 * scrange[0] + scrange[1]) / 3.0
            # print("automatic isosurface value =", value)
            cf.SetValue(0, value)
        else:
            if utils.is_sequence(value):
                cf.SetNumberOfContours(len(value))
                for i, t in enumerate(value):
                    cf.SetValue(i, t)
            else:
                cf.SetValue(0, value)

        cf.Update()
        poly = cf.GetOutput()

        out = vedo.mesh.Mesh(poly, c=None).phong()
        out.mapper.SetScalarRange(scrange[0], scrange[1])

        out.pipeline = utils.OperationNode(
            "isosurface",
            parents=[self],
            comment=f"#pts {out.dataset.GetNumberOfPoints()}",
            c="#4cc9f0:#e9c46a",
        )
        return out

actor property writable

Return the vtkActor of the object.

clone(deep=True)

Return a clone copy of the StructuredGrid. Alias of copy().

Source code in vedo/grids/structured.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def clone(self, deep=True) -> StructuredGrid:
    """Return a clone copy of the StructuredGrid. Alias of `copy()`."""
    if deep:
        newrg = vtki.vtkStructuredGrid()
        newrg.CopyStructure(self.dataset)
        newrg.CopyAttributes(self.dataset)
        newvol = StructuredGrid(newrg)
    else:
        newvol = StructuredGrid(self.dataset)

    prop = vtki.vtkProperty()
    prop.DeepCopy(self.properties)
    newvol.actor.SetProperty(prop)
    newvol.properties = prop
    newvol.pipeline = utils.OperationNode(
        "clone", parents=[self], c="#bbd0ff", shape="diamond"
    )
    return newvol

cut_with_mesh(mesh, invert=False, whole_cells=False, on_boundary=False)

Cut a RectilinearGrid with a Mesh.

Use invert to return cut off part of the input object.

Returns an UnstructuredGrid object.

Source code in vedo/grids/structured.py
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
def cut_with_mesh(
    self, mesh: Mesh, invert=False, whole_cells=False, on_boundary=False
) -> UnstructuredGrid:
    """
    Cut a `RectilinearGrid` with a `Mesh`.

    Use `invert` to return cut off part of the input object.

    Returns an `UnstructuredGrid` object.
    """
    ug = self.dataset

    ippd = vtki.new("ImplicitPolyDataDistance")
    ippd.SetInput(mesh.dataset)

    if whole_cells or on_boundary:
        clipper = vtki.new("ExtractGeometry")
        clipper.SetInputData(ug)
        clipper.SetImplicitFunction(ippd)
        clipper.SetExtractInside(not invert)
        clipper.SetExtractBoundaryCells(False)
        if on_boundary:
            clipper.SetExtractBoundaryCells(True)
            clipper.SetExtractOnlyBoundaryCells(True)
    else:
        signed_dists = vtki.vtkFloatArray()
        signed_dists.SetNumberOfComponents(1)
        signed_dists.SetName("SignedDistance")
        for pointId in range(ug.GetNumberOfPoints()):
            p = ug.GetPoint(pointId)
            signed_dist = ippd.EvaluateFunction(p)
            signed_dists.InsertNextValue(signed_dist)
        ug.GetPointData().AddArray(signed_dists)
        ug.GetPointData().SetActiveScalars("SignedDistance")  # NEEDED
        clipper = vtki.new("ClipDataSet")
        clipper.SetInputData(ug)
        clipper.SetInsideOut(not invert)
        clipper.SetValue(0.0)

    clipper.Update()

    out = UnstructuredGrid(clipper.GetOutput())
    out.pipeline = utils.OperationNode("cut_with_mesh", parents=[self], c="#9e2a2b")
    return out

cut_with_plane(origin=(0, 0, 0), normal='x')

Cut the object with the plane defined by a point and a normal.

Parameters:

Name Type Description Default
origin list

the cutting plane goes through this point

(0, 0, 0)
normal (list, str)

normal vector to the cutting plane

'x'

Returns an UnstructuredGrid object.

Source code in vedo/grids/structured.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> vedo.UnstructuredGrid:
    """
    Cut the object with the plane defined by a point and a normal.

    Args:
        origin (list):
            the cutting plane goes through this point
        normal (list, str):
            normal vector to the cutting plane

    Returns an `UnstructuredGrid` object.
    """
    strn = str(normal)
    if strn == "x":
        normal = (1, 0, 0)
    elif strn == "y":
        normal = (0, 1, 0)
    elif strn == "z":
        normal = (0, 0, 1)
    elif strn == "-x":
        normal = (-1, 0, 0)
    elif strn == "-y":
        normal = (0, -1, 0)
    elif strn == "-z":
        normal = (0, 0, -1)
    plane = vtki.new("Plane")
    plane.SetOrigin(origin)
    plane.SetNormal(normal)
    clipper = vtki.new("ClipDataSet")
    clipper.SetInputData(self.dataset)
    clipper.SetClipFunction(plane)
    clipper.GenerateClipScalarsOff()
    clipper.GenerateClippedOutputOff()
    clipper.SetValue(0)
    clipper.Update()
    cout = clipper.GetOutput()
    ug = vedo.UnstructuredGrid(cout)
    if isinstance(self, vedo.UnstructuredGrid):
        self._update(cout)
        self.pipeline = utils.OperationNode(
            "cut_with_plane", parents=[self], c="#9e2a2b"
        )
        return self
    ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b")
    return ug

dimensions()

Return the number of points in the x, y and z directions.

Source code in vedo/grids/structured.py
310
311
312
313
314
315
316
317
def dimensions(self) -> np.ndarray:
    """Return the number of points in the x, y and z directions."""
    try:
        dims = [0, 0, 0]
        self.dataset.GetDimensions(dims)
    except Exception:
        dims = self.dataset.GetDimensions()
    return np.array(dims)

find_cell(x)

Given a position x, return the id of the closest cell.

Source code in vedo/grids/structured.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
def find_cell(self, x: list) -> dict:
    """Given a position `x`, return the id of the closest cell."""
    cell = vtki.vtkHexagonalPrism()
    cellid = vtki.mutable(0)
    tol2 = 0.001  # vtki.mutable(0)
    subid = vtki.mutable(0)
    pcoords = [0.0, 0.0, 0.0]
    weights = [0.0, 0.0, 0.0]
    res = self.dataset.FindCell(x, cell, cellid, tol2, subid, pcoords, weights)
    result = {}
    result["cellid"] = cellid
    result["subid"] = subid
    result["pcoords"] = pcoords
    result["weights"] = weights
    result["status"] = res
    return result

find_point(x)

Given a position x, return the id of the closest point.

Source code in vedo/grids/structured.py
338
339
340
def find_point(self, x: list) -> int:
    """Given a position `x`, return the id of the closest point."""
    return self.dataset.FindPoint(x)

isosurface(value=None)

Return a Mesh isosurface extracted from the object.

Set value as single float or list of values to draw the isosurface(s).

Source code in vedo/grids/structured.py
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
def isosurface(self, value=None) -> vedo.Mesh:
    """
    Return a `Mesh` isosurface extracted from the object.

    Set `value` as single float or list of values to draw the isosurface(s).
    """
    scrange = self.dataset.GetScalarRange()

    cf = vtki.new("ContourFilter")
    cf.UseScalarTreeOn()
    cf.SetInputData(self.dataset)
    cf.ComputeNormalsOn()

    if value is None:
        value = (2 * scrange[0] + scrange[1]) / 3.0
        # print("automatic isosurface value =", value)
        cf.SetValue(0, value)
    else:
        if utils.is_sequence(value):
            cf.SetNumberOfContours(len(value))
            for i, t in enumerate(value):
                cf.SetValue(i, t)
        else:
            cf.SetValue(0, value)

    cf.Update()
    poly = cf.GetOutput()

    out = vedo.mesh.Mesh(poly, c=None).phong()
    out.mapper.SetScalarRange(scrange[0], scrange[1])

    out.pipeline = utils.OperationNode(
        "isosurface",
        parents=[self],
        comment=f"#pts {out.dataset.GetNumberOfPoints()}",
        c="#4cc9f0:#e9c46a",
    )
    return out

explicit

ExplicitStructuredGrid

Build an explicit structured grid.

An explicit structured grid is a dataset where edges of the hexahedrons are not necessarily parallel to the coordinate axes. It can be thought of as a tessellation of a block of 3D space, similar to a RectilinearGrid except that the cells are not necessarily cubes, they can have different orientations but are connected in the same way as a RectilinearGrid.

Parameters:

Name Type Description Default
inputobj (vtkExplicitStructuredGrid, list, str)

list of points and indices, or filename

None
Source code in vedo/grids/explicit.py
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
class ExplicitStructuredGrid:
    """
    Build an explicit structured grid.

    An explicit structured grid is a dataset where edges of the hexahedrons are
    not necessarily parallel to the coordinate axes.
    It can be thought of as a tessellation of a block of 3D space,
    similar to a `RectilinearGrid`
    except that the cells are not necessarily cubes, they can have different
    orientations but are connected in the same way as a `RectilinearGrid`.

    Args:
        inputobj (vtkExplicitStructuredGrid, list, str):
            list of points and indices, or filename
    """

    def __init__(self, inputobj=None):
        """
        A StructuredGrid is a dataset where edges of the hexahedrons are
        not necessarily parallel to the coordinate axes.
        It can be thought of as a tessellation of a block of 3D space,
        similar to a `RectilinearGrid`
        except that the cells are not necessarily cubes, they can have different
        orientations but are connected in the same way as a `RectilinearGrid`.

        Args:
            inputobj (vtkExplicitStructuredGrid, list, str):
                list of points and indices, or filename"
        """
        self.dataset = None
        self.mapper = vtki.new("PolyDataMapper")
        self._actor = vtki.vtkActor()
        self._actor.retrieve_object = weak_ref_to(self)
        self._actor.SetMapper(self.mapper)
        self.properties = self._actor.GetProperty()

        self.transform = LinearTransform()
        self.point_locator = None
        self.cell_locator = None
        self.line_locator = None

        self.name = "ExplicitStructuredGrid"
        self.filename = ""

        self.info = {}
        self.time = time.time()

        ###############################
        if inputobj is None:
            self.dataset = vtkExplicitStructuredGrid_()

        elif isinstance(inputobj, vtkExplicitStructuredGrid_):
            self.dataset = inputobj

        elif isinstance(inputobj, ExplicitStructuredGrid):
            self.dataset = inputobj.dataset

        elif isinstance(inputobj, str):
            if "https://" in inputobj:
                inputobj = download(inputobj, verbose=False)
            if inputobj.endswith(".vts"):
                reader = vtki.new("XMLExplicitStructuredGridReader")
            else:
                reader = vtki.new("ExplicitStructuredGridReader")
            self.filename = inputobj
            reader.SetFileName(inputobj)
            reader.Update()
            self.dataset = reader.GetOutput()

        elif utils.is_sequence(inputobj):
            self.dataset = vtkExplicitStructuredGrid_()
            x, y, z = inputobj
            xyz = np.vstack(
                (x.flatten(order="F"), y.flatten(order="F"), z.flatten(order="F"))
            ).T
            dims = x.shape
            self.dataset.SetDimensions(dims)
            # self.dataset.SetDimensions(dims[1], dims[0], dims[2])
            vpoints = vtki.vtkPoints()
            vpoints.SetData(utils.numpy2vtk(xyz))
            self.dataset.SetPoints(vpoints)

        ###############################
        if not self.dataset:
            vedo.logger.error(
                f"ExplicitStructuredGrid: cannot understand input type {type(inputobj)}"
            )
            return

        self.properties.SetColor(0.352, 0.612, 0.996)  # blue7
        self.pipeline = utils.OperationNode(
            self, comment=f"#cells {self.dataset.GetNumberOfCells()}", c="#9e2a2b"
        )

    @property
    def actor(self):
        """Return the `vtkActor` of the object."""
        gf = vtki.new("GeometryFilter")
        gf.SetInputData(self.dataset)
        gf.Update()
        self.mapper.SetInputData(gf.GetOutput())
        self.mapper.Modified()
        return self._actor

    @actor.setter
    def actor(self, _):
        pass

    def _update(self, data, reset_locators=False):
        self.dataset = data
        if reset_locators:
            self.cell_locator = None
            self.point_locator = None
        return self

    def __str__(self):
        return summary_string(self, self._summary_rows(), color="cyan")

    def __repr__(self):
        return self.__str__()

    def __rich__(self):
        return summary_panel(self, self._summary_rows(), color="cyan")

    def print(self):
        """Print object info."""
        print(self)
        return self

    def _summary_rows(self):
        rows = [("name", str(self.name))]
        if self.filename:
            rows.append(("filename", str(self.filename)))
        rows.append(("dimensions", str(self.dimensions())))
        rows.append(("cell dimensions", str(self.cell_dimensions())))
        rows.append(("data dimension", str(self.data_dimension())))
        rows.append(("center", utils.precision(self.dataset.GetCenter(), 6)))
        rows.append(
            ("bounds", format_bounds(self.dataset.GetBounds(), utils.precision))
        )
        rows.append(
            (
                "memory size",
                utils.precision(self.dataset.GetActualMemorySize() / 1024, 2) + " MB",
            )
        )
        rows.append(("blank points", str(self.has_blank_points())))
        rows.append(("blank cells", str(self.has_blank_cells())))
        rows.append(("ghost points", str(self.has_ghost_points())))
        rows.append(("ghost cells", str(self.has_ghost_cells())))

        point_data = self.dataset.GetPointData()
        for i in range(point_data.GetNumberOfArrays()):
            key = point_data.GetArrayName(i)
            if not key:
                continue
            arr = point_data.GetArray(key)
            if arr is None:
                continue
            narr = utils.vtk2numpy(arr)
            label = active_array_label(self.dataset, "point", key, "pointdata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(narr, utils.precision, dim_label="ndim"),
                )
            )

        cell_data = self.dataset.GetCellData()
        for i in range(cell_data.GetNumberOfArrays()):
            key = cell_data.GetArrayName(i)
            if not key:
                continue
            arr = cell_data.GetArray(key)
            if arr is None:
                continue
            narr = utils.vtk2numpy(arr)
            label = active_array_label(self.dataset, "cell", key, "celldata")
            rows.append(
                (
                    label,
                    f'"{key}" '
                    + summarize_array(narr, utils.precision, dim_label="ndim"),
                )
            )

        field_data = self.dataset.GetFieldData()
        for i in range(field_data.GetNumberOfArrays()):
            arr = field_data.GetAbstractArray(i)
            if arr is None or not arr.GetName():
                continue
            rows.append(
                ("metadata", f'"{arr.GetName()}" ({arr.GetNumberOfTuples()} values)')
            )
        return rows

    def dimensions(self) -> np.ndarray:
        """Return the number of points in the x, y and z directions."""
        try:
            dims = self.dataset.GetDimensions()
        except TypeError:
            dims = [0, 0, 0]
            self.dataset.GetDimensions(dims)
        return np.array(dims)

    def data_dimension(self) -> int:
        """Return the dimensionality of the data."""
        return self.dataset.GetDataDimension()

    def cell_dimensions(self) -> np.ndarray:
        """Return the number of cells in the x, y and z directions."""
        dims = [0, 0, 0]
        self.dataset.GetCellDims(dims)
        return np.array(dims)

    def extent(self) -> np.ndarray:
        """Return the structured grid extent."""
        return np.array(self.dataset.GetExtent())

    def extent_type(self) -> int:
        """Return the extent type identifier."""
        return self.dataset.GetExtentType()

    def set_dimensions(self, *dims) -> Self:
        """Set the grid dimensions as number of points along x, y and z."""
        if len(dims) == 1 and utils.is_sequence(dims[0]):
            dims = dims[0]
        self.dataset.SetDimensions(*dims)
        self.mapper.Modified()
        return self

    def set_extent(self, *extent) -> Self:
        """Set the structured grid extent."""
        if len(extent) == 1 and utils.is_sequence(extent[0]):
            extent = extent[0]
        self.dataset.SetExtent(*extent)
        self.mapper.Modified()
        return self

    def build_links(self) -> Self:
        """Build topological links from points to the cells that use them."""
        self.dataset.BuildLinks()
        return self

    def cell_points(self, cell_id: int) -> np.ndarray:
        """Return the point ids that define cell `cell_id`."""
        pt_ids = vtki.vtkIdList()
        self.dataset.GetCellPoints(cell_id, pt_ids)
        return np.array(
            [pt_ids.GetId(i) for i in range(pt_ids.GetNumberOfIds())], dtype=int
        )

    def point_cells(self, point_id: int) -> np.ndarray:
        """Return the cell ids that use point `point_id`."""
        cell_ids = vtki.vtkIdList()
        self.dataset.GetPointCells(point_id, cell_ids)
        return np.array(
            [cell_ids.GetId(i) for i in range(cell_ids.GetNumberOfIds())], dtype=int
        )

    def cell_neighbors(
        self,
        cell_id: int,
        pt_ids=None,
        whole_extent=None,
    ) -> np.ndarray:
        """
        Return the neighbors of cell `cell_id`.

        If `pt_ids` is given, return the ids of the cells sharing all those points.
        Otherwise return the six face-neighbor ids.
        """
        if pt_ids is None:
            neighbors = [-1] * 6
            if whole_extent is None:
                self.dataset.GetCellNeighbors(cell_id, neighbors)
            else:
                self.dataset.GetCellNeighbors(cell_id, neighbors, whole_extent)
            return np.array(neighbors, dtype=int)

        ids = vtki.vtkIdList()
        for pid in pt_ids:
            ids.InsertNextId(int(pid))
        neighbors = vtki.vtkIdList()
        self.dataset.GetCellNeighbors(cell_id, ids, neighbors)
        return np.array(
            [neighbors.GetId(i) for i in range(neighbors.GetNumberOfIds())], dtype=int
        )

    def compute_cell_structured_coords(
        self,
        cell_id: int,
        adjust_for_extent=True,
    ) -> np.ndarray:
        """Return the structured `(i, j, k)` coordinates of cell `cell_id`."""
        i = vtki.mutable(0)
        j = vtki.mutable(0)
        k = vtki.mutable(0)
        self.dataset.ComputeCellStructuredCoords(cell_id, i, j, k, adjust_for_extent)
        return np.array([int(i), int(j), int(k)])

    def compute_cellid(self, ijk, adjust_for_extent=True) -> int:
        """Return the cell id for the structured coordinates `(i, j, k)`."""
        if utils.is_sequence(ijk):
            return self.dataset.ComputeCellId(
                int(ijk[0]), int(ijk[1]), int(ijk[2]), adjust_for_extent
            )
        raise TypeError(
            "compute_cellid() expects a sequence of 3 structured coordinates"
        )

    def compute_faces_connectivity_flags_array(self) -> Self:
        """Compute the faces connectivity flags array."""
        self.dataset.ComputeFacesConnectivityFlagsArray()
        return self

    def has_blank_points(self) -> bool:
        """Return True if the grid has blank points."""
        return self.dataset.HasAnyBlankPoints()

    def has_blank_cells(self) -> bool:
        """Return True if the grid has blank cells."""
        return self.dataset.HasAnyBlankCells()

    def is_cell_visible(self, cell_id: int) -> bool:
        """Return True if cell `cell_id` is visible."""
        return bool(self.dataset.IsCellVisible(cell_id))

    def is_cell_ghost(self, cell_id: int) -> bool:
        """Return True if cell `cell_id` is marked as ghost."""
        return bool(self.dataset.IsCellGhost(cell_id))

    def has_ghost_cells(self) -> bool:
        """Return True if the grid has ghost cells."""
        return self.dataset.HasAnyGhostCells()

    def has_ghost_points(self) -> bool:
        """Return True if the grid has ghost points."""
        return self.dataset.HasAnyGhostPoints()

    def blank_cell(self, cell_id: int) -> Self:
        """Blank cell `cell_id`."""
        self.dataset.BlankCell(cell_id)
        return self

    def unblank_cell(self, cell_id: int) -> Self:
        """Unblank cell `cell_id`."""
        self.dataset.UnBlankCell(cell_id)
        return self

    def check_and_reorder_faces(self) -> Self:
        """Check and reorder cell faces to match the structured orientation."""
        self.dataset.CheckAndReorderFaces()
        return self

    def cell_bounds(self, cell_id: int) -> np.ndarray:
        """Return the bounds of cell `cell_id`."""
        bounds = [0.0] * 6
        self.dataset.GetCellBounds(cell_id, bounds)
        return np.array(bounds)

    def cell_type(self, cell_id: int) -> int:
        """Return the VTK cell type id of cell `cell_id`."""
        return self.dataset.GetCellType(cell_id)

    def cell_size(self, cell_id: int) -> int:
        """Return the number of points used by cell `cell_id`."""
        return self.dataset.GetCellSize(cell_id)

    def number_of_cells(self) -> int:
        """Return the number of cells."""
        return self.dataset.GetNumberOfCells()

    def max_cell_size(self) -> int:
        """Return the maximum cell size."""
        return self.dataset.GetMaxCellSize()

    def max_spatial_dimension(self) -> int:
        """Return the maximum spatial dimension across all cells."""
        return self.dataset.GetMaxSpatialDimension()

    def min_spatial_dimension(self) -> int:
        """Return the minimum spatial dimension across all cells."""
        return self.dataset.GetMinSpatialDimension()

    def find_point(self, x: list) -> int:
        """Given a position `x`, return the id of the closest point."""
        return self.dataset.FindPoint(x)

    def clone(self, deep=True) -> ExplicitStructuredGrid:
        """Return a clone copy of the StructuredGrid. Alias of `copy()`."""
        if deep:
            newrg = vtkExplicitStructuredGrid_()
            newrg.CopyStructure(self.dataset)
            newrg.CopyAttributes(self.dataset)
            newvol = ExplicitStructuredGrid(newrg)
        else:
            newvol = ExplicitStructuredGrid(self.dataset)

        prop = vtki.vtkProperty()
        prop.DeepCopy(self.properties)
        newvol.actor.SetProperty(prop)
        newvol.properties = prop
        newvol.pipeline = utils.OperationNode(
            "clone", parents=[self], c="#bbd0ff", shape="diamond"
        )
        return newvol

    def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> vedo.UnstructuredGrid:
        """
        Cut the object with the plane defined by a point and a normal.

        Args:
            origin (list):
                the cutting plane goes through this point
            normal (list, str):
                normal vector to the cutting plane

        Returns an `UnstructuredGrid` object.
        """
        strn = str(normal)
        if strn == "x":
            normal = (1, 0, 0)
        elif strn == "y":
            normal = (0, 1, 0)
        elif strn == "z":
            normal = (0, 0, 1)
        elif strn == "-x":
            normal = (-1, 0, 0)
        elif strn == "-y":
            normal = (0, -1, 0)
        elif strn == "-z":
            normal = (0, 0, -1)
        plane = vtki.new("Plane")
        plane.SetOrigin(origin)
        plane.SetNormal(normal)
        clipper = vtki.new("ClipDataSet")
        clipper.SetInputData(self.dataset)
        clipper.SetClipFunction(plane)
        clipper.GenerateClipScalarsOff()
        clipper.GenerateClippedOutputOff()
        clipper.SetValue(0)
        clipper.Update()
        cout = clipper.GetOutput()
        ug = vedo.UnstructuredGrid(cout)
        if isinstance(self, vedo.UnstructuredGrid):
            self._update(cout)
            self.pipeline = utils.OperationNode(
                "cut_with_plane", parents=[self], c="#9e2a2b"
            )
            return self
        ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b")
        return ug

actor property writable

Return the vtkActor of the object.

blank_cell(cell_id)

Blank cell cell_id.

Source code in vedo/grids/explicit.py
374
375
376
377
def blank_cell(self, cell_id: int) -> Self:
    """Blank cell `cell_id`."""
    self.dataset.BlankCell(cell_id)
    return self

Build topological links from points to the cells that use them.

Source code in vedo/grids/explicit.py
273
274
275
276
def build_links(self) -> Self:
    """Build topological links from points to the cells that use them."""
    self.dataset.BuildLinks()
    return self

cell_bounds(cell_id)

Return the bounds of cell cell_id.

Source code in vedo/grids/explicit.py
389
390
391
392
393
def cell_bounds(self, cell_id: int) -> np.ndarray:
    """Return the bounds of cell `cell_id`."""
    bounds = [0.0] * 6
    self.dataset.GetCellBounds(cell_id, bounds)
    return np.array(bounds)

cell_dimensions()

Return the number of cells in the x, y and z directions.

Source code in vedo/grids/explicit.py
243
244
245
246
247
def cell_dimensions(self) -> np.ndarray:
    """Return the number of cells in the x, y and z directions."""
    dims = [0, 0, 0]
    self.dataset.GetCellDims(dims)
    return np.array(dims)

cell_neighbors(cell_id, pt_ids=None, whole_extent=None)

Return the neighbors of cell cell_id.

If pt_ids is given, return the ids of the cells sharing all those points. Otherwise return the six face-neighbor ids.

Source code in vedo/grids/explicit.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
def cell_neighbors(
    self,
    cell_id: int,
    pt_ids=None,
    whole_extent=None,
) -> np.ndarray:
    """
    Return the neighbors of cell `cell_id`.

    If `pt_ids` is given, return the ids of the cells sharing all those points.
    Otherwise return the six face-neighbor ids.
    """
    if pt_ids is None:
        neighbors = [-1] * 6
        if whole_extent is None:
            self.dataset.GetCellNeighbors(cell_id, neighbors)
        else:
            self.dataset.GetCellNeighbors(cell_id, neighbors, whole_extent)
        return np.array(neighbors, dtype=int)

    ids = vtki.vtkIdList()
    for pid in pt_ids:
        ids.InsertNextId(int(pid))
    neighbors = vtki.vtkIdList()
    self.dataset.GetCellNeighbors(cell_id, ids, neighbors)
    return np.array(
        [neighbors.GetId(i) for i in range(neighbors.GetNumberOfIds())], dtype=int
    )

cell_points(cell_id)

Return the point ids that define cell cell_id.

Source code in vedo/grids/explicit.py
278
279
280
281
282
283
284
def cell_points(self, cell_id: int) -> np.ndarray:
    """Return the point ids that define cell `cell_id`."""
    pt_ids = vtki.vtkIdList()
    self.dataset.GetCellPoints(cell_id, pt_ids)
    return np.array(
        [pt_ids.GetId(i) for i in range(pt_ids.GetNumberOfIds())], dtype=int
    )

cell_size(cell_id)

Return the number of points used by cell cell_id.

Source code in vedo/grids/explicit.py
399
400
401
def cell_size(self, cell_id: int) -> int:
    """Return the number of points used by cell `cell_id`."""
    return self.dataset.GetCellSize(cell_id)

cell_type(cell_id)

Return the VTK cell type id of cell cell_id.

Source code in vedo/grids/explicit.py
395
396
397
def cell_type(self, cell_id: int) -> int:
    """Return the VTK cell type id of cell `cell_id`."""
    return self.dataset.GetCellType(cell_id)

check_and_reorder_faces()

Check and reorder cell faces to match the structured orientation.

Source code in vedo/grids/explicit.py
384
385
386
387
def check_and_reorder_faces(self) -> Self:
    """Check and reorder cell faces to match the structured orientation."""
    self.dataset.CheckAndReorderFaces()
    return self

clone(deep=True)

Return a clone copy of the StructuredGrid. Alias of copy().

Source code in vedo/grids/explicit.py
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def clone(self, deep=True) -> ExplicitStructuredGrid:
    """Return a clone copy of the StructuredGrid. Alias of `copy()`."""
    if deep:
        newrg = vtkExplicitStructuredGrid_()
        newrg.CopyStructure(self.dataset)
        newrg.CopyAttributes(self.dataset)
        newvol = ExplicitStructuredGrid(newrg)
    else:
        newvol = ExplicitStructuredGrid(self.dataset)

    prop = vtki.vtkProperty()
    prop.DeepCopy(self.properties)
    newvol.actor.SetProperty(prop)
    newvol.properties = prop
    newvol.pipeline = utils.OperationNode(
        "clone", parents=[self], c="#bbd0ff", shape="diamond"
    )
    return newvol

compute_cell_structured_coords(cell_id, adjust_for_extent=True)

Return the structured (i, j, k) coordinates of cell cell_id.

Source code in vedo/grids/explicit.py
323
324
325
326
327
328
329
330
331
332
333
def compute_cell_structured_coords(
    self,
    cell_id: int,
    adjust_for_extent=True,
) -> np.ndarray:
    """Return the structured `(i, j, k)` coordinates of cell `cell_id`."""
    i = vtki.mutable(0)
    j = vtki.mutable(0)
    k = vtki.mutable(0)
    self.dataset.ComputeCellStructuredCoords(cell_id, i, j, k, adjust_for_extent)
    return np.array([int(i), int(j), int(k)])

compute_cellid(ijk, adjust_for_extent=True)

Return the cell id for the structured coordinates (i, j, k).

Source code in vedo/grids/explicit.py
335
336
337
338
339
340
341
342
343
def compute_cellid(self, ijk, adjust_for_extent=True) -> int:
    """Return the cell id for the structured coordinates `(i, j, k)`."""
    if utils.is_sequence(ijk):
        return self.dataset.ComputeCellId(
            int(ijk[0]), int(ijk[1]), int(ijk[2]), adjust_for_extent
        )
    raise TypeError(
        "compute_cellid() expects a sequence of 3 structured coordinates"
    )

compute_faces_connectivity_flags_array()

Compute the faces connectivity flags array.

Source code in vedo/grids/explicit.py
345
346
347
348
def compute_faces_connectivity_flags_array(self) -> Self:
    """Compute the faces connectivity flags array."""
    self.dataset.ComputeFacesConnectivityFlagsArray()
    return self

cut_with_plane(origin=(0, 0, 0), normal='x')

Cut the object with the plane defined by a point and a normal.

Parameters:

Name Type Description Default
origin list

the cutting plane goes through this point

(0, 0, 0)
normal (list, str)

normal vector to the cutting plane

'x'

Returns an UnstructuredGrid object.

Source code in vedo/grids/explicit.py
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def cut_with_plane(self, origin=(0, 0, 0), normal="x") -> vedo.UnstructuredGrid:
    """
    Cut the object with the plane defined by a point and a normal.

    Args:
        origin (list):
            the cutting plane goes through this point
        normal (list, str):
            normal vector to the cutting plane

    Returns an `UnstructuredGrid` object.
    """
    strn = str(normal)
    if strn == "x":
        normal = (1, 0, 0)
    elif strn == "y":
        normal = (0, 1, 0)
    elif strn == "z":
        normal = (0, 0, 1)
    elif strn == "-x":
        normal = (-1, 0, 0)
    elif strn == "-y":
        normal = (0, -1, 0)
    elif strn == "-z":
        normal = (0, 0, -1)
    plane = vtki.new("Plane")
    plane.SetOrigin(origin)
    plane.SetNormal(normal)
    clipper = vtki.new("ClipDataSet")
    clipper.SetInputData(self.dataset)
    clipper.SetClipFunction(plane)
    clipper.GenerateClipScalarsOff()
    clipper.GenerateClippedOutputOff()
    clipper.SetValue(0)
    clipper.Update()
    cout = clipper.GetOutput()
    ug = vedo.UnstructuredGrid(cout)
    if isinstance(self, vedo.UnstructuredGrid):
        self._update(cout)
        self.pipeline = utils.OperationNode(
            "cut_with_plane", parents=[self], c="#9e2a2b"
        )
        return self
    ug.pipeline = utils.OperationNode("cut_with_plane", parents=[self], c="#9e2a2b")
    return ug

data_dimension()

Return the dimensionality of the data.

Source code in vedo/grids/explicit.py
239
240
241
def data_dimension(self) -> int:
    """Return the dimensionality of the data."""
    return self.dataset.GetDataDimension()

dimensions()

Return the number of points in the x, y and z directions.

Source code in vedo/grids/explicit.py
230
231
232
233
234
235
236
237
def dimensions(self) -> np.ndarray:
    """Return the number of points in the x, y and z directions."""
    try:
        dims = self.dataset.GetDimensions()
    except TypeError:
        dims = [0, 0, 0]
        self.dataset.GetDimensions(dims)
    return np.array(dims)

extent()

Return the structured grid extent.

Source code in vedo/grids/explicit.py
249
250
251
def extent(self) -> np.ndarray:
    """Return the structured grid extent."""
    return np.array(self.dataset.GetExtent())

extent_type()

Return the extent type identifier.

Source code in vedo/grids/explicit.py
253
254
255
def extent_type(self) -> int:
    """Return the extent type identifier."""
    return self.dataset.GetExtentType()

find_point(x)

Given a position x, return the id of the closest point.

Source code in vedo/grids/explicit.py
419
420
421
def find_point(self, x: list) -> int:
    """Given a position `x`, return the id of the closest point."""
    return self.dataset.FindPoint(x)

has_blank_cells()

Return True if the grid has blank cells.

Source code in vedo/grids/explicit.py
354
355
356
def has_blank_cells(self) -> bool:
    """Return True if the grid has blank cells."""
    return self.dataset.HasAnyBlankCells()

has_blank_points()

Return True if the grid has blank points.

Source code in vedo/grids/explicit.py
350
351
352
def has_blank_points(self) -> bool:
    """Return True if the grid has blank points."""
    return self.dataset.HasAnyBlankPoints()

has_ghost_cells()

Return True if the grid has ghost cells.

Source code in vedo/grids/explicit.py
366
367
368
def has_ghost_cells(self) -> bool:
    """Return True if the grid has ghost cells."""
    return self.dataset.HasAnyGhostCells()

has_ghost_points()

Return True if the grid has ghost points.

Source code in vedo/grids/explicit.py
370
371
372
def has_ghost_points(self) -> bool:
    """Return True if the grid has ghost points."""
    return self.dataset.HasAnyGhostPoints()

is_cell_ghost(cell_id)

Return True if cell cell_id is marked as ghost.

Source code in vedo/grids/explicit.py
362
363
364
def is_cell_ghost(self, cell_id: int) -> bool:
    """Return True if cell `cell_id` is marked as ghost."""
    return bool(self.dataset.IsCellGhost(cell_id))

is_cell_visible(cell_id)

Return True if cell cell_id is visible.

Source code in vedo/grids/explicit.py
358
359
360
def is_cell_visible(self, cell_id: int) -> bool:
    """Return True if cell `cell_id` is visible."""
    return bool(self.dataset.IsCellVisible(cell_id))

max_cell_size()

Return the maximum cell size.

Source code in vedo/grids/explicit.py
407
408
409
def max_cell_size(self) -> int:
    """Return the maximum cell size."""
    return self.dataset.GetMaxCellSize()

max_spatial_dimension()

Return the maximum spatial dimension across all cells.

Source code in vedo/grids/explicit.py
411
412
413
def max_spatial_dimension(self) -> int:
    """Return the maximum spatial dimension across all cells."""
    return self.dataset.GetMaxSpatialDimension()

min_spatial_dimension()

Return the minimum spatial dimension across all cells.

Source code in vedo/grids/explicit.py
415
416
417
def min_spatial_dimension(self) -> int:
    """Return the minimum spatial dimension across all cells."""
    return self.dataset.GetMinSpatialDimension()

number_of_cells()

Return the number of cells.

Source code in vedo/grids/explicit.py
403
404
405
def number_of_cells(self) -> int:
    """Return the number of cells."""
    return self.dataset.GetNumberOfCells()

point_cells(point_id)

Return the cell ids that use point point_id.

Source code in vedo/grids/explicit.py
286
287
288
289
290
291
292
def point_cells(self, point_id: int) -> np.ndarray:
    """Return the cell ids that use point `point_id`."""
    cell_ids = vtki.vtkIdList()
    self.dataset.GetPointCells(point_id, cell_ids)
    return np.array(
        [cell_ids.GetId(i) for i in range(cell_ids.GetNumberOfIds())], dtype=int
    )

print()

Print object info.

Source code in vedo/grids/explicit.py
157
158
159
160
def print(self):
    """Print object info."""
    print(self)
    return self

set_dimensions(*dims)

Set the grid dimensions as number of points along x, y and z.

Source code in vedo/grids/explicit.py
257
258
259
260
261
262
263
def set_dimensions(self, *dims) -> Self:
    """Set the grid dimensions as number of points along x, y and z."""
    if len(dims) == 1 and utils.is_sequence(dims[0]):
        dims = dims[0]
    self.dataset.SetDimensions(*dims)
    self.mapper.Modified()
    return self

set_extent(*extent)

Set the structured grid extent.

Source code in vedo/grids/explicit.py
265
266
267
268
269
270
271
def set_extent(self, *extent) -> Self:
    """Set the structured grid extent."""
    if len(extent) == 1 and utils.is_sequence(extent[0]):
        extent = extent[0]
    self.dataset.SetExtent(*extent)
    self.mapper.Modified()
    return self

unblank_cell(cell_id)

Unblank cell cell_id.

Source code in vedo/grids/explicit.py
379
380
381
382
def unblank_cell(self, cell_id: int) -> Self:
    """Unblank cell `cell_id`."""
    self.dataset.UnBlankCell(cell_id)
    return self