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