mj-kdl-wrapper  0.1.0
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_record.cpp
Go to the documentation of this file.
1/* ex_record.cpp (MJCF)
2 * Headless H.264 video recording via VideoRecorder (EGL + ffmpeg pipe).
3 *
4 * Records the Kinova GEN3 arm holding the home pose via gravity compensation
5 * for 5 s at 60 fps, loaded from MuJoCo Menagerie MJCF.
6 * No display or GLFW window is needed.
7 *
8 * Requires third_party/menagerie submodule.
9 *
10 * Requirements:
11 * - BUILD_RECORDER=ON (default) -- EGL support compiled into the library
12 * - ffmpeg available in PATH -- sudo apt install ffmpeg
13 *
14 * Usage:
15 * ex_record [output.mp4] [360p|480p|720p|1080p|2k|4k]
16 *
17 * Output defaults to sim.mp4; resolution defaults to 1080p. */
18
20
21#include <kdl/chaindynparam.hpp>
22
23#include <cmath>
24#include <filesystem>
25#include <iostream>
26#include <string>
27
28static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
29static constexpr int kFps = 60;
30static constexpr double kDuration = 5.0; // seconds
31
32namespace fs = std::filesystem;
33static fs::path repo_root() { return fs::path(__FILE__).parent_path().parent_path().parent_path(); }
34
35static mj_kdl::VideoResolution parse_resolution(const std::string &s)
36{
37 if (s == "360p") return mj_kdl::VideoResolution::R360p;
38 if (s == "480p") return mj_kdl::VideoResolution::R480p;
39 if (s == "720p") return mj_kdl::VideoResolution::R720p;
40 if (s == "1080p") return mj_kdl::VideoResolution::R1080p;
41 if (s == "2k") return mj_kdl::VideoResolution::R2K;
42 if (s == "4k") return mj_kdl::VideoResolution::R4K;
43 std::cerr << "Unknown resolution '" << s << "'; using 1080p\n";
45}
46
47int main(int argc, char *argv[])
48{
49 std::string outmp4 = "sim.mp4";
51 for (int i = 1; i < argc; ++i) {
52 std::string a(argv[i]);
53 if (a.size() >= 4 && a.substr(a.size() - 4) == ".mp4")
54 outmp4 = a;
55 else
56 res = parse_resolution(a);
57 }
58
59 const fs::path root = repo_root();
60 if (!fs::exists(root / "third_party/menagerie")) {
61 std::cerr << "third_party/menagerie/ not found - run:\n"
62 " cmake -B build -DFETCH_MENAGERIE=ON\n";
63 return 1;
64 }
65
66 const std::string mjcf = (root / "third_party/menagerie/kinova_gen3/gen3.xml").string();
67
68 // Build scene
71 r.path = mjcf.c_str();
72 sc.robots.push_back(r);
73
74 mjModel *model = nullptr;
75 mjData *data = nullptr;
76 if (!mj_kdl::build_scene(&model, &data, &sc)) {
77 std::cerr << "build_scene() failed\n";
78 return 1;
79 }
80
81 mj_kdl::Robot robot;
82 if (!mj_kdl::init_robot_from_mjcf(&robot, model, data, "base_link", "bracelet_link")) {
83 std::cerr << "init_robot_from_mjcf() failed\n";
84 mj_kdl::destroy_scene(model, data);
85 return 1;
86 }
87
88 unsigned n = static_cast<unsigned>(robot.n_joints);
89 KDL::ChainDynParam dyn(robot.chain, KDL::Vector(0.0, 0.0, sc.gravity_z));
90
91 // Set home pose
92 KDL::JntArray q_home(n);
93 for (unsigned i = 0; i < n; ++i) q_home(i) = kHomePose[i];
94 mj_kdl::set_joint_pos(&robot, q_home);
96
97 // Prime gravity torques so first step gets correct compensation.
98 KDL::JntArray q(n), g(n);
99 dyn.JntToGravity(q_home, g);
100 for (unsigned i = 0; i < n; ++i) robot.jnt_trq_cmd[i] = g(i);
101
102 // Init video recorder
104 if (!mj_kdl::init_video_recorder(&vr, model, outmp4.c_str(), res, kFps)) {
105 std::cerr << "init_video_recorder() failed -- is EGL available and ffmpeg installed?\n";
106 mj_kdl::cleanup(&robot);
107 mj_kdl::destroy_scene(model, data);
108 return 1;
109 }
110
111 // Orbit camera slowly around the arm for a nicer recording.
112 vr.cam.elevation = -20.0;
113 vr.cam.distance = 1.8;
114 vr.cam.lookat[2] = 0.5;
115
116 const int total_steps = static_cast<int>(kDuration / model->opt.timestep);
117 const double step_per_deg = 360.0 / total_steps; // full orbit over kDuration
118 const int steps_per_frame = std::max(1, static_cast<int>(1.0 / (kFps * model->opt.timestep)));
119
120 std::cout << "Recording " << kDuration << " s to " << outmp4 << " (" << total_steps
121 << " steps, " << kFps << " fps)...\n";
122
123 for (int step = 0; step < total_steps; ++step) {
124 // Gravity compensation control
125 mj_kdl::update(&robot);
126 for (unsigned i = 0; i < n; ++i) q(i) = robot.jnt_pos_msr[i];
127 dyn.JntToGravity(q, g);
128 for (unsigned i = 0; i < n; ++i) robot.jnt_trq_cmd[i] = g(i);
129 mj_kdl::step(&robot);
130
131 // Record a frame at the target frame rate.
132 if (step % steps_per_frame == 0) {
133 vr.cam.azimuth = std::fmod(step * step_per_deg, 360.0);
134 if (!mj_kdl::record_frame(&vr, model, data)) {
135 std::cerr << "record_frame() failed at step " << step << "\n";
136 break;
137 }
138 }
139 }
140
141 mj_kdl::cleanup(&vr); // flushes pipe, finalises MP4
142
143 std::cout << "Saved: " << outmp4 << "\n";
144
145 mj_kdl::cleanup(&robot);
146 mj_kdl::destroy_scene(model, data);
147 return 0;
148}
int main(int argc, char *argv[])
Definition ex_record.cpp:47
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)
void step(Robot *s)
bool init_robot_from_mjcf(Robot *r, mjModel *model, mjData *data, const char *base_body, const char *tip_body, const char *prefix="", const char *tool_body=nullptr)
void cleanup(Robot *r)
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)
std::vector< double > jnt_pos_msr
std::vector< double > jnt_trq_cmd
std::vector< RobotSpec > robots