7d54123303
The `vert0` array, used for parametric vertex coordinates in flexes, is now computed in the flex's local, unrotated frame when using interpolation. This ensures that the parametric coordinates are independent of the flex's initial orientation. A new test confirms that `flex_vert0` is identical for an unrotated and a rotated flex grid. PiperOrigin-RevId: 908196464 Change-Id: I47d6bcc2bc5df581479d485480e0e947ec6d3ffd
1825 lines
53 KiB
C++
1825 lines
53 KiB
C++
// Copyright 2021 DeepMind Technologies Limited
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Tests for xml/xml_native_writer.cc.
|
|
|
|
#include <cctype>
|
|
#include <memory>
|
|
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <array>
|
|
#include <clocale>
|
|
#include <cstdio>
|
|
#include <filesystem> // NOLINT(build/c++17)
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
#include <absl/strings/match.h>
|
|
#include <mujoco/mjmodel.h>
|
|
#include <mujoco/mjtnum.h>
|
|
#include <mujoco/mujoco.h>
|
|
#include "src/xml/xml_numeric_format.h"
|
|
#include "test/fixture.h"
|
|
|
|
namespace mujoco {
|
|
namespace {
|
|
|
|
using ::testing::ElementsAre;
|
|
using ::testing::FloatEq;
|
|
using ::testing::HasSubstr;
|
|
using ::testing::Not;
|
|
using ::testing::NotNull;
|
|
|
|
using XMLWriterTest = MujocoTest;
|
|
|
|
static const char* const kNonRgbTextureXMLPath =
|
|
"xml/testdata/hfield_png_nonrgb.xml";
|
|
|
|
TEST_F(XMLWriterTest, EmptyModel) {
|
|
static constexpr char xml[] = "<mujoco/>";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("default")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, SavesMemory) {
|
|
{
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<size memory=" 1023 "/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"1023\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
{
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<size memory="1024"/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"1K\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
{
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<size memory="4096"/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"4K\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
{
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<size memory="1048576"/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"1M\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
{
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<size memory="1047552"/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("memory=\"1023K\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, SavesDisableSensor) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<option>
|
|
<flag sensor="disable"/>
|
|
</option>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("sensor=\"disable\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, SavesInertial) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler saveinertial="true"/>
|
|
<worldbody>
|
|
<body>
|
|
<geom type="box" size=".05 .05 .05"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("mass=\"1\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, EmptyUserSensor) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<sensor>
|
|
<user dim="2" needstage="vel"/>
|
|
</sensor>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("dim=\"2\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("needstage=\"vel\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("datatype=\"real\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("objtype")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("objname")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsEmptyClasses) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<default class="empty_referenced"/>
|
|
<default class="empty_unreferenced"/>
|
|
<default class="regular">
|
|
<geom size="0.3"/>
|
|
</default>
|
|
</default>
|
|
<worldbody>
|
|
<geom class="regular"/>
|
|
<geom class="empty_referenced" size="0.2"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("default class=\"regular\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("default class=\"empty_referenced\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("default class=\"empty_unreferenced\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsExplicitInertial) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="0.2"/>
|
|
<inertial pos="0 1 2" mass="3"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("<inertial pos=\"0 1 2\" mass=\"3\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, NotAddsInertial) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="0.2"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("inertial")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsBoundMassInertia) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler boundmass="0.1" boundinertia="0.2"/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("boundmass=\"0.1\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("boundinertia=\"0.2\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DropsZeroBoundMassInertia) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler boundmass="0" boundinertia="0"/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("boundmass")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("boundinertia")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DropsInertialIfFromGeom) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler inertiafromgeom="true"/>
|
|
<worldbody>
|
|
<body>
|
|
<inertial pos="0 1 2" mass="3"/>
|
|
<geom size="0.2"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("inertial")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsAutoLimitsFalse) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler autolimits="false"/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("autolimits=\"false\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DoesNotKeepInferredJointLimited) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler angle="radian" autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge" range="-1 1"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("limited=\"true\"")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("autolimits=\"true\"")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsExplicitJointLimited) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler angle="radian" autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge" limited="true" range="-1 1"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("autolimits=\"true\"")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("limited=\"true\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsJointLimitedFalseIfAutoLimits) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler angle="radian" autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge" limited="false" range="-1 1"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("limited=\"false\" range=\"-1 1\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DoesNotKeepInferredTendonLimited) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler angle="radian" autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint type="slide"/>
|
|
<geom size="1"/>
|
|
<site name="s1"/>
|
|
</body>
|
|
<site name="s2"/>
|
|
</worldbody>
|
|
<tendon>
|
|
<spatial range="-1 1">
|
|
<site site="s1"/>
|
|
<site site="s2"/>
|
|
</spatial>
|
|
</tendon>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("autolimits=\"true\"")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("limited=\"true\"")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsExplicitTendonLimitedIfAutoLimits) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler angle="radian" autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint type="slide"/>
|
|
<geom size="1"/>
|
|
<site name="s1"/>
|
|
</body>
|
|
<site name="s2"/>
|
|
</worldbody>
|
|
<tendon>
|
|
<spatial limited="true" range="-1 1">
|
|
<site site="s1"/>
|
|
<site site="s2"/>
|
|
</spatial>
|
|
</tendon>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("autolimits=\"true\"")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("limited=\"true\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsTendonLimitedFalseIfAutoLimits) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler angle="radian" autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint type="slide"/>
|
|
<geom size="1"/>
|
|
<site name="s1"/>
|
|
</body>
|
|
<site name="s2"/>
|
|
</worldbody>
|
|
<tendon>
|
|
<spatial limited="false" range="-1 1">
|
|
<site site="s1"/>
|
|
<site site="s2"/>
|
|
</spatial>
|
|
</tendon>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("limited=\"false\" range=\"-1 1\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DoesNotKeepInferredActlimited) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general dyntype="filter" joint="hinge" actrange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("autolimits=\"true\"")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("actlimited=\"true\"")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsExplicitActlimitedIfAutoLimits) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general dyntype="filter" joint="hinge" actlimited="true" actrange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("autolimits=\"true\"")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("actlimited=\"true\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsActlimitedFalse) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general dyntype="filter" joint="hinge" actlimited="false" actrange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("actlimited=\"false\" actrange=\"-1 1\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DoesNotKeepInferredCtrllimited) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general joint="hinge" ctrlrange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("ctrllimited=\"true\"")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsExplicitCtrllimitedIfAutoLimits) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general joint="hinge" ctrllimited="true" ctrlrange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("ctrllimited=\"true\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsCtrllimitedFalse) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general joint="hinge" ctrllimited="false" ctrlrange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("ctrllimited=\"false\" ctrlrange=\"-1 1\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DoesNotKeepInferredForcelimited) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler autolimits="true" />
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general joint="hinge" forcerange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("autolimits=\"true\"")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("forcerange=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("forcelimited=\"true\"")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsExplicitForcelimited) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general joint="hinge" forcelimited="true" forcerange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("autolimits=\"true\"")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("forcerange=\"-1 1\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("forcelimited=\"true\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, KeepsForcelimitedFalse) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<joint name="hinge"/>
|
|
<geom size="1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general joint="hinge" forcelimited="false" forcerange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml,
|
|
HasSubstr("forcelimited=\"false\" forcerange=\"-1 1\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesGravComp) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body gravcomp=".25"/>
|
|
<body/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("gravcomp=\"0.25\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("gravcomp=\"0\"")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesBodyPos) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body pos="1 2 3"/>
|
|
<body pos="0 0 0"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("pos=\"1 2 3\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("pos=\"0 0 0\"")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, UndefinedMassDensity) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom type="box" size=".05 .05 .05"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesDefaults) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<geom density="100"/>
|
|
</default>
|
|
<worldbody>
|
|
<body>
|
|
<geom type="box" size=".05 .05 .05"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("<geom density=\"100\"/>"));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesActuatorDefaults) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<default class="position">
|
|
<position kp="3" kv="4" />
|
|
</default>
|
|
<default class="intvelocity">
|
|
<intvelocity kp="5" kv="6" />
|
|
</default>
|
|
</default>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="1"/>
|
|
<joint name="jnt" type="slide" axis="1 0 0" range="0 2"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<position joint="jnt" class="position"/>
|
|
<intvelocity joint="jnt" actrange="-1 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
|
EXPECT_THAT(saved_xml, HasSubstr(
|
|
"<general biastype=\"affine\" gainprm=\"3\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesFrameDefaults) {
|
|
#ifdef mjUSESINGLE
|
|
GTEST_SKIP() << "Quat values differ in float32 (euler->quat rounding)";
|
|
#endif
|
|
static constexpr char xml[] = R"(
|
|
<mujoco model="test">
|
|
<default>
|
|
<default class="dframe">
|
|
<geom size=".1"/>
|
|
</default>
|
|
</default>
|
|
|
|
<worldbody>
|
|
<frame name="f1" euler="0 0 30">
|
|
<geom size=".5" euler="0 0 20"/>
|
|
</frame>
|
|
|
|
<body name="body">
|
|
<frame pos="0 1 0" name="f2" childclass="dframe">
|
|
<geom pos="0 1 0"/>
|
|
<frame pos="0 1 0" name="f3">
|
|
<frame pos="0 1 0">
|
|
<body pos="1 0 0">
|
|
<geom pos="0 0 1"/>
|
|
</body>
|
|
</frame>
|
|
</frame>
|
|
</frame>
|
|
<frame>
|
|
<light pos="0 0 1"/>
|
|
</frame>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
|
|
static constexpr char xml_expected[] = R"(<mujoco model="test">
|
|
<compiler angle="radian"/>
|
|
|
|
<default>
|
|
<default class="dframe">
|
|
<geom size="0.1 0 0"/>
|
|
</default>
|
|
</default>
|
|
|
|
<worldbody>
|
|
<body name="body">
|
|
<frame name="f2" childclass="dframe">
|
|
<geom pos="0 2 0"/>
|
|
<frame name="f3" childclass="dframe">
|
|
<frame childclass="dframe">
|
|
<body pos="1 3 0">
|
|
<geom pos="0 0 1"/>
|
|
</body>
|
|
</frame>
|
|
</frame>
|
|
</frame>
|
|
<light pos="0 0 1" dir="0 0 -1"/>
|
|
</body>
|
|
<frame name="f1">
|
|
<geom size="0.5" quat="0.906308 0 0 0.422618"/>
|
|
</frame>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
|
|
std::array<char, 1024> error;
|
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
|
EXPECT_THAT(model, NotNull()) << error.data();
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_STREQ(saved_xml.c_str(), xml_expected);
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesDensity) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom type="box" size=".05 .05 .05" density="100"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("density=\"100\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("mass")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesMass) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom type="box" size=".05 .05 .05" mass="0.1"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("mass=\"0.1\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, ZeroMass) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom type="box" size=".05 .05 .05" mass="0"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("mass=\"0\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, OverwritesDensity) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="0.2" density="100" mass="100"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("density")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("mass=\"100\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, SaveDefaultMass) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<asset>
|
|
<mesh name="example"
|
|
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
|
|
face="2 0 3 0 1 3 1 2 3 0 2 1" />
|
|
</asset>
|
|
<default class="main">
|
|
<geom type="mesh" mass="1"/>
|
|
</default>
|
|
<worldbody>
|
|
<body>
|
|
<geom mesh="example" size=".1 .2 .3"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
EXPECT_THAT(model, NotNull());
|
|
std::string content = SaveAndReadXml(model);
|
|
EXPECT_THAT(content, HasSubstr("mass=\"1\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, UsesTwoSpaces) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr(" "));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr(" ")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesSkin) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body pos="1 -1 .6" name="B0_parent">
|
|
<flexcomp name="B0" type="grid" count="4 4 1" spacing=".2 .2 .2" radius=".1" dim="2"/>
|
|
</body>
|
|
</worldbody>
|
|
<deformable>
|
|
<skin name="B0Skin" rgba="1 1 0 1" inflate="0.1"
|
|
vertex="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"
|
|
face="0 4 5 0 5 1 1 5 6 1 6 2 2 6 7 2 7 3 4 8 9 4 9 5 5 9 10 5 10 6 6 10 11 6 11 7 8
|
|
12 13 8 13 9 9 13 14 9 14 10 10 14 15 10 15 11 16 21 20 16 17 21 17 22 21 17 18
|
|
22 18 23 22 18 19 23 20 25 24 20 21 25 21 26 25 21 22 26 22 27 26 22 23 27 24 29
|
|
28 24 25 29 25 30 29 25 26 30 26 31 30 26 27 31 0 20 4 0 16 20 4 24 8 4 20 24 8
|
|
28 12 8 24 28 3 7 23 3 23 19 7 11 27 7 27 23 11 15 31 11 31 27 0 1 17 0 17 16 1
|
|
2 18 1 18 17 2 3 19 2 19 18 12 29 13 12 28 29 13 30 14 13 29 30 14 31 15 14 30 31">
|
|
<bone body="B0_0" bindpos="0 0 0" bindquat="1 0 0 0" vertid="0 16" vertweight="1 1"/>
|
|
<bone body="B0_1" bindpos="0 0 0" bindquat="1 0 0 0" vertid="1 17" vertweight="1 1"/>
|
|
<bone body="B0_2" bindpos="0 0 0" bindquat="1 0 0 0" vertid="2 18" vertweight="1 1"/>
|
|
<bone body="B0_3" bindpos="0 0 0" bindquat="1 0 0 0" vertid="3 19" vertweight="1 1"/>
|
|
<bone body="B0_4" bindpos="0 0 0" bindquat="1 0 0 0" vertid="4 20" vertweight="1 1"/>
|
|
<bone body="B0_5" bindpos="0 0 0" bindquat="1 0 0 0" vertid="5 21" vertweight="1 1"/>
|
|
<bone body="B0_6" bindpos="0 0 0" bindquat="1 0 0 0" vertid="6 22" vertweight="1 1"/>
|
|
<bone body="B0_7" bindpos="0 0 0" bindquat="1 0 0 0" vertid="7 23" vertweight="1 1"/>
|
|
<bone body="B0_8" bindpos="0 0 0" bindquat="1 0 0 0" vertid="8 24" vertweight="1 1"/>
|
|
<bone body="B0_9" bindpos="0 0 0" bindquat="1 0 0 0" vertid="9 25" vertweight="1 1"/>
|
|
<bone body="B0_10" bindpos="0 0 0" bindquat="1 0 0 0" vertid="10 26" vertweight="1 1"/>
|
|
<bone body="B0_11" bindpos="0 0 0" bindquat="1 0 0 0" vertid="11 27" vertweight="1 1"/>
|
|
<bone body="B0_12" bindpos="0 0 0" bindquat="1 0 0 0" vertid="12 28" vertweight="1 1"/>
|
|
<bone body="B0_13" bindpos="0 0 0" bindquat="1 0 0 0" vertid="13 29" vertweight="1 1"/>
|
|
<bone body="B0_14" bindpos="0 0 0" bindquat="1 0 0 0" vertid="14 30" vertweight="1 1"/>
|
|
<bone body="B0_15" bindpos="0 0 0" bindquat="1 0 0 0" vertid="15 31" vertweight="1 1"/>
|
|
</skin>
|
|
</deformable>
|
|
</mujoco>
|
|
)";
|
|
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
EXPECT_THAT(model->nskin, 1);
|
|
|
|
char error[1024];
|
|
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(model),
|
|
error, sizeof(error));
|
|
ASSERT_THAT(mtemp, NotNull()) << error;
|
|
EXPECT_THAT(mtemp->nskin, 1);
|
|
|
|
mj_deleteModel(model);
|
|
mj_deleteModel(mtemp);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesHfield) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<asset>
|
|
<hfield name="hf" nrow="3" ncol="2" size="0.5 0.5 1 0.1"
|
|
elevation="1 2
|
|
3 4
|
|
5 6"/>
|
|
</asset>
|
|
</mujoco>
|
|
)";
|
|
// load model
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
int size = model->hfield_nrow[0]*model->hfield_ncol[0];
|
|
EXPECT_EQ(size, 6);
|
|
|
|
// check that the data is normalized and in row-major, bottom-to-top order
|
|
EXPECT_THAT(AsVector(model->hfield_data, 6),
|
|
ElementsAre(.8, 1, .4, .6, 0, .2));
|
|
|
|
// save and read, compare data
|
|
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(model));
|
|
ASSERT_THAT(mtemp, NotNull());
|
|
for (int i = 0; i < size; ++i) {
|
|
EXPECT_THAT(mtemp->hfield_data[i], FloatEq(model->hfield_data[i]));
|
|
}
|
|
mj_deleteModel(mtemp);
|
|
|
|
// modify data, save read and compare
|
|
model->hfield_data[0] = 0.25;
|
|
model->hfield_data[1] = 0.0;
|
|
model->hfield_data[2] = 0.5;
|
|
model->hfield_data[3] = 0.0;
|
|
model->hfield_data[4] = 1.0;
|
|
model->hfield_data[5] = 0.0;
|
|
|
|
mtemp = LoadModelFromString(SaveAndReadXml(model));
|
|
ASSERT_THAT(mtemp, NotNull());
|
|
for (int i = 0; i < size; ++i) {
|
|
EXPECT_THAT(mtemp->hfield_data[i], FloatEq(model->hfield_data[i]));
|
|
}
|
|
mj_deleteModel(mtemp);
|
|
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesLight) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<default class="r1">
|
|
<light bulbradius="1"/>
|
|
</default>
|
|
</default>
|
|
<worldbody>
|
|
<light/>
|
|
<light class="r1"/>
|
|
<light class="r1" bulbradius="2"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
|
|
// save and read, compare data
|
|
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(model));
|
|
EXPECT_EQ(mtemp->nlight, 3);
|
|
EXPECT_FLOAT_EQ(mtemp->light_bulbradius[0], 0.02);
|
|
EXPECT_FLOAT_EQ(mtemp->light_bulbradius[1], 1);
|
|
EXPECT_FLOAT_EQ(mtemp->light_bulbradius[2], 2);
|
|
|
|
mj_deleteModel(mtemp);
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesMaterial) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<default class="mat">
|
|
<material metallic="2" roughness="3"/>
|
|
</default>
|
|
</default>
|
|
<asset>
|
|
<material name="0" class="mat"/>
|
|
<material name="1" class="mat" metallic="4" roughness="5"/>
|
|
</asset>
|
|
</mujoco>
|
|
)";
|
|
std::array<char, 1000> error;
|
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
|
ASSERT_THAT(model, NotNull()) << error.data();
|
|
|
|
// save and read, compare data
|
|
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(model));
|
|
EXPECT_EQ(mtemp->nmat, 2);
|
|
EXPECT_EQ(mtemp->mat_metallic[0], 2);
|
|
EXPECT_EQ(mtemp->mat_metallic[1], 4);
|
|
EXPECT_EQ(mtemp->mat_roughness[0], 3);
|
|
EXPECT_EQ(mtemp->mat_roughness[1], 5);
|
|
|
|
mj_deleteModel(mtemp);
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, SpringlengthOneValue) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<site name="1"/>
|
|
<site name="2" pos="0 0 1"/>
|
|
</worldbody>
|
|
|
|
<tendon>
|
|
<spatial springlength="0.5">
|
|
<site site="1"/>
|
|
<site site="2"/>
|
|
</spatial>
|
|
</tendon>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("springlength=\"0.5\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, SpringlengthTwoValues) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<site name="1"/>
|
|
<site name="2" pos="0 0 1"/>
|
|
</worldbody>
|
|
|
|
<tendon>
|
|
<spatial springlength="0 0.5">
|
|
<site site="1"/>
|
|
<site site="2"/>
|
|
</spatial>
|
|
</tendon>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("springlength=\"0 0.5\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, Actdim) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="1"/>
|
|
<joint name="hinge"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general joint="hinge" dyntype="user" actdim="2"/>
|
|
<general joint="hinge" dyntype="filter" dynprm="1"/>
|
|
<motor joint="hinge"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("actdim=\"2\""));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("actdim=\"1\"")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("actdim=\"0\"")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, ActdimDefaults) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<general actdim="2"/>
|
|
</default>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="1"/>
|
|
<joint name="hinge"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<general joint="hinge" dyntype="user"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("actdim=\"2\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, TrimsDefaults) {
|
|
#ifdef mjUSESINGLE
|
|
GTEST_SKIP() << "float(0.9) != double(0.9), so default-trimming fails";
|
|
#endif
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="1" friction="0.9" solref="0.1" solimp="0.1 0.2"/>
|
|
<joint name="jnt" type="slide" axis="1 0 0" range="0 2"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<position joint="jnt" kp="3" kv="4"/>
|
|
<damper joint="jnt" kv="10" ctrlrange="0 1"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("friction=\"0.9\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("solref=\"0.1\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("solimp=\"0.1 0.2\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("gainprm=\"3\" biasprm=\"0 -3 -4\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("gainprm=\"0 0 -10\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DoesntSaveGlobal) {
|
|
static constexpr char xml[] = "<mujoco/>";
|
|
std::array<char, 1000> error;
|
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
|
ASSERT_THAT(model, NotNull()) << error.data();
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("global")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, InheritrangeSavedAsRange) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler angle="radian"/>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="1"/>
|
|
<joint name="slide" type="slide" range="0 2"/>
|
|
</body>
|
|
<body>
|
|
<geom size="1"/>
|
|
<joint name="hinge" type="hinge" range="-2 0"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<position joint="slide" inheritrange="2"/>
|
|
<intvelocity joint="hinge" inheritrange="0.5"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 3\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"-1.5 -0.5\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, InheritedInheritrangeSavedAsRange) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<default class="position">
|
|
<position kp="3" kv="4" inheritrange="2"/>
|
|
</default>
|
|
<default class="intvelocity">
|
|
<intvelocity kp="5" kv="6" inheritrange="0.5"/>
|
|
</default>
|
|
</default>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="1"/>
|
|
<joint name="jnt" type="slide" range="0 2"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<position joint="jnt" class="position"/>
|
|
<intvelocity joint="jnt" class="intvelocity"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("inheritrange")));
|
|
EXPECT_THAT(saved_xml, HasSubstr("ctrlrange=\"-1 3\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("actrange=\"0.5 1.5\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
// check that no precision is lost when saving XMLs with FullFloatPrecision
|
|
TEST_F(XMLWriterTest, SetPrecision) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<geom type="box" size="0.1 0.123456 0.1234567812345678"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
|
|
// save to XML and re-load, expect to lose precision
|
|
mjModel* model_lo = LoadModelFromString(SaveAndReadXml(model));
|
|
ASSERT_THAT(model_lo, NotNull());
|
|
|
|
EXPECT_EQ(model->geom_size[1], model_lo->geom_size[1]);
|
|
EXPECT_NE(model->geom_size[2], model_lo->geom_size[2]);
|
|
{
|
|
// save to XML and reload with FullFloatPrecision
|
|
// expect to maintain precision
|
|
FullFloatPrecision increase_precision;
|
|
mjModel* model_hi = LoadModelFromString(SaveAndReadXml(model));
|
|
EXPECT_EQ(model->geom_size[2], model_hi->geom_size[2]);
|
|
mj_deleteModel(model_hi);
|
|
}
|
|
mj_deleteModel(model_lo);
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
class XMLWriterLocaleTest : public MujocoTest {
|
|
public:
|
|
XMLWriterLocaleTest() : old_locale_(std::setlocale(LC_ALL, nullptr)) {}
|
|
|
|
protected:
|
|
void SetUp() override {
|
|
if (!std::setlocale(LC_ALL, "de_DE.UTF-8")) {
|
|
GTEST_SKIP() << "This system doesn't support the de_DE.UTF-8 locale";
|
|
}
|
|
}
|
|
|
|
void TearDown() override {
|
|
std::setlocale(LC_ALL, old_locale_.c_str());
|
|
}
|
|
|
|
private:
|
|
std::string old_locale_;
|
|
};
|
|
|
|
TEST_F(XMLWriterLocaleTest, IgnoresLocale) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<geom type="box" size="0.1 1.23 2.345"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
|
|
std::array<char, 1024> error;
|
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
|
ASSERT_THAT(model, NotNull()) << error.data();
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("0.1 1.23 2.345"));
|
|
mj_deleteModel(model);
|
|
|
|
// Test that MuJoCo doesn't override locales for subsequent calls.
|
|
char formatted[8];
|
|
std::snprintf(formatted, sizeof(formatted), "%.4f", 3.9375);
|
|
EXPECT_EQ(std::string(formatted), "3,9375");
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, NonRGBTextures) {
|
|
const std::string xml_path = GetTestDataFilePath(kNonRgbTextureXMLPath);
|
|
std::array<char, 1024> error;
|
|
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
|
|
ASSERT_THAT(model, NotNull()) << error.data();
|
|
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_FALSE(saved_xml.empty());
|
|
|
|
// check that layers are written correctly
|
|
EXPECT_THAT(saved_xml, HasSubstr("<material name=\"hfield\">"));
|
|
EXPECT_THAT(saved_xml, HasSubstr("<layer texture=\"hfield\" role=\"rgb\"/>"));
|
|
EXPECT_THAT(saved_xml, HasSubstr("<layer texture=\"hfield\" role=\"orm\"/>"));
|
|
EXPECT_THAT(saved_xml,
|
|
HasSubstr("<layer texture=\"hfield\" role=\"normal\"/>"));
|
|
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
// ------------------- test loading and saving multiple files ------------------
|
|
std::vector<std::string> GetWriteReadTestModels() {
|
|
std::vector<std::string> models;
|
|
std::string ext(".xml");
|
|
for (const auto& path : {GetTestDataFilePath("."), GetModelPath(".")}) {
|
|
for (const auto& p : std::filesystem::recursive_directory_iterator(path)) {
|
|
if (p.path().extension() == ext) {
|
|
std::string xml = p.path().string();
|
|
if ( // if file is meant to fail, skip it
|
|
absl::StrContains(xml, "malformed_") ||
|
|
absl::StrContains(xml, "_fail") ||
|
|
// exclude files that are too slow to load
|
|
absl::StrContains(xml, "cow") ||
|
|
absl::StrContains(xml, "gmsh_") ||
|
|
absl::StrContains(xml, "shark_") ||
|
|
absl::StrContains(xml, "perf") ||
|
|
// exclude files that fail the comparison test
|
|
absl::StrContains(xml, "rfcamera") ||
|
|
absl::StrContains(xml, "tactile") ||
|
|
absl::StrContains(xml, "makemesh") ||
|
|
absl::StrContains(xml, "many_dependencies") ||
|
|
absl::StrContains(xml, "usd") ||
|
|
absl::StrContains(xml, "torus_maxhull") ||
|
|
absl::StrContains(xml, "fitmesh_") ||
|
|
absl::StrContains(xml, "lengthrange") ||
|
|
absl::StrContains(xml, "hfield_xml") ||
|
|
absl::StrContains(xml, "fromto_convex") ||
|
|
absl::StrContains(xml, "cube_skin") ||
|
|
absl::StrContains(xml, "cube_3x3x3") ||
|
|
// exclude files that fail since we do not save pinned flex nodes
|
|
absl::StrContains(xml, "gripper_trilinear") ||
|
|
absl::StrContains(xml, "strain")) {
|
|
continue;
|
|
}
|
|
models.push_back(xml);
|
|
}
|
|
}
|
|
}
|
|
return models;
|
|
}
|
|
|
|
class WriteReadCompareTest : public XMLWriterTest,
|
|
public ::testing::WithParamInterface<std::string> {
|
|
public:
|
|
};
|
|
TEST_P(WriteReadCompareTest, WriteReadCompare) {
|
|
std::string xml = GetParam();
|
|
|
|
// full precision float printing
|
|
FullFloatPrecision increase_precision;
|
|
|
|
// load model
|
|
std::array<char, 1000> error;
|
|
mjSpec* s =
|
|
mj_parseXML(xml.c_str(), nullptr, error.data(), error.size());
|
|
if (!s) {
|
|
GTEST_SKIP() << "Failed to load " << xml.c_str() << ": " << error.data();
|
|
}
|
|
|
|
mjModel* m = mj_compile(s, nullptr);
|
|
if (!m) {
|
|
mj_deleteSpec(s);
|
|
GTEST_SKIP() << "Failed to compile " << xml.c_str() << ": " << error.data();
|
|
}
|
|
|
|
// make data
|
|
mjData* d = mj_makeData(m);
|
|
ASSERT_THAT(d, testing::NotNull()) << "Failed to create data\n";
|
|
|
|
// save and load back
|
|
auto abs_path = std::filesystem::path(xml);
|
|
mjSpec* stemp = mj_parseXMLString(SaveAndReadXml(s).c_str(), 0, error.data(),
|
|
error.size());
|
|
ASSERT_THAT(stemp, NotNull())
|
|
<< "Failed to load " << xml.c_str() << ": " << error.data();
|
|
mjs_setString(stemp->modelfiledir,
|
|
abs_path.remove_filename().string().c_str());
|
|
mjModel* mtemp = mj_compile(stemp, nullptr);
|
|
|
|
ASSERT_THAT(mtemp, NotNull()) << error.data() << " from " << xml.c_str();
|
|
|
|
mjtNum tol = 0;
|
|
|
|
// for particularly sensitive models, relax the tolerance
|
|
if (absl::StrContains(xml, "belt.xml") ||
|
|
absl::StrContains(xml, "cable.xml")) {
|
|
tol = 1e-13;
|
|
}
|
|
|
|
// compare and delete
|
|
std::string field = "";
|
|
mjtNum result = CompareModel(m, mtemp, field);
|
|
EXPECT_LE(result, tol) << "Loaded and saved models are different!\n"
|
|
<< "Affected file " << xml << '\n'
|
|
<< "Different field: " << field << '\n';
|
|
mj_deleteModel(mtemp);
|
|
|
|
// check for stack memory leak
|
|
mj_step(m, d);
|
|
EXPECT_EQ(d->pstack, 0) << "mjData stack memory leak detected in " <<
|
|
xml << '\n';
|
|
|
|
// delete data
|
|
mj_deleteData(d);
|
|
|
|
// allocate buffer, save m into it
|
|
size_t sz = mj_sizeModel(m);
|
|
void* buffer = mju_malloc(sz);
|
|
mj_saveModel(m, nullptr, buffer, sz);
|
|
|
|
// make new VFS add buffer to it
|
|
mjVFS* vfs = (mjVFS*)mju_malloc(sizeof(mjVFS));
|
|
mj_defaultVFS(vfs);
|
|
int failed = mj_addBufferVFS(vfs, "model.mjb", buffer, sz);
|
|
EXPECT_EQ(failed, 0) << "Failed to add buffer to VFS";
|
|
|
|
// load model from VFS
|
|
mtemp = mj_loadModel("model.mjb", vfs);
|
|
ASSERT_THAT(mtemp, NotNull());
|
|
|
|
// compare with 0 tolerance
|
|
field = "";
|
|
result = CompareModel(m, mtemp, field);
|
|
EXPECT_EQ(result, 0) << "Loaded and saved binary models are different!\n"
|
|
<< "Affected file " << xml << '\n'
|
|
<< "Different field: " << field << '\n';
|
|
|
|
// clean up
|
|
mj_deleteSpec(s);
|
|
mj_deleteSpec(stemp);
|
|
mj_deleteModel(m);
|
|
mj_deleteModel(mtemp);
|
|
mj_deleteVFS(vfs);
|
|
mju_free(vfs);
|
|
mju_free(buffer);
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
AllModels, WriteReadCompareTest,
|
|
::testing::ValuesIn(GetWriteReadTestModels()),
|
|
[](const ::testing::TestParamInfo<std::string>& info) {
|
|
std::string name = std::filesystem::path(info.param).filename().string();
|
|
std::replace_if(
|
|
name.begin(), name.end(),
|
|
[](char c) { return !std::isalnum(c); }, '_');
|
|
return name + "_" + std::to_string(info.index);
|
|
});
|
|
|
|
// ---------------- test CopyBack functionality (decompiler) ------------------
|
|
using DecompilerTest = MujocoTest;
|
|
TEST_F(DecompilerTest, SavesStatistics) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<statistic meansize="2" extent="3" center="4 5 6" meanmass="7" meaninertia="8"/>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
model->stat.meansize = 7;
|
|
model->stat.extent = 8;
|
|
model->stat.center[0] = 9;
|
|
model->stat.center[1] = 10;
|
|
model->stat.center[2] = 11;
|
|
model->stat.meanmass = 12;
|
|
model->stat.meaninertia = 13;
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("meansize=\"7\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("extent=\"8\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("center=\"9 10 11\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("meanmass=\"12\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("meaninertia=\"13\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(DecompilerTest, SaveAndReadXml) {
|
|
static constexpr char xml1[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<geom size="1"/>
|
|
<geom size="2"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
static constexpr char xml2[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<geom size="1"/>
|
|
<geom size="2"/>
|
|
<geom size="3"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
std::array<char, 1024> error;
|
|
mjModel* m1 = LoadModelFromString(xml1, error.data(), error.size());
|
|
ASSERT_THAT(m1, NotNull()) << error.data();
|
|
m1->geom_size[0] = 10;
|
|
m1->geom_size[3] = 20;
|
|
std::string saved_xml = SaveAndReadXml(m1);
|
|
EXPECT_THAT(saved_xml, HasSubstr("geom size=\"10\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("geom size=\"20\""));
|
|
|
|
// parse the mjSpec, save it and read it back
|
|
mjSpec* spec = mj_parseXMLString(xml2, nullptr, error.data(), error.size());
|
|
EXPECT_THAT(spec, NotNull()) << error.data();
|
|
mjModel* m2 = mj_compile(spec, nullptr);
|
|
std::string saved_xml1 = SaveAndReadXml(spec);
|
|
EXPECT_THAT(saved_xml1, HasSubstr("geom size=\"1\""));
|
|
EXPECT_THAT(saved_xml1, HasSubstr("geom size=\"2\""));
|
|
EXPECT_THAT(saved_xml1, HasSubstr("geom size=\"3\""));
|
|
|
|
// modify the mjModel, save it and read it back
|
|
m2->geom_size[0] = .1;
|
|
m2->geom_size[3] = .2;
|
|
m2->geom_size[6] = .3;
|
|
EXPECT_EQ(mj_copyBack(spec, m1), 0);
|
|
EXPECT_THAT(mjs_getError(spec), HasSubstr("CopyBack"));
|
|
EXPECT_EQ(mj_copyBack(spec, m2), 1);
|
|
std::string saved_xml2 = SaveAndReadXml(spec);
|
|
EXPECT_THAT(saved_xml2, HasSubstr("geom size=\"0.1\""));
|
|
EXPECT_THAT(saved_xml2, HasSubstr("geom size=\"0.2\""));
|
|
EXPECT_THAT(saved_xml2, HasSubstr("geom size=\"0.3\""));
|
|
|
|
// check that using mjModel as argument writes in the wrong mjSpec
|
|
std::string saved_xml3 = SaveAndReadXml(m2);
|
|
EXPECT_THAT(saved_xml3, Not(HasSubstr("geom size=\"0.1\"")));
|
|
EXPECT_THAT(saved_xml3, Not(HasSubstr("geom size=\"0.2\"")));
|
|
EXPECT_THAT(saved_xml3, Not(HasSubstr("geom size=\"0.3\"")));
|
|
|
|
mj_deleteSpec(spec);
|
|
mj_deleteModel(m1);
|
|
mj_deleteModel(m2);
|
|
}
|
|
|
|
TEST_F(DecompilerTest, DoesntSaveInferredStatistics) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<geom size="0.2"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("meansize")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("meanmass")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("meaninertia")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("center")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("extent")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("statistic")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(DecompilerTest, VeryLargeNumbers) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<compiler angle="radian"/>
|
|
<worldbody>
|
|
<camera focal="16777217 1" sensorsize="1 1" resolution="100 100"/>
|
|
<body pos="1e+20 0 0">
|
|
<geom size="1"/>
|
|
<joint axis="1 0 0" range="-1e+10 1e+10"/>
|
|
</body>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
std::array<char, 1024> error;
|
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
|
ASSERT_THAT(model, NotNull()) << error.data();
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
// note, focal is float and loses precision 16777217 -> 16777216
|
|
EXPECT_THAT(saved_xml, HasSubstr("focal=\"16777216 1\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("pos=\"1e+20 0 0\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1e+10 1e+10\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, ExpandAttach) {
|
|
static constexpr char xml_parent[] = R"(
|
|
<mujoco>
|
|
<asset>
|
|
<model name="b" file="b.xml" />
|
|
</asset>
|
|
|
|
<worldbody>
|
|
<attach model="b" body="b" prefix="b" />
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
|
|
static constexpr char xml_child[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<default class="b"/>
|
|
</default>
|
|
|
|
<worldbody>
|
|
<body name="b">
|
|
<geom type="box" size="0.1 0.1 0.1"/>
|
|
<joint name="b"/>
|
|
</body>
|
|
</worldbody>
|
|
|
|
<actuator>
|
|
<position joint="b" class="b"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
|
|
auto vfs = std::make_unique<mjVFS>();
|
|
mj_defaultVFS(vfs.get());
|
|
mj_addBufferVFS(vfs.get(), "b.xml", xml_child, sizeof(xml_child));
|
|
|
|
std::array<char, 1024> er;
|
|
mjModel* m = LoadModelFromString(xml_parent, er.data(), er.size(), vfs.get());
|
|
ASSERT_THAT(m, NotNull()) << er.data();
|
|
|
|
std::string saved_xml = SaveAndReadXml(m);
|
|
EXPECT_THAT(saved_xml, HasSubstr("class=\"bb\""));
|
|
mj_deleteModel(m);
|
|
mj_deleteVFS(vfs.get());
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesCameraOutput) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<camera name="default_cam"/>
|
|
<camera name="multi_cam" output="depth normal"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
// default output="rgb" should not be written
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("output=\"rgb\"")));
|
|
// non-default output should be written
|
|
EXPECT_THAT(saved_xml, HasSubstr("output=\"depth normal\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesCameraOutputDefault) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<default>
|
|
<default class="multi">
|
|
<camera output="depth distance"/>
|
|
</default>
|
|
</default>
|
|
<worldbody>
|
|
<camera name="default_cam"/>
|
|
<camera name="class_cam" class="multi"/>
|
|
<camera name="override_cam" class="multi" output="segmentation"/>
|
|
</worldbody>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
// save and reload to verify round-trip
|
|
mjModel* mtemp = LoadModelFromString(saved_xml);
|
|
ASSERT_THAT(mtemp, NotNull());
|
|
EXPECT_EQ(mtemp->ncam, 3);
|
|
EXPECT_EQ(mtemp->cam_output[0], mjCAMOUT_RGB);
|
|
EXPECT_EQ(mtemp->cam_output[1], mjCAMOUT_DEPTH | mjCAMOUT_DIST);
|
|
EXPECT_EQ(mtemp->cam_output[2], mjCAMOUT_SEG);
|
|
mj_deleteModel(mtemp);
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesSensorDelayIntervalHistory) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<joint name="slide" type="slide"/>
|
|
<geom size="0.1"/>
|
|
</body>
|
|
</worldbody>
|
|
<sensor>
|
|
<jointpos joint="slide" delay="0.05" interval="0.1 -0.02" nsample="20" interp="linear"/>
|
|
</sensor>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("delay=\"0.05\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("interval=\"0.1 -0.02\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("nsample=\"20\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("interp=\"linear\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, DoesNotWriteDefaultSensorAttributes) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<joint name="slide" type="slide"/>
|
|
<geom size="0.1"/>
|
|
</body>
|
|
</worldbody>
|
|
<sensor>
|
|
<jointpos joint="slide"/>
|
|
</sensor>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("delay=")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("interval=")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("nsample=")));
|
|
EXPECT_THAT(saved_xml, Not(HasSubstr("interp=")));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
TEST_F(XMLWriterTest, WritesActuatorDelayHistory) {
|
|
static constexpr char xml[] = R"(
|
|
<mujoco>
|
|
<worldbody>
|
|
<body>
|
|
<joint name="slide" type="slide"/>
|
|
<geom size="0.1"/>
|
|
</body>
|
|
</worldbody>
|
|
<actuator>
|
|
<motor joint="slide" delay="0.03" nsample="15" interp="cubic"/>
|
|
</actuator>
|
|
</mujoco>
|
|
)";
|
|
mjModel* model = LoadModelFromString(xml);
|
|
ASSERT_THAT(model, NotNull());
|
|
std::string saved_xml = SaveAndReadXml(model);
|
|
EXPECT_THAT(saved_xml, HasSubstr("delay=\"0.03\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("nsample=\"15\""));
|
|
EXPECT_THAT(saved_xml, HasSubstr("interp=\"cubic\""));
|
|
mj_deleteModel(model);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace mujoco
|