mujoco
mujoco copied to clipboard
Inconsistent behavior with VFS and mjSpec
Intro
Hi!
One of the early MuJoCo developer
My setup
Mac, python, mujoco==3.2.4
What's happening? What did you expect?
When using a virtual file system with mjSpec, there are inconsistencies in the behavior (especially when names with Caps are involved)
Case: when the asset/mesh/file is only the filename
| name on disc | name in XML | loads w/ mjSpec? | loads natively? |
|---|---|---|---|
| cube.stl | Cube.stl | True | True |
| cube.stl | cube.stl | True | True |
| Cube.stl | Cube.stl | True | True |
| Cube.stl | cube.stl | False | True |
Case: when the asset/mesh/file is more complex
| name on disc | name in XML | loads w/ mjSpec? | loads natively? |
|---|---|---|---|
| cube.stl | path/Cube.stl | True | True |
| cube.stl | path/cube.stl | True | True |
| Cube.stl | path/Cube.stl | False | True |
| Cube.stl | path/cube.stl | False | True |
In summary,
- capitalization is not respected natively. Everything works
- capitalization is partially respected in mjSpec
- capitalization behavior depends on how the name is specified
Steps for reproduction
- Unzip the folder
- Run
python test.py - Change filenames on disc and on XML as specified above.
NOTE:
- When trying out cases with VFS, ensure that the fallback of loading assets from the disc doesn't work otherwise the bug won't highlight.
- This is extremely important as this bug is about discrepancies between VFS and load from the disc.
Recommendation: The docs around how caps, relative paths, partial paths, etc are handled in VFS are non-existent. Consider updating the docs.
Minimal model for reproduction
See attached
Code required for reproduction
python test.py
Confirmations
- [x] I searched the latest documentation thoroughly before posting.
- [x] I searched previous Issues and Discussions, I am certain this has not been raised before.
Here is a simpler example.
@quagla can you try this will MuJoCo==3.2.4
import mujoco
mesh_name = "Cube.stl"
assets = {
mesh_name: b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x00\x00\x80?\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x00\x00\x80?\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x80\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00"
}
model_str = """
<mujoco model="test model">
<asset>
<mesh name="cube" file="cube.stl" scale=".05 .05 .05"/>
</asset>
<worldbody>
<geom type="mesh" mesh="cube" pos="0 0 0" contype="0" conaffinity="0"/>
</worldbody>
</mujoco>
"""
spec = mujoco.MjSpec.from_string(model_str)
model = spec.compile(assets)
This looks to be a VFS issue, not an mjSpec one. It's reproducible also by loading from XML directly if assets are given with VFS.
Im observing something different
import mujoco
mesh_name = "Cube.stl"
assets = {
mesh_name: b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x00\x00\x80?\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x00\x00\x80?\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x80\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80\xbf\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80?\x00\x00\x80\xbf\x00\x00"
}
model_str = """
<mujoco model="test model">
<asset>
<mesh name="cube" file="cube.stl" scale=".05 .05 .05"/>
</asset>
<worldbody>
<geom type="mesh" mesh="cube" pos="0 0 0" contype="0" conaffinity="0"/>
</worldbody>
</mujoco>
"""
print("Loading model directly", end=": ")
mj_model = mujoco.MjModel.from_xml_string(model_str, assets)
print("success")
print("Loading model via spec", end=": ")
spec = mujoco.MjSpec.from_string(model_str)
model = spec.compile(assets)
print("success")
output
Loading model directly: success
Loading model via spec: Traceback (most recent call last):
File "/Users/vikashplus/Repos/myolab/myo_api/myo_api/sandbox/vfs_caps_bug/test2.py", line 25, in <module>
model = spec.compile(assets)
ValueError: Error: Error opening file 'cube.stl': No such file or directory
summary direct loading of model via XML and VFS works loading model using spec and VFS fails
This is my MRE directly in C which indicates a VFS issue (note that every time you do from_xml_string an mjSpec is created under the hood in the same way as my MRE below). I don't know how your example succeeds unless Python does some magic or you have a file with that name in the folder where you run the test.
TEST_F(MjCMeshTest, LoadWithVFS) {
static constexpr char cube[] = R"(
v -1 -1 1
v 1 -1 1
v -1 1 1
v 1 1 1
v -1 1 -1
v 1 1 -1
v -1 -1 -1
v 1 -1 -1)";
auto vfs = std::make_unique<mjVFS>();
mj_defaultVFS(vfs.get());
mj_addBufferVFS(vfs.get(), "Cube.obj", cube, sizeof(cube));
static constexpr char xml[] = R"(
<mujoco>
<asset>
<mesh name="cube" file="cube.obj"/>
</asset>
<worldbody>
<geom type="mesh" mesh="cube"/>
</worldbody>
</mujoco>
)";
std::array<char, 1024> error;
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
ASSERT_THAT(spec, NotNull()) << error.data();
mjModel* model = mj_compile(spec, vfs.get());
EXPECT_THAT(model, IsNull()) << mjs_getError(spec);
mj_deleteSpec(spec);
mj_deleteVFS(vfs.get());
}
Note that also here if I use Cube in the XML and cube in VFS, then the model is not NULL.
Maybe @kbayes who knows VFS well has got any ideas?
@quagla VFS historically stores all paths in lowercase. Changing this behavior breaks a lot of packages, so it can't be change.
@vikashplus is there a specific outcome you're looking for from this issue?
I have included my summary and recommendations in the initial post
Summary
- capitalization is not respected natively. All specified conditions works
- capitalization is partially respected in mjSpec
- capitalization behavior depends on how the name is specified
Recommendation:
- Docs around how caps, relative paths, partial paths, etc are handled in VFS and Specs are non-existent.
- Consider updating the docs.