11#include <gtest/gtest.h>
13#include <kdl/chaindynparam.hpp>
14#include <kdl/chainfksolverpos_recursive.hpp>
15#include <kdl/chainiksolverpos_nr_jl.hpp>
16#include <kdl/chainiksolvervel_pinv.hpp>
17#include <kdl/chainiksolvervel_wdls.hpp>
26namespace fs = std::filesystem;
28static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
29static constexpr double kCubeX = 0.4;
30static constexpr double kCubeY = 0.0;
31static constexpr double kCubeHS = 0.02;
32static constexpr double kCubeZ = kCubeHS;
33static constexpr double kIkTol = 2e-3;
35static constexpr double kKp[7] = { 100, 200, 100, 200, 100, 200, 100 };
36static constexpr double kKd[7] = { 10, 20, 10, 20, 10, 20, 10 };
38static fs::path repo_root() {
return fs::path(__FILE__).parent_path().parent_path(); }
40static double clamp01(
double v) {
return std::max(0.0, std::min(1.0, v)); }
42static void lerp_q(
const KDL::JntArray &a,
const KDL::JntArray &b,
double t, KDL::JntArray &out)
44 for (
unsigned i = 0; i < a.rows(); ++i) out(i) = a(i) + t * (b(i) - a(i));
47static double max_abs_joint_delta(
const KDL::JntArray &a,
const KDL::JntArray &b)
49 double max_delta = 0.0;
50 for (
unsigned i = 0; i < a.rows(); ++i) max_delta = std::max(max_delta, std::abs(a(i) - b(i)));
54static bool solve_near_seed(
55 KDL::ChainIkSolverVel_wdls &ik_vel,
56 KDL::ChainFkSolverPos_recursive &fk,
57 const KDL::JntArray &seed,
58 const KDL::Frame &target,
59 const std::vector<bool> &joint_limited,
60 const KDL::JntArray &q_min,
61 const KDL::JntArray &q_max,
66 KDL::JntArray dq(out.rows());
67 for (
int iter = 0; iter < 300; ++iter) {
69 fk.JntToCart(out, fk_out);
70 KDL::Twist dx = KDL::diff(fk_out, target);
71 if (dx.vel.Norm() <= kIkTol && dx.rot.Norm() <= 2e-2)
return true;
73 double vel_norm = dx.vel.Norm();
74 if (vel_norm > 0.05) dx.vel = dx.vel * (0.05 / vel_norm);
75 double rot_norm = dx.rot.Norm();
76 if (rot_norm > 0.20) dx.rot = dx.rot * (0.20 / rot_norm);
78 if (ik_vel.CartToJnt(out, dx, dq) < 0)
return false;
79 for (
unsigned i = 0; i < out.rows(); ++i) {
81 if (joint_limited[i]) out(i) = std::max(q_min(i), std::min(q_max(i), out(i)));
86 fk.JntToCart(out, fk_out);
87 KDL::Twist dx = KDL::diff(fk_out, target);
88 return dx.vel.Norm() <= kIkTol && dx.rot.Norm() <= 2e-2;
92 impedance_ctrl(
mj_kdl::Robot &s,
const KDL::JntArray &q_des,
unsigned n, KDL::ChainDynParam &dyn)
94 KDL::JntArray q(n), g(n);
95 for (
unsigned i = 0; i < n; ++i) q(i) = s.
jnt_pos_msr[i];
96 dyn.JntToGravity(q, g);
97 for (
unsigned i = 0; i < n; ++i) {
115 std::unique_ptr<KDL::ChainFkSolverPos_recursive>
fk_;
116 std::unique_ptr<KDL::ChainDynParam>
dyn_;
117 std::unique_ptr<KDL::ChainIkSolverVel_wdls>
ik_vel_;
124 const std::string arm_mjcf =
125 (
root_ /
"third_party/menagerie/kinova_gen3/gen3.xml").
string();
126 const std::string grp_mjcf =
127 (
root_ /
"third_party/menagerie/robotiq_2f85/2f85.xml").
string();
128 if (!fs::exists(arm_mjcf)) {
129 GTEST_SKIP() << arm_mjcf <<
" not found";
132 if (!fs::exists(grp_mjcf)) {
133 GTEST_SKIP() << grp_mjcf <<
" not found";
139 .attach_to =
"bracelet_link",
141 .pos = { 0.0, 0.0, -0.061525 },
142 .euler = { 180.0, 0.0, 0.0 },
143 .contact_exclusions = {},
146 rs.
path = arm_mjcf.c_str();
153 .size = { kCubeHS, kCubeHS, kCubeHS },
154 .pos = { kCubeX, kCubeY, kCubeZ },
155 .rgba = { 1.0f, 0.5f, 0.0f, 1.0f },
158 .friction = { 0.8, 0.02, 0.001 },
175 ASSERT_GE(
fingers_act_, 0) <<
"g_fingers_actuator not found";
177 ASSERT_GE(
cube_jnt_, 0) <<
"cube_joint not found";
178 ASSERT_GE(mj_name2id(
model_, mjOBJ_SITE,
"g_pinch"), 0) <<
"g_pinch site not found";
181 KDL::JntArray q_min(
n_), q_max(
n_);
182 std::vector<bool> joint_limited(
n_,
false);
183 for (
unsigned i = 0; i <
n_; ++i) {
185 if (
model_->jnt_limited[jid]) {
186 joint_limited[i] =
true;
187 q_min(i) =
model_->jnt_range[2 * jid];
188 q_max(i) =
model_->jnt_range[2 * jid + 1];
190 q_min(i) = -2 * M_PI;
194 fk_ = std::make_unique<KDL::ChainFkSolverPos_recursive>(
s_.
chain);
195 dyn_ = std::make_unique<KDL::ChainDynParam>(
s_.
chain, KDL::Vector(0, 0, -9.81));
196 ik_vel_ = std::make_unique<KDL::ChainIkSolverVel_wdls>(
s_.
chain, 1e-5, 150);
200 for (
unsigned i = 0; i <
n_; ++i)
q_home_(i) = kHomePose[i];
202 const double kGraspZ = kCubeZ;
203 const double kPreGraspZ = kGraspZ + 0.20;
204 const double kLiftZ = kGraspZ + 0.30;
215 const KDL::JntArray *seed;
222 for (
auto &wp : wps) {
223 KDL::Frame target(kGraspRot, KDL::Vector(kCubeX, kCubeY, wp.z));
224 ASSERT_TRUE(solve_near_seed(
225 *
ik_vel_, *
fk_, *wp.seed, target, joint_limited, q_min, q_max, *wp.out
226 )) <<
"IK failed for z="
245 data_->qpos[qadr] = kCubeX;
246 data_->qpos[qadr + 1] = kCubeY;
247 data_->qpos[qadr + 2] = kCubeZ;
248 data_->qpos[qadr + 3] = 1.0;
249 data_->qpos[qadr + 4] =
data_->qpos[qadr + 5] =
data_->qpos[qadr + 6] = 0.0;
252 for (
unsigned i = 0; i <
n_; ++i) {
266 const KDL::JntArray &q_enter,
267 const KDL::JntArray &q_target,
274 double t_enter =
data_->time;
275 KDL::JntArray q_des(
n_);
279 double alpha = clamp01((
data_->time - t_enter) / duration);
280 lerp_q(q_enter, q_target, alpha, q_des);
281 impedance_ctrl(
s_, q_des,
n_, *
dyn_);
286 double t_rel =
data_->time - t_enter;
287 bool done_time = t_rel >= duration;
288 bool done_pose = (settle_tol < 0.0);
290 double max_err = 0.0;
291 for (
unsigned i = 0; i <
n_; ++i)
292 max_err = std::max(max_err, std::abs(q_target(i) -
s_.
jnt_pos_msr[i]));
293 done_pose = (max_err <= settle_tol);
295 if ((done_time && done_pose) || t_rel >= timeout)
break;
308 &wrist, model_, data_,
"base_link",
"bracelet_link",
"", &wrist_tool
311 EXPECT_EQ(wrist.
chain.getNrOfJoints(), s_.chain.getNrOfJoints());
313 KDL::ChainFkSolverPos_recursive wrist_fk(wrist.
chain);
314 KDL::Frame wrist_frame, tcp_frame;
315 ASSERT_GE(wrist_fk.JntToCart(q_home_, wrist_frame), 0);
316 ASSERT_GE(fk_->JntToCart(q_home_, tcp_frame), 0);
317 EXPECT_GT((tcp_frame.p - wrist_frame.p).Norm(), 0.05);
323 const KDL::Rotation kGraspRot = s_.tip_T_tcp.M;
325 const double kGraspZ = kCubeZ;
326 const double kPreGraspZ = kGraspZ + 0.20;
327 const double kLiftZ = kGraspZ + 0.30;
333 const KDL::JntArray *q;
334 const KDL::JntArray *seed;
337 {
"pre-grasp", kPreGraspZ, &q_pregrasp_, &q_home_ },
338 {
"grasp", kGraspZ, &q_grasp_, &q_pregrasp_ },
339 {
"lift", kLiftZ, &q_lift_, &q_grasp_ },
341 for (
auto &wp : wps) {
342 KDL::Frame tgt(kGraspRot, KDL::Vector(kCubeX, kCubeY, wp.z));
344 fk_->JntToCart(*wp.q, fk_out);
345 double pos_err = (tgt.p - fk_out.p).Norm();
346 double joint_delta = max_abs_joint_delta(*wp.seed, *wp.q);
347 EXPECT_LE(pos_err, kIkTol) << wp.label <<
" IK error " << pos_err * 1000.0 <<
" mm";
348 EXPECT_LT(joint_delta, 4.0) << wp.label <<
" IK jumped to a distant joint branch";
356 KDL::JntArray q_enter(n_);
359 for (
unsigned i = 0; i < n_; ++i) q_enter(i) = s_.jnt_pos_msr[i];
363 run_phase(q_enter, q_home_, 1.0, 2.5, 0.08, 0.0);
365 run_phase(q_enter, q_pregrasp_, 5.0, 7.0, 0.08, 0.0);
367 run_phase(q_enter, q_grasp_, 5.0, 8.0, 0.03, 0.0);
369 run_phase(q_enter, q_grasp_, 1.5, 2.5, -1.0, 255.0);
371 run_phase(q_enter, q_lift_, 3.0, 5.0, 0.08, 255.0);
372 run_phase(q_lift_, q_lift_, 1.0, 1.0, -1.0, 255.0);
374 int qadr = model_->jnt_qposadr[cube_jnt_];
375 double cube_z = data_->qpos[qadr + 2];
376 EXPECT_GT(cube_z, 0.20) <<
"cube was not lifted (z=" << cube_z <<
" m)";
379int main(
int argc,
char *argv[])
381 testing::InitGoogleTest(&argc, argv);
382 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::vector< AttachmentSpec > attachments
std::vector< int > kdl_to_mj_qpos
std::vector< int > kdl_to_mj_dof
std::vector< double > jnt_pos_msr
std::vector< double > jnt_pos_cmd
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)