Skip to content

vedo.file_io

loaders

from_numpy(d)

Create a Mesh object from a dictionary.

Source code in vedo/file_io/loaders.py
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
def from_numpy(d: dict) -> Mesh:
    """Create a Mesh object from a dictionary."""
    # recreate a mesh from numpy arrays
    keys = d.keys()

    points = d["points"]
    cells = d["cells"] if "cells" in keys else None
    lines = d["lines"] if "lines" in keys else None

    msh = Mesh([points, cells, lines])

    if "pointdata" in keys and isinstance(d["pointdata"], dict):
        for arrname, arr in d["pointdata"].items():
            msh.pointdata[arrname] = arr
    if "celldata" in keys and isinstance(d["celldata"], dict):
        for arrname, arr in d["celldata"].items():
            msh.celldata[arrname] = arr
    if "metadata" in keys and isinstance(d["metadata"], dict):
        for arrname, arr in d["metadata"].items():
            msh.metadata[arrname] = arr

    prp = msh.properties
    prp.SetAmbient(d["ambient"])
    prp.SetDiffuse(d["diffuse"])
    prp.SetSpecular(d["specular"])
    prp.SetSpecularPower(d["specularpower"])
    prp.SetSpecularColor(d["specularcolor"])

    prp.SetInterpolation(0)
    # prp.SetInterpolation(d['shading'])

    prp.SetOpacity(d["alpha"])
    prp.SetRepresentation(d["representation"])
    prp.SetPointSize(d["pointsize"])
    if d["color"] is not None:
        msh.color(d["color"])
    if "lighting_is_on" in d.keys():
        prp.SetLighting(d["lighting_is_on"])
    # Must check keys for backwards compatibility:
    if "linecolor" in d.keys() and d["linecolor"] is not None:
        msh.linecolor(d["linecolor"])
    if "backcolor" in d.keys() and d["backcolor"] is not None:
        msh.backcolor(d["backcolor"])

    if d["linewidth"] is not None:
        msh.linewidth(d["linewidth"])
    if "edge_visibility" in d.keys():
        prp.SetEdgeVisibility(d["edge_visibility"])  # new

    lut_list = d["LUT"]
    lut_range = d["LUT_range"]
    ncols = len(lut_list)
    lut = vtki.vtkLookupTable()
    lut.SetNumberOfTableValues(ncols)
    lut.SetRange(lut_range)
    for i in range(ncols):
        r, g, b, a = lut_list[i]
        lut.SetTableValue(i, r, g, b, a)
    lut.Build()
    msh.mapper.SetLookupTable(lut)
    msh.mapper.SetScalarRange(lut_range)

    try:  # NEW in vedo 5.0
        arname = d["array_name_to_color_by"]
        msh.mapper.SetArrayName(arname)
        msh.mapper.SetInterpolateScalarsBeforeMapping(
            d["interpolate_scalars_before_mapping"]
        )
        msh.mapper.SetUseLookupTableScalarRange(d["use_lookup_table_scalar_range"])
        msh.mapper.SetScalarRange(d["scalar_range"])
        msh.mapper.SetScalarVisibility(d["scalar_visibility"])
        msh.mapper.SetScalarMode(d["scalar_mode"])
        msh.mapper.SetColorMode(d["color_mode"])
        if d["scalar_visibility"]:
            if d["scalar_mode"] == 1:
                msh.dataset.GetPointData().SetActiveScalars(arname)
            if d["scalar_mode"] == 2:
                msh.dataset.GetCellData().SetActiveScalars(arname)

        if "texture_array" in keys and d["texture_array"] is not None:
            # recreate a vtkTexture object from numpy arrays:
            t = vtki.vtkTexture()
            t.SetInterpolate(d["texture_interpolate"])
            t.SetRepeat(d["texture_repeat"])
            t.SetQuality(d["texture_quality"])
            t.SetColorMode(d["texture_color_mode"])
            t.SetMipmap(d["texture_mipmap"])
            t.SetBlendingMode(d["texture_blending_mode"])
            t.SetEdgeClamp(d["texture_edge_clamp"])
            t.SetBorderColor(d["texture_border_color"])
            msh.actor.SetTexture(t)
            tcarray = None
            for arrname in msh.pointdata.keys():
                if "Texture" in arrname or "TCoord" in arrname:
                    tcarray = arrname
                    break
            if tcarray is not None:
                t.SetInputData(vedo.Image(d["texture_array"]).dataset)
                msh.pointdata.select_texture_coords(tcarray)

        # print("color_mode", d["color_mode"])
        # print("scalar_mode", d["scalar_mode"])
        # print("scalar_range", d["scalar_range"])
        # print("scalar_visibility", d["scalar_visibility"])
        # print("array_name_to_color_by", arname)
    except KeyError:
        pass

    if "time" in keys:
        msh.time = d["time"]
    if "name" in keys:
        msh.name = d["name"]
    # if "info" in keys: msh.info = d["info"]
    if "filename" in keys:
        msh.filename = d["filename"]
    if "pickable" in keys:
        msh.pickable(d["pickable"])
    if "dragable" in keys:
        msh.draggable(d["dragable"])
    return msh

load(inputobj, unpack=True, force=False)

Load any vedo objects from file or from the web.

The output will depend on the file extension. See examples below. Unzip is made on the fly, if file ends with .gz. Can load an object directly from a URL address.

Parameters:

Name Type Description Default
unpack bool

unpack MultiBlockData into a flat list of objects.

True
force bool

when downloading a file ignore any previous cached downloads and force a new one.

False

Examples:

from vedo import dataurl, load, show
# Return a list of 2 meshes
g = load([dataurl+'250.vtk', dataurl+'270.vtk'])
show(g)
# Return a list of meshes by reading all files in a directory
# (if directory contains DICOM files then a Volume is returned)
g = load('mydicomdir/')
show(g)
Source code in vedo/file_io/loaders.py
 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
def load(inputobj: list | str | os.PathLike, unpack=True, force=False) -> Any:
    """
    Load any vedo objects from file or from the web.

    The output will depend on the file extension. See examples below.
    Unzip is made on the fly, if file ends with `.gz`.
    Can load an object directly from a URL address.

    Args:
        unpack (bool):
            unpack MultiBlockData into a flat list of objects.
        force (bool):
            when downloading a file ignore any previous cached downloads and force a new one.

    Examples:
        ```python
        from vedo import dataurl, load, show
        # Return a list of 2 meshes
        g = load([dataurl+'250.vtk', dataurl+'270.vtk'])
        show(g)
        # Return a list of meshes by reading all files in a directory
        # (if directory contains DICOM files then a Volume is returned)
        g = load('mydicomdir/')
        show(g)
        ```
    """
    if isinstance(inputobj, list):
        inputobj = [str(f) for f in inputobj]
    else:
        inputobj = str(inputobj)

    acts = []
    if utils.is_sequence(inputobj):
        flist = inputobj
    elif isinstance(inputobj, str) and inputobj.startswith("https://"):
        flist = [inputobj]
    else:
        flist = utils.humansort(glob.glob(inputobj))

    for fod in flist:
        if fod.startswith("https://"):
            fod = download(fod, force=force, verbose=False)

        if os.path.isfile(fod):  ### it's a file
            if fod.endswith(".gz"):
                fod = gunzip(fod)

            a = _load_file(fod, unpack)
            acts.append(a)

        elif os.path.isdir(fod):  ### it's a directory or DICOM
            flist = utils.humansort(os.listdir(fod))
            if not flist:
                vedo.logger.warning(f"Cannot load empty directory {fod!r}")
                continue
            is_dicom_dir = any(fname.lower().endswith(".dcm") for fname in flist)
            if is_dicom_dir:  ### it's DICOM
                reader = vtki.new("DICOMImageReader")
                reader.SetDirectoryName(fod)
                reader.Update()
                image = reader.GetOutput()
                vol = Volume(image)
                try:
                    vol.metadata["PixelSpacing"] = reader.GetPixelSpacing()
                    vol.metadata["Width"] = reader.GetWidth()
                    vol.metadata["Height"] = reader.GetHeight()
                    vol.metadata["PositionPatient"] = reader.GetImagePositionPatient()
                    vol.metadata["OrientationPatient"] = (
                        reader.GetImageOrientationPatient()
                    )
                    vol.metadata["BitsAllocated"] = reader.GetBitsAllocated()
                    vol.metadata["PixelRepresentation"] = (
                        reader.GetPixelRepresentation()
                    )
                    vol.metadata["NumberOfComponents"] = reader.GetNumberOfComponents()
                    vol.metadata["TransferSyntaxUID"] = reader.GetTransferSyntaxUID()
                    vol.metadata["RescaleSlope"] = reader.GetRescaleSlope()
                    vol.metadata["RescaleOffset"] = reader.GetRescaleOffset()
                    vol.metadata["PatientName"] = reader.GetPatientName()
                    vol.metadata["StudyUID"] = reader.GetStudyUID()
                    vol.metadata["StudyID"] = reader.GetStudyID()
                    vol.metadata["GantryAngle"] = reader.GetGantryAngle()
                except Exception as e:
                    vedo.logger.warning(f"Cannot read DICOM metadata: {e}")
                acts.append(vol)

            else:  ### it's a normal directory
                for ifile in flist:
                    full_path = os.path.join(fod, ifile)
                    if not os.path.isfile(full_path):
                        continue
                    a = _load_file(full_path, unpack)
                    acts.append(a)
        else:
            vedo.logger.error(f"in load(), cannot find {fod}")

    if len(acts) == 1:
        if "numpy" in str(type(acts[0])):
            return acts[0]
        if not acts[0]:
            vedo.logger.error(f"in load(), cannot load {inputobj}")
        return acts[0]

    if len(acts) == 0:
        vedo.logger.error(f"in load(), cannot load {inputobj}")
        return None

    else:
        return acts

load3DS(filename)

Load 3DS file format from file.

Source code in vedo/file_io/loaders.py
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
def load3DS(filename: str | os.PathLike) -> Assembly:
    """Load `3DS` file format from file."""
    filename = str(filename)
    renderer = vtki.vtkRenderer()
    renWin = vtki.vtkRenderWindow()
    renWin.AddRenderer(renderer)

    importer = vtki.new("3DSImporter")
    importer.SetFileName(filename)
    importer.ComputeNormalsOn()
    importer.SetRenderWindow(renWin)
    importer.Update()

    actors = renderer.GetActors()  # vtkActorCollection
    acts = []
    for i in range(actors.GetNumberOfItems()):
        a = actors.GetItemAsObject(i)
        acts.append(a)
    del renWin

    wrapped_acts = []
    for a in acts:
        try:
            newa = Mesh(a.GetMapper().GetInput())
            newa.actor = a
            wrapped_acts.append(newa)
            # print("loaded 3DS object", [a])
        except:
            print("ERROR: cannot load 3DS object part", [a])
    return vedo.Assembly(wrapped_acts)

loadGaussianCube(filename, b_scale=1.0, hb_scale=1.0)

Read a Gaussian cube file.

Parameters:

Name Type Description Default
b_scale float

Bond scaling factor used by vtkGaussianCubeReader.

1.0
hb_scale float

Hydrogen bond scaling factor used by vtkGaussianCubeReader.

1.0
Source code in vedo/file_io/loaders.py
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
def loadGaussianCube(
    filename: str | os.PathLike,
    b_scale: float = 1.0,
    hb_scale: float = 1.0,
    ) -> tuple[Mesh, Volume]:
    """
    Read a Gaussian cube file.

    Args:
        b_scale (float):
            Bond scaling factor used by `vtkGaussianCubeReader`.
        hb_scale (float):
            Hydrogen bond scaling factor used by `vtkGaussianCubeReader`.
    """
    reader = vtki.new("GaussianCubeReader")
    reader.SetFileName(str(filename))
    reader.SetBScale(b_scale)
    reader.SetHBScale(hb_scale)
    reader.Update()
    poly = Mesh(reader.GetOutput())
    volume = Volume(reader.GetGridOutput())
    return poly, volume

loadGeoJSON(filename)

Load GeoJSON files.

Source code in vedo/file_io/loaders.py
535
536
537
538
539
540
541
def loadGeoJSON(filename: str | os.PathLike) -> Mesh:
    """Load GeoJSON files."""
    filename = str(filename)
    jr = vtki.new("GeoJSONReader")
    jr.SetFileName(filename)
    jr.Update()
    return Mesh(jr.GetOutput())

loadGmesh(filename)

Reads a gmesh file format. Return an Mesh object.

Source code in vedo/file_io/loaders.py
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
def loadGmesh(filename: str | os.PathLike) -> Mesh:
    """Reads a `gmesh` file format. Return an `Mesh` object."""
    filename = str(filename)
    with open(filename, "r", encoding="UTF-8") as f:
        lines = f.readlines()

    nnodes = 0
    index_nodes = 0
    for i, line in enumerate(lines):
        if "$Nodes" in line:
            index_nodes = i + 1
            nnodes = int(lines[index_nodes])
            break
    node_coords = []
    for i in range(index_nodes + 1, index_nodes + 1 + nnodes):
        cn = lines[i].split()
        node_coords.append([float(cn[1]), float(cn[2]), float(cn[3])])

    nelements = 0
    index_elements = 0
    for i, line in enumerate(lines):
        if "$Elements" in line:
            index_elements = i + 1
            nelements = int(lines[index_elements])
            break
    elements = []
    for i in range(index_elements + 1, index_elements + 1 + nelements):
        ele = lines[i].split()
        elements.append([int(ele[-3]), int(ele[-2]), int(ele[-1])])

    poly = utils.buildPolyData(node_coords, elements, index_offset=1)
    return Mesh(poly)

loadImageData(filename)

Read and return a vtkImageData object from file.

Source code in vedo/file_io/loaders.py
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
def loadImageData(filename: str | os.PathLike) -> vtki.vtkImageData | None:
    """Read and return a `vtkImageData` object from file."""
    filename = str(filename)
    if ".cube" in filename.lower():
        _, volume = loadGaussianCube(filename)
        return volume.dataset

    if ".ome.tif" in filename.lower():
        reader = vtki.new("OMETIFFReader")
        # print("GetOrientationType ", reader.GetOrientationType())
        reader.SetOrientationType(vedo.settings.tiff_orientation_type)
    elif ".tif" in filename.lower():
        reader = vtki.new("TIFFReader")
        # print("GetOrientationType ", reader.GetOrientationType())
        reader.SetOrientationType(vedo.settings.tiff_orientation_type)
    elif ".slc" in filename.lower():
        reader = vtki.new("SLCReader")
        if not reader.CanReadFile(filename):
            vedo.logger.error(f"sorry, bad SLC file {filename}")
            return None
    elif ".vti" in filename.lower():
        reader = vtki.new("XMLImageDataReader")
    elif ".mhd" in filename.lower():
        reader = vtki.new("MetaImageReader")
    elif ".dem" in filename.lower():
        reader = vtki.new("DEMReader")
    elif ".nii" in filename.lower():
        reader = vtki.new("NIFTIImageReader")
    elif ".nrrd" in filename.lower():
        reader = vtki.new("NrrdReader")
        if not reader.CanReadFile(filename):
            vedo.logger.error(f"sorry, bad NRRD file {filename}")
            return None
    else:
        vedo.logger.error(f"cannot read file {filename}")
        return None
    reader.SetFileName(filename)
    reader.Update()
    return reader.GetOutput()

loadNeutral(filename)

Reads a Neutral tetrahedral file format.

Returns an TetMesh object.

Source code in vedo/file_io/loaders.py
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
def loadNeutral(filename: str | os.PathLike) -> vedo.TetMesh:
    """
    Reads a `Neutral` tetrahedral file format.

    Returns an `TetMesh` object.
    """
    filename = str(filename)
    with open(filename, "r", encoding="UTF-8") as f:
        lines = f.readlines()

    ncoords = int(lines[0])
    coords = []
    for i in range(1, ncoords + 1):
        x, y, z = lines[i].split()
        coords.append([float(x), float(y), float(z)])

    ntets = int(lines[ncoords + 1])
    idolf_tets = []
    for i in range(ncoords + 2, ncoords + ntets + 2):
        text = lines[i].split()
        v0, v1, v2, v3 = (
            int(text[1]) - 1,
            int(text[2]) - 1,
            int(text[3]) - 1,
            int(text[4]) - 1,
        )
        idolf_tets.append([v0, v1, v2, v3])

    return vedo.TetMesh([coords, idolf_tets])

loadOFF(filename)

Read the OFF file format (polygonal mesh).

Source code in vedo/file_io/loaders.py
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
def loadOFF(filename: str | os.PathLike) -> Mesh:
    """Read the OFF file format (polygonal mesh)."""
    filename = str(filename)
    with open(filename, "r", encoding="UTF-8") as f:
        lines = f.readlines()

    vertices = []
    faces = []
    NumberOfVertices = 0
    i = -1
    for text in lines:
        if len(text) == 0:
            continue
        if text == "\n":
            continue
        if "#" in text:
            continue
        if "OFF" in text:
            continue

        ts = text.split()
        n = len(ts)

        if not NumberOfVertices and n > 1:
            NumberOfVertices, NumberOfFaces = int(ts[0]), int(ts[1])
            continue
        i += 1

        if i < NumberOfVertices and n == 3:
            x, y, z = float(ts[0]), float(ts[1]), float(ts[2])
            vertices.append([x, y, z])

        ids = []
        if NumberOfVertices <= i < (NumberOfVertices + NumberOfFaces + 1) and n > 2:
            ids += [int(xx) for xx in ts[1:]]
            faces.append(ids)

    return Mesh(utils.buildPolyData(vertices, faces))

loadPCD(filename)

Return a Mesh made of only vertex points from the PointCloud library file format.

Returns an Points object.

Source code in vedo/file_io/loaders.py
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
def loadPCD(filename: str | os.PathLike) -> Points:
    """Return a `Mesh` made of only vertex points
    from the `PointCloud` library file format.

    Returns an `Points` object.
    """
    filename = str(filename)
    with open(filename, "r", encoding="UTF-8") as f:
        lines = f.readlines()

    start = False
    pts = []
    N, expN = 0, 0
    for text in lines:
        if start:
            if N >= expN:
                break
            l = text.split()
            pts.append([float(l[0]), float(l[1]), float(l[2])])
            N += 1
        if not start and "POINTS" in text:
            expN = int(text.split()[1])
        if not start and "DATA ascii" in text:
            start = True
    if expN != N:
        vedo.logger.warning(f"Mismatch in PCD file {expN} != {len(pts)}")
    poly = utils.buildPolyData(pts)
    return Points(poly).point_size(4)

loadPVD(filename)

Read paraview files.

Source code in vedo/file_io/loaders.py
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
def loadPVD(filename: str | os.PathLike) -> list[Any] | None:
    """Read paraview files."""
    filename = str(filename)
    import xml.etree.ElementTree as et

    tree = et.parse(filename)

    dname = os.path.dirname(filename)
    if not dname:
        dname = "."

    listofobjs = []
    for coll in tree.getroot():
        for dataset in coll:
            fname = dataset.get("file")
            if not fname:
                continue
            ob = load(dname + "/" + fname)
            tm = dataset.get("timestep")
            if tm:
                ob.time = tm
            listofobjs.append(ob)
    if len(listofobjs) == 1:
        return listofobjs[0]
    if len(listofobjs) == 0:
        return None
    return listofobjs

loadSTEP(filename, deflection=1.0)

Reads a 3D STEP file and returns its mesh representation as vertices and triangles.

Parameters: - filename (str): Path to the STEP file. - deflection (float): Linear deflection for meshing accuracy (smaller values yield finer meshes).

Returns: - vertices (list of tuples): List of (x, y, z) coordinates of the mesh vertices. - triangles (list of tuples): List of (i, j, k) indices representing the triangles.

Raises: - Exception: If the STEP file cannot be read.

Source code in vedo/file_io/loaders.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
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
def loadSTEP(filename: str | os.PathLike, deflection=1.0) -> Mesh:
    """
    Reads a 3D STEP file and returns its mesh representation as vertices and triangles.

    Parameters:
    - filename (str): Path to the STEP file.
    - deflection (float): Linear deflection for meshing accuracy (smaller values yield finer meshes).

    Returns:
    - vertices (list of tuples): List of (x, y, z) coordinates of the mesh vertices.
    - triangles (list of tuples): List of (i, j, k) indices representing the triangles.

    Raises:
    - Exception: If the STEP file cannot be read.
    """
    try:
        from OCC.Core.STEPControl import STEPControl_Reader  # type: ignore
        from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh  # type: ignore
        from OCC.Core.TopExp import TopExp_Explorer  # type: ignore
        from OCC.Core.TopoDS import topods  # type: ignore
        from OCC.Core.BRep import BRep_Tool  # type: ignore
        from OCC.Core.TopAbs import TopAbs_FACE  # type: ignore
        from OCC.Core.TopLoc import TopLoc_Location  # type: ignore
    except ImportError:
        raise ImportError(
            "OCC library not found.\n\nPlease install 'pythonocc-core'. "
            "You can install it using the following command:\n"
            "\t\tconda install -c conda-forge pythonocc-core"
        )

    # Initialize the STEP reader
    reader = STEPControl_Reader()
    status = reader.ReadFile(str(filename))
    if status != 1:  # Check if reading was successful (IFSelect_RetDone = 1)
        raise Exception("Error reading STEP file")

    # Transfer the STEP data into a shape
    reader.TransferRoots()
    shape = reader.OneShape()

    # Mesh the shape with the specified deflection
    mesh = BRepMesh_IncrementalMesh(shape, deflection)
    mesh.Perform()

    # Extract vertices and triangles
    explorer = TopExp_Explorer(shape, TopAbs_FACE)
    vertices = []
    triangles = []
    vertex_index = 0

    # Iterate over all faces in the shape
    while explorer.More():
        face = topods.Face(explorer.Current())
        location = TopLoc_Location()
        triangulation = BRep_Tool.Triangulation(face, location)

        if triangulation:
            # Extract vertices from the triangulation
            for i in range(1, triangulation.NbNodes() + 1):
                point = triangulation.Node(i).Transformed(location.Transformation())
                vertices.append((point.X(), point.Y(), point.Z()))

            # Extract triangles with adjusted indices
            for i in range(1, triangulation.NbTriangles() + 1):
                triangle = triangulation.Triangle(i)
                n1, n2, n3 = triangle.Get()  # 1-based indices
                triangles.append(
                    (
                        n1 + vertex_index - 1,
                        n2 + vertex_index - 1,
                        n3 + vertex_index - 1,
                    )
                )

            # Update the vertex index offset for the next face
            vertex_index += triangulation.NbNodes()

        explorer.Next()

    # Create a mesh object
    mesh = Mesh([vertices, triangles])
    return mesh

loadStructuredGrid(filename)

Load and return a vtkStructuredGrid object from file.

Source code in vedo/file_io/loaders.py
364
365
366
367
368
369
370
371
372
373
def loadStructuredGrid(filename: str | os.PathLike):
    """Load and return a `vtkStructuredGrid` object from file."""
    filename = str(filename)
    if filename.endswith(".vts"):
        reader = vtki.new("XMLStructuredGridReader")
    else:
        reader = vtki.new("StructuredGridReader")
    reader.SetFileName(filename)
    reader.Update()
    return reader.GetOutput()

loadStructuredPoints(filename, as_points=True)

Load and return a vtkStructuredPoints object from file.

If as_points is True, return a Points object instead of a vtkStructuredPoints.

Source code in vedo/file_io/loaders.py
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
def loadStructuredPoints(filename: str | os.PathLike, as_points=True):
    """
    Load and return a `vtkStructuredPoints` object from file.

    If `as_points` is True, return a `Points` object
    instead of a `vtkStructuredPoints`.
    """
    filename = str(filename)
    reader = vtki.new("StructuredPointsReader")
    reader.SetFileName(filename)
    reader.Update()
    if as_points:
        v2p = vtki.new("ImageToPoints")
        v2p.SetInputData(reader.GetOutput())
        v2p.Update()
        pts = Points(v2p.GetOutput())
        return pts
    return reader.GetOutput()

load_obj(fileinput, mtl_file=None, texture_path=None)

Import a set of meshes from a OBJ wavefront file.

Parameters:

Name Type Description Default
mtl_file str

MTL file for OBJ wavefront files

None
texture_path str

path of the texture files directory

None

Returns:

Type Description
list[Mesh]

list(Mesh)

Source code in vedo/file_io/loaders.py
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
def load_obj(
    fileinput: str | os.PathLike, mtl_file=None, texture_path=None
) -> list[Mesh]:
    """
    Import a set of meshes from a OBJ wavefront file.

    Args:
        mtl_file (str):
            MTL file for OBJ wavefront files
        texture_path (str):
            path of the texture files directory

    Returns:
        `list(Mesh)`
    """
    fileinput = str(fileinput)

    window = vtki.vtkRenderWindow()
    window.SetOffScreenRendering(1)
    renderer = vtki.vtkRenderer()
    window.AddRenderer(renderer)

    importer = vtki.new("OBJImporter")
    importer.SetFileName(fileinput)
    if mtl_file is None:
        mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
    if os.path.isfile(mtl_file):
        importer.SetFileNameMTL(mtl_file)
    if texture_path is None:
        texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
    # since the texture_path may be a directory which contains textures
    if os.path.exists(texture_path):
        importer.SetTexturePath(texture_path)
    importer.SetRenderWindow(window)
    importer.Update()

    actors = renderer.GetActors()
    actors.InitTraversal()
    objs = []
    for _ in range(actors.GetNumberOfItems()):
        vactor = actors.GetNextActor()
        msh = Mesh(vactor)
        msh.name = "OBJMesh"
        msh.copy_properties_from(vactor)
        tx = vactor.GetTexture()
        if tx:
            msh.texture(tx)
        objs.append(msh)
    return objs

network

download(url, force=False, verbose=True)

Retrieve a file from a URL, save it locally and return its path. Use force=True to force a reload and discard cached copies.

Source code in vedo/file_io/network.py
16
17
18
19
20
21
22
23
24
25
26
27
28
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
def download(url: str, force=False, verbose=True) -> str:
    """
    Retrieve a file from a URL, save it locally and return its path.
    Use `force=True` to force a reload and discard cached copies.
    """
    if not url.startswith("https://"):
        # assume it's a file so no need to download
        return url
    url = url.replace("www.dropbox", "dl.dropbox")

    if "github.com" in url:
        url = url.replace("/blob/", "/raw/")

    basename = os.path.basename(url)

    if "?" in basename:
        basename = basename.split("?")[0]

    home_directory = os.path.expanduser("~")
    cachedir = os.path.join(home_directory, settings.cache_directory, "vedo")
    fname = os.path.join(cachedir, basename)
    # Create the directory if it does not exist
    if not os.path.exists(cachedir):
        os.makedirs(cachedir)

    if not force and os.path.exists(fname):
        if verbose:
            vedo.logger.info(f"Reusing cached file: {fname}")
        return fname

    try:
        from urllib.request import urlopen, Request

        req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
        if verbose:
            vedo.logger.info(
                f"Reading {basename} from {url.split('/')[2][:40]}..."
            )

    except ImportError:
        import urllib2  # type: ignore
        import contextlib

        def urlopen(url_):
            return contextlib.closing(urllib2.urlopen(url_))

        req = url
        if verbose:
            vedo.logger.info(
                f"Reading {basename} from {url.split('/')[2][:40]}..."
            )

    with urlopen(req) as response, open(fname, "wb") as output:
        output.write(response.read())

    return fname

file_info(file_path)

Return the file size and creation time of input file

Source code in vedo/file_io/network.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def file_info(file_path: str) -> tuple[str, str]:
    """Return the file size and creation time of input file"""
    siz, created = "", ""
    if os.path.isfile(file_path):
        f_info = os.stat(file_path)
        num = f_info.st_size
        for x in ["B", "KB", "MB", "GB", "TB"]:
            if num < 1024.0:
                break
            num /= 1024.0
        siz = "%3.1f%s" % (num, x)
        created = time.ctime(os.path.getmtime(file_path))
    return siz, created

gunzip(filename)

Unzip a .gz file to a temporary file and returns its path.

Source code in vedo/file_io/network.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def gunzip(filename: str) -> str:
    """Unzip a `.gz` file to a temporary file and returns its path."""
    if not filename.endswith(".gz"):
        # colors.printc("gunzip() error: file must end with .gz", c='r')
        return filename

    import gzip

    tmp_file = NamedTemporaryFile(delete=False)
    tmp_file.name = os.path.join(
        os.path.dirname(tmp_file.name), os.path.basename(filename).replace(".gz", "")
    )
    inF = gzip.open(filename, "rb")
    with open(tmp_file.name, "wb") as outF:
        outF.write(inF.read())
    inF.close()
    return tmp_file.name

scene

export_window(fileoutput, binary=False, plt=None, backend=None, backend_options=None)

Exporter which writes out the rendered scene into an HTML, X3D or Numpy file.

Examples:

Check out the HTML generated webpage here.

.. note:: the rendering window can also be exported to numpy file scene.npz by pressing E key at any moment during visualization.

For `.html` exports, the default backend remains `k3d`.
Pass `backend="threejs"` to generate a standalone Three.js scene page instead.
Optional `backend_options` can tune the Three.js material mapping, e.g.
`{"headlight_intensity": 1.1, "specular_scale": 0.55, "preserve_base_color": True}`.
Source code in vedo/file_io/scene.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
def export_window(
    fileoutput: str | os.PathLike,
    binary=False,
    plt=None,
    backend: str | None = None,
    backend_options: dict | None = None,
) -> vedo.Plotter | None:
    """
    Exporter which writes out the rendered scene into an HTML, X3D or Numpy file.

    Examples:
        - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/extras/export_x3d.py)

        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).

        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>

    .. note::
        the rendering window can also be exported to `numpy` file `scene.npz`
        by pressing `E` key at any moment during visualization.

        For `.html` exports, the default backend remains `k3d`.
        Pass `backend="threejs"` to generate a standalone Three.js scene page instead.
        Optional `backend_options` can tune the Three.js material mapping, e.g.
        `{"headlight_intensity": 1.1, "specular_scale": 0.55, "preserve_base_color": True}`.
    """
    fileoutput = str(fileoutput)
    if plt is None:
        plt = vedo.current_plotter()
    if plt is None:
        vedo.logger.error("export_window(): no active Plotter found")
        return None

    fr = fileoutput.lower()
    ####################################################################
    if fr.endswith(".npy") or fr.endswith(".npz"):
        _export_npy(plt, fileoutput)

    ####################################################################
    elif fr.endswith(".x3d"):
        _export_x3d(plt, fileoutput, binary=binary)

    ####################################################################
    elif fr.endswith(".html"):
        html_backend = "k3d" if backend is None else backend.lower()
        if html_backend == "threejs":
            _export_threejs(plt, fileoutput, backend_options=backend_options)
        elif html_backend == "k3d":
            savebk = vedo.current_notebook_backend()
            try:
                vedo.set_current_notebook_backend("k3d")
                vedo.settings.default_backend = "k3d"
                backend_obj = vedo.backends.get_notebook_backend(plt.objects)
                with open(fileoutput, "w", encoding="UTF-8") as fp:
                    fp.write(backend_obj.get_snapshot())
            finally:
                vedo.set_current_notebook_backend(savebk)
                vedo.settings.default_backend = savebk
        else:
            vedo.logger.error(f"Unsupported html export backend '{backend}'")

    else:
        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")

    return plt

import_window(fileinput)

Import a whole scene from a Numpy NPZ file.

Returns:

Type Description
Plotter | None

vedo.Plotter instance

Source code in vedo/file_io/scene.py
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
def import_window(fileinput: str | os.PathLike) -> vedo.Plotter | None:
    """
    Import a whole scene from a Numpy NPZ file.

    Returns:
        `vedo.Plotter` instance
    """
    fileinput = str(fileinput)

    if fileinput.endswith(".npy") or fileinput.endswith(".npz"):
        return _import_npy(fileinput)

    # elif ".obj" in fileinput.lower():
    #     meshes = load_obj(fileinput, mtl_file, texture_path)
    #     plt = vedo.Plotter()
    #     plt.add(meshes)
    #     return plt

    # elif fileinput.endswith(".h5") or fileinput.endswith(".hdf5"):
    #     return _import_hdf5(fileinput) # in store/file_io_HDF5.py

    return None

screenshot(filename='screenshot.png', scale=1, asarray=False)

Save a screenshot of the current rendering window.

Alternatively, press key Shift-S in the rendering window to save a screenshot. You can also use keyword screenshot in show(..., screenshot="pic.png").

Parameters:

Name Type Description Default
scale int

Set image magnification as an integer multiplicative factor. E.g. setting a magnification of 2 produces an image twice as large, but 10x slower to generate.

1
asarray bool

Return a numpy array of the image

False
Source code in vedo/file_io/scene.py
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
def screenshot(
    filename="screenshot.png", scale=1, asarray=False
) -> vedo.Plotter | np.ndarray | None:
    """
    Save a screenshot of the current rendering window.

    Alternatively, press key `Shift-S` in the rendering window to save a screenshot.
    You can also use keyword `screenshot` in `show(..., screenshot="pic.png")`.

    Args:
        scale (int):
            Set image magnification as an integer multiplicative factor.
            E.g. setting a magnification of 2 produces an image twice as large,
            but 10x slower to generate.
        asarray (bool):
            Return a numpy array of the image
    """
    filename = str(filename)
    filename_lower = filename.lower()
    # print("calling screenshot", filename, scale, asarray)

    plt = vedo.current_plotter()
    if not plt or not plt.window:
        # vedo.logger.error("in screenshot(), rendering window is not present, skip.")
        return plt  ##########

    if plt.renderer:
        plt.renderer.ResetCameraClippingRange()

    if asarray and scale == 1 and not plt.offscreen:
        nx, ny = plt.window.GetSize()
        arr = vtki.vtkUnsignedCharArray()
        plt.window.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr)
        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
        narr = np.flip(narr, axis=0)
        return narr  ##########

    ###########################
    if filename_lower.endswith(".pdf"):
        writer = vtki.new("GL2PSExporter")
        writer.SetRenderWindow(plt.window)
        writer.Write3DPropsAsRasterImageOff()
        writer.SilentOn()
        writer.SetSortToBSP()
        writer.SetFileFormatToPDF()
        writer.SetFilePrefix(filename.replace(".pdf", ""))
        writer.Write()
        return plt  ##########

    elif filename_lower.endswith(".svg"):
        writer = vtki.new("GL2PSExporter")
        writer.SetRenderWindow(plt.window)
        writer.Write3DPropsAsRasterImageOff()
        writer.SilentOn()
        writer.SetSortToBSP()
        writer.SetFileFormatToSVG()
        writer.SetFilePrefix(filename.replace(".svg", ""))
        writer.Write()
        return plt  ##########

    elif filename_lower.endswith(".eps"):
        writer = vtki.new("GL2PSExporter")
        writer.SetRenderWindow(plt.window)
        writer.Write3DPropsAsRasterImageOff()
        writer.SilentOn()
        writer.SetSortToBSP()
        writer.SetFileFormatToEPS()
        writer.SetFilePrefix(filename.replace(".eps", ""))
        writer.Write()
        return plt  ##########

    if settings.screenshot_large_image:
        w2if = vtki.new("RenderLargeImage")
        w2if.SetInput(plt.renderer)
        w2if.SetMagnification(scale)
    else:
        w2if = vtki.new("WindowToImageFilter")
        w2if.SetInput(plt.window)
        if hasattr(w2if, "SetScale"):
            w2if.SetScale(int(scale), int(scale))
        if settings.screenshot_transparent_background:
            w2if.SetInputBufferTypeToRGBA()
        w2if.ReadFrontBufferOff()  # read from the back buffer
    w2if.Update()

    if asarray:
        pd = w2if.GetOutput().GetPointData()
        npdata = utils.vtk2numpy(pd.GetArray("ImageScalars"))
        # npdata = npdata[:, [0, 1, 2]]  # remove alpha channel, issue #1199
        ydim, xdim, _ = w2if.GetOutput().GetDimensions()
        npdata = npdata.reshape([xdim, ydim, -1])
        npdata = np.flip(npdata, axis=0)
        return npdata  ###########################

    if filename.lower().endswith(".png"):
        writer = vtki.new("PNGWriter")
        writer.SetFileName(filename)
        writer.SetInputData(w2if.GetOutput())
        writer.Write()
    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
        writer = vtki.new("JPEGWriter")
        writer.SetFileName(filename)
        writer.SetInputData(w2if.GetOutput())
        writer.Write()
    else:  # add .png
        writer = vtki.new("PNGWriter")
        writer.SetFileName(filename + ".png")
        writer.SetInputData(w2if.GetOutput())
        writer.Write()
    return plt

to_numpy(act)

Encode a vedo object to numpy format.

Source code in vedo/file_io/scene.py
 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
def to_numpy(act: Any) -> dict:
    """Encode a vedo object to numpy format."""

    ########################################################
    def _fillcommon(obj, adict):
        adict["filename"] = obj.filename
        adict["name"] = obj.name
        adict["time"] = obj.time
        adict["rendered_at"] = obj.rendered_at
        try:
            adict["transform"] = obj.transform.matrix
        except AttributeError:
            adict["transform"] = np.eye(4)

    ####################################################################
    try:
        obj = act.retrieve_object()
    except AttributeError:
        obj = act

    adict = {}
    adict["type"] = "unknown"

    ######################################################## Points/Mesh
    if isinstance(obj, (Points, vedo.UnstructuredGrid)):
        adict["type"] = "Mesh"
        _fillcommon(obj, adict)

        if isinstance(obj, vedo.UnstructuredGrid):
            # adict["type"] = "UnstructuredGrid"
            # adict["cells"] = obj.cells_as_flat_array
            poly = obj._actor.GetMapper().GetInput()
            mapper = obj._actor.GetMapper()
        else:
            poly = obj.dataset
            mapper = obj.mapper

        # Keep geometry arrays at single precision to reduce exported scene size.
        adict["points"] = obj.vertices.astype(np.float32, copy=False)

        adict["cells"] = None
        if poly.GetNumberOfPolys():
            adict["cells"] = obj.cells_as_flat_array

        adict["lines"] = None
        if poly.GetNumberOfLines():
            adict["lines"] = obj.lines  # _as_flat_array

        adict["pointdata"] = {}
        for iname in obj.pointdata.keys():
            if "normals" in iname.lower():
                continue
            adict["pointdata"][iname] = obj.pointdata[iname]

        adict["celldata"] = {}
        for iname in obj.celldata.keys():
            if "normals" in iname.lower():
                continue
            adict["celldata"][iname] = obj.celldata[iname]

        adict["metadata"] = {}
        for iname in obj.metadata.keys():
            adict["metadata"][iname] = obj.metadata[iname]

        adict["point_normals"] = None
        normals = poly.GetPointData().GetNormals()
        if normals:
            adict["point_normals"] = vedo.vtk2numpy(normals).astype(
                np.float32, copy=False
            )

        adict["texture_coordinates"] = None
        tcoords = poly.GetPointData().GetTCoords()
        if tcoords:
            adict["texture_coordinates"] = vedo.vtk2numpy(tcoords).astype(
                np.float32, copy=False
            )

        # NEW in vedo 5.0
        adict["scalar_mode"] = mapper.GetScalarMode()
        adict["array_name_to_color_by"] = mapper.GetArrayName()
        adict["color_mode"] = mapper.GetColorMode()
        adict["interpolate_scalars_before_mapping"] = (
            mapper.GetInterpolateScalarsBeforeMapping()
        )
        adict["use_lookup_table_scalar_range"] = mapper.GetUseLookupTableScalarRange()
        adict["scalar_range"] = mapper.GetScalarRange()
        adict["scalar_visibility"] = mapper.GetScalarVisibility()
        adict["pickable"] = obj.actor.GetPickable()
        adict["dragable"] = obj.actor.GetDragable()

        # adict["color_map_colors"]  = mapper.GetColorMapColors()   #vtkUnsignedCharArray
        # adict["color_coordinates"] = mapper.GetColorCoordinates() #vtkFloatArray
        texmap = mapper.GetColorTextureMap()  # vtkImageData
        if texmap:
            adict["color_texture_map"] = vedo.Image(texmap).tonumpy()
            # print("color_texture_map", adict["color_texture_map"].shape)

        adict["texture_array"] = None
        texture = obj.actor.GetTexture()
        if texture:
            adict["texture_array"] = vedo.Image(texture.GetInput()).tonumpy()
            adict["texture_interpolate"] = texture.GetInterpolate()
            adict["texture_repeat"] = texture.GetRepeat()
            adict["texture_quality"] = texture.GetQuality()
            adict["texture_color_mode"] = texture.GetColorMode()
            adict["texture_mipmap"] = texture.GetMipmap()
            adict["texture_blending_mode"] = texture.GetBlendingMode()
            adict["texture_edge_clamp"] = texture.GetEdgeClamp()
            adict["texture_border_color"] = texture.GetBorderColor()
            # print("tonumpy: texture", obj.name, adict["texture_array"].shape)

        adict["LUT"] = None
        adict["LUT_range"] = None
        lut = mapper.GetLookupTable()
        if lut:
            nlut = lut.GetNumberOfTableValues()
            lutvals = []
            for i in range(nlut):
                v4 = lut.GetTableValue(i)  # (r, g, b, alpha)
                lutvals.append(v4)
            adict["LUT"] = np.array(lutvals, dtype=np.float32)
            adict["LUT_range"] = np.array(lut.GetRange())

        prp = obj.properties
        adict["alpha"] = prp.GetOpacity()
        adict["representation"] = prp.GetRepresentation()
        adict["pointsize"] = prp.GetPointSize()

        adict["linecolor"] = None
        adict["linewidth"] = None
        adict["edge_visibility"] = prp.GetEdgeVisibility()  # new in vedo 5.0
        if prp.GetEdgeVisibility():
            adict["linewidth"] = prp.GetLineWidth()
            adict["linecolor"] = prp.GetEdgeColor()

        adict["ambient"] = prp.GetAmbient()
        adict["diffuse"] = prp.GetDiffuse()
        adict["specular"] = prp.GetSpecular()
        adict["specularpower"] = prp.GetSpecularPower()
        adict["specularcolor"] = prp.GetSpecularColor()
        adict["shading"] = prp.GetInterpolation()  # flat phong..:
        adict["color"] = prp.GetColor()
        adict["lighting_is_on"] = prp.GetLighting()
        adict["backcolor"] = None
        if obj.actor.GetBackfaceProperty():
            adict["backcolor"] = obj.actor.GetBackfaceProperty().GetColor()
        if adict["point_normals"] is None and poly.GetNumberOfPolys():
            if adict["representation"] != 1 and adict["shading"] != 0:
                # Compute normals from the already transformed world-space
                # points so exported shading follows the exact geometry that
                # the browser receives.
                world_poly = utils.buildPolyData(adict["points"], faces=adict["cells"])
                pdnorm = vtki.new("PolyDataNormals")
                pdnorm.SetInputData(world_poly)
                pdnorm.SetComputePointNormals(True)
                pdnorm.SetComputeCellNormals(False)
                pdnorm.SetConsistency(True)
                pdnorm.FlipNormalsOff()
                pdnorm.SetSplitting(False)
                pdnorm.Update()
                wn = pdnorm.GetOutput().GetPointData().GetNormals()
                if wn:
                    adict["point_normals"] = vedo.vtk2numpy(wn).astype(
                        np.float32, copy=False
                    )

    ######################################################## Volume
    elif isinstance(obj, Volume):
        adict["type"] = "Volume"
        _fillcommon(obj, adict)
        adict["array"] = obj.tonumpy()
        adict["mode"] = obj.mode()
        adict["spacing"] = obj.spacing()
        adict["origin"] = obj.origin()

        prp = obj.properties
        ctf = prp.GetRGBTransferFunction()
        otf = prp.GetScalarOpacity()
        gotf = prp.GetGradientOpacity()
        smin, smax = ctf.GetRange()
        xs = np.linspace(smin, smax, num=256, endpoint=True)
        cols, als, algrs = [], [], []
        for x in xs:
            cols.append(ctf.GetColor(x))
            als.append(otf.GetValue(x))
            if gotf:
                algrs.append(gotf.GetValue(x))
        adict["color"] = cols
        adict["alpha"] = als
        adict["alphagrad"] = algrs

    ######################################################## Image
    elif isinstance(obj, Image):
        adict["type"] = "Image"
        _fillcommon(obj, adict)
        adict["array"] = obj.tonumpy()
        adict["scale"] = obj.actor.GetScale()
        adict["position"] = obj.actor.GetPosition()
        adict["orientation"] = obj.actor.GetOrientation()
        adict["origin"] = obj.actor.GetOrigin()
        adict["alpha"] = obj.alpha()

    ######################################################## Text2D
    elif isinstance(obj, vedo.Text2D):
        adict["type"] = "Text2D"
        adict["rendered_at"] = obj.rendered_at
        adict["text"] = obj.text()
        adict["position"] = obj.actor.GetPosition()
        adict["color"] = obj.properties.GetColor()
        adict["font"] = obj.fontname
        adict["size"] = obj.properties.GetFontSize() / 22.5
        adict["bgcol"] = obj.properties.GetBackgroundColor()
        adict["alpha"] = obj.properties.GetBackgroundOpacity()
        adict["frame"] = obj.properties.GetFrame()

    ######################################################## Assembly
    elif isinstance(obj, Assembly):
        adict["type"] = "Assembly"
        _fillcommon(obj, adict)
        adict["parts"] = []
        for a in obj.unpack():
            adict["parts"].append(to_numpy(a))

    else:
        # vedo.logger.warning(f"to_numpy: cannot export object of type {type(obj)}")
        pass

    return adict

terminal

ask(*question, **kwarg)

Ask a question from command line. Return the answer as a string. See function colors.printc() for the description of the keyword options.

Parameters:

Name Type Description Default
options list

a python list of possible answers to choose from.

required
default str

the default answer when just hitting return.

required

Examples:

import vedo
res = vedo.ask("Continue?", options=['Y','n'], default='Y', c='g')
print(res)

Source code in vedo/file_io/terminal.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
def ask(*question, **kwarg) -> str:
    """
    Ask a question from command line. Return the answer as a string.
    See function `colors.printc()` for the description of the keyword options.

    Args:
        options (list):
            a python list of possible answers to choose from.
        default (str):
            the default answer when just hitting return.

    Examples:
    ```python
    import vedo
    res = vedo.ask("Continue?", options=['Y','n'], default='Y', c='g')
    print(res)
    ```
    """
    kwarg.update({"end": " "})
    if "invert" not in kwarg:
        kwarg.update({"invert": True})
    if "box" in kwarg:
        kwarg.update({"box": ""})

    options = kwarg.pop("options", [])
    default = kwarg.pop("default", "")
    if options:
        opt = "["
        for o in options:
            opt += o + "/"
        opt = opt[:-1] + "]"
        colors.printc(*question, opt, **kwarg)
    else:
        colors.printc(*question, **kwarg)

    try:
        resp = input()
    except Exception:
        resp = ""
        return resp

    if options:
        if resp not in options:
            if default and str(repr(resp)) == "''":
                return default
            colors.printc("Please choose one option in:", opt, italic=True, bold=False)
            kwarg["options"] = options
            return ask(*question, **kwarg)  # ask again
    return resp

video

Video

Generate a video from a rendering window.

Source code in vedo/file_io/video.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 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
class Video:
    """
    Generate a video from a rendering window.
    """

    def __init__(
        self, name="movie.mp4", duration=None, fps=24, scale=1, backend="imageio"
    ):
        """
        Class to generate a video from the specified rendering window.
        Program `ffmpeg` is used to create video from each generated frame.

        Args:
            name (str | os.PathLike):
                name of the output file.
            duration (float):
                set the total `duration` of the video and recalculates `fps` accordingly.
            fps (int):
                set the number of frames per second.
            scale (int):
                set the image magnification as an integer multiplicative factor.
            backend (str):
                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`

        Examples:
            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/extras/make_video.py)

            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
        """
        self.name = str(name)
        self.duration = duration
        self.backend = backend
        self.fps = float(fps)
        self.command = "ffmpeg -loglevel panic -y -r"
        self.options = "-b:v 8000k"
        self.scale = scale

        self.frames = []
        self.tmp_dir = TemporaryDirectory()
        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
        vedo.logger.info(f"Video file {self.name} is open.")

    def add_frame(self) -> Video:
        """Add frame to current video."""
        fr = self.get_filename(str(len(self.frames)) + ".png")
        screenshot(fr, scale=self.scale)
        self.frames.append(fr)
        return self

    def pause(self, pause=0) -> Video:
        """Insert a `pause`, in seconds."""
        fr = self.frames[-1]
        n = int(self.fps * pause)
        for _ in range(n):
            fr2 = self.get_filename(str(len(self.frames)) + ".png")
            self.frames.append(fr2)
            shutil.copyfile(fr, fr2)
        return self

    def action(
        self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False
    ) -> Video:
        """
        Automatic shooting of a static scene by specifying rotation and elevation ranges.

        Args:
            elevation : list
                initial and final elevation angles
            azimuth_range : list
                initial and final azimuth angles
            cameras : list
                list of cameras to go through, each camera can be dictionary or a vtkCamera
        """
        if not self.duration:
            self.duration = 5

        plt = vedo.current_plotter()
        if not plt:
            vedo.logger.error("No vedo plotter found, cannot make video.")
            return self
        n = int(self.fps * self.duration)

        cams = []
        for cm in cameras:
            cams.append(utils.camera_from_dict(cm))
        nc = len(cams)

        plt.show(resetcam=resetcam, interactive=False)

        if nc:
            for i in range(n):
                plt.move_camera(cams, i / (n - 1))
                plt.render()
                self.add_frame()

        else:  ########################################
            for i in range(n):
                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
                plt.render()
                self.add_frame()

        return self

    def close(self) -> None:
        """
        Render the video and write it to file.
        """
        if self.duration:
            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
            vedo.logger.info(f"Recalculated fps: {self.fps}")
        else:
            self.fps = int(self.fps)

        ########################################
        if self.backend == "ffmpeg":
            out = os.system(
                self.command
                + " "
                + str(self.fps)
                + " -i "
                + f"'{self.tmp_dir.name}'"
                + os.sep
                + "%01d.png "
                + self.options
                + " "
                + f"'{self.name}'"
            )
            if out:
                vedo.logger.error(
                    f":noentry: backend {self.backend} returning error: {out}"
                )
            else:
                vedo.logger.info(f"Saved to {self.name}")

        ########################################
        elif "cv" in self.backend:
            try:
                import cv2  # type: ignore
            except ImportError:
                vedo.logger.error("opencv is not installed")
                return

            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
            plt = vedo.current_plotter()
            if plt:
                w, h = plt.window.GetSize()
                writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
            else:
                vedo.logger.error("No vedo plotter found, cannot make video.")
                return

            while True:
                ret, frame = cap.read()
                if not ret:
                    break
                writer.write(frame)

            cap.release()
            writer.release()

        ########################################
        elif "imageio" in self.backend:
            try:
                import imageio
            except ImportError:
                vedo.logger.error(
                    "Please install imageio with:\n pip install imageio[ffmpeg]"
                )
                return

            if self.name.endswith(".mp4"):
                writer = imageio.get_writer(self.name, fps=self.fps)
            elif self.name.endswith(".gif"):
                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
            elif self.name.endswith(".webm"):
                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
            else:
                vedo.logger.error(f"Unknown format of {self.name}.")
                return

            for f in utils.humansort(self.frames):
                image = imageio.v3.imread(f)
                try:
                    writer.append_data(image)
                except TypeError:
                    vedo.logger.error(f"Could not append data to video {self.name}")
                    vedo.logger.error(
                        "Please install imageio with: pip install imageio[ffmpeg]"
                    )
                    break
            try:
                writer.close()
                vedo.logger.info(f"Saved as {self.name}")
            except Exception:
                vedo.logger.error(f"Could not save video {self.name}")

        # finalize cleanup
        self.tmp_dir.cleanup()

    def split_frames(
        self, output_dir="video_frames", prefix="frame_", file_format="png"
    ) -> None:
        """Split an existing video file into frames."""
        try:
            import imageio
        except ImportError:
            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
            return

        # Create the output directory if it doesn't exist
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)

        # Create a reader object to read the video
        reader = imageio.get_reader(self.name)

        # Loop through each frame of the video and save it as image
        print()
        for i, frame in utils.progressbar(
            enumerate(reader), title=f"writing {file_format} frames", c="m", width=20
        ):
            output_file = os.path.join(
                output_dir, f"{prefix}{str(i).zfill(5)}.{file_format}"
            )
            imageio.imwrite(output_file, frame, format=file_format)

action(elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False)

Automatic shooting of a static scene by specifying rotation and elevation ranges.

Parameters:

Name Type Description Default
elevation

list initial and final elevation angles

required
azimuth_range

list initial and final azimuth angles

required
cameras

list list of cameras to go through, each camera can be dictionary or a vtkCamera

required
Source code in vedo/file_io/video.py
 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
def action(
    self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False
) -> Video:
    """
    Automatic shooting of a static scene by specifying rotation and elevation ranges.

    Args:
        elevation : list
            initial and final elevation angles
        azimuth_range : list
            initial and final azimuth angles
        cameras : list
            list of cameras to go through, each camera can be dictionary or a vtkCamera
    """
    if not self.duration:
        self.duration = 5

    plt = vedo.current_plotter()
    if not plt:
        vedo.logger.error("No vedo plotter found, cannot make video.")
        return self
    n = int(self.fps * self.duration)

    cams = []
    for cm in cameras:
        cams.append(utils.camera_from_dict(cm))
    nc = len(cams)

    plt.show(resetcam=resetcam, interactive=False)

    if nc:
        for i in range(n):
            plt.move_camera(cams, i / (n - 1))
            plt.render()
            self.add_frame()

    else:  ########################################
        for i in range(n):
            plt.camera.Elevation((elevation[1] - elevation[0]) / n)
            plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
            plt.render()
            self.add_frame()

    return self

add_frame()

Add frame to current video.

Source code in vedo/file_io/video.py
61
62
63
64
65
66
def add_frame(self) -> Video:
    """Add frame to current video."""
    fr = self.get_filename(str(len(self.frames)) + ".png")
    screenshot(fr, scale=self.scale)
    self.frames.append(fr)
    return self

close()

Render the video and write it to file.

Source code in vedo/file_io/video.py
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
def close(self) -> None:
    """
    Render the video and write it to file.
    """
    if self.duration:
        self.fps = int(len(self.frames) / float(self.duration) + 0.5)
        vedo.logger.info(f"Recalculated fps: {self.fps}")
    else:
        self.fps = int(self.fps)

    ########################################
    if self.backend == "ffmpeg":
        out = os.system(
            self.command
            + " "
            + str(self.fps)
            + " -i "
            + f"'{self.tmp_dir.name}'"
            + os.sep
            + "%01d.png "
            + self.options
            + " "
            + f"'{self.name}'"
        )
        if out:
            vedo.logger.error(
                f":noentry: backend {self.backend} returning error: {out}"
            )
        else:
            vedo.logger.info(f"Saved to {self.name}")

    ########################################
    elif "cv" in self.backend:
        try:
            import cv2  # type: ignore
        except ImportError:
            vedo.logger.error("opencv is not installed")
            return

        cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
        fourcc = cv2.VideoWriter_fourcc(*"mp4v")
        plt = vedo.current_plotter()
        if plt:
            w, h = plt.window.GetSize()
            writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
        else:
            vedo.logger.error("No vedo plotter found, cannot make video.")
            return

        while True:
            ret, frame = cap.read()
            if not ret:
                break
            writer.write(frame)

        cap.release()
        writer.release()

    ########################################
    elif "imageio" in self.backend:
        try:
            import imageio
        except ImportError:
            vedo.logger.error(
                "Please install imageio with:\n pip install imageio[ffmpeg]"
            )
            return

        if self.name.endswith(".mp4"):
            writer = imageio.get_writer(self.name, fps=self.fps)
        elif self.name.endswith(".gif"):
            writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
        elif self.name.endswith(".webm"):
            writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
        else:
            vedo.logger.error(f"Unknown format of {self.name}.")
            return

        for f in utils.humansort(self.frames):
            image = imageio.v3.imread(f)
            try:
                writer.append_data(image)
            except TypeError:
                vedo.logger.error(f"Could not append data to video {self.name}")
                vedo.logger.error(
                    "Please install imageio with: pip install imageio[ffmpeg]"
                )
                break
        try:
            writer.close()
            vedo.logger.info(f"Saved as {self.name}")
        except Exception:
            vedo.logger.error(f"Could not save video {self.name}")

    # finalize cleanup
    self.tmp_dir.cleanup()

pause(pause=0)

Insert a pause, in seconds.

Source code in vedo/file_io/video.py
68
69
70
71
72
73
74
75
76
def pause(self, pause=0) -> Video:
    """Insert a `pause`, in seconds."""
    fr = self.frames[-1]
    n = int(self.fps * pause)
    for _ in range(n):
        fr2 = self.get_filename(str(len(self.frames)) + ".png")
        self.frames.append(fr2)
        shutil.copyfile(fr, fr2)
    return self

split_frames(output_dir='video_frames', prefix='frame_', file_format='png')

Split an existing video file into frames.

Source code in vedo/file_io/video.py
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
def split_frames(
    self, output_dir="video_frames", prefix="frame_", file_format="png"
) -> None:
    """Split an existing video file into frames."""
    try:
        import imageio
    except ImportError:
        vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
        return

    # Create the output directory if it doesn't exist
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # Create a reader object to read the video
    reader = imageio.get_reader(self.name)

    # Loop through each frame of the video and save it as image
    print()
    for i, frame in utils.progressbar(
        enumerate(reader), title=f"writing {file_format} frames", c="m", width=20
    ):
        output_file = os.path.join(
            output_dir, f"{prefix}{str(i).zfill(5)}.{file_format}"
        )
        imageio.imwrite(output_file, frame, format=file_format)

writers

read(obj, unpack=True, force=False)

Read an object from file. Same as load().

Source code in vedo/file_io/writers.py
177
178
179
def read(obj: Any, unpack=True, force=False) -> Any:
    """Read an object from file. Same as `load()`."""
    return load(obj, unpack, force)

save(obj, fileoutput='out.png', binary=True)

Save an object to file. Same as write().

Source code in vedo/file_io/writers.py
172
173
174
def save(obj: Any, fileoutput="out.png", binary=True) -> Any:
    """Save an object to file. Same as `write()`."""
    return write(obj, fileoutput, binary)

write(objct, fileoutput, binary=True)

Write object to file. Same as save().

Supported extensions are:

  • vtk, vti, ply, obj, stl, byu, vtp, vti, mhd, xyz, xml, tif, png, bmp
Source code in vedo/file_io/writers.py
 23
 24
 25
 26
 27
 28
 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
def write(objct: Any, fileoutput: str | os.PathLike, binary=True) -> Any:
    """
    Write object to file. Same as `save()`.

    Supported extensions are:

    - `vtk, vti, ply, obj, stl, byu, vtp, vti, mhd, xyz, xml, tif, png, bmp`
    """
    fileoutput = str(fileoutput)

    ###############################
    if isinstance(objct, Assembly):
        dd = to_numpy(objct)
        sdict = {"objects": [dd]}
        np.save(fileoutput, sdict)
        return objct

    ###############################
    obj = objct.dataset

    # Check if object actor has a non-identity transform and bake it before writing.
    try:
        M = objct.actor.GetMatrix()
    except AttributeError:
        M = None
    if M and not M.IsIdentity():
        objct.apply_transform_from_actor()
        obj = objct.dataset
        vedo.logger.info(
            f"object '{objct.name}' was manually moved. Writing uses current position."
        )

    fr = fileoutput.lower()
    if fr.endswith(".vtk"):
        writer = vtki.new("DataSetWriter")
    elif fr.endswith(".ply"):
        writer = vtki.new("PLYWriter")
        writer.AddComment("PLY file generated by vedo")
        lut = objct.mapper.GetLookupTable()
        if lut:
            pscal = obj.GetPointData().GetScalars()
            if not pscal:
                pscal = obj.GetCellData().GetScalars()
            if pscal and pscal.GetName():
                writer.SetArrayName(pscal.GetName())
            writer.SetLookupTable(lut)
    elif fr.endswith(".stl"):
        writer = vtki.new("STLWriter")
    elif fr.endswith(".vtp"):
        writer = vtki.new("XMLPolyDataWriter")
    elif fr.endswith(".vtu"):
        writer = vtki.new("XMLUnstructuredGridWriter")
    elif fr.endswith(".xyz"):
        writer = vtki.new("SimplePointsWriter")
    elif fr.endswith(".facet"):
        writer = vtki.new("FacetWriter")
    elif fr.endswith(".vti"):
        writer = vtki.new("XMLImageDataWriter")
    elif fr.endswith(".vtr"):
        writer = vtki.new("XMLRectilinearGridWriter")
    elif fr.endswith(".vtm"):
        g = vtki.new("MultiBlockDataGroupFilter")
        for ob in objct:
            try:
                g.AddInputData(ob)
            except TypeError:
                vedo.logger.warning(f"cannot save object of type {type(ob)}")
        g.Update()
        mb = g.GetOutputDataObject(0)
        wri = vtki.new("vtkXMLMultiBlockDataWriter")
        wri.SetInputData(mb)
        wri.SetFileName(fileoutput)
        wri.Write()
        return objct
    elif fr.endswith(".mhd"):
        writer = vtki.new("MetaImageWriter")
    elif fr.endswith(".nii"):
        writer = vtki.new("NIFTIImageWriter")
    elif fr.endswith(".png"):
        writer = vtki.new("PNGWriter")
    elif fr.endswith(".jpg"):
        writer = vtki.new("JPEGWriter")
    elif fr.endswith(".bmp"):
        writer = vtki.new("BMPWriter")
    elif fr.endswith(".tif") or fr.endswith(".tiff"):
        writer = vtki.new("TIFFWriter")
        writer.SetFileDimensionality(len(obj.GetDimensions()))
    elif fr.endswith(".obj"):
        with open(fileoutput, "w", encoding="UTF-8") as outF:
            outF.write("# OBJ file format with ext .obj\n")
            outF.write("# File generated by vedo\n")

            for p in objct.vertices:
                outF.write("v {:.8g} {:.8g} {:.8g}\n".format(*p))

            ptxt = objct.dataset.GetPointData().GetTCoords()
            if ptxt:
                ntxt = utils.vtk2numpy(ptxt)
                for vt in ntxt:
                    outF.write("vt " + str(vt[0]) + " " + str(vt[1]) + " 0.0\n")

            if isinstance(objct, Mesh):
                for i, f in enumerate(objct.cells):
                    fs = ""
                    for fi in f:
                        if ptxt:
                            fs += f" {fi + 1}/{fi + 1}"
                        else:
                            fs += f" {fi + 1}"
                    outF.write(f"f{fs}\n")

                for l in objct.lines:
                    ls = ""
                    for li in l:
                        ls += str(li + 1) + " "
                    outF.write(f"l {ls}\n")
        return objct

    elif fr.endswith(".off"):
        with open(fileoutput, "w", encoding="UTF-8") as outF:
            outF.write("OFF\n")
            outF.write(str(objct.npoints) + " " + str(objct.ncells) + " 0\n\n")
            for p in objct.vertices:
                outF.write(" ".join([str(i) for i in p]) + "\n")
            for c in objct.cells:
                outF.write(str(len(c)) + " " + " ".join([str(i) for i in c]) + "\n")
        return objct

    else:
        vedo.logger.error(f"Unknown format {fileoutput}, file not saved")
        return objct

    try:
        if binary:
            writer.SetFileTypeToBinary()
        else:
            writer.SetFileTypeToASCII()
    except AttributeError:
        pass

    try:
        writer.SetInputData(obj)
        writer.SetFileName(fileoutput)
        writer.Write()
    except Exception as e:
        vedo.logger.error(f"could not save {fileoutput}: {e}")
    return objct