Coverage for trimesh/exchange/off.py: 96%
28 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +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 header = np.array(splits[0].split(), dtype=np.int64)
40 vertex_count, face_count = header[:2]
42 vertices = np.array(
43 [i.split()[:3] for i in splits[1 : vertex_count + 1]], dtype=np.float64
44 )
46 # will fail if incorrect number of vertices loaded
47 vertices = vertices.reshape((vertex_count, 3))
49 # get lines with face data
50 faces = [i.split() for i in splits[vertex_count + 1 : vertex_count + face_count + 1]]
51 # the first value is count
52 faces = [line[1 : int(line[0]) + 1] for line in faces]
54 faces = triangulate_quads(faces)
55 # save data as kwargs for a trimesh.Trimesh
56 kwargs = {"vertices": vertices, "faces": faces}
58 return kwargs
61def export_off(mesh, digits=10) -> str:
62 """
63 Export a mesh as an OFF file, a simple text format
65 Parameters
66 -----------
67 mesh : trimesh.Trimesh
68 Geometry to export
69 digits : int
70 Number of digits to include on floats
72 Returns
73 -----------
74 export : str
75 OFF format output
76 """
77 # make sure specified digits is an int
78 digits = int(digits)
79 # prepend a 3 (face count) to each face
80 faces_stacked = np.column_stack((np.ones(len(mesh.faces)) * 3, mesh.faces)).astype(
81 np.int64
82 )
83 # the header is vertex count, face count, another number
84 export = "\n".join(
85 [
86 "OFF",
87 str(len(mesh.vertices)) + " " + str(len(mesh.faces)) + " 0",
88 array_to_string(mesh.vertices, col_delim=" ", row_delim="\n", digits=digits),
89 array_to_string(faces_stacked, col_delim=" ", row_delim="\n"),
90 "",
91 ]
92 )
94 return export
97_off_loaders = {"off": load_off}
98_off_exporters = {"off": export_off}