Mujoco KDL Wrapper  0.2.2
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
test_init.cpp
Go to the documentation of this file.
1/* test_init.cpp
2 * Loads the Kinova GEN3 MJCF (menagerie gen3.xml), runs 100 simulation steps,
3 * and verifies basic model properties are consistent.
4 * Self-skips when Menagerie is absent. */
5
7#include "example_paths.hpp"
8
9#include <gtest/gtest.h>
10
11#include <cstdlib>
12#include <filesystem>
13#include <fstream>
14#include <string>
15
16static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
17
18namespace fs = std::filesystem;
19
20TEST(ExamplePaths, StaleMenagerieEnvFallsBackToCache)
21{
22 const auto tmp = fs::temp_directory_path() / "mj_kdl_wrapper_paths_test";
23 fs::remove_all(tmp);
24 fs::create_directories(tmp / "cache" / "mj_kdl_wrapper" / "menagerie" / "kinova_gen3");
25
26 const auto model = tmp / "cache" / "mj_kdl_wrapper" / "menagerie" / "kinova_gen3" / "gen3.xml";
27 std::ofstream(model) << "<mujoco/>";
28
29 const char *old_xdg = std::getenv("XDG_CACHE_HOME");
30 const char *old_menagerie = std::getenv("MJ_KDL_MENAGERIE");
31 const std::string saved_xdg = old_xdg ? old_xdg : "";
32 const std::string saved_menagerie = old_menagerie ? old_menagerie : "";
33
34 setenv("XDG_CACHE_HOME", (tmp / "cache").c_str(), 1);
35 setenv("MJ_KDL_MENAGERIE", (tmp / "missing").c_str(), 1);
36
37 EXPECT_EQ(mj_kdl_examples::find_menagerie_model("kinova_gen3/gen3.xml"), model.string());
38
39 old_xdg ? setenv("XDG_CACHE_HOME", saved_xdg.c_str(), 1) : unsetenv("XDG_CACHE_HOME");
40 old_menagerie ? setenv("MJ_KDL_MENAGERIE", saved_menagerie.c_str(), 1)
41 : unsetenv("MJ_KDL_MENAGERIE");
42 fs::remove_all(tmp);
43}
44
45class InitTest : public testing::Test
46{
47 protected:
48 mjModel *model_ = nullptr;
49 mjData *data_ = nullptr;
52
53 void SetUp() override
54 {
55 std::string mjcf = mj_kdl_examples::find_menagerie_model("kinova_gen3/gen3.xml");
56 if (!fs::exists(mjcf)) {
57 GTEST_SKIP() << mjcf << " not found";
58 return;
59 }
60
61 sc_.timestep = 0.002;
62 sc_.add_floor = true;
63 sc_.add_skybox = true;
64 sc_.robots.push_back(mj_kdl::RobotSpec{ .path = mjcf.c_str(), .attachments = {} });
65
66 ASSERT_TRUE(mj_kdl::build_scene(&model_, &data_, &sc_)) << "build_scene() returned false";
67 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&s, model_, data_, "base_link", "bracelet_link"))
68 << "init_robot_from_mjcf() returned false";
69 }
70
71 void TearDown() override
72 {
75 }
76};
77
78TEST_F(InitTest, BasicDOF)
79{
80 EXPECT_EQ(s.n_joints, 7) << "expected 7 KDL joints, got " << s.n_joints;
81}
82
83TEST_F(InitTest, SimulationAdvance)
84{
85 unsigned n = static_cast<unsigned>(s.n_joints);
86 KDL::JntArray q_home(n);
87 for (unsigned i = 0; i < n; ++i) q_home(i) = kHomePose[i];
88 mj_kdl::set_joint_pos(&s, q_home);
89 mj_forward(s.model, s.data);
90
91 const double t0 = s.data->time;
92 mj_kdl::step_n(&s, 100);
93 ASSERT_TRUE(s.data->time > t0) << "simulation time did not advance after 100 steps";
94}
95
96/* reset() tests */
97
98TEST_F(InitTest, ResetRestoresDefaultPose)
99{
100 // Displace the arm and advance, then reset -- qpos must return to default.
101 unsigned n = static_cast<unsigned>(s.n_joints);
102 KDL::JntArray q_displaced(n);
103 for (unsigned i = 0; i < n; ++i) q_displaced(i) = kHomePose[i] + 0.3;
104 mj_kdl::set_joint_pos(&s, q_displaced);
105 mj_kdl::step_n(&s, 50);
106
107 double pos_before = s.data->qpos[s.kdl_to_mj_qpos[0]];
108
109 mj_kdl::Env env;
110 env.model = model_;
111 env.data = data_;
112 mj_kdl::env_add_robot(&env, &s);
113 mj_kdl::reset(&env);
114
115 double pos_after = s.data->qpos[s.kdl_to_mj_qpos[0]];
116 EXPECT_NE(pos_after, pos_before);
117}
118
119TEST_F(InitTest, ResetSyncsCmdPorts)
120{
121 // After reset, jnt_pos_cmd must equal measured qpos and jnt_trq_cmd must be zero.
122 unsigned n = static_cast<unsigned>(s.n_joints);
123 for (unsigned i = 0; i < n; ++i) {
124 s.jnt_pos_cmd[i] = 99.0;
125 s.jnt_trq_cmd[i] = 42.0;
126 }
127
128 mj_kdl::Env env;
129 env.model = model_;
130 env.data = data_;
131 mj_kdl::env_add_robot(&env, &s);
132 mj_kdl::reset(&env);
133
134 for (unsigned i = 0; i < n; ++i) {
135 double qpos = s.data->qpos[s.kdl_to_mj_qpos[i]];
136 EXPECT_DOUBLE_EQ(s.jnt_pos_cmd[i], qpos)
137 << "jnt_pos_cmd[" << i << "] not synced to qpos after reset";
138 EXPECT_DOUBLE_EQ(s.jnt_trq_cmd[i], 0.0)
139 << "jnt_trq_cmd[" << i << "] not zeroed after reset";
140 }
141}
142
143TEST_F(InitTest, ResetInvokesOnResetCallback)
144{
145 // on_reset must be called by reset() exactly once.
146 int call_count = 0;
147
148 mj_kdl::Env env;
149 env.model = model_;
150 env.data = data_;
151 env.on_reset = [&](mj_kdl::ResetContext *) { ++call_count; };
152 mj_kdl::env_add_robot(&env, &s);
153 mj_kdl::reset(&env);
154
155 EXPECT_EQ(call_count, 1) << "on_reset was not called by reset()";
156}
157
158TEST_F(InitTest, ResetWithoutOnResetCallbackIsNoOp)
159{
160 // reset() must not crash when on_reset is not set.
161 mj_kdl::Env env;
162 env.model = model_;
163 env.data = data_;
164 mj_kdl::env_add_robot(&env, &s);
165 EXPECT_NO_FATAL_FAILURE(mj_kdl::reset(&env));
166}
167
168TEST_F(InitTest, EnvResetInvokesHookAndSyncsRobot)
169{
170 mj_kdl::Env env;
171 env.spec = sc_;
172 env.model = model_;
173 env.data = data_;
174 mj_kdl::env_add_robot(&env, &s);
175
176 int call_count = 0;
177 env.on_reset = [&](mj_kdl::ResetContext *ctx) {
178 ++call_count;
179 EXPECT_EQ(ctx->env, &env);
180 EXPECT_EQ(ctx->model, model_);
181 EXPECT_EQ(ctx->data, data_);
182 };
183
184 for (int i = 0; i < s.n_joints; ++i) {
185 s.jnt_pos_cmd[i] = 99.0;
186 s.jnt_trq_cmd[i] = 42.0;
187 s.data->qfrc_applied[s.kdl_to_mj_dof[i]] = 12.0;
188 }
189
190 mj_kdl::ResetInfo info = mj_kdl::reset(&env);
191
192 EXPECT_EQ(call_count, 1);
193 if (model_->nkey > 0) {
194 EXPECT_TRUE(info.used_keyframe);
195 EXPECT_EQ(info.keyframe, 0);
196 }
197 for (int i = 0; i < s.n_joints; ++i) {
198 double qpos = s.data->qpos[s.kdl_to_mj_qpos[i]];
199 EXPECT_DOUBLE_EQ(s.jnt_pos_msr[i], qpos);
200 EXPECT_DOUBLE_EQ(s.jnt_pos_cmd[i], qpos);
201 EXPECT_DOUBLE_EQ(s.jnt_trq_cmd[i], 0.0);
202 EXPECT_DOUBLE_EQ(s.data->qfrc_applied[s.kdl_to_mj_dof[i]], 0.0);
203 }
204
205 env.model = nullptr;
206 env.data = nullptr;
207}
208
209int main(int argc, char *argv[])
210{
211 testing::InitGoogleTest(&argc, argv);
212 return RUN_ALL_TESTS();
213}
mjModel * model_
Definition test_init.cpp:48
mjData * data_
Definition test_init.cpp:49
mj_kdl::Robot s
Definition test_init.cpp:51
void SetUp() override
Definition test_init.cpp:53
mj_kdl::SceneSpec sc_
Definition test_init.cpp:50
void TearDown() override
Definition test_init.cpp:71
void env_add_robot(Env *env, Robot *robot)
ResetInfo reset(Env *env, const ResetOptions *options=nullptr)
bool step_n(Robot *s, int n)
void cleanup(Robot *r)
bool init_robot_from_mjcf(Robot *r, mjModel *model, mjData *data, const char *base_body, const char *tip_body, const char *prefix="", const ToolFrameSpec *tool=nullptr)
void set_joint_pos(Robot *r, const KDL::JntArray &q, bool call_forward=true)
bool build_scene(mjModel **out_model, mjData **out_data, const SceneSpec *spec)
void destroy_scene(mjModel *model, mjData *data)
std::string find_menagerie_model(const fs::path &relative)
ResetHook on_reset
TEST(ExamplePaths, StaleMenagerieEnvFallsBackToCache)
Definition test_init.cpp:20
int main(int argc, char *argv[])
TEST_F(InitTest, BasicDOF)
Definition test_init.cpp:78