Mujoco KDL Wrapper  0.2.2
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_rnea_pick_place.cpp
Go to the documentation of this file.
1/* ex_rnea_pick_place.cpp
2 * Kinova GEN3 + Robotiq 2F-85 picks a cube from one table location and places
3 * it at another using full computed-torque control via KDL ChainIdSolver_RNE:
4 *
5 * qddot_des[i] = Kp[i]*(q_des[i] - q[i]) - Kd[i]*qdot[i]
6 * tau = ChainIdSolver_RNE(q, qdot, qddot_des, f_ext=0)
7 * = M(q)*qddot_des + C(q,qdot)*qdot + g(q)
8 *
9 * mj_kdl_wrapper's TORQUE mode fully nulls the MuJoCo position actuators
10 * (ctrl = qpos + kv/kp * qvel), so qfrc_applied is the sole torque source.
11 * The closed loop then reduces to: qddot = qddot_des (exact decoupling).
12 *
13 * Usage:
14 * ex_rnea_pick_place [--headless]
15 *
16 * With --headless runs the full sequence and prints final cube position. */
17
19#include "example_paths.hpp"
20
21#include <kdl/chainidsolver_recursive_newton_euler.hpp>
22#include <kdl/chainfksolverpos_recursive.hpp>
23#include <kdl/chainiksolvervel_wdls.hpp>
24
25#include <algorithm>
26#include <cmath>
27#include <iomanip>
28#include <iostream>
29#include <string>
30#include <vector>
31
32static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
33static constexpr double kCubeHS = 0.02;
34static constexpr double kPickX = 0.40;
35static constexpr double kPickY = 0.00;
36static constexpr double kPlaceX = 0.40;
37static constexpr double kPlaceY = 0.24;
38static constexpr double kTableZ = 0.70;
39static constexpr double kIkTol = 2e-3;
40
41// Kp [rad/s^2 per rad], Kd [rad/s per rad/s] -- act as acceleration gains under
42// full computed-torque: closed loop is qddot = Kp*e - Kd*edot.
43static constexpr double kKp[7] = { 100, 200, 100, 200, 100, 200, 100 };
44static constexpr double kKd[7] = { 20, 28, 20, 28, 20, 28, 20 };
45
46static double clamp01(double v) { return std::max(0.0, std::min(1.0, v)); }
47
48static void lerp_q(const KDL::JntArray &a, const KDL::JntArray &b, double t, KDL::JntArray &out)
49{
50 for (unsigned i = 0; i < a.rows(); ++i) out(i) = a(i) + t * (b(i) - a(i));
51}
52
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,
61 KDL::JntArray &out
62)
63{
64 out = seed;
65 KDL::JntArray dq(out.rows());
66 for (int iter = 0; iter < 300; ++iter) {
67 KDL::Frame fk_out;
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;
71
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);
76
77 if (ik_vel.CartToJnt(out, dx, dq) < 0) return false;
78 for (unsigned i = 0; i < out.rows(); ++i) {
79 out(i) += dq(i);
80 if (joint_limited[i]) out(i) = std::max(q_min(i), std::min(q_max(i), out(i)));
81 }
82 }
83
84 KDL::Frame fk_out;
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;
88}
89
90static void snapshot_q(const mj_kdl::Robot &robot, unsigned n, KDL::JntArray &q)
91{
92 for (unsigned i = 0; i < n; ++i) q(i) = robot.jnt_pos_msr[i];
93}
94
95static double max_abs_joint_err(const mj_kdl::Robot &robot, const KDL::JntArray &q, unsigned n)
96{
97 double max_err = 0.0;
98 for (unsigned i = 0; i < n; ++i)
99 max_err = std::max(max_err, std::abs(q(i) - robot.jnt_pos_msr[i]));
100 return max_err;
101}
102
103static void rnea_ctrl(
104 mj_kdl::Robot &robot,
105 const KDL::JntArray &q_des,
106 unsigned n,
107 KDL::ChainIdSolver_RNE &rnea,
108 KDL::JntArray &q_buf,
109 KDL::JntArray &qdot_buf,
110 KDL::JntArray &qddot_des,
111 KDL::JntArray &torques,
112 KDL::Wrenches &f_ext
113)
114{
115 for (unsigned i = 0; i < n; ++i) {
116 q_buf(i) = robot.jnt_pos_msr[i];
117 qdot_buf(i) = robot.jnt_vel_msr[i];
118 qddot_des(i) = kKp[i] * (q_des(i) - q_buf(i)) - kKd[i] * qdot_buf(i);
119 }
120 rnea.CartToJnt(q_buf, qdot_buf, qddot_des, f_ext, torques);
121 for (unsigned i = 0; i < n; ++i) robot.jnt_trq_cmd[i] = torques(i);
122}
123
124static mj_kdl::SceneObject make_cube(double surface_z)
125{
126 return {
127 .name = "cube",
128 .mjcf_path = "",
129 .shape = mj_kdl::Shape::BOX,
130 .size = { kCubeHS, kCubeHS, kCubeHS },
131 .pos = { kPickX, kPickY, surface_z + kCubeHS },
132 .rgba = { 0.1f, 0.35f, 1.0f, 1.0f },
133 .mass = 0.1,
135 .friction = { 0.8, 0.02, 0.001 },
136 };
137}
138
139struct Phase
140{
141 const char *name;
142 const KDL::JntArray *target;
143 double duration;
144 double timeout;
146 double gripper_cmd;
147};
148
149int main(int argc, char *argv[])
150{
151 bool headless = false;
152 for (int i = 1; i < argc; ++i)
153 if (std::string(argv[i]) == "--headless") headless = true;
154
155 const std::string arm_mjcf = mj_kdl_examples::menagerie_model("kinova_gen3/gen3.xml");
156 const std::string grp_mjcf = mj_kdl_examples::asset("robotiq_2f85/2f85.xml");
157 const std::string table_mjcf = mj_kdl_examples::asset("table.xml");
158
160 gripper.mjcf_path = grp_mjcf.c_str();
161 gripper.attach_to = { mj_kdl::AttachKind::Site, "pinch_site" };
162 gripper.prefix = "g_";
163
164 mj_kdl::RobotSpec robot_spec;
165 robot_spec.path = arm_mjcf.c_str();
166 robot_spec.pos[2] = kTableZ;
167 robot_spec.attachments.push_back(gripper);
168
169 mj_kdl::SceneSpec scene;
170 scene.timestep = 0.002;
171 scene.add_floor = true;
172 scene.add_skybox = true;
173 scene.robots.push_back(robot_spec);
175 .name = "table",
176 .mjcf_path = table_mjcf,
177 .pos = { 0.0, 0.0, kTableZ },
178 .fixed = true,
179 };
180 scene.objects.push_back(table);
181 scene.objects.push_back(make_cube(kTableZ));
182
183 mjModel *model = nullptr;
184 mjData *data = nullptr;
185 if (!mj_kdl::build_scene(&model, &data, &scene)) {
186 std::cerr << "build_scene() failed\n";
187 return 1;
188 }
189
191 tool.tool_body = "g_base";
192 tool.tcp_site = "g_pinch";
193
194 mj_kdl::Robot robot;
196 &robot, model, data, "base_link", "bracelet_link", "", &tool
197 )) {
198 std::cerr << "init_robot_from_mjcf() failed\n";
199 mj_kdl::destroy_scene(model, data);
200 return 1;
201 }
202
203 const unsigned n = robot.chain.getNrOfJoints();
204 const unsigned ns = robot.chain.getNrOfSegments();
205 const int fingers_act = mj_name2id(model, mjOBJ_ACTUATOR, "g_fingers_actuator");
206 const int cube_jnt = mj_name2id(model, mjOBJ_JOINT, "cube_joint");
207 if (fingers_act < 0 || cube_jnt < 0) {
208 std::cerr << "required actuator or cube joint not found\n";
209 mj_kdl::cleanup(&robot);
210 mj_kdl::destroy_scene(model, data);
211 return 1;
212 }
213
214 KDL::JntArray q_home(n);
215 for (unsigned i = 0; i < n; ++i) q_home(i) = kHomePose[i];
216
217 KDL::ChainFkSolverPos_recursive fk(robot.chain);
218 KDL::JntArray q_min(n), q_max(n);
219 std::vector<bool> joint_limited(n, false);
220 for (unsigned i = 0; i < n; ++i) {
221 int jid = model->dof_jntid[robot.kdl_to_mj_dof[i]];
222 if (model->jnt_limited[jid]) {
223 joint_limited[i] = true;
224 q_min(i) = model->jnt_range[2 * jid];
225 q_max(i) = model->jnt_range[2 * jid + 1];
226 } else {
227 q_min(i) = -2 * M_PI;
228 q_max(i) = 2 * M_PI;
229 }
230 }
231 KDL::ChainIkSolverVel_wdls ik_vel(robot.chain, 1e-5, 150);
232 ik_vel.setLambda(0.05);
233
234 KDL::ChainIdSolver_RNE rnea(robot.chain, KDL::Vector(0.0, 0.0, scene.gravity_z));
235 KDL::JntArray q_buf(n), qdot_buf(n), qddot_des(n), torques(n);
236 KDL::Wrenches f_ext(ns, KDL::Wrench::Zero());
237
238 const KDL::Rotation kGraspRot = robot.tip_T_tcp.M;
239
240 const double z_grasp = kCubeHS;
241 const double z_above = z_grasp + 0.20;
242 const double z_lift = z_grasp + 0.30;
243
244 KDL::JntArray q_pick_above(n), q_pick(n), q_lift(n), q_place_above(n), q_place(n);
245 struct Waypoint
246 {
247 double world_x;
248 double world_y;
249 double world_z;
250 KDL::JntArray *out;
251 const KDL::JntArray *seed;
252 };
253 Waypoint waypoints[] = {
254 { kPickX, kPickY, kTableZ + z_above, &q_pick_above, &q_home },
255 { kPickX, kPickY, kTableZ + z_grasp, &q_pick, &q_pick_above },
256 { kPickX, kPickY, kTableZ + z_lift, &q_lift, &q_pick },
257 { kPlaceX, kPlaceY, kTableZ + z_above, &q_place_above, &q_lift },
258 { kPlaceX, kPlaceY, kTableZ + z_grasp, &q_place, &q_place_above },
259 };
260 KDL::Frame world_T_base(KDL::Rotation::Identity(), KDL::Vector(0.0, 0.0, kTableZ));
261 KDL::Frame base_T_world = world_T_base.Inverse();
262 for (const auto &wp : waypoints) {
263 KDL::Frame world_target(kGraspRot, KDL::Vector(wp.world_x, wp.world_y, wp.world_z));
264 KDL::Frame base_target = base_T_world * world_target;
265 if (!solve_near_seed(
266 ik_vel, fk, *wp.seed, base_target, joint_limited, q_min, q_max, *wp.out
267 )) {
268 std::cerr << "IK failed for waypoint at world [" << wp.world_x << ", " << wp.world_y
269 << ", " << wp.world_z << "]\n";
270 mj_kdl::cleanup(&robot);
271 mj_kdl::destroy_scene(model, data);
272 return 1;
273 }
274 KDL::Frame fk_out;
275 fk.JntToCart(*wp.out, fk_out);
276 double pos_err = (base_target.p - fk_out.p).Norm();
277 if (pos_err > kIkTol) {
278 std::cerr << "IK pose error " << pos_err << " exceeds tolerance at world ["
279 << wp.world_x << ", " << wp.world_y << ", " << wp.world_z << "]\n";
280 mj_kdl::cleanup(&robot);
281 mj_kdl::destroy_scene(model, data);
282 return 1;
283 }
284 }
285
286 const std::vector<Phase> phases = {
287 { "HOME", &q_home, 1.0, 2.5, 0.08, 0.0 },
288 { "PICK_ABOVE", &q_pick_above, 5.0, 7.0, 0.08, 0.0 },
289 { "PICK", &q_pick, 5.0, 8.0, 0.03, 0.0 },
290 { "CLOSE", &q_pick, 1.5, 2.5, -1.0, 0.8 },
291 { "LIFT", &q_lift, 3.0, 5.0, 0.08, 0.8 },
292 { "PLACE_ABOVE", &q_place_above, 3.0, 5.0, 0.08, 0.8 },
293 { "PLACE", &q_place, 5.0, 8.0, 0.03, 0.8 },
294 { "OPEN", &q_place, 1.0, 2.0, -1.0, 0.0 },
295 { "RETREAT", &q_place_above, 2.0, 4.0, 0.08, 0.0 },
296 { "HOLD", &q_place_above, headless ? 1.0 : 1e9, headless ? 1.0 : 1e9, -1.0, 0.0 },
297 };
298
300 int qadr = model->jnt_qposadr[cube_jnt];
301
302 auto reset_cube = [&]() {
303 data->qpos[qadr] = kPickX;
304 data->qpos[qadr + 1] = kPickY;
305 data->qpos[qadr + 2] = kTableZ + kCubeHS;
306 data->qpos[qadr + 3] = 1.0;
307 data->qpos[qadr + 4] = data->qpos[qadr + 5] = data->qpos[qadr + 6] = 0.0;
308 };
309
310 mj_kdl::Env env;
311 env.spec = scene;
312 env.model = model;
313 env.data = data;
314 mj_kdl::env_add_robot(&env, &robot);
315
316 env.on_reset = [&](mj_kdl::ResetContext *) {
317 mj_kdl::set_joint_pos(&robot, q_home, false);
318 reset_cube();
319 data->ctrl[fingers_act] = 0.0;
320 };
321
322 KDL::JntArray q_enter(n), q_des(n);
323 double prev_sim_time = data->time;
324 bool aborted = false;
325 bool restart = false;
326
327 auto reset_scene = [&]() {
328 mj_kdl::reset(&env);
329 prev_sim_time = data->time;
330 restart = true;
331 };
332
333 reset_scene();
334
335 mj_kdl::Viewer viewer;
336 if (!headless && !mj_kdl::init_window_sim(&viewer, &robot)) {
337 std::cerr << "init_window_sim() failed\n";
338 mj_kdl::cleanup(&robot);
339 mj_kdl::destroy_scene(model, data);
340 return 1;
341 }
342
343 do {
344 restart = false;
345 for (const Phase &phase : phases) {
346 if (aborted || restart) break;
347 std::cout << "State: " << phase.name << "\n";
348 double t_enter = data->time;
349 snapshot_q(robot, n, q_enter);
350
351 while (true) {
352 if (data->time < prev_sim_time - 1e-6) {
353 reset_scene();
354 break;
355 }
356 prev_sim_time = data->time;
357
358 double alpha =
359 phase.duration > 0.0 ? clamp01((data->time - t_enter) / phase.duration) : 1.0;
360 lerp_q(q_enter, *phase.target, alpha, q_des);
361 rnea_ctrl(robot, q_des, n, rnea, q_buf, qdot_buf, qddot_des, torques, f_ext);
362 mj_kdl::update(&robot);
363 data->ctrl[fingers_act] = phase.gripper_cmd;
364
365 double t_rel = data->time - t_enter;
366 bool done_time = t_rel >= phase.duration;
367 bool done_pose = phase.settle_tol < 0.0
368 || max_abs_joint_err(robot, *phase.target, n) <= phase.settle_tol;
369 bool done_timeout = phase.timeout > 0.0 && t_rel >= phase.timeout;
370 if ((done_time && done_pose) || done_timeout) break;
371
372 if (!mj_kdl::step(&robot)) {
373 aborted = true;
374 break;
375 }
376 }
377 }
378 } while (restart);
379
380 int ret = 0;
381 if (!aborted) {
382 double cube_x = data->qpos[qadr];
383 double cube_y = data->qpos[qadr + 1];
384 double cube_z = data->qpos[qadr + 2];
385 double place_err_xy = std::hypot(cube_x - kPlaceX, cube_y - kPlaceY);
386 std::cout << "cube final position: [" << std::fixed << std::setprecision(3) << cube_x
387 << ", " << cube_y << ", " << cube_z << "]"
388 << " target=[" << kPlaceX << ", " << kPlaceY << ", " << kTableZ + kCubeHS
389 << "] xy_error=" << place_err_xy << "\n";
390 if (headless && place_err_xy > 0.08) ret = 1;
391 }
392 if (!headless) mj_kdl::cleanup(&viewer);
393 mj_kdl::cleanup(&robot);
394 mj_kdl::destroy_scene(model, data);
395 return ret;
396}
int main(int argc, char *argv[])
void env_add_robot(Env *env, Robot *robot)
ResetInfo reset(Env *env, const ResetOptions *options=nullptr)
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 build_scene(mjModel **out_model, mjData **out_data, const SceneSpec *spec)
void destroy_scene(mjModel *model, mjData *data)
bool init_window_sim(Viewer *v, Robot *r, const char *title="MuJoCo")
std::string asset(const fs::path &relative)
std::string menagerie_model(const fs::path &relative)
KDL::Frame target
const char * name
ResetHook on_reset
std::vector< AttachmentSpec > attachments
std::vector< int > kdl_to_mj_dof
std::vector< double > jnt_pos_msr
std::vector< double > jnt_vel_msr
std::vector< double > jnt_trq_cmd
KDL::Frame tip_T_tcp
std::vector< RobotSpec > robots
std::vector< SceneObject > objects