Coverage for trimesh/exchange/off.py: 97%
32 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-08-31 18:21 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-08-31 18:21 +0000
1import re
3import numpy as np
5from ..geometry import triangulate_quads
6from ..util import array_to_string, comment_strip, decode_text
9def load_off(file_obj, **kwargs) -> dict:
10 """
11 Load an OFF file into the kwargs for a Trimesh constructor.
13 Parameters
14 ----------
15 file_obj : file object
16 Contains an OFF file
18 Returns
19 ----------
20 loaded : dict
21 kwargs for Trimesh constructor
22 """
23 text = file_obj.read()
24 # will magically survive weird encoding sometimes
25 # comment strip will handle all cases of commenting
26 text = comment_strip(decode_text(text)).strip()
28 # split the first key
29 _, header, raw = re.split("(COFF|OFF)", text, maxsplit=1)
30 if header.upper() not in ["OFF", "COFF"]:
31 raise NameError(f"Not an OFF file! Header was: `{header}`")
33 # split into lines and remove whitespace
34 splits = [i.strip() for i in str.splitlines(str(raw))]
35 # remove empty lines
36 splits = [i for i in splits if len(i) > 0]
38 # the first non-comment line should be the counts
39 if not splits:
40 raise ValueError("OFF file is missing the vertex/face count line")
41 header = np.array(splits[0].split(), dtype=np.int64)
42 if header.size < 2:
43 raise ValueError("OFF file has a malformed vertex/face count line")
44 vertex_count, face_count = header[:2]
46 vertices = np.array(
47 [i.split()[:3] for i in splits[1 : vertex_count + 1]], dtype=np.float64
48 )
50 # will fail if incorrect number of vertices loaded
51 vertices = vertices.reshape((vertex_count, 3))
53 # get lines with face data
54 faces = [i.split() for i in splits[vertex_count + 1 : vertex_count + face_count + 1]]
55 # the first value is count
56 faces = [line[1 : int(line[0]) + 1] for line in faces]
58 faces = triangulate_quads(faces)
59 # save data as kwargs for a trimesh.Trimesh
60 kwargs = {"vertices": vertices, "faces": faces}
62 return kwargs
65def export_off(mesh, digits=10) -> str:
66 """
67 Export a mesh as an OFF file, a simple text format
69 Parameters
70 -----------
71 mesh : trimesh.Trimesh
72 Geometry to export
73 digits : int
74 Number of digits to include on floats
76 Returns
77 -----------
78 export : str
79 OFF format output
80 """
81 # make sure specified digits is an int
82 digits = int(digits)
83 # prepend a 3 (face count) to each face
84 faces_stacked = np.column_stack((np.ones(len(mesh.faces)) * 3, mesh.faces)).astype(
85 np.int64
86 )
87 # the header is vertex count, face count, another number
88 export = "\n".join(
89 [
90 "OFF",
91 str(len(mesh.vertices)) + " " + str(len(mesh.faces)) + " 0",
92 array_to_string(mesh.vertices, col_delim=" ", row_delim="\n", digits=digits),
93 array_to_string(faces_stacked, col_delim=" ", row_delim="\n"),
94 "",
95 ]
96 )
98 return export
101_off_loaders = {"off": load_off}
102_off_exporters = {"off": export_off}