msh2obj emits face indices for absent normals and texture coordinates
Intro
I'm reporting a mesh-interchange issue in the legacy MSH-to-OBJ conversion utility.
My setup
- Reproduced with the MuJoCo 3.13.0 Python release on macOS arm64, Python 3.13.15, NumPy 2.5.3.
- The current
mainversion ofpython/mujoco/msh2obj.pyhas the same behavior. - No renderer or robot hardware is involved.
What's happening? What did you expect?
The documented MSH format allows normals and texture coordinates to be absent (nnormal=0 and/or ntexcoord=0).
The converter always emits faces as v/vt/vn. With neither attribute present, its output has no vt or vn records but still contains:
f 1/1/1 3/3/3 2/2/2The expected face is:
f 1 3 2With only normals present the expected form is v//vn; with only texture coordinates it is v/vt. The current converter emits nonexistent attribute references in all three cases.
MuJoCo's own OBJ loader tolerates these absent attribute arrays, so merely loading the converted OBJ does not catch the output-format defect.
Steps for reproduction
Create this complete tetrahedral MSH file:
from pathlib import Path
import numpy as np
vertices = np.array(
[[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]],
dtype=np.float32,
)
faces = np.array(
[[0, 2, 1], [0, 1, 3], [0, 3, 2], [1, 2, 3]],
dtype=np.int32,
)
with Path("tetra.msh").open("wb") as f:
for data in (np.array([4, 0, 0, 4], dtype=np.int32), vertices, faces):
data.tofile(f)Then run:
python -m mujoco.msh2obj -i tetra.msh -o tetra.objInspect tetra.obj: there are 4 vertex records, zero normal/texture-coordinate records, and faces referencing all three attributes.
Minimal model
The generated MSH is loadable by:
<mujoco>
<asset>
<mesh name="tetra" file="tetra.msh"/>
</asset>
</mujoco>Investigation
A small fix and tests are ready: choose face syntax based on which attribute arrays are present. Tests write real MSH files for all four combinations and check exported syntax, attribute counts, and MSH/OBJ loading. Three new cases fail before the fix; all five tests, including the existing full-attribute roundtrip, pass afterward.
I checked the relevant format documentation and searched issues, discussions and PRs. Open #3493 changes header validation and does not modify face emission.
Source: google-deepmind/mujoco