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