Mujoco KDL Wrapper  0.5.1
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 * The bundled table as an MJCF-backed SceneObject: its quat and prefix, a Kinova GEN3 standing on
3 * its top site holding its pose under KDL gravity compensation, and runtime scene_add_object /
4 * scene_remove_object. Self-skips when a bundled asset is missing. */
5
7#include "common.hpp"
8#include "example_paths.hpp"
9
10#include <gtest/gtest.h>
11
12#include <kdl/chaindynparam.hpp>
13#include <kdl/chainfksolverpos_recursive.hpp>
14
15#include <filesystem>
16#include <memory>
17#include <string>
18#include <vector>
19
20namespace ex = mj_kdl_examples;
21namespace fs = std::filesystem;
22
23static constexpr double kSurfaceZ = 0.7;
24
25static mj_kdl::SceneSpec empty_scene()
26{
28 sc.timestep = 0.002;
29 sc.add_floor = false;
30 sc.add_skybox = false;
31 return sc;
32}
33
34static mj_kdl::SceneObject table(const std::string &path, const char *name, const char *prefix)
35{
37 t.name = name;
38 t.mjcf_path = path;
39 t.prefix = prefix;
40 t.fixed = true;
41 return t;
42}
43
44// A free primitive resting on the tabletop: a box of half-size half, or a sphere of radius half.
45static mj_kdl::SceneObject make_object(
46 const char *name,
47 mj_kdl::Shape shape,
48 double x,
49 double y,
50 double half,
51 const float rgb[3]
52)
53{
55 o.name = name;
56 o.shape = shape;
57 for (int k = 0; k < 3; ++k) o.size[k] = shape == mj_kdl::Shape::BOX ? half : 0.0;
58 o.size[0] = half;
59 o.pos[0] = x;
60 o.pos[1] = y;
61 o.pos[2] = kSurfaceZ + half;
62 for (int k = 0; k < 3; ++k) o.rgba[k] = rgb[k];
63 o.rgba[3] = 1.0f;
64 o.mass = shape == mj_kdl::Shape::BOX ? 0.1 : 0.05;
65 o.friction[0] = 0.8;
66 o.friction[1] = 0.02;
67 o.friction[2] = 0.001;
68 return o;
69}
70
71TEST(SceneObjectTransform, PathBackedObjectAppliesQuat)
72{
73 const std::string table_mjcf = ex::find_asset("table.xml");
74 if (!fs::exists(table_mjcf)) GTEST_SKIP() << "table.xml not found";
75 mj_kdl::SceneSpec spec = empty_scene();
76 mj_kdl::SceneObject turned = table(table_mjcf, "turned", "");
77 // extrinsic XYZ euler (30, 40, 50) deg
78 const double q[4] = {
79 0.08080468869083995, 0.40219849353410964, 0.30337177447125957, 0.860042173697679
80 };
81 std::copy(q, q + 4, turned.quat);
82 spec.objects.push_back(turned);
83
84 mj_kdl::Env env;
85 ASSERT_TRUE(mj_kdl::init_env(&env, &spec));
86 KDL::Frame frame;
87 ASSERT_TRUE(mj_kdl::get_site_frame(&env, "table_top", &frame));
88 const KDL::Vector y = frame.M * KDL::Vector(0.0, 1.0, 0.0);
89 EXPECT_NEAR(y.x(), -0.456825992585671, 1e-9);
90 EXPECT_NEAR(y.y(), 0.802872337479472, 1e-9);
91 EXPECT_NEAR(y.z(), 0.383022221559489, 1e-9);
92}
93
94TEST(SceneObjectPrefix, NamesStayAsAuthoredUnlessAPrefixIsSet)
95{
96 const std::string table_mjcf = ex::find_asset("table.xml");
97 if (!fs::exists(table_mjcf)) GTEST_SKIP() << "table.xml not found";
98 KDL::Frame frame;
99
100 mj_kdl::SceneSpec one = empty_scene();
101 one.objects = { table(table_mjcf, "t1", "") };
102 mj_kdl::Env env;
103 ASSERT_TRUE(mj_kdl::init_env(&env, &one));
104 EXPECT_TRUE(mj_kdl::get_site_frame(&env, "table_top", &frame));
105 mj_kdl::cleanup(&env);
106
107 mj_kdl::SceneSpec twins = one;
108 twins.objects = { table(table_mjcf, "t1", ""), table(table_mjcf, "t2", "") };
109 const mj_kdl::Status s = mj_kdl::init_env(&env, &twins);
110 EXPECT_FALSE(s);
111 EXPECT_NE(s.error.find("SceneObject::prefix"), std::string::npos) << s.error;
112
113 twins.objects = { table(table_mjcf, "t1", "a_"), table(table_mjcf, "t2", "b_") };
114 ASSERT_TRUE(mj_kdl::init_env(&env, &twins));
115 EXPECT_TRUE(mj_kdl::get_site_frame(&env, "a_table_top", &frame));
116 EXPECT_TRUE(mj_kdl::get_site_frame(&env, "b_table_top", &frame));
117 mj_kdl::cleanup(&env);
118}
119
120class TableSceneTest : public testing::Test
121{
122 protected:
126 std::unique_ptr<KDL::ChainDynParam> dyn_;
128
129 void SetUp() override
130 {
131 const std::string mjcf = ex::find_asset("kinova_gen3/gen3.xml");
132 const std::string table_mjcf = ex::find_asset("table.xml");
133 if (!fs::exists(mjcf)) GTEST_SKIP() << mjcf << " not found";
134 if (!fs::exists(table_mjcf)) GTEST_SKIP() << "table.xml not found";
135
136 // Free bodies must hang off the worldbody; the robot, with no freejoint, uses the site.
137 const float red[3] = { 1.0f, 0.2f, 0.2f }, green[3] = { 0.2f, 1.0f, 0.2f },
138 blue[3] = { 0.2f, 0.2f, 1.0f }, orange[3] = { 1.0f, 0.55f, 0.0f },
139 purple[3] = { 0.7f, 0.0f, 0.9f };
140 const auto box = mj_kdl::Shape::BOX, sphere = mj_kdl::Shape::SPHERE;
141 mj_kdl::SceneObject top = table(table_mjcf, "table", "");
142 top.pos[2] = kSurfaceZ;
143 spec_ = empty_scene();
144 spec_.add_floor = true;
145 spec_.objects = {
146 top,
147 make_object("red_cube", box, 0.35, 0.10, 0.03, red),
148 make_object("green_cube", box, 0.35, -0.10, 0.03, green),
149 make_object("blue_cube", box, 0.35, 0.30, 0.04, blue),
150 make_object("orange_sphere", sphere, -0.20, 0.20, 0.035, orange),
151 make_object("purple_sphere", sphere, -0.20, -0.20, 0.025, purple),
152 };
154 rs.path = mjcf;
155 rs.attach_to = { mj_kdl::AttachKind::Site, "table_top" };
156 spec_.robots.push_back(rs);
157
158 ASSERT_TRUE(mj_kdl::init_env(&env_, &spec_));
159 KDL::Frame world_T_table_top;
160 ASSERT_TRUE(mj_kdl::get_site_frame(&env_, "table_top", &world_T_table_top));
161 EXPECT_NEAR(world_T_table_top.p.z(), kSurfaceZ, 1e-9);
162
163 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&s_, &env_, "base_link", "bracelet_link"));
164 dyn_ = std::make_unique<KDL::ChainDynParam>(s_.chain, KDL::Vector(0, 0, spec_.gravity_z));
166 }
167};
168
169TEST_F(TableSceneTest, GravityCompDrift)
170{
172 KDL::Frame ee_init;
173 fk.JntToCart(q_home_, ee_init);
174
176 ex::prime_gravity(s_, *dyn_, q_home_);
177 for (int i = 0; i < 500; ++i) {
178 mj_kdl::update(&env_);
179 ex::pd_gravity(s_, *dyn_, q_home_);
180 mj_kdl::step(&env_);
181 }
182 mj_kdl::update(&env_);
183 EXPECT_LE((ex::tcp_frame(fk, s_).p - ee_init.p).Norm(), 1e-6) << "measured 7e-19 m";
184}
185
186TEST_F(TableSceneTest, EnvAddRemoveReinitsRobot)
187{
188 const int nq_before = env_.model->nq;
189
190 // Objects are compiled ahead of the robot, so each rebuild moves every robot address.
191 auto expect_maps_match_model = [&] {
192 for (int i = 0; i < s_.n_joints; ++i) {
193 const int jid = mj_name2id(env_.model, mjOBJ_JOINT, s_.joint_names[i].c_str());
194 ASSERT_GE(jid, 0);
195 env_.data->qpos[env_.model->jnt_qposadr[jid]] = 0.1 * (i + 1);
196 }
197 mj_kdl::update(&env_);
198 for (int i = 0; i < s_.n_joints; ++i) EXPECT_DOUBLE_EQ(s_.jnt_pos_msr[i], 0.1 * (i + 1));
199 };
200
201 mj_kdl::SceneFreeBodySlot *red = mj_kdl::bind_scene_free_body(&env_.scene, "red_cube");
202 ASSERT_NE(red, nullptr);
203
204 KDL::Frame yellow_frame;
205 EXPECT_FALSE(mj_kdl::get_body_frame(&env_, "yellow_cube", &yellow_frame));
206
207 const float yellow[3] = { 1.0f, 1.0f, 0.0f };
208 ASSERT_TRUE(mj_kdl::scene_add_object(
209 &env_, make_object("yellow_cube", mj_kdl::Shape::BOX, 0.0, 0.4, 0.03, yellow)
210 ));
211 EXPECT_GT(env_.model->nq, nq_before) << "nq should grow after adding a free object";
212 EXPECT_EQ(s_.n_joints, 7) << "robot chain should still have 7 joints after rebuild";
213 EXPECT_EQ(s_.model, env_.model) << "robot model pointer should be updated";
214 expect_maps_match_model();
215 // A name looked up on the freed model must not answer for the new one at the same address.
216 EXPECT_TRUE(mj_kdl::get_body_frame(&env_, "yellow_cube", &yellow_frame));
217
218 mj_kdl::SceneFreeBodySlot *slot = mj_kdl::bind_scene_free_body(&env_.scene, "yellow_cube");
219 ASSERT_NE(slot, nullptr);
220
221 // The object is removed even though a slot bound to it goes unbound with it.
222 EXPECT_TRUE(mj_kdl::scene_remove_object(&env_, "yellow_cube"));
223 EXPECT_EQ(env_.model->nq, nq_before) << "nq should return to original after removal";
224 EXPECT_EQ(s_.n_joints, 7) << "robot chain should still have 7 joints after removal";
225 expect_maps_match_model();
226
227 EXPECT_EQ(slot->qpos_adr, -1) << "yellow_cube is gone";
228 const int red_bid = mj_name2id(env_.model, mjOBJ_BODY, "red_cube");
229 EXPECT_EQ(red->qpos_adr, env_.model->jnt_qposadr[env_.model->body_jntadr[red_bid]]);
230 mj_kdl::update(&env_);
231 EXPECT_GT(red->seq, 0u);
232}
233
234int main(int argc, char *argv[])
235{
236 testing::InitGoogleTest(&argc, argv);
237 return RUN_ALL_TESTS();
238}
virtual int JntToCart(const JntArray &q_in, Frame &p_out, int segmentNr=-1)
Rotation M
double z() const
double x() const
double y() const
const KDL::JntArray q_home_
void SetUp() override
std::unique_ptr< KDL::ChainDynParam > dyn_
mj_kdl::SceneSpec spec_
Status init_env(Env *env, const SceneSpec *spec)
void update(Env *env)
bool step(Env *env)
Status set_control_mode(Robot *r, CtrlMode mode)
Status init_robot_from_mjcf(Robot *r, Env *env, 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)
void cleanup(Robot *r)
bool get_body_frame(Env *env, const char *body_name, KDL::Frame *out)
Status scene_remove_object(Env *env, const std::string &name)
bool get_site_frame(Env *env, const char *site_name, KDL::Frame *out)
Status scene_add_object(Env *env, const SceneObject &obj)
SceneFreeBodySlot * bind_scene_free_body(SceneState *s, const char *body_name)
KDL::Frame tcp_frame(KDL::ChainFkSolverPos_recursive &fk, const mj_kdl::Robot &robot)
Definition common.hpp:102
std::string find_asset(const fs::path &relative)
KDL::JntArray home_q(unsigned n)
Definition common.hpp:68
void pd_gravity(mj_kdl::Robot &robot, KDL::ChainDynParam &dyn, const KDL::JntArray &q_des, const double *kp=nullptr, const double *kd=nullptr)
Definition common.hpp:112
void prime_gravity(mj_kdl::Robot &robot, KDL::ChainDynParam &dyn, const KDL::JntArray &q)
Definition common.hpp:131
std::vector< SceneObject > objects
std::string error
int main(int argc, char *argv[])
TEST_F(TableSceneTest, GravityCompDrift)
TEST(SceneObjectTransform, PathBackedObjectAppliesQuat)