File size: 2,661 Bytes
5a90b18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
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
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
"""
I/O helpers for point clouds (PLY/PCD) and meshes (OBJ/PLY/GLB).
"""

from __future__ import annotations

from pathlib import Path
from typing import Optional, Tuple, Union

import numpy as np


def load_point_cloud(
    path: Union[str, Path],
    *,
    estimate_normals: bool = False,
    normal_knn: int = 30,
) -> Tuple[np.ndarray, Optional[np.ndarray]]:
    """
    Load a point cloud from a ``.ply`` or ``.pcd`` file.

    Parameters
    ----------
    path : str or Path
        Path to the input file.
    estimate_normals : bool, default False
        If the file does not contain normals, estimate them with Open3D.
    normal_knn : int, default 30
        k-NN neighbourhood size for Open3D normal estimation.

    Returns
    -------
    points : np.ndarray
        (N, 3) float array of point positions.
    normals : np.ndarray or None
        (N, 3) float array of point normals, if available or estimated.
    """
    path = Path(path)
    suffix = path.suffix.lower()

    if suffix in (".ply", ".pcd"):
        points, normals = _load_with_open3d(path, estimate_normals, normal_knn)
    else:
        raise ValueError(f"Unsupported point-cloud format: {suffix}")

    return points, normals


def _load_with_open3d(
    path: Path, estimate_normals: bool, normal_knn: int
) -> Tuple[np.ndarray, Optional[np.ndarray]]:
    try:
        import open3d as o3d
    except ImportError as exc:
        raise ImportError(
            "open3d is required to load PLY/PCD files. "
            "Install it with:  pip install open3d"
        ) from exc

    pcd = o3d.io.read_point_cloud(str(path))
    points = np.asarray(pcd.points)

    normals = None
    if pcd.has_normals():
        normals = np.asarray(pcd.normals)
    elif estimate_normals and len(points) > 0:
        pcd.estimate_normals(
            search_param=o3d.geometry.KDTreeSearchParamKNN(knn=normal_knn)
        )
        pcd.orient_normals_consistent_tangent_plane(k=normal_knn)
        normals = np.asarray(pcd.normals)

    return points, normals


def save_mesh(
    path: Union[str, Path],
    vertices: np.ndarray,
    faces: np.ndarray,
    vertex_colors: Optional[np.ndarray] = None,
) -> None:
    """
    Save a triangle mesh to a file.

    Supported formats: ``.ply``, ``.obj``, ``.glb``, ``.stl``, ``.off``
    (anything Trimesh supports).
    """
    import trimesh

    path = Path(path)

    # Ensure correct dtypes
    vertices = np.asarray(vertices, dtype=np.float32)
    faces = np.asarray(faces, dtype=np.int32)

    mesh = trimesh.Trimesh(
        vertices=vertices,
        faces=faces,
        vertex_colors=vertex_colors,
    )
    mesh.export(str(path))