12#include <gtest/gtest.h>
14#include <kdl/chaindynparam.hpp>
15#include <kdl/chainfksolverpos_recursive.hpp>
16#include <kdl/chainiksolverpos_nr_jl.hpp>
17#include <kdl/chainiksolvervel_pinv.hpp>
18#include <kdl/chainiksolvervel_wdls.hpp>
27namespace fs = std::filesystem;
29static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
30static constexpr double kCubeX = 0.4;
31static constexpr double kCubeY = 0.0;
32static constexpr double kCubeHS = 0.02;
33static constexpr double kCubeZ = kCubeHS;
34static constexpr double kIkTol = 2e-3;
36static constexpr double kKp[7] = { 100, 200, 100, 200, 100, 200, 100 };
37static constexpr double kKd[7] = { 10, 20, 10, 20, 10, 20, 10 };
39static double clamp01(
double v) {
return std::max(0.0, std::min(1.0, v)); }
41static void lerp_q(
const KDL::JntArray &a,
const KDL::JntArray &b,
double t, KDL::JntArray &out)
43 for (
unsigned i = 0; i < a.rows(); ++i) out(i) = a(i) + t * (b(i) - a(i));
46static double max_abs_joint_delta(
const KDL::JntArray &a,
const KDL::JntArray &b)
48 double max_delta = 0.0;
49 for (
unsigned i = 0; i < a.rows(); ++i) max_delta = std::max(max_delta, std::abs(a(i) - b(i)));
53static bool solve_near_seed(
54 KDL::ChainIkSolverVel_wdls &ik_vel,
55 KDL::ChainFkSolverPos_recursive &fk,
56 const KDL::JntArray &seed,
57 const KDL::Frame &target,
58 const std::vector<bool> &joint_limited,
59 const KDL::JntArray &q_min,
60 const KDL::JntArray &q_max,
65 KDL::JntArray dq(out.rows());
66 for (
int iter = 0; iter < 300; ++iter) {
68 fk.JntToCart(out, fk_out);
69 KDL::Twist dx = KDL::diff(fk_out, target);
70 if (dx.vel.Norm() <= kIkTol && dx.rot.Norm() <= 2e-2)
return true;
72 double vel_norm = dx.vel.Norm();
73 if (vel_norm > 0.05) dx.vel = dx.vel * (0.05 / vel_norm);
74 double rot_norm = dx.rot.Norm();
75 if (rot_norm > 0.20) dx.rot = dx.rot * (0.20 / rot_norm);
77 if (ik_vel.CartToJnt(out, dx, dq) < 0)
return false;
78 for (
unsigned i = 0; i < out.rows(); ++i) {
80 if (joint_limited[i]) out(i) = std::max(q_min(i), std::min(q_max(i), out(i)));
85 fk.JntToCart(out, fk_out);
86 KDL::Twist dx = KDL::diff(fk_out, target);
87 return dx.vel.Norm() <= kIkTol && dx.rot.Norm() <= 2e-2;
91 impedance_ctrl(
mj_kdl::Robot &s,
const KDL::JntArray &q_des,
unsigned n, KDL::ChainDynParam &dyn)
93 KDL::JntArray q(n), g(n);
94 for (
unsigned i = 0; i < n; ++i) q(i) = s.
jnt_pos_msr[i];
95 dyn.JntToGravity(q, g);
96 for (
unsigned i = 0; i < n; ++i) {
114 std::unique_ptr<KDL::ChainFkSolverPos_recursive>
fk_;
115 std::unique_ptr<KDL::ChainDynParam>
dyn_;
116 std::unique_ptr<KDL::ChainIkSolverVel_wdls>
ik_vel_;
122 const std::string arm_mjcf =
124 const std::string grp_mjcf =
126 if (!fs::exists(arm_mjcf)) {
127 GTEST_SKIP() << arm_mjcf <<
" not found";
130 if (!fs::exists(grp_mjcf)) {
131 GTEST_SKIP() << grp_mjcf <<
" not found";
136 .mjcf_path = grp_mjcf.c_str(),
139 .contact_exclusions = {},
142 rs.
path = arm_mjcf.c_str();
149 .size = { kCubeHS, kCubeHS, kCubeHS },
150 .pos = { kCubeX, kCubeY, kCubeZ },
151 .rgba = { 1.0f, 0.5f, 0.0f, 1.0f },
154 .friction = { 0.8, 0.02, 0.001 },
170 n_ =
s_.chain.getNrOfJoints();
174 ASSERT_GE(
fingers_act_, 0) <<
"g_fingers_actuator not found";
176 ASSERT_GE(
cube_jnt_, 0) <<
"cube_joint not found";
177 ASSERT_GE(mj_name2id(
model_, mjOBJ_SITE,
"g_pinch"), 0) <<
"g_pinch site not found";
180 KDL::JntArray q_min(
n_), q_max(
n_);
181 std::vector<bool> joint_limited(
n_,
false);
182 for (
unsigned i = 0; i <
n_; ++i) {
183 int jid =
model_->dof_jntid[
s_.kdl_to_mj_dof[i]];
184 if (
model_->jnt_limited[jid]) {
185 joint_limited[i] =
true;
186 q_min(i) =
model_->jnt_range[2 * jid];
187 q_max(i) =
model_->jnt_range[2 * jid + 1];
189 q_min(i) = -2 * M_PI;
193 fk_ = std::make_unique<KDL::ChainFkSolverPos_recursive>(
s_.chain);
194 dyn_ = std::make_unique<KDL::ChainDynParam>(
s_.chain, KDL::Vector(0, 0, -9.81));
195 ik_vel_ = std::make_unique<KDL::ChainIkSolverVel_wdls>(
s_.chain, 1e-5, 150);
199 for (
unsigned i = 0; i <
n_; ++i)
q_home_(i) = kHomePose[i];
201 const double kGraspZ = kCubeZ;
202 const double kPreGraspZ = kGraspZ + 0.20;
203 const double kLiftZ = kGraspZ + 0.30;
204 const KDL::Rotation kGraspRot =
s_.tip_T_tcp.M;
214 const KDL::JntArray *seed;
221 for (
auto &wp : wps) {
222 KDL::Frame target(kGraspRot, KDL::Vector(kCubeX, kCubeY, wp.z));
223 ASSERT_TRUE(solve_near_seed(
224 *
ik_vel_, *
fk_, *wp.seed, target, joint_limited, q_min, q_max, *wp.out
225 )) <<
"IK failed for z="
244 data_->qpos[qadr] = kCubeX;
245 data_->qpos[qadr + 1] = kCubeY;
246 data_->qpos[qadr + 2] = kCubeZ;
247 data_->qpos[qadr + 3] = 1.0;
248 data_->qpos[qadr + 4] =
data_->qpos[qadr + 5] =
data_->qpos[qadr + 6] = 0.0;
251 for (
unsigned i = 0; i <
n_; ++i) {
252 s_.jnt_pos_cmd[i] =
data_->qpos[
s_.kdl_to_mj_qpos[i]];
253 s_.jnt_trq_cmd[i] = 0.0;
254 data_->qfrc_applied[
s_.kdl_to_mj_dof[i]] = 0.0;
265 const KDL::JntArray &q_enter,
266 const KDL::JntArray &q_target,
273 double t_enter =
data_->time;
274 KDL::JntArray q_des(
n_);
278 double alpha = clamp01((
data_->time - t_enter) / duration);
279 lerp_q(q_enter, q_target, alpha, q_des);
280 impedance_ctrl(
s_, q_des,
n_, *
dyn_);
285 double t_rel =
data_->time - t_enter;
286 bool done_time = t_rel >= duration;
287 bool done_pose = (settle_tol < 0.0);
289 double max_err = 0.0;
290 for (
unsigned i = 0; i <
n_; ++i)
291 max_err = std::max(max_err, std::abs(q_target(i) -
s_.jnt_pos_msr[i]));
292 done_pose = (max_err <= settle_tol);
294 if ((done_time && done_pose) || t_rel >= timeout)
break;
307 &wrist, model_, data_,
"base_link",
"bracelet_link",
"", &wrist_tool
310 EXPECT_EQ(wrist.
chain.getNrOfJoints(), s_.chain.getNrOfJoints());
312 KDL::ChainFkSolverPos_recursive wrist_fk(wrist.
chain);
313 KDL::Frame wrist_frame, tcp_frame;
314 ASSERT_GE(wrist_fk.JntToCart(q_home_, wrist_frame), 0);
315 ASSERT_GE(fk_->JntToCart(q_home_, tcp_frame), 0);
316 EXPECT_GT((tcp_frame.p - wrist_frame.p).Norm(), 0.05);
322 const KDL::Rotation kGraspRot = s_.tip_T_tcp.M;
324 const double kGraspZ = kCubeZ;
325 const double kPreGraspZ = kGraspZ + 0.20;
326 const double kLiftZ = kGraspZ + 0.30;
332 const KDL::JntArray *q;
333 const KDL::JntArray *seed;
336 {
"pre-grasp", kPreGraspZ, &q_pregrasp_, &q_home_ },
337 {
"grasp", kGraspZ, &q_grasp_, &q_pregrasp_ },
338 {
"lift", kLiftZ, &q_lift_, &q_grasp_ },
340 for (
auto &wp : wps) {
341 KDL::Frame tgt(kGraspRot, KDL::Vector(kCubeX, kCubeY, wp.z));
343 fk_->JntToCart(*wp.q, fk_out);
344 double pos_err = (tgt.p - fk_out.p).Norm();
345 double joint_delta = max_abs_joint_delta(*wp.seed, *wp.q);
346 EXPECT_LE(pos_err, kIkTol) << wp.label <<
" IK error " << pos_err * 1000.0 <<
" mm";
347 EXPECT_LT(joint_delta, 4.0) << wp.label <<
" IK jumped to a distant joint branch";
355 KDL::JntArray q_enter(n_);
358 for (
unsigned i = 0; i < n_; ++i) q_enter(i) = s_.jnt_pos_msr[i];
362 run_phase(q_enter, q_home_, 1.0, 2.5, 0.08, 0.0);
364 run_phase(q_enter, q_pregrasp_, 5.0, 7.0, 0.08, 0.0);
366 run_phase(q_enter, q_grasp_, 5.0, 8.0, 0.03, 0.0);
368 run_phase(q_enter, q_grasp_, 1.5, 2.5, -1.0, 0.8);
370 run_phase(q_enter, q_lift_, 3.0, 5.0, 0.08, 0.8);
371 run_phase(q_lift_, q_lift_, 1.0, 1.0, -1.0, 0.8);
373 int qadr = model_->jnt_qposadr[cube_jnt_];
374 double cube_z = data_->qpos[qadr + 2];
375 EXPECT_GT(cube_z, 0.20) <<
"cube was not lifted (z=" << cube_z <<
" m)";
378int main(
int argc,
char *argv[])
380 testing::InitGoogleTest(&argc, argv);
381 return RUN_ALL_TESTS();
std::unique_ptr< KDL::ChainFkSolverPos_recursive > fk_
std::unique_ptr< KDL::ChainIkSolverVel_wdls > ik_vel_
std::unique_ptr< KDL::ChainDynParam > dyn_
KDL::JntArray q_pregrasp_
void run_phase(const KDL::JntArray &q_enter, const KDL::JntArray &q_target, double duration, double timeout, double settle_tol, double gripper_cmd)
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_asset(const fs::path &relative)
std::string find_menagerie_model(const fs::path &relative)
std::vector< AttachmentSpec > attachments
std::vector< double > jnt_pos_msr
std::vector< double > jnt_vel_msr
std::vector< double > jnt_trq_cmd
std::vector< RobotSpec > robots
std::vector< SceneObject > objects
int main(int argc, char *argv[])
TEST_F(MjcfPickTest, KDLChain)