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