From 567793c26fc40462f1f33c827df8bf0371fb8697 Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Wed, 11 Sep 2024 05:19:08 -0700 Subject: [PATCH] Store model paths in assets. This enables to preserve the paths of the child spec during `attach`. PiperOrigin-RevId: 673349237 Change-Id: Ibf075c7ec037ff5d540bc60d6e77c12f60269791 --- doc/changelog.rst | 2 ++ src/user/user_mesh.cc | 35 +++++++++++++++++++++++--- src/user/user_objects.cc | 49 ++++++++++++++++++++++++++++++------ src/user/user_objects.h | 19 ++++++++++++++ test/user/user_model_test.cc | 43 +++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 12 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 0e7576a2..17164571 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -72,6 +72,8 @@ Bug fixes integrators, wrong derivatives would be computed. - Fixed a bug in tendon wrapping around spheres. Before this fix, tendons that wrapped around spheres with an externally-placed :ref:`sidesite` could jump inside the sphere instead of wrapping around it. +- Fixed a bug that caused :at:`meshdir` and :at:`texturedir` to be overwritten during model + :ref:`attachment. Python bindings ^^^^^^^^^^^^^^^ diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 22fc1ca6..75c3abfd 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -72,6 +72,7 @@ extern "C" { namespace { using mujoco::user::VectorToString; + using mujoco::user::FilePath; } // namespace // compute triangle area, surface normal, center @@ -218,6 +219,14 @@ void mjCMesh::PointToLocal() { +void mjCMesh::NameSpace(const mjCModel* m) { + mjCBase::NameSpace(m); + modelfiledir_ = FilePath(m->spec_modelfiledir_); + meshdir_ = FilePath(m->spec_meshdir_); +} + + + void mjCMesh::CopyFromSpec() { *static_cast(this) = spec; file_ = spec_file_; @@ -388,6 +397,14 @@ void mjCMesh::Compile(const mjVFS* vfs) { facenormal_.clear(); facetexcoord_.clear(); + // copy paths from model if not already defined + if (modelfiledir_.empty()) { + modelfiledir_ = FilePath(model->modelfiledir_); + } + if (meshdir_.empty()) { + meshdir_ = FilePath(model->meshdir_); + } + // remove path from file if necessary if (model->strippath) { file_ = mjuu_strippath(file_); @@ -402,8 +419,8 @@ void mjCMesh::Compile(const mjVFS* vfs) { throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - std::string filename = mjuu_combinePaths(model->meshdir_, file_); - resource = LoadResource(model->modelfiledir_, filename, vfs); + FilePath filename = meshdir_ + FilePath(file_); + resource = LoadResource(modelfiledir_.Str(), filename.Str(), vfs); // try loading from cache if (cache != nullptr && LoadCachedMesh(cache, resource)) { @@ -2016,6 +2033,8 @@ void mjCSkin::NameSpace(const mjCModel* m) { for (auto& name : spec_bodyname_) { name = m->prefix + name + m->suffix; } + modelfiledir_ = FilePath(m->spec_modelfiledir_); + meshdir_ = FilePath(m->spec_meshdir_); } @@ -2097,8 +2116,16 @@ void mjCSkin::Compile(const mjVFS* vfs) { throw mjCError(this, "Unknown skin file type: %s", file_.c_str()); } - std::string filename = mjuu_combinePaths(model->meshdir_, file_); - mjResource* resource = LoadResource(model->modelfiledir_, filename, vfs); + // copy paths from model if not already defined + if (modelfiledir_.empty()) { + modelfiledir_ = FilePath(model->modelfiledir_); + } + if (meshdir_.empty()) { + meshdir_ = FilePath(model->meshdir_); + } + + FilePath filename = meshdir_ + FilePath(file_); + mjResource* resource = LoadResource(modelfiledir_.Str(), filename.Str(), vfs); try { LoadSKN(resource); diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index d92c8a2c..a2b0d852 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -50,6 +50,7 @@ namespace { namespace mju = ::mujoco::util; +using mujoco::user::FilePath; class PNGImage { public: @@ -3402,6 +3403,14 @@ void mjCHField::CopyFromSpec() { +void mjCHField::NameSpace(const mjCModel* m) { + mjCBase::NameSpace(m); + modelfiledir_ = FilePath(m->spec_modelfiledir_); + meshdir_ = FilePath(m->spec_meshdir_); +} + + + // destructor mjCHField::~mjCHField() { data.clear(); @@ -3514,8 +3523,16 @@ void mjCHField::Compile(const mjVFS* vfs) { throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - std::string filename = mjuu_combinePaths(model->meshdir_, file_); - mjResource* resource = LoadResource(model->modelfiledir_, filename, vfs); + // copy paths from model if not already defined + if (modelfiledir_.empty()) { + modelfiledir_ = FilePath(model->modelfiledir_); + } + if (meshdir_.empty()) { + meshdir_ = FilePath(model->meshdir_); + } + + FilePath filename = meshdir_ + FilePath(file_); + mjResource* resource = LoadResource(modelfiledir_.Str(), filename.Str(), vfs); try { if (asset_type == "image/png") { @@ -3631,6 +3648,14 @@ void mjCTexture::CopyFromSpec() { +void mjCTexture::NameSpace(const mjCModel* m) { + mjCBase::NameSpace(m); + modelfiledir_ = FilePath(m->spec_modelfiledir_); + texturedir_ = FilePath(m->spec_texturedir_); +} + + + // free data storage allocated by lodepng mjCTexture::~mjCTexture() { data_.clear(); @@ -3960,7 +3985,7 @@ void mjCTexture::LoadFlip(std::string filename, const mjVFS* vfs, throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - mjResource* resource = LoadResource(model->modelfiledir_, filename, vfs); + mjResource* resource = LoadResource(modelfiledir_.Str(), filename, vfs); try { if (asset_type == "image/png") { @@ -4176,12 +4201,12 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { } // make filename - std::string filename = mjuu_combinePaths(model->texturedir_, cubefiles_[i]); + FilePath filename = texturedir_ + FilePath(cubefiles_[i]); // load PNG or custom unsigned int w, h; std::vector image; - LoadFlip(filename, vfs, image, w, h); + LoadFlip(filename.Str(), vfs, image, w, h); // PNG must be square if (w != h) { @@ -4244,6 +4269,14 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { void mjCTexture::Compile(const mjVFS* vfs) { CopyFromSpec(); + // copy paths from model if not already defined + if (modelfiledir_.empty()) { + modelfiledir_ = FilePath(model->modelfiledir_); + } + if (texturedir_.empty()) { + texturedir_ = FilePath(model->texturedir_); + } + // buffer from user if (!data_.empty()) { if (data_.size() != nchannel*width*height) { @@ -4299,13 +4332,13 @@ void mjCTexture::Compile(const mjVFS* vfs) { } // make filename - std::string filename = mjuu_combinePaths(model->texturedir_, file_); + FilePath filename = texturedir_ + FilePath(file_); // dispatch if (type==mjTEXTURE_2D) { - Load2D(filename, vfs); + Load2D(filename.Str(), vfs); } else { - LoadCubeSingle(filename, vfs); + LoadCubeSingle(filename.Str(), vfs); } } diff --git a/src/user/user_objects.h b/src/user/user_objects.h index bf514b9b..5e6b14dd 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -808,6 +808,10 @@ class mjCMesh_ : public mjCBase { mjCBoundingVolumeHierarchy tree_; // bounding volume hierarchy std::vector face_aabb_; // bounding boxes of all faces + + // paths stored during model attachment + mujoco::user::FilePath modelfiledir_; + mujoco::user::FilePath meshdir_; }; class mjCMesh: public mjCMesh_, private mjsMesh { @@ -823,6 +827,7 @@ class mjCMesh: public mjCMesh_, private mjsMesh { void CopyFromSpec(void); void PointToLocal(void); + void NameSpace(const mjCModel* m); // accessors const mjsPlugin& Plugin() const { return plugin; } @@ -952,6 +957,10 @@ class mjCSkin_ : public mjCBase { int matid; // material id std::vector bodyid; // body ids + + // paths stored during model attachment + mujoco::user::FilePath modelfiledir_; + mujoco::user::FilePath meshdir_; }; class mjCSkin: public mjCSkin_, private mjsSkin { @@ -1005,6 +1014,10 @@ class mjCHField_ : public mjCBase { std::string spec_file_; std::string spec_content_type_; std::vector spec_userdata_; + + // paths stored during model attachment + mujoco::user::FilePath modelfiledir_; + mujoco::user::FilePath meshdir_; }; class mjCHField : public mjCHField_, private mjsHField { @@ -1024,6 +1037,7 @@ class mjCHField : public mjCHField_, private mjsHField { void CopyFromSpec(void); void PointToLocal(void); + void NameSpace(const mjCModel* m); std::string File() const { return file_; } @@ -1052,6 +1066,10 @@ class mjCTexture_ : public mjCBase { std::string spec_file_; std::string spec_content_type_; std::vector spec_cubefiles_; + + // paths stored during model attachment + mujoco::user::FilePath modelfiledir_; + mujoco::user::FilePath texturedir_; }; class mjCTexture : public mjCTexture_, private mjsTexture { @@ -1071,6 +1089,7 @@ class mjCTexture : public mjCTexture_, private mjsTexture { void CopyFromSpec(void); void PointToLocal(void); + void NameSpace(const mjCModel* m); std::string File() const { return file_; } std::string get_content_type() const { return content_type_; } diff --git a/test/user/user_model_test.cc b/test/user/user_model_test.cc index 6e6f1bbc..be6d9c4e 100644 --- a/test/user/user_model_test.cc +++ b/test/user/user_model_test.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -502,5 +503,47 @@ TEST_F(LengthRangeTest, LengthRangeThreading) { mj_deleteSpec(spec); } +// ----------------------------- test modeldir -------------------------------- + +TEST_F(MujocoTest, Modeldir) { + 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(); + mj_defaultVFS(vfs.get()); + mj_addBufferVFS(vfs.get(), "meshdir/cube.obj", cube, sizeof(cube)); + + // child with the asset + mjSpec* child = mj_makeSpec(); + mjsMesh* mesh = mjs_addMesh(child, 0); + mjsFrame* frame = mjs_addFrame(mjs_findBody(child, "world"), 0); + mjsGeom* geom = mjs_addGeom(mjs_findBody(child, "world"), 0); + mjs_setString(child->meshdir, "meshdir"); + mjs_setString(mesh->file, "cube.obj"); + mjs_setString(mesh->name, "cube"); + mjs_setString(geom->meshname, "cube"); + mjs_setFrame(geom->element, frame); + geom->type = mjGEOM_MESH; + + // parent attaching the child + mjSpec* spec = mj_makeSpec(); + mjs_setString(spec->meshdir, "asset"); + mjs_attachFrame(mjs_findBody(spec, "world"), frame, "_", ""); + mjModel* model = mj_compile(spec, vfs.get()); + EXPECT_THAT(model, NotNull()); + + mj_deleteSpec(child); + mj_deleteSpec(spec); + mj_deleteModel(model); + mj_deleteVFS(vfs.get()); +} + } // namespace } // namespace mujoco