mirror of
https://github.com/facebookresearch/pytorch3d.git
synced 2026-10-20 13:12:25 +08:00
Accept tab-separated lines in load_obj (#2061)
Summary:
`load_obj` recognizes vertex, texture, normal and face lines with `line.startswith("v ")`, `"vt "`, `"vn "` and `"f "`. The OBJ format separates fields with any whitespace, and some exporters write a tab after the keyword. Those lines are skipped without any error, so the mesh silently loses vertices and faces:
```python
from io import StringIO
from pytorch3d.io import load_obj
verts, faces, _ = load_obj(StringIO("v\t0 0 0\nv 1 0 0\nv 0 1 0\nf\t1 2 3\n"))
verts.shape, faces.verts_idx.shape
# before: (2, 3), (0, 3) after: (3, 3), (1, 3)
```
This dispatches on the first token of the already split line (`tokens[:1] == ["v"]`), the same way the `usemtl` branch already does.
Pull Request resolved: https://github.com/facebookresearch/pytorch3d/pull/2061
Test Plan: added `test_load_obj_tab_separated` to `tests/test_io_obj.py`, using tabs in `v`, `vt`, `vn` and `f` lines. It fails on main and passes with the change. I ran `tests/test_io_obj.py` on Windows without the C++ extension: the same 19 tests fail before and after (they reopen a `NamedTemporaryFile` or need `_C`), and the rest pass. flake8 is clean on the changed lines.
Reviewed By: MichaelRamamonjisoa
Differential Revision: D124656223
Pulled By: bottler
fbshipit-source-id: ab0ef3dc0f3c998c44874e6a416e6a9649f29378
This commit is contained in:
committed by
meta-codesync[bot]
parent
88e182f989
commit
8b3ba6ede4
@@ -472,26 +472,26 @@ def _parse_obj(f, data_dir: str):
|
||||
materials_idx = len(material_names) - 1
|
||||
else:
|
||||
materials_idx = material_names.index(material_name)
|
||||
elif line.startswith("v "): # Line is a vertex.
|
||||
elif tokens[:1] == ["v"]: # Line is a vertex.
|
||||
vert = [float(x) for x in tokens[1:4]]
|
||||
if len(vert) != 3:
|
||||
msg = "Vertex %s does not have 3 values. Line: %s"
|
||||
raise ValueError(msg % (str(vert), str(line)))
|
||||
verts.append(vert)
|
||||
elif line.startswith("vt "): # Line is a texture.
|
||||
elif tokens[:1] == ["vt"]: # Line is a texture.
|
||||
tx = [float(x) for x in tokens[1:3]]
|
||||
if len(tx) != 2:
|
||||
raise ValueError(
|
||||
"Texture %s does not have 2 values. Line: %s" % (str(tx), str(line))
|
||||
)
|
||||
verts_uvs.append(tx)
|
||||
elif line.startswith("vn "): # Line is a normal.
|
||||
elif tokens[:1] == ["vn"]: # Line is a normal.
|
||||
norm = [float(x) for x in tokens[1:4]]
|
||||
if len(norm) != 3:
|
||||
msg = "Normal %s does not have 3 values. Line: %s"
|
||||
raise ValueError(msg % (str(norm), str(line)))
|
||||
normals.append(norm)
|
||||
elif line.startswith("f "): # Line is a face.
|
||||
elif tokens[:1] == ["f"]: # Line is a face.
|
||||
# Update face properties info.
|
||||
_parse_face(
|
||||
line,
|
||||
|
||||
@@ -319,6 +319,29 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
|
||||
self.assertTrue(materials is None)
|
||||
self.assertTrue(tex_maps is None)
|
||||
|
||||
def test_load_obj_tab_separated(self):
|
||||
# Tabs are whitespace too, including between the keyword and the values.
|
||||
obj_file = "\n".join(
|
||||
[
|
||||
"v\t0.1\t0.2\t0.3",
|
||||
"v 0.2 0.3 0.4",
|
||||
"v\t0.3 0.4 0.5",
|
||||
"vt\t0.5\t0.5",
|
||||
"vn\t0.0\t0.0\t1.0",
|
||||
"f\t1/1/1\t2/1/1\t3/1/1",
|
||||
]
|
||||
)
|
||||
verts, faces, aux = load_obj(StringIO(obj_file))
|
||||
expected_verts = torch.tensor(
|
||||
[[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5]], dtype=torch.float32
|
||||
)
|
||||
self.assertClose(verts, expected_verts)
|
||||
self.assertClose(faces.verts_idx, torch.tensor([[0, 1, 2]]))
|
||||
self.assertClose(faces.textures_idx, torch.tensor([[0, 0, 0]]))
|
||||
self.assertClose(faces.normals_idx, torch.tensor([[0, 0, 0]]))
|
||||
self.assertClose(aux.verts_uvs, torch.tensor([[0.5, 0.5]]))
|
||||
self.assertClose(aux.normals, torch.tensor([[0.0, 0.0, 1.0]]))
|
||||
|
||||
def test_load_obj_error_textures(self):
|
||||
obj_file = "\n".join(["vt 0.1"])
|
||||
with NamedTemporaryFile(mode="w", suffix=".obj") as f:
|
||||
|
||||
Reference in New Issue
Block a user