nschloe / meshio

:spider_web: input/output for many mesh formats
MIT License
1.94k stars 399 forks source link

[BUG] Reading `.obj` mesh with multiple texture vertices/vertex fails #1487

Open nikolas-claussen opened 2 months ago

nikolas-claussen commented 2 months ago

Reading .obj mesh with multiple texture vertices/vertex fails. This situation occurs frequently in UV mapping - when cuts are made to map a mesh which is not topologically a disk to the UV square, vertices along the cut are assigned multiple texture positions.

To reproduce, create a .obj mesh with UV mapping and cuts (e.g. in blender, create a UV sphere and export it as uv_sphere.obj).

mesh = meshio.read("uv_sphere.obj")

yields

ValueError: len(points) = 482, but len(point_data["obj:vt"]) = 559

Version: meshio==5.3.5

TimeTravelerFromNow commented 1 month ago

Here's a PR from earlier this year that suggests a solution for this. Also, issues #1261, #1377 are related.

Here's the source that causes the error, I think this check is useful, however won't suffice for .objs line 162 src/meshio/_mesh.py

       # assert point data consistency and convert to numpy arrays
        for key, item in self.point_data.items():
            self.point_data[key] = np.asarray(item)
            if len(self.point_data[key]) != len(self.points):
                raise ValueError(
                    f"len(points) = {len(self.points)}, "
                    f'but len(point_data["{key}"]) = {len(self.point_data[key])}'
                )

The problem with this assertion for .obj loading is that the vertex texture attribute "obj:vt" and other keys in valid .obj files commonly are not the same count as the number of vertices. I have a suggestion how this could be changed to validate the "face" data which appears as

f 1/1 2/2 3/3 4/4
f 5/1 8/2 7/3 6/4
f 1/1 5/2 6/3 2/4

in an obj file. For example, f 5/1 8/2 7/3 6/4 is saying face 2 is made up of vertex 5 8 7 6. With corresponding vertex textures index 1 2 3 4 defined in the file as vt 0.000000 0.000000 etc.

Reading this documentation about the obj format https://learnwebgl.brown37.net/, what we should validate is that the indices in each face data doesn't exceed the length of the available attributes. So if there are 4 "obj:vt" we should read each face, so

f 1/1 2/2 3/3 4/4 would pass the validation since for each vertex_index/texture_index, texture_index <= len(point_data["obj:vt"])

f 1/1 2/2 3/3 4/5 would fail since 4/5 has a texture_index = 5 > len(point_data["obj:vt"])