Refactor user codebase to use LoadResource helper method.
* Fix small bug when registering resource providers. * Add test cases for OS filesystem fallback with VFS. * Rewrite LoadModelFromString using a resource provider (now can pass custom VFSs). PiperOrigin-RevId: 526959705 Change-Id: Iee1773e5584535d868aab6b206d1bf362e600c8a
This commit is contained in:
committed by
Copybara-Service
parent
c2115a1f51
commit
23961e24b7
@@ -55,7 +55,7 @@ TEST_F(SensorTest, DisableSensors) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// before calling anything, check that sensors are initialised to 0
|
||||
@@ -105,7 +105,7 @@ TEST_F(RelativeFrameSensorTest, ReferencePosMat) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
mj_forward(model, data);
|
||||
|
||||
@@ -143,7 +143,7 @@ TEST_F(RelativeFrameSensorTest, ReferenceQuatMat) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// call mj_forward and convert orientation matrix to quaternion
|
||||
@@ -190,7 +190,7 @@ TEST_F(RelativeFrameSensorTest, ReferencePosMatQuat) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
constexpr int nsensordata = 32;
|
||||
ASSERT_EQ(model->nsensordata, nsensordata);
|
||||
mjData* data = mj_makeData(model);
|
||||
@@ -240,7 +240,7 @@ TEST_F(RelativeFrameSensorTest, FrameVelLinearFixed) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
data->qvel[0] = mju_sqrt(2);
|
||||
data->qvel[1] = 1;
|
||||
@@ -272,7 +272,7 @@ TEST_F(RelativeFrameSensorTest, FrameVelAngFixed) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// set joint velocities and call forward dynamics
|
||||
@@ -307,7 +307,7 @@ TEST_F(RelativeFrameSensorTest, FrameVelAngOpposing) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// set joint velocities and call forward dynamics
|
||||
@@ -350,7 +350,7 @@ TEST_F(RelativeFrameSensorTest, FrameVelGeneral) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
mjtNum dt = 1e-6; // timestep used for finite differencing
|
||||
|
||||
@@ -411,7 +411,7 @@ TEST_F(SensorTest, Clock) {
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// call step 4 times, checking that clock works as expected
|
||||
|
||||
+42
-11
@@ -68,18 +68,49 @@ const std::string GetModelPath(std::string_view path) {
|
||||
return absl::StrCat("../model/", path);
|
||||
}
|
||||
|
||||
int datastr_length(mjResource* resource) {
|
||||
int len = strlen(resource->name)-1;
|
||||
|
||||
// string should have the format "S(data).ext"
|
||||
for (; len > 2; len--) {
|
||||
if (resource->name[len] == ')') {
|
||||
return len - 2;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int str_open_callback(mjResource* resource) {
|
||||
if (datastr_length(resource) < 0) {
|
||||
return 0;
|
||||
}
|
||||
resource->data = resource->name+2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int str_read_callback(mjResource* resource, const void** buffer) {
|
||||
*buffer = resource->data;
|
||||
return datastr_length(resource);
|
||||
}
|
||||
|
||||
void str_close_callback(mjResource* resource) {
|
||||
}
|
||||
|
||||
mjModel* LoadModelFromString(std::string_view xml, char* error,
|
||||
int error_size) {
|
||||
static constexpr char file[] = "filename.xml";
|
||||
// mjVFS structs need to be allocated on the heap, because it's ~2MB
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
mj_makeEmptyFileVFS(vfs.get(), file, xml.size());
|
||||
int file_idx = mj_findFileVFS(vfs.get(), file);
|
||||
memcpy(vfs->filedata[file_idx], xml.data(), xml.size());
|
||||
mjModel* m = mj_loadXML(file, vfs.get(), error, error_size);
|
||||
mj_deleteFileVFS(vfs.get(), file);
|
||||
return m;
|
||||
int error_size, mjVFS* vfs) {
|
||||
|
||||
// register string resource provider if not registered before
|
||||
if (mjp_getResourceProvider("S(") == nullptr) {
|
||||
mjpResourceProvider resourceProvider;
|
||||
resourceProvider.prefix = "S(";
|
||||
resourceProvider.open = str_open_callback;
|
||||
resourceProvider.read = str_read_callback;
|
||||
resourceProvider.close = str_close_callback;
|
||||
mjp_registerResourceProvider(&resourceProvider);
|
||||
}
|
||||
std::string xml2 = {xml.begin(), xml.end()};
|
||||
std::string str = "S(" + xml2 + ")";
|
||||
return mj_loadXML(str.c_str(), vfs, error, error_size);
|
||||
}
|
||||
|
||||
static void AssertModelNotNull(mjModel* model,
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ const std::string GetModelPath(absl::string_view path);
|
||||
// Returns a newly-allocated mjModel, loaded from the contents of xml.
|
||||
// On failure returns nullptr and populates the error array if present.
|
||||
mjModel* LoadModelFromString(absl::string_view xml, char* error = nullptr,
|
||||
int error_size = 0);
|
||||
int error_size = 0, mjVFS* vfs = nullptr);
|
||||
|
||||
// Returns a newly-allocated mjModel, loaded from the contents in model_path.
|
||||
// On failure it asserts that model is null.
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "src/cc/array_safety.h"
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
@@ -58,6 +59,7 @@ static const char* const kMalformedFaceOBJPath =
|
||||
"user/testdata/malformed_face.xml";
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::IsNull;
|
||||
|
||||
// ------------- test invalid filenames ----------------------------------------
|
||||
|
||||
@@ -91,6 +93,83 @@ TEST_F(MjCMeshTest, UnknownMeshFormat) {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- test OS filesystem fallback ----------------------------
|
||||
|
||||
TEST_F(MjCMeshTest, LoadMSHWithVFS) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh name="mesh1" file="unknown_file.msh"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="mesh1"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
|
||||
// load VFS on the heap
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
|
||||
// should fallback to OS filesystem
|
||||
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
|
||||
EXPECT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, LoadOBJWithVFS) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh name="mesh1" file="unknown_file.obj"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="mesh1"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
|
||||
// load VFS on the heap
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
|
||||
// should fallback to OS filesystem
|
||||
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
|
||||
EXPECT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, LoadSTLWithVFS) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh name="mesh1" file="unknown_file.stl"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="mesh1"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
|
||||
// load VFS on the heap
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
|
||||
// should fallback to OS filesystem
|
||||
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
|
||||
EXPECT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
|
||||
}
|
||||
|
||||
// ------------- test vertex de-duplication (STL) ------------------------------
|
||||
|
||||
TEST_F(MjCMeshTest, DeDuplicateSTLVertices) {
|
||||
@@ -102,7 +181,8 @@ TEST_F(MjCMeshTest, DeDuplicateSTLVertices) {
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// ------------- test Mesh loading (MSH) ------------------------------
|
||||
// -------------------- test Mesh loading (MSH) --------------------------------
|
||||
|
||||
TEST_F(MjCMeshTest, LoadMSH) {
|
||||
const std::string xml_path = GetTestDataFilePath(kMeshPath);
|
||||
char error[1024];
|
||||
@@ -184,7 +264,7 @@ TEST_F(MjCMeshTest, TinyMeshLoads) {
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
ASSERT_THAT(model, testing::NotNull());
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
@@ -204,7 +284,7 @@ TEST_F(MjCMeshTest, FaceNormalAutogenerated) {
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
ASSERT_THAT(model, testing::NotNull());
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
@@ -226,7 +306,7 @@ TEST_F(MjCMeshTest, SmallInertiaLoads) {
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
ASSERT_THAT(model, testing::NotNull());
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
@@ -533,7 +613,7 @@ TEST_F(MjCMeshTest, ExactConcaveInertia) {
|
||||
EXPECT_LE(fabs(model->body_mass[2] - m_concave_cube), max_abs_err);
|
||||
EXPECT_LE(fabs(model->body_mass[3] - m_concave_cube), max_abs_err);
|
||||
EXPECT_LE(fabs(model->body_mass[4] - m_concave_cube), max_abs_err);
|
||||
for (int i=3; i<15; i+=3) {
|
||||
for (int i = 3; i < 15; i += 3) {
|
||||
EXPECT_LE(fabs(model->body_inertia[i] - I1), max_abs_err);
|
||||
EXPECT_LE(fabs(model->body_inertia[i+1] - I2), max_abs_err);
|
||||
EXPECT_LE(fabs(model->body_inertia[i+2] - I2), max_abs_err);
|
||||
@@ -550,7 +630,7 @@ TEST_F(MjCMeshTest, ExactConvexInertia) {
|
||||
mjtNum I_solid_cube = 1./6. * m_solid_cube;
|
||||
EXPECT_LE(fabs(model->body_mass[1] - m_solid_cube), max_abs_err);
|
||||
EXPECT_LE(fabs(model->body_mass[2] - m_solid_cube), max_abs_err);
|
||||
for (int i=3; i<9; i++) {
|
||||
for (int i = 3; i < 9; i++) {
|
||||
EXPECT_LE(fabs(model->body_inertia[i] - I_solid_cube), max_abs_err);
|
||||
}
|
||||
mj_deleteModel(model);
|
||||
|
||||
@@ -14,15 +14,18 @@
|
||||
|
||||
// Tests for user/user_objects.cc.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "src/cc/array_safety.h"
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
@@ -37,6 +40,116 @@ using ::testing::HasSubstr;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
|
||||
// -------------------- test OS filesystem fallback ----------------------------
|
||||
|
||||
using VfsTest = MujocoTest;
|
||||
|
||||
TEST_F(VfsTest, HFieldPngWithVFS) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<hfield name="hfield" file="unknown_file.png" size="0.5 0.5 1 0.1"/>
|
||||
</asset>
|
||||
|
||||
<worldbody>
|
||||
<geom type="hfield" hfield="hfield" pos="-.4 .6 .05"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
|
||||
// load VFS on the heap
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
|
||||
// should fallback to OS filesystem
|
||||
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
|
||||
EXPECT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
|
||||
}
|
||||
|
||||
TEST_F(VfsTest, HFieldCustomWithVFS) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<hfield name="hfield" file="unknown_file" size="0.5 0.5 1 0.1"/>
|
||||
</asset>
|
||||
|
||||
<worldbody>
|
||||
<geom type="hfield" hfield="hfield" pos="-.4 .6 .05"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
|
||||
// load VFS on the heap
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
|
||||
// should fallback to OS filesystem
|
||||
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
|
||||
EXPECT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
|
||||
}
|
||||
|
||||
TEST_F(VfsTest, TexturePngWithVFS) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<texture name="texture" file="unknown_file.png" type="2d"/>
|
||||
<material name="material" texture="texture"/>
|
||||
</asset>
|
||||
|
||||
<worldbody>
|
||||
<geom type="plane" material="material" size="4 4 4"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
|
||||
// load VFS on the heap
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
|
||||
// should fallback to OS filesystem
|
||||
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
|
||||
EXPECT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
|
||||
}
|
||||
|
||||
TEST_F(VfsTest, TextureCustomWithVFS) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<texture name="texture" file="unknown_file" type="2d"/>
|
||||
<material name="material" texture="texture"/>
|
||||
</asset>
|
||||
|
||||
<worldbody>
|
||||
<geom type="plane" material="material" size="4 4 4"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
|
||||
// load VFS on the heap
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
|
||||
// should fallback to OS filesystem
|
||||
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
|
||||
EXPECT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error, HasSubstr("resource not found via provider or OS filesystem"));
|
||||
}
|
||||
|
||||
// ------------------------ test keyframes -------------------------------------
|
||||
|
||||
using KeyframeTest = MujocoTest;
|
||||
@@ -289,6 +402,7 @@ TEST_F(MjCGeomTest, CapsuleInertiaX) {
|
||||
}
|
||||
|
||||
// ------------- test inertiagrouprange ----------------------------------------
|
||||
|
||||
TEST_F(MjCGeomTest, IgnoreGeomOutsideInertiagrouprange) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
|
||||
Reference in New Issue
Block a user