Mujoco KDL Wrapper  0.2.2
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
test_table_scene.cpp
Go to the documentation of this file.
1/* test_table_scene.cpp
2 * Load a robot arm on a table with pickable objects (cubes and spheres).
3 * Runs KDL gravity compensation so the arm holds position.
4 * Also tests runtime scene_add_object / scene_remove_object.
5 * Self-skips when Menagerie is absent. */
6
7#include <gtest/gtest.h>
8
10#include "example_paths.hpp"
11
12#include <kdl/chainfksolverpos_recursive.hpp>
13#include <kdl/chaindynparam.hpp>
14
15#include <memory>
16#include <string>
17#include <filesystem>
18
19static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
20
21namespace fs = std::filesystem;
22// Free-jointed primitives must stay world-anchored (MuJoCo: freejoint only on
23// top level), so their z is surface_z + half_height. The robot, which has no
24// freejoint at its root, attaches to the table via a site instead.
25static constexpr double kCubeMass = 0.1; // [kg]
26static constexpr double kSphereMass = 0.05;
27static constexpr double kPickFriction[3] = { 0.8, 0.02, 0.001 };
28
29static mj_kdl::SceneObject make_box(
30 const char *name, double x, double y, double hx, double hy, double hz,
31 float r, float g, float b, double surface_z
32)
33{
35 .name = name,
36 .mjcf_path = "",
37 .shape = mj_kdl::Shape::BOX,
38 .size = { hx, hy, hz },
39 .pos = { x, y, surface_z + hz },
40 .rgba = { r, g, b, 1.0f },
41 .mass = kCubeMass,
42 .friction = { kPickFriction[0], kPickFriction[1], kPickFriction[2] },
43 };
44}
45
46static mj_kdl::SceneObject make_sphere(
47 const char *name, double x, double y, double radius,
48 float r, float g, float b, double surface_z
49)
50{
52 .name = name,
53 .mjcf_path = "",
54 .shape = mj_kdl::Shape::SPHERE,
55 .size = { radius, 0.0, 0.0 },
56 .pos = { x, y, surface_z + radius },
57 .rgba = { r, g, b, 1.0f },
58 .mass = kSphereMass,
59 .friction = { kPickFriction[0], kPickFriction[1], kPickFriction[2] },
60 };
61}
62
63class TableSceneTest : public testing::Test
64{
65 protected:
66 std::string mjcf_;
69 std::string table_mount_site_; // compiled site name; lifetime backs RobotSpec.attach_to.name
70 mjModel *model_ = nullptr;
71 mjData *data_ = nullptr;
73 bool s_cleaned_ = false;
74
75 std::unique_ptr<KDL::ChainFkSolverPos_recursive> fk_;
76 std::unique_ptr<KDL::ChainDynParam> dyn_;
77
78 unsigned n_ = 0;
79 KDL::JntArray q_home_;
80
81 void SetUp() override
82 {
83 mjcf_ = mj_kdl_examples::find_menagerie_model("kinova_gen3/gen3.xml");
84 if (!fs::exists(mjcf_)) {
85 GTEST_SKIP() << mjcf_ << " not found";
86 return;
87 }
88 std::string table_mjcf = mj_kdl_examples::find_asset("table.xml");
89
90 // Table asset origin is the tabletop surface center; only the table
91 // itself carries a world-frame z. Robot and objects derive their
92 // height from attach_to.
93 const double surface_z = 0.7;
94
95 table_obj_ = {
96 .name = "table",
97 .mjcf_path = table_mjcf,
98 .pos = { 0.0, 0.0, surface_z },
99 .fixed = true,
100 };
102
103 std::vector<mj_kdl::SceneObject> objects;
104 objects.push_back(table_obj_);
105 objects.push_back(make_box("red_cube", 0.35, 0.10, 0.03, 0.03, 0.03, 1.0f, 0.2f, 0.2f, surface_z));
106 objects.push_back(make_box("green_cube", 0.35, -0.10, 0.03, 0.03, 0.03, 0.2f, 1.0f, 0.2f, surface_z));
107 objects.push_back(make_box("blue_cube", 0.35, 0.30, 0.04, 0.04, 0.04, 0.2f, 0.2f, 1.0f, surface_z));
108 objects.push_back(make_sphere("orange_sphere", -0.20, 0.20, 0.035, 1.0f, 0.55f, 0.0f, surface_z));
109 objects.push_back(make_sphere("purple_sphere", -0.20, -0.20, 0.025, 0.7f, 0.0f, 0.9f, surface_z));
110
111 spec_.objects = objects;
112 spec_.timestep = 0.002;
113 spec_.gravity_z = -9.81;
114 spec_.add_floor = true;
115 spec_.add_skybox = true;
116
117 spec_.robots.push_back(mj_kdl::RobotSpec{
118 .path = mjcf_.c_str(),
119 .attach_to = { mj_kdl::AttachKind::Site, table_mount_site_.c_str() },
120 .attachments = {},
121 });
122
123 ASSERT_TRUE(mj_kdl::build_scene(&model_, &data_, &spec_));
124 KDL::Frame world_T_table_top;
125 ASSERT_TRUE(mj_kdl::get_site_frame(model_, data_, table_mount_site_.c_str(), &world_T_table_top));
126 EXPECT_NEAR(world_T_table_top.p.z(), surface_z, 1e-9);
127
128 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&s_, model_, data_, "base_link", "bracelet_link"));
129
130 n_ = static_cast<unsigned>(s_.n_joints);
131
132 fk_ = std::make_unique<KDL::ChainFkSolverPos_recursive>(s_.chain);
133 dyn_ = std::make_unique<KDL::ChainDynParam>(s_.chain, KDL::Vector(0, 0, spec_.gravity_z));
134
135 q_home_.resize(n_);
136 for (unsigned i = 0; i < n_; ++i) q_home_(i) = kHomePose[i];
138 mj_forward(model_, data_);
139 }
140
141 void TearDown() override
142 {
145 }
146};
147
148TEST_F(TableSceneTest, GravityCompDrift)
149{
150 KDL::Frame ee_init;
151 fk_->JntToCart(q_home_, ee_init);
152
153 s_.ctrl_mode = mj_kdl::CtrlMode::TORQUE;
154 KDL::JntArray q(n_), g(n_);
155 // Prime jnt_trq_cmd so the first update() applies compensation immediately.
156 dyn_->JntToGravity(q_home_, g);
157 for (unsigned j = 0; j < n_; ++j) s_.jnt_trq_cmd[j] = g(j);
158 for (int i = 0; i < 500; ++i) {
159 mj_kdl::update(&s_);
160 for (unsigned j = 0; j < n_; ++j) q(j) = s_.jnt_pos_msr[j];
161 dyn_->JntToGravity(q, g);
162 for (unsigned j = 0; j < n_; ++j) s_.jnt_trq_cmd[j] = g(j);
163 mj_kdl::step(&s_);
164 }
165
166 KDL::JntArray q_end(n_);
167 KDL::Frame ee_end;
168 for (unsigned j = 0; j < n_; ++j) q_end(j) = s_.jnt_pos_msr[j];
169 fk_->JntToCart(q_end, ee_end);
170 double drift = (ee_init.p - ee_end.p).Norm();
171
172 ASSERT_LE(drift, 0.001) << "drift " << drift * 1000.0 << " mm exceeds 1 mm threshold";
173}
174
175TEST_F(TableSceneTest, AddRemoveObject)
176{
177 mj_kdl::cleanup(&s_);
178 s_cleaned_ = true;
179
180 const double surface_z = 0.7;
181 mj_kdl::SceneObject extra =
182 make_box("yellow_cube", 0.0, 0.4, 0.03, 0.03, 0.03, 1.0f, 1.0f, 0.0f, surface_z);
183
184 ASSERT_TRUE(mj_kdl::scene_add_object(&model_, &data_, &spec_, extra))
185 << "scene_add_object() returned false";
186
187 ASSERT_TRUE(mj_kdl::scene_remove_object(&model_, &data_, &spec_, "yellow_cube"))
188 << "scene_remove_object() returned false";
189}
190
191TEST_F(TableSceneTest, EnvAddRemoveReinitsRobot)
192{
193 mj_kdl::Env env;
194 env.spec = spec_;
195 env.model = model_;
196 env.data = data_;
197 mj_kdl::env_add_robot(&env, &s_);
198 // env now owns the rebuild lifecycle; null out fixture pointers to prevent double-free
199 model_ = nullptr;
200 data_ = nullptr;
201
202 int nq_before = env.model->nq;
203
204 const double surface_z = 0.7;
205 mj_kdl::SceneObject extra =
206 make_box("yellow_cube", 0.0, 0.4, 0.03, 0.03, 0.03, 1.0f, 1.0f, 0.0f, surface_z);
207
208 ASSERT_TRUE(mj_kdl::scene_add_object(&env, extra)) << "Env scene_add_object() failed";
209 EXPECT_GT(env.model->nq, nq_before) << "nq should grow after adding a free object";
210 EXPECT_EQ(s_.n_joints, 7) << "robot chain should still have 7 joints after rebuild";
211 EXPECT_EQ(s_.model, env.model) << "robot model pointer should be updated";
212
213 ASSERT_TRUE(mj_kdl::scene_remove_object(&env, "yellow_cube")) << "Env scene_remove_object() failed";
214 EXPECT_EQ(env.model->nq, nq_before) << "nq should return to original after removal";
215 EXPECT_EQ(s_.n_joints, 7) << "robot chain should still have 7 joints after removal";
216
217 mj_kdl::cleanup(&env);
218 s_cleaned_ = true;
219}
220
221int main(int argc, char *argv[])
222{
223 testing::InitGoogleTest(&argc, argv);
224 return RUN_ALL_TESTS();
225}
KDL::JntArray q_home_
mj_kdl::SceneObject table_obj_
std::string table_mount_site_
void SetUp() override
void TearDown() override
std::unique_ptr< KDL::ChainDynParam > dyn_
mj_kdl::SceneSpec spec_
std::unique_ptr< KDL::ChainFkSolverPos_recursive > fk_
void env_add_robot(Env *env, Robot *robot)
bool step(Robot *s)
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)
void update(Robot *r)
bool get_site_frame(const mjModel *model, mjData *data, const char *site_name, KDL::Frame *out)
bool build_scene(mjModel **out_model, mjData **out_data, const SceneSpec *spec)
bool scene_add_object(mjModel **model, mjData **data, SceneSpec *spec, const SceneObject &obj)
bool scene_remove_object(mjModel **model, mjData **data, SceneSpec *spec, const std::string &name)
void destroy_scene(mjModel *model, mjData *data)
std::string scene_object_site_name(const SceneObject &obj, const char *site_name)
std::string find_asset(const fs::path &relative)
std::string find_menagerie_model(const fs::path &relative)
int main(int argc, char *argv[])
TEST_F(TableSceneTest, GravityCompDrift)