Mujoco KDL Wrapper  0.1.0
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_achd_pick_place.cpp
Go to the documentation of this file.
1/* ex_achd_pick_place.cpp
2 * Kinova GEN3 + Robotiq 2F-85 picks a cube from one table location and places
3 * it at another using Cartesian acceleration-constrained hybrid dynamics
4 * via KDL ChainHdSolver_Vereshchagin_Fixed_Joint + RNEA:
5 *
6 * Xddot_des = Kp * diff(T_tcp, T_target) + Kd * d/dt(diff)
7 * beta = alpha^T * Xddot_des, with alpha = I_6
8 * qddot = ACHD(q, qdot, alpha, beta) [constrained joint accelerations]
9 * tau = RNEA(q, qdot, qddot) [full inverse dynamics for MuJoCo]
10 *
11 * mj_kdl_wrapper's TORQUE mode fully nulls the MuJoCo position actuators
12 * (ctrl = qpos + kv/kp * qvel), so qfrc_applied is the sole torque source.
13 *
14 * Usage:
15 * ex_achd_pick_place [--headless] [--record output.mp4]
16 *
17 * With --headless runs the full sequence and prints final cube position. */
18
20
21#include "chainhdsolver_vereshchagin_fixed_joint.hpp"
22#include <kdl/chainfksolverpos_recursive.hpp>
23#include <kdl/chainfksolvervel_recursive.hpp>
24#include <kdl/chainidsolver_recursive_newton_euler.hpp>
25
26#include <algorithm>
27#include <cmath>
28#include <filesystem>
29#include <iomanip>
30#include <iostream>
31#include <string>
32#include <vector>
33
34namespace fs = std::filesystem;
35
36static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
37static constexpr double kCubeHS = 0.02;
38static constexpr double kSceneBaseZ = 0.70;
39static constexpr double kPickX = 0.40;
40static constexpr double kPickY = 0.00;
41static constexpr double kPlaceX = 0.40;
42static constexpr double kPlaceY = 0.24;
43static constexpr double kTableZ = 0.0;
44
45// Cartesian acceleration gains. ACHD consumes acceleration-energy setpoints; with
46// alpha = I_6 these are the desired TCP linear/angular accelerations.
47static constexpr double kKpLin = 200.0;
48static constexpr double kKiLin = 100.0;
49static constexpr double kKdLin = 40.0;
50static constexpr double kKpRot = 120.0;
51static constexpr double kKiRot = 50.0;
52static constexpr double kKdRot = 80.0;
53static constexpr double kBetaMaxLin = 120.0;
54static constexpr double kBetaMaxRot = 80.0;
55static constexpr double kTauMax = 59.0;
56static constexpr double kIntegralMax = 0.5;
57static constexpr int kRecordFps = 30;
58static constexpr const char *kSupportLink = "half_arm_2_link";
59static constexpr double kSupportKp = 800.0;
60static constexpr double kSupportKd = 80.0;
61static constexpr double kSupportFMax = 45.0;
62static constexpr double kSupportLift = 0.06;
63
64static fs::path repo_root() { return fs::path(__FILE__).parent_path().parent_path().parent_path(); }
65static double clamp01(double v) { return std::max(0.0, std::min(1.0, v)); }
66static double clamp_abs(double v, double limit) { return std::max(-limit, std::min(limit, v)); }
67static double smoothstep(double t)
68{
69 t = clamp01(t);
70 return t * t * (3.0 - 2.0 * t);
71}
72
73static KDL::Frame lerp_frame(const KDL::Frame &a, const KDL::Frame &b, double t)
74{
75 return KDL::addDelta(a, KDL::diff(a, b), clamp01(t));
76}
77
78static void fill_q_state(
79 const mj_kdl::Robot &robot,
80 unsigned n,
81 KDL::JntArray &q,
82 KDL::JntArray &qdot
83)
84{
85 for (unsigned i = 0; i < n; ++i) {
86 q(i) = robot.jnt_pos_msr[i];
87 qdot(i) = robot.jnt_vel_msr[i];
88 }
89}
90
91static int find_segment_index(const KDL::Chain &chain, const std::string &name)
92{
93 for (unsigned i = 0; i < chain.getNrOfSegments(); ++i) {
94 if (chain.getSegment(i).getName() == name) return static_cast<int>(i);
95 }
96 return -1;
97}
98
99static bool support_phase(const std::string &name)
100{
101 return name == "PLACE_ABOVE" || name == "PLACE" || name == "OPEN" || name == "RETREAT"
102 || name == "HOLD";
103}
104
105static void clear_wrenches(KDL::Wrenches &wrenches)
106{
107 std::fill(wrenches.begin(), wrenches.end(), KDL::Wrench::Zero());
108}
109
110static double link_world_z(
111 KDL::ChainFkSolverPos_recursive &fk_pos,
112 const KDL::JntArray &q,
113 int segment_index
114)
115{
116 KDL::Frame base_T_link;
117 fk_pos.JntToCart(q, base_T_link, segment_index + 1);
118 return kSceneBaseZ + base_T_link.p.z();
119}
120
121static double apply_support_wrench(
122 KDL::ChainFkSolverPos_recursive &fk_pos,
123 const KDL::JntArray &q,
124 int segment_index,
125 double z_ref,
126 double dt,
127 double &prev_z,
128 bool &prev_z_valid,
129 KDL::Wrenches &f_ext_achd
130)
131{
132 KDL::Frame base_T_link;
133 fk_pos.JntToCart(q, base_T_link, segment_index + 1);
134
135 const double z_world = kSceneBaseZ + base_T_link.p.z();
136 const double vz = prev_z_valid ? (z_world - prev_z) / dt : 0.0;
137 prev_z = z_world;
138 prev_z_valid = true;
139
140 const double fz = std::min(
141 kSupportFMax,
142 std::max(0.0, kSupportKp * (z_ref - z_world) - kSupportKd * vz)
143 );
144 const KDL::Wrench wrench_base(KDL::Vector(0.0, 0.0, fz), KDL::Vector::Zero());
145 f_ext_achd[segment_index] = wrench_base;
146 return fz;
147}
148
149static void achd_cartesian_ctrl(
150 mj_kdl::Robot &robot,
151 const KDL::Frame &target,
152 unsigned n,
153 KDL::ChainFkSolverPos_recursive &fk_pos,
154 KDL::ChainFkSolverVel_recursive &fk_vel,
155 KDL::ChainHdSolver_Vereshchagin_Fixed_Joint &achd,
156 KDL::ChainIdSolver_RNE &rnea,
157 KDL::JntArray &q,
158 KDL::JntArray &qdot,
159 KDL::JntArray &qddot,
160 KDL::Jacobian &alpha,
161 KDL::JntArray &beta,
162 KDL::Wrenches &f_ext_achd,
163 KDL::Wrenches &f_ext_rnea_zero,
164 KDL::JntArray &ff_torques,
165 KDL::JntArray &constraint_torques,
166 KDL::JntArray &tau_cmd,
167 const KDL::Twist &target_twist,
168 std::array<double, 6> &err_i,
169 std::array<double, 6> &err_prev,
170 bool &first_pid
171)
172{
173 fill_q_state(robot, n, q, qdot);
174
175 KDL::Frame current;
176 fk_pos.JntToCart(q, current);
177 KDL::FrameVel current_vel;
178 fk_vel.JntToCart(KDL::JntArrayVel(q, qdot), current_vel);
179 const KDL::Twist tcp_twist = current_vel.deriv();
180
181 const KDL::Twist err = KDL::diff(current, target);
182 const double dt = robot.model ? robot.model->opt.timestep : 0.002;
183 const double e[6] = { err.vel.x(), err.vel.y(), err.vel.z(),
184 err.rot.x(), err.rot.y(), err.rot.z() };
185 if (first_pid) {
186 for (unsigned i = 0; i < 6; ++i) err_prev[i] = e[i];
187 first_pid = false;
188 }
189 for (unsigned i = 0; i < 6; ++i) err_i[i] = clamp_abs(err_i[i] + e[i] * dt, kIntegralMax);
190 const double de[6] = {
191 (e[0] - err_prev[0]) / dt,
192 (e[1] - err_prev[1]) / dt,
193 (e[2] - err_prev[2]) / dt,
194 (e[3] - err_prev[3]) / dt,
195 (e[4] - err_prev[4]) / dt,
196 (e[5] - err_prev[5]) / dt,
197 };
198
199 beta(0) = clamp_abs(kKpLin * e[0] + kKiLin * err_i[0] + kKdLin * de[0], kBetaMaxLin);
200 beta(1) = clamp_abs(kKpLin * e[1] + kKiLin * err_i[1] + kKdLin * de[1], kBetaMaxLin);
201 beta(2) = clamp_abs(kKpLin * e[2] + kKiLin * err_i[2] + kKdLin * de[2], kBetaMaxLin);
202 beta(3) = clamp_abs(kKpRot * e[3] + kKiRot * err_i[3] + kKdRot * (target_twist.rot.x() - tcp_twist.rot.x()), kBetaMaxRot);
203 beta(4) = clamp_abs(kKpRot * e[4] + kKiRot * err_i[4] + kKdRot * (target_twist.rot.y() - tcp_twist.rot.y()), kBetaMaxRot);
204 beta(5) = clamp_abs(kKpRot * e[5] + kKiRot * err_i[5] + kKdRot * (target_twist.rot.z() - tcp_twist.rot.z()), kBetaMaxRot);
205 for (unsigned i = 0; i < 6; ++i) err_prev[i] = e[i];
206 KDL::SetToZero(ff_torques);
207 if (achd.CartToJnt(q, qdot, qddot, alpha, beta, f_ext_achd, ff_torques, constraint_torques) < 0) {
208 std::cerr << "ACHD CartToJnt() failed\n";
209 std::fill(robot.jnt_trq_cmd.begin(), robot.jnt_trq_cmd.end(), 0.0);
210 return;
211 }
212 if (rnea.CartToJnt(q, qdot, qddot, f_ext_rnea_zero, tau_cmd) < 0) {
213 std::cerr << "RNEA CartToJnt() failed\n";
214 std::fill(robot.jnt_trq_cmd.begin(), robot.jnt_trq_cmd.end(), 0.0);
215 return;
216 }
217 for (unsigned i = 0; i < n; ++i) robot.jnt_trq_cmd[i] = clamp_abs(tau_cmd(i), kTauMax);
218}
219
220static mj_kdl::SceneObject make_cube(double surface_z)
221{
222 return {
223 .name = "cube",
224 .mjcf_path = "",
225 .shape = mj_kdl::Shape::BOX,
226 .size = { kCubeHS, kCubeHS, kCubeHS },
227 .pos = { kPickX, kPickY, surface_z + kCubeHS },
228 .rgba = { 0.1f, 0.35f, 1.0f, 1.0f },
229 .mass = 0.1,
230 .condim = 4,
231 .friction = { 0.8, 0.02, 0.001 },
232 };
233}
234
235struct Phase
236{
237 const char *name;
238 KDL::Frame target;
239 double duration;
240 double timeout;
244};
245
246int main(int argc, char *argv[])
247{
248 bool headless = false;
249 bool do_record = false;
250 bool print_ee_angular_vel = false;
251 std::string record_path = "achd_pick_place.mp4";
252 for (int i = 1; i < argc; ++i) {
253 std::string arg(argv[i]);
254 if (arg == "--headless") {
255 headless = true;
256 } else if (arg == "--record") {
257 do_record = true;
258 headless = true;
259 if (i + 1 < argc && argv[i + 1][0] != '-') record_path = argv[++i];
260 } else if (arg == "--print-ee-angular-vel") {
261 print_ee_angular_vel = true;
262 }
263 }
264
265 const fs::path root = repo_root();
266 if (!fs::exists(root / "third_party/menagerie")) {
267 std::cerr << "third_party/menagerie/ not found - run:\n"
268 " cmake -B build -DFETCH_MENAGERIE=ON\n";
269 return 1;
270 }
271
272 const std::string arm_mjcf = (root / "third_party/menagerie/kinova_gen3/gen3.xml").string();
273 const std::string grp_mjcf = (root / "third_party/menagerie/robotiq_2f85/2f85.xml").string();
274 const std::string table_mjcf = (root / "src/examples/assets/table.xml").string();
275
277 gripper.mjcf_path = grp_mjcf.c_str();
278 gripper.attach_to = "bracelet_link";
279 gripper.prefix = "g_";
280 gripper.pos[2] = -0.061525;
281 gripper.euler[0] = 180.0;
282
283 mj_kdl::RobotSpec robot_spec;
284 robot_spec.path = arm_mjcf.c_str();
285 robot_spec.pos[2] = kSceneBaseZ;
286 robot_spec.attachments.push_back(gripper);
287
288 mj_kdl::SceneSpec scene;
289 scene.robots.push_back(robot_spec);
291 .name = "table",
292 .mjcf_path = table_mjcf,
293 .pos = { 0.0, 0.0, kSceneBaseZ },
294 .fixed = true,
295 };
296 scene.objects.push_back(table);
297 scene.objects.push_back(make_cube(kSceneBaseZ));
298
299 mjModel *model = nullptr;
300 mjData *data = nullptr;
301 if (!mj_kdl::build_scene(&model, &data, &scene)) {
302 std::cerr << "build_scene() failed\n";
303 return 1;
304 }
305
307 tool.tool_body = "g_base";
308 tool.tcp_site = "g_pinch";
309
310 mj_kdl::Robot robot;
312 &robot, model, data, "base_link", "bracelet_link", "", &tool
313 )) {
314 std::cerr << "init_robot_from_mjcf() failed\n";
315 mj_kdl::destroy_scene(model, data);
316 return 1;
317 }
318
319 const unsigned n = robot.chain.getNrOfJoints();
320 const unsigned ns = robot.chain.getNrOfSegments();
321 const int fingers_act = mj_name2id(model, mjOBJ_ACTUATOR, "g_fingers_actuator");
322 const int cube_jnt = mj_name2id(model, mjOBJ_JOINT, "cube_joint");
323 if (fingers_act < 0 || cube_jnt < 0) {
324 std::cerr << "required actuator or cube joint not found\n";
325 mj_kdl::cleanup(&robot);
326 mj_kdl::destroy_scene(model, data);
327 return 1;
328 }
329
330 KDL::JntArray q_home(n);
331 for (unsigned i = 0; i < n; ++i) q_home(i) = kHomePose[i];
332
333 KDL::ChainFkSolverPos_recursive fk_pos(robot.chain);
334 KDL::ChainFkSolverVel_recursive fk_vel(robot.chain);
335 KDL::Twist root_acc(KDL::Vector(0.0, 0.0, -scene.gravity_z), KDL::Vector::Zero());
336 KDL::ChainHdSolver_Vereshchagin_Fixed_Joint achd(robot.chain, root_acc, 6);
337 KDL::ChainIdSolver_RNE rnea(robot.chain, KDL::Vector(0.0, 0.0, scene.gravity_z));
338 KDL::JntArray q_buf(n), qdot_buf(n), qddot(n), ff_torques(n), constraint_torques(n), tau_cmd(n);
339 KDL::Wrenches f_ext_achd(ns, KDL::Wrench::Zero());
340 KDL::Wrenches f_ext_rnea_zero(ns, KDL::Wrench::Zero());
341 KDL::Jacobian alpha(6);
342 KDL::JntArray beta(6);
343 for (unsigned i = 0; i < 6; ++i) alpha(i, i) = 1.0;
344
345 const int support_segment = find_segment_index(robot.chain, kSupportLink);
346 if (support_segment < 0) {
347 std::cerr << "support segment not found: " << kSupportLink << "\n";
348 mj_kdl::cleanup(&robot);
349 mj_kdl::destroy_scene(model, data);
350 return 1;
351 }
352 std::cout << "support segment: " << kSupportLink << " index=" << support_segment << "\n";
353
354 const double z_grasp = kCubeHS;
355 const double z_above = z_grasp + 0.20;
356 const double z_lift = z_grasp + 0.30;
357
358 mj_kdl::set_joint_pos(&robot, q_home, false);
359 mj_kdl::update(&robot);
360 fill_q_state(robot, n, q_buf, qdot_buf);
361 KDL::Frame home_tcp;
362 fk_pos.JntToCart(q_buf, home_tcp);
363 const KDL::Rotation grasp_rot = robot.tip_T_tcp.M;
364
365 auto target_frame = [&](double base_x, double base_y, double base_z) {
366 return KDL::Frame(grasp_rot, KDL::Vector(base_x, base_y, base_z));
367 };
368
369 const std::vector<Phase> phases = {
370 { "HOME", home_tcp, 1.0, 2.5, 0.03, 0.05, 0.0 },
371 { "PICK_ABOVE", target_frame(kPickX, kPickY, kTableZ + z_above), 8.0, 14.0, 0.04, 0.03, 0.0 },
372 { "PICK", target_frame(kPickX, kPickY, kTableZ + z_grasp), 5.0, 12.0, 0.02, 0.03, 0.0 },
373 { "CLOSE", target_frame(kPickX, kPickY, kTableZ + z_grasp), 1.5, 2.5, -1.0, -1.0, 255.0 },
374 { "LIFT", target_frame(kPickX, kPickY, kTableZ + z_lift), 3.0, 8.0, 0.04, 0.03, 255.0 },
375 { "PLACE_ABOVE", target_frame(kPlaceX, kPlaceY, kTableZ + z_above), 5.0, 12.0, 0.04, 0.03, 255.0 },
376 { "PLACE", target_frame(kPlaceX, kPlaceY, kTableZ + z_grasp), 5.0, 14.0, 0.02, 0.03, 255.0 },
377 { "OPEN", target_frame(kPlaceX, kPlaceY, kTableZ + z_grasp), 1.0, 2.0, -1.0, -1.0, 0.0 },
378 { "RETREAT", target_frame(kPlaceX, kPlaceY, kTableZ + z_above), 3.0, 6.0, 0.04, 0.08, 0.0 },
379 { "HOLD", target_frame(kPlaceX, kPlaceY, kTableZ + z_above), headless ? 4.0 : 1e9, headless ? 4.0 : 1e9, -1.0, -1.0, 0.0 },
380 };
381
383 int qadr = model->jnt_qposadr[cube_jnt];
384
385 auto reset_cube = [&]() {
386 data->qpos[qadr] = kPickX;
387 data->qpos[qadr + 1] = kPickY;
388 data->qpos[qadr + 2] = kSceneBaseZ + kCubeHS;
389 data->qpos[qadr + 3] = 1.0;
390 data->qpos[qadr + 4] = data->qpos[qadr + 5] = data->qpos[qadr + 6] = 0.0;
391 };
392
393 mj_kdl::Env env;
394 env.spec = scene;
395 env.model = model;
396 env.data = data;
397 mj_kdl::env_add_robot(&env, &robot);
398
399 env.on_reset = [&](mj_kdl::ResetContext *) {
400 mj_kdl::set_joint_pos(&robot, q_home, false);
401 reset_cube();
402 data->ctrl[fingers_act] = 0.0;
403 };
404
405 double prev_sim_time = data->time;
406 bool aborted = false;
407 bool restart = false;
408
409 auto reset_scene = [&]() {
410 mj_kdl::reset(&env);
411 prev_sim_time = data->time;
412 restart = true;
413 };
414
415 reset_scene();
416
417 mj_kdl::Viewer viewer;
418 if (!headless && !mj_kdl::init_window_sim(&viewer, &robot)) {
419 std::cerr << "init_window_sim() failed\n";
420 mj_kdl::cleanup(&robot);
421 mj_kdl::destroy_scene(model, data);
422 return 1;
423 }
424
425 mj_kdl::VideoRecorder recorder;
426 bool recorder_ok = false;
427 if (do_record) {
428 recorder_ok = mj_kdl::init_video_recorder(
429 &recorder, model, record_path.c_str(), mj_kdl::VideoResolution::R720p, kRecordFps
430 );
431 if (!recorder_ok)
432 std::cerr << "init_video_recorder() failed -- is EGL available and ffmpeg installed?\n";
433 else {
434 recorder.cam.azimuth = 145.0;
435 recorder.cam.elevation = -22.0;
436 recorder.cam.distance = 1.35;
437 recorder.cam.lookat[0] = 0.12;
438 recorder.cam.lookat[1] = 0.12;
439 recorder.cam.lookat[2] = 0.90;
440 }
441 }
442 const int steps_per_record_frame =
443 std::max(1, static_cast<int>(1.0 / (kRecordFps * model->opt.timestep)));
444 int sim_step = 0;
445
446 do {
447 restart = false;
448 double support_z_ref = 0.0;
449 bool support_z_ref_valid = false;
450 double support_prev_z = 0.0;
451 bool support_prev_z_valid = false;
452 for (const Phase &phase : phases) {
453 if (aborted || restart) break;
454 std::cout << "State: " << phase.name << "\n";
455 double t_enter = data->time;
456 mj_kdl::update(&robot);
457 fill_q_state(robot, n, q_buf, qdot_buf);
458 KDL::Frame phase_start;
459 fk_pos.JntToCart(q_buf, phase_start);
460 if (std::string(phase.name) == "PLACE_ABOVE" || (support_phase(phase.name) && !support_z_ref_valid)) {
461 support_z_ref = link_world_z(fk_pos, q_buf, support_segment) + kSupportLift;
462 support_z_ref_valid = true;
463 support_prev_z = support_z_ref;
464 support_prev_z_valid = true;
465 std::cout << " support_z_ref=" << std::fixed << std::setprecision(3)
466 << support_z_ref << "\n";
467 }
468
469 std::array<double, 6> err_i{}, err_prev{};
470 bool first_pid = true;
471 KDL::Frame prev_target = phase_start;
472 bool first_target = true;
473 while (true) {
474 if (data->time < prev_sim_time - 1e-6) {
475 reset_scene();
476 break;
477 }
478 prev_sim_time = data->time;
479
480 double phase_alpha =
481 phase.duration > 0.0 ? smoothstep((data->time - t_enter) / phase.duration) : 1.0;
482 KDL::Frame target = lerp_frame(phase_start, phase.target, phase_alpha);
483 const double dt = model->opt.timestep;
484 KDL::Twist target_twist = KDL::Twist::Zero();
485 if (!first_target) target_twist = KDL::diff(prev_target, target, dt);
486 prev_target = target;
487 first_target = false;
488 mj_kdl::update(&robot);
489 fill_q_state(robot, n, q_buf, qdot_buf);
490 clear_wrenches(f_ext_achd);
491 clear_wrenches(f_ext_rnea_zero);
492 double support_force = 0.0;
493 if (support_phase(phase.name) && support_z_ref_valid) {
494 support_force = apply_support_wrench(
495 fk_pos,
496 q_buf,
497 support_segment,
498 support_z_ref,
499 dt,
500 support_prev_z,
501 support_prev_z_valid,
502 f_ext_achd
503 );
504 }
505 achd_cartesian_ctrl(
506 robot,
507 target,
508 n,
509 fk_pos,
510 fk_vel,
511 achd,
512 rnea,
513 q_buf,
514 qdot_buf,
515 qddot,
516 alpha,
517 beta,
518 f_ext_achd,
519 f_ext_rnea_zero,
520 ff_torques,
521 constraint_torques,
522 tau_cmd,
523 target_twist,
524 err_i,
525 err_prev,
526 first_pid
527 );
528 mj_kdl::update(&robot);
529 data->ctrl[fingers_act] = phase.gripper_cmd;
530
531 fill_q_state(robot, n, q_buf, qdot_buf);
532 KDL::Frame current;
533 fk_pos.JntToCart(q_buf, current);
534 if (print_ee_angular_vel) {
535 KDL::FrameVel current_vel;
536 fk_vel.JntToCart(KDL::JntArrayVel(q_buf, qdot_buf), current_vel);
537 const KDL::Twist ee_twist = current_vel.deriv();
538 std::cout << "ee_ang_vel"
539 << " phase=" << phase.name
540 << " t=" << std::fixed << std::setprecision(4) << (data->time - t_enter)
541 << " wx=" << ee_twist.rot.x()
542 << " wy=" << ee_twist.rot.y()
543 << " wz=" << ee_twist.rot.z()
544 << " target_wx=" << target_twist.rot.x()
545 << " target_wy=" << target_twist.rot.y()
546 << " target_wz=" << target_twist.rot.z()
547 << " support_fz=" << support_force
548 << "\n";
549 }
550 KDL::Twist err = KDL::diff(current, phase.target);
551
552 double t_rel = data->time - t_enter;
553 bool done_time = t_rel >= phase.duration;
554 bool done_pose = phase.settle_pos_tol < 0.0
555 || (err.vel.Norm() <= phase.settle_pos_tol
556 && err.rot.Norm() <= phase.settle_rot_tol);
557 bool done_timeout = phase.timeout > 0.0 && t_rel >= phase.timeout;
558 if ((done_time && done_pose) || done_timeout) break;
559
560 if (!mj_kdl::step(&robot)) {
561 aborted = true;
562 break;
563 }
564 if (recorder_ok && sim_step % steps_per_record_frame == 0) {
565 if (!mj_kdl::record_frame(&recorder, model, data)) {
566 std::cerr << "record_frame() failed at sim step " << sim_step << "\n";
567 mj_kdl::cleanup(&recorder);
568 recorder_ok = false;
569 }
570 }
571 ++sim_step;
572 }
573 fill_q_state(robot, n, q_buf, qdot_buf);
574 KDL::Frame phase_end;
575 fk_pos.JntToCart(q_buf, phase_end);
576 KDL::Twist phase_err = KDL::diff(phase_end, phase.target);
577 std::cout << " pos_err=" << std::fixed << std::setprecision(3)
578 << phase_err.vel.Norm() << " rot_err=" << phase_err.rot.Norm()
579 << " t=" << data->time - t_enter << "\n";
580 }
581 } while (restart);
582
583 int ret = 0;
584 if (!aborted) {
585 double cube_x = data->qpos[qadr];
586 double cube_y = data->qpos[qadr + 1];
587 double cube_z = data->qpos[qadr + 2];
588 double place_err_xy = std::hypot(cube_x - kPlaceX, cube_y - kPlaceY);
589 std::cout << "cube final position: [" << std::fixed << std::setprecision(3) << cube_x
590 << ", " << cube_y << ", " << cube_z << "]"
591 << " target=[" << kPlaceX << ", " << kPlaceY << ", " << kSceneBaseZ + kCubeHS
592 << "] xy_error=" << place_err_xy << "\n";
593 if (headless && place_err_xy > 0.08) ret = 1;
594 }
595 if (!headless) mj_kdl::cleanup(&viewer);
596 if (recorder_ok) {
597 mj_kdl::cleanup(&recorder);
598 std::cout << "Saved recording: " << record_path << "\n";
599 }
600 mj_kdl::cleanup(&robot);
601 mj_kdl::destroy_scene(model, data);
602 return ret;
603}
int main(int argc, char *argv[])
bool init_video_recorder(VideoRecorder *vr, mjModel *model, const char *out_path, int width=1280, int height=720, int fps=60)
bool record_frame(VideoRecorder *vr, mjModel *model, mjData *data)
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)
KDL::Frame target
double settle_rot_tol
const char * name
double settle_pos_tol
ResetHook on_reset
std::vector< AttachmentSpec > attachments
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