246int main(
int argc,
char *argv[])
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") {
256 }
else if (arg ==
"--record") {
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;
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";
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();
280 gripper.
pos[2] = -0.061525;
281 gripper.
euler[0] = 180.0;
284 robot_spec.
path = arm_mjcf.c_str();
285 robot_spec.
pos[2] = kSceneBaseZ;
289 scene.
robots.push_back(robot_spec);
292 .mjcf_path = table_mjcf,
293 .pos = { 0.0, 0.0, kSceneBaseZ },
296 scene.
objects.push_back(table);
297 scene.
objects.push_back(make_cube(kSceneBaseZ));
299 mjModel *model =
nullptr;
300 mjData *data =
nullptr;
302 std::cerr <<
"build_scene() failed\n";
312 &robot, model, data,
"base_link",
"bracelet_link",
"", &tool
314 std::cerr <<
"init_robot_from_mjcf() failed\n";
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";
330 KDL::JntArray q_home(n);
331 for (
unsigned i = 0; i < n; ++i) q_home(i) = kHomePose[i];
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;
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";
352 std::cout <<
"support segment: " << kSupportLink <<
" index=" << support_segment <<
"\n";
354 const double z_grasp = kCubeHS;
355 const double z_above = z_grasp + 0.20;
356 const double z_lift = z_grasp + 0.30;
360 fill_q_state(robot, n, q_buf, qdot_buf);
362 fk_pos.JntToCart(q_buf, home_tcp);
363 const KDL::Rotation grasp_rot = robot.
tip_T_tcp.M;
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));
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 },
383 int qadr = model->jnt_qposadr[cube_jnt];
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;
402 data->ctrl[fingers_act] = 0.0;
405 double prev_sim_time = data->time;
406 bool aborted =
false;
407 bool restart =
false;
409 auto reset_scene = [&]() {
411 prev_sim_time = data->time;
419 std::cerr <<
"init_window_sim() failed\n";
426 bool recorder_ok =
false;
432 std::cerr <<
"init_video_recorder() failed -- is EGL available and ffmpeg installed?\n";
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;
442 const int steps_per_record_frame =
443 std::max(1,
static_cast<int>(1.0 / (kRecordFps * model->opt.timestep)));
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;
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";
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;
474 if (data->time < prev_sim_time - 1e-6) {
478 prev_sim_time = data->time;
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;
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(
501 support_prev_z_valid,
529 data->ctrl[fingers_act] = phase.gripper_cmd;
531 fill_q_state(robot, n, q_buf, qdot_buf);
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
550 KDL::Twist err = KDL::diff(current, phase.target);
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;
564 if (recorder_ok && sim_step % steps_per_record_frame == 0) {
566 std::cerr <<
"record_frame() failed at sim step " << sim_step <<
"\n";
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";
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;
598 std::cout <<
"Saved recording: " << record_path <<
"\n";