Skip to content

io

triwarp.io

Mesh file loading via the optional meshio dependency.

load_mesh_data

load_mesh_data(
    path: str | Path, *, device: DeviceLike = None
) -> dict[str, wp.array[Any]]

Load every mesh attribute meshio can read from a file into Warp arrays.

Reads path once with meshio and returns a dictionary whose keys are present only when the corresponding data exists in the file. vertices is always present; everything else is optional.

Parameters:

Name Type Description Default
path str | Path

Path to a mesh file in any format supported by meshio (PLY, STL, OBJ, OFF, VTK, glTF, ...).

required
device DeviceLike

Warp device for the returned arrays. Defaults to the current Warp device.

None

Returns:

Type Description
dict

Mapping with these possible keys:

  • vertices : wp.array[wp.vec3] of float32 vertex positions (always present).
  • faces : flat wp.array[wp.int32] of length 3 * n_faces (triangle cells only).
  • vertex_normals : wp.array[wp.vec3] from nx, ny, nz per-vertex data.
  • uv : wp.array[wp.vec2] from u, v (or s, t / texture_u, texture_v).
  • colors : wp.array[wp.vec3] or wp.array[wp.vec4] from red, green, blue (and optional alpha); integer channels are normalized to [0, 1].
  • face_normals : wp.array[wp.vec3] — only when the file stored per-face normals.
See Also

load_mesh

Notes

Only triangle cells are consumed; non-triangle cell blocks are ignored. meshio reads attributes verbatim and never computes them, so face_normals appears only when the file itself contained per-face normals.

Source code in triwarp/io.py
 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
def load_mesh_data(path: str | Path, *, device: wp.DeviceLike = None) -> dict[str, wp.array[Any]]:
    """
    Load every mesh attribute ``meshio`` can read from a file into Warp arrays.

    Reads ``path`` once with ``meshio`` and returns a dictionary whose keys are present
    only when the corresponding data exists in the file. ``vertices`` is always present;
    everything else is optional.

    Parameters
    ----------
    path
        Path to a mesh file in any format supported by ``meshio`` (PLY, STL, OBJ, OFF,
        VTK, glTF, ...).
    device
        Warp device for the returned arrays. Defaults to the current Warp device.

    Returns
    -------
    dict
        Mapping with these possible keys:

        - ``vertices`` : ``wp.array[wp.vec3]`` of ``float32`` vertex positions (always present).
        - ``faces`` : flat ``wp.array[wp.int32]`` of length ``3 * n_faces`` (triangle cells only).
        - ``vertex_normals`` : ``wp.array[wp.vec3]`` from ``nx, ny, nz`` per-vertex data.
        - ``uv`` : ``wp.array[wp.vec2]`` from ``u, v`` (or ``s, t`` / ``texture_u, texture_v``).
        - ``colors`` : ``wp.array[wp.vec3]`` or ``wp.array[wp.vec4]`` from ``red, green, blue``
          (and optional ``alpha``); integer channels are normalized to ``[0, 1]``.
        - ``face_normals`` : ``wp.array[wp.vec3]`` — only when the file stored per-face normals.

    See Also
    --------
    [`load_mesh`][triwarp.io.load_mesh]

    Notes
    -----
    Only triangle cells are consumed; non-triangle cell blocks are ignored. ``meshio`` reads
    attributes verbatim and never computes them, so ``face_normals`` appears only when the
    file itself contained per-face normals.
    """
    meshio = _import_meshio()
    mesh = meshio.read(path)

    result: dict[str, wp.array[Any]] = {}

    points = np.ascontiguousarray(mesh.points, dtype=np.float32)
    result["vertices"] = wp.array(points, dtype=wp.vec3, device=device)

    faces_np = mesh.cells_dict.get("triangle")
    if faces_np is not None and len(faces_np) > 0:
        faces_flat = np.ascontiguousarray(faces_np.reshape(-1), dtype=np.int32)
        result["faces"] = wp.array(faces_flat, dtype=wp.int32, device=device)

    point_data = mesh.point_data

    normals = _stack_columns(point_data, _NORMAL_COLUMNS)
    if normals is not None:
        result["vertex_normals"] = wp.array(
            np.ascontiguousarray(normals, dtype=np.float32), dtype=wp.vec3, device=device
        )

    for names in _UV_COLUMN_SETS:
        uv = _stack_columns(point_data, names)
        if uv is not None:
            result["uv"] = wp.array(
                np.ascontiguousarray(uv, dtype=np.float32), dtype=wp.vec2, device=device
            )
            break

    colors_raw = _stack_columns(point_data, _COLOR_COLUMNS_RGBA)
    color_dtype = wp.vec4
    if colors_raw is None:
        colors_raw = _stack_columns(point_data, _COLOR_COLUMNS)
        color_dtype = wp.vec3
    if colors_raw is not None:
        colors = colors_raw.astype(np.float32)
        if np.issubdtype(colors_raw.dtype, np.integer):
            colors /= 255.0
        result["colors"] = wp.array(
            np.ascontiguousarray(colors, dtype=np.float32), dtype=color_dtype, device=device
        )

    face_normals = _face_normals_from_cell_data(mesh)
    if face_normals is not None:
        result["face_normals"] = wp.array(
            np.ascontiguousarray(face_normals, dtype=np.float32), dtype=wp.vec3, device=device
        )

    return result

load_mesh

load_mesh(
    path: str | Path, *, device: DeviceLike = None
) -> wp.Mesh

Load a triangle mesh file into a warp.Mesh.

Thin wrapper over load_mesh_data that keeps only the vertices and triangle faces and wraps them in a warp.Mesh that owns its buffers.

Parameters:

Name Type Description Default
path str | Path

Path to a mesh file in any format supported by meshio.

required
device DeviceLike

Warp device for the returned mesh. Defaults to the current Warp device.

None

Returns:

Type Description
Mesh

Mesh with vec3 points and flat int32 indices.

Raises:

Type Description
ValueError

If the file contains no triangle faces (e.g. a point cloud or a triangle-strip PLY that meshio cannot decode), or if a face references a vertex index the file's own vertex count does not cover.

See Also

load_mesh_data

Source code in triwarp/io.py
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
def load_mesh(path: str | Path, *, device: wp.DeviceLike = None) -> wp.Mesh:
    """
    Load a triangle mesh file into a ``warp.Mesh``.

    Thin wrapper over [`load_mesh_data`][triwarp.io.load_mesh_data] that keeps only the
    vertices and triangle faces and wraps them in a ``warp.Mesh`` that owns its buffers.

    Parameters
    ----------
    path
        Path to a mesh file in any format supported by ``meshio``.
    device
        Warp device for the returned mesh. Defaults to the current Warp device.

    Returns
    -------
    warp.Mesh
        Mesh with ``vec3`` points and flat ``int32`` indices.

    Raises
    ------
    ValueError
        If the file contains no triangle faces (e.g. a point cloud or a triangle-strip
        PLY that ``meshio`` cannot decode), or if a face references a vertex index the file's own
        vertex count does not cover.

    See Also
    --------
    [`load_mesh_data`][triwarp.io.load_mesh_data]
    """
    data = load_mesh_data(path, device=device)
    if "faces" not in data:
        raise ValueError(f"Mesh file {path!r} has no triangle faces; cannot build a wp.Mesh.")
    require_nonempty_mesh(data["faces"], "load_mesh")
    # A malformed file (or a meshio decoder bug) can hand back a face index the vertex buffer
    # doesn't cover; this is the trust boundary where that first enters triwarp, so check it once
    # here rather than trusting it through every downstream kernel that indexes ``vertices[faces]``.
    require_valid_faces(data["faces"], int(data["vertices"].shape[0]), "load_mesh")
    # ``load_mesh_data`` allocated both buffers on the line above and nothing else holds
    # them, so the mesh can own them directly.
    return wp.Mesh(points=data["vertices"], indices=data["faces"])

mesh_from_numpy

mesh_from_numpy(
    vertices: ndarray,
    faces: ndarray,
    *,
    device: DeviceLike = None,
) -> Trimesh

Build a Trimesh from NumPy vertex and face arrays.

Parameters:

Name Type Description Default
vertices ndarray

(n_vertices, 3) vertex positions, any NumPy float dtype.

required
faces ndarray

(n_faces, 3) (or already-flat length-3 * n_faces) triangle vertex indices, any NumPy integer dtype.

required
device DeviceLike

Warp device for the returned mesh. Defaults to the current Warp device.

None

Returns:

Type Description
Trimesh

New mesh with vec3 vertices and a flat int32 face buffer, owning freshly allocated Warp arrays (no aliasing with vertices / faces).

Raises:

Type Description
ValueError

If a face references a vertex index vertices does not cover.

See Also

load_mesh

Source code in triwarp/io.py
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
def mesh_from_numpy(
    vertices: np.ndarray, faces: np.ndarray, *, device: wp.DeviceLike = None
) -> Trimesh:
    """
    Build a [`Trimesh`][triwarp.mesh.Trimesh] from NumPy vertex and face arrays.

    Parameters
    ----------
    vertices
        ``(n_vertices, 3)`` vertex positions, any NumPy float dtype.
    faces
        ``(n_faces, 3)`` (or already-flat length-``3 * n_faces``) triangle vertex indices,
        any NumPy integer dtype.
    device
        Warp device for the returned mesh. Defaults to the current Warp device.

    Returns
    -------
    Trimesh
        New mesh with ``vec3`` vertices and a flat ``int32`` face buffer, owning freshly
        allocated Warp arrays (no aliasing with ``vertices`` / ``faces``).

    Raises
    ------
    ValueError
        If a face references a vertex index ``vertices`` does not cover.

    See Also
    --------
    [`load_mesh`][triwarp.io.load_mesh]
    """
    vertices_wp = wp.array(
        np.ascontiguousarray(vertices, dtype=np.float32), dtype=wp.vec3, device=device
    )
    faces_wp = wp.array(
        np.ascontiguousarray(faces.reshape(-1), dtype=np.int32), dtype=wp.int32, device=device
    )
    # This is the trust boundary where an external caller's raw NumPy connectivity first enters
    # triwarp -- check it once here rather than trusting it through every downstream kernel that
    # indexes ``vertices[faces]``.
    require_valid_faces(faces_wp, int(vertices_wp.shape[0]), "mesh_from_numpy")
    return Trimesh(vertices_wp, faces_wp)