Skip to content

Commit f391a50

Browse files
committed
make binvox header parsing more robust to whitespace
1 parent 44ffd9d commit f391a50

File tree

1 file changed

+48
-27
lines changed

1 file changed

+48
-27
lines changed

trimesh/exchange/binvox.py

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616

1717
from .. import util
1818
from ..base import Trimesh
19+
from ..util import comment_strip, decode_text
1920

2021
# find the executable for binvox in PATH
2122
binvox_encoder = util.which("binvox")
2223
Binvox = collections.namedtuple("Binvox", ["rle_data", "shape", "translate", "scale"])
2324

25+
_header_required = {"dim", "translate", "scale"}
2426

25-
def parse_binvox_header(fp):
27+
28+
def _parse_binvox_header(file_obj):
2629
"""
2730
Read the header from a binvox file.
2831
Spec available:
@@ -48,21 +51,40 @@ def parse_binvox_header(fp):
4851
If invalid binvox file.
4952
"""
5053

51-
line = fp.readline().strip()
52-
if hasattr(line, "decode"):
53-
binvox = b"#binvox"
54-
space = b" "
55-
else:
56-
binvox = "#binvox"
57-
space = " "
58-
if not line.startswith(binvox):
59-
raise OSError("Not a binvox file")
60-
fp.readline()
61-
shape = tuple(int(s) for s in fp.readline().strip().split(space)[1:])
62-
translate = tuple(float(s) for s in fp.readline().strip().split(space)[1:])
63-
scale = float(fp.readline().strip().split(space)[1])
64-
fp.readline()
65-
return shape, translate, scale
54+
# check for the magic string in the first line
55+
first = decode_text(file_obj.readline()).strip()
56+
if "binvox" not in first.lower():
57+
raise ValueError("File is not in the binvox format!")
58+
59+
header = {}
60+
# do a capped iteration
61+
for _ in range(100):
62+
# get the line as a lower-case, comment-stripped split list
63+
line = (
64+
comment_strip(decode_text(file_obj.readline()).lower(), "#").strip().split()
65+
)
66+
# if the line was a comment or whitespace don't include it
67+
if len(line) == 0:
68+
continue
69+
70+
elif line[0] == "data":
71+
# we need to read up until we see "data" so the
72+
# read-the-rest-of-the-payload operation is correct
73+
break
74+
75+
# save the keyed header data
76+
header[line[0]] = line[1:]
77+
78+
if set(header.keys()) != _header_required:
79+
raise ValueError(
80+
f"Malformed binvox header: `{header.keys()}` != `{_header_required}`"
81+
)
82+
83+
shape = np.array(header["dim"], dtype=np.int64)
84+
translate = np.array(header["translate"], np.float64)
85+
scale = np.array(header["scale"], dtype=np.float64)
86+
87+
return shape, translate, scale[0]
6688

6789

6890
def parse_binvox(fp, writeable=False):
@@ -88,25 +110,17 @@ def parse_binvox(fp, writeable=False):
88110
If invalid binvox file
89111
"""
90112
# get the header info
91-
shape, translate, scale = parse_binvox_header(fp)
113+
shape, translate, scale = _parse_binvox_header(fp)
92114
# get the rest of the file
93115
data = fp.read()
94116
# convert to numpy array
95117
rle_data = np.frombuffer(data, dtype=np.uint8)
118+
96119
if writeable:
97120
rle_data = rle_data.copy()
98121
return Binvox(rle_data, shape, translate, scale)
99122

100123

101-
_binvox_header = """#binvox 1
102-
# Generated by trimesh using Patrick Min format ---
103-
dim {sx} {sy} {sz}
104-
translate {tx} {ty} {tz}
105-
scale {scale}
106-
data
107-
"""
108-
109-
110124
def binvox_header(shape, translate, scale):
111125
"""
112126
Get a binvox header string.
@@ -123,7 +137,14 @@ def binvox_header(shape, translate, scale):
123137
"""
124138
sx, sy, sz = (int(s) for s in shape)
125139
tx, ty, tz = translate
126-
return _binvox_header.format(sx=sx, sy=sy, sz=sz, tx=tx, ty=ty, tz=tz, scale=scale)
140+
141+
return f"""#binvox 1
142+
# generated in `trimesh`
143+
dim {sx} {sy} {sz}
144+
translate {tx} {ty} {tz}
145+
scale {scale}
146+
data
147+
"""
127148

128149

129150
def binvox_bytes(rle_data, shape, translate=(0, 0, 0), scale=1):

0 commit comments

Comments
 (0)