Mujoco KDL Wrapper  0.2.2
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_dual_arm.cpp
Go to the documentation of this file.
1/* ex_dual_arm.cpp
2 * Two Kinova Gen3 arms, each fitted with a Robotiq 2F-85 gripper,
3 * in a shared MuJoCo scene.
4 *
5 * arm1 at x = -1.5 m, facing +X.
6 * arm2 at x = +1.5 m, facing +X; all element names prefixed "r2_".
7 *
8 * Both arms hold the home pose via PD + KDL gravity compensation.
9 * Grippers cycle open/closed every 3 s.
10 *
11 * Gravity comp uses KDL::ChainDynParam built from a chain that includes
12 * the gripper's lumped inertia (via init_robot_from_mjcf tool_body).
13 * jnt_trq_cmd is primed before the loop so the first physics step already
14 * gets correct compensation.
15 *
16 * Requires MuJoCo Menagerie in cache.
17 *
18 * Usage:
19 * ex_dual_arm [--headless]
20 *
21 * --headless: run 600 steps and print both EE positions, then exit. */
22
24#include "example_paths.hpp"
25
26#include <kdl/chaindynparam.hpp>
27#include <kdl/chainfksolverpos_recursive.hpp>
28
29#include <cmath>
30#include <iostream>
31#include <string>
32
33static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
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
38int main(int argc, char *argv[])
39{
40 bool headless = false;
41 for (int i = 1; i < argc; ++i)
42 if (std::string(argv[i]) == "--headless") headless = true;
43
44 const std::string arm_mjcf = mj_kdl_examples::menagerie_model("kinova_gen3/gen3.xml");
45 const std::string grp_mjcf = mj_kdl_examples::asset("robotiq_2f85/2f85.xml");
46
48 gs.mjcf_path = grp_mjcf.c_str();
49 gs.attach_to = { mj_kdl::AttachKind::Site, "pinch_site" };
50 gs.prefix = "g_";
51
52 mj_kdl::RobotSpec arm1_spec;
53 arm1_spec.path = arm_mjcf.c_str();
54 arm1_spec.prefix = "";
55 arm1_spec.pos[0] = -1.0;
56 arm1_spec.attachments.push_back(gs);
57
58 mj_kdl::RobotSpec arm2_spec;
59 arm2_spec.path = arm_mjcf.c_str();
60 arm2_spec.prefix = "r2_";
61 arm2_spec.pos[0] = 1.0;
62 arm2_spec.attachments.push_back(gs);
63
65 sc.timestep = 0.002;
66 sc.add_floor = true;
67 sc.add_skybox = true;
68 sc.robots.push_back(arm1_spec);
69 sc.robots.push_back(arm2_spec);
70
71 mjModel *model = nullptr;
72 mjData *data = nullptr;
73 if (!mj_kdl::build_scene(&model, &data, &sc)) {
74 std::cerr << "build_scene() failed\n";
75 return 1;
76 }
77
78 mj_kdl::ToolFrameSpec tool1, tool2;
79 tool1.tool_body = "g_base";
80 tool1.tcp_site = "g_pinch";
81 tool2.tool_body = "r2_g_base";
82 tool2.tcp_site = "r2_g_pinch";
83
84 mj_kdl::Robot arm1, arm2;
85 if (
86 !mj_kdl::init_robot_from_mjcf(&arm1, model, data, "base_link", "bracelet_link", "", &tool1)
88 &arm2, model, data, "r2_base_link", "r2_bracelet_link", "", &tool2
89 )
90 ) {
91 std::cerr << "init_robot_from_mjcf() failed\n";
92 mj_kdl::destroy_scene(model, data);
93 return 1;
94 }
95
96 const int n = arm1.n_joints;
97 int fing1 = mj_name2id(model, mjOBJ_ACTUATOR, "g_fingers_actuator");
98 int fing2 = mj_name2id(model, mjOBJ_ACTUATOR, "r2_g_fingers_actuator");
99
100 KDL::ChainDynParam dyn1(arm1.chain, KDL::Vector(0.0, 0.0, -9.81));
101 KDL::ChainDynParam dyn2(arm2.chain, KDL::Vector(0.0, 0.0, -9.81));
102
103 KDL::JntArray q_home(n), q1(n), q2(n), g1(n), g2(n);
104 for (int i = 0; i < n; ++i) q_home(i) = kHomePose[i];
107
108 mj_kdl::Env env;
109 env.spec = sc;
110 env.model = model;
111 env.data = data;
112 mj_kdl::env_add_robot(&env, &arm1);
113 mj_kdl::env_add_robot(&env, &arm2);
114
115 // Prime jnt_trq_cmd so the first physics step already gets gravity compensation.
116 auto prime_grav = [&]() {
117 dyn1.JntToGravity(q_home, g1);
118 dyn2.JntToGravity(q_home, g2);
119 for (int i = 0; i < n; ++i) {
120 arm1.jnt_trq_cmd[i] = g1(i);
121 arm2.jnt_trq_cmd[i] = g2(i);
122 }
123 };
124
125 env.on_reset = [&](mj_kdl::ResetContext *) {
126 mj_kdl::set_joint_pos(&arm1, q_home, false);
127 mj_kdl::set_joint_pos(&arm2, q_home, false);
128 };
129
130 auto reset_to_home = [&]() {
131 mj_kdl::reset(&env);
132 prime_grav();
133 mj_kdl::update(&arm1);
134 mj_kdl::update(&arm2);
135 if (fing1 >= 0) data->ctrl[fing1] = 0.8;
136 if (fing2 >= 0) data->ctrl[fing2] = 0.8;
137 };
138
139 reset_to_home();
140
141 // Per-step: update() reads sensors and flushes the previous jnt_trq_cmd;
142 // then compute PD + KDL gravity for the next step.
143 auto ctrl_step = [&]() {
144 mj_kdl::update(&arm1);
145 mj_kdl::update(&arm2);
146 for (int i = 0; i < n; ++i) q1(i) = arm1.jnt_pos_msr[i];
147 for (int i = 0; i < n; ++i) q2(i) = arm2.jnt_pos_msr[i];
148 dyn1.JntToGravity(q1, g1);
149 dyn2.JntToGravity(q2, g2);
150 for (int i = 0; i < n; ++i) {
151 arm1.jnt_trq_cmd[i] =
152 kKp[i] * (kHomePose[i] - arm1.jnt_pos_msr[i]) - kKd[i] * arm1.jnt_vel_msr[i] + g1(i);
153 arm2.jnt_trq_cmd[i] =
154 kKp[i] * (kHomePose[i] - arm2.jnt_pos_msr[i]) - kKd[i] * arm2.jnt_vel_msr[i] + g2(i);
155 }
156 if (fing1 >= 0) data->ctrl[fing1] = (std::fmod(data->time, 6.0) < 3.0) ? 0.8 : 0.0;
157 if (fing2 >= 0) data->ctrl[fing2] = (std::fmod(data->time, 6.0) < 3.0) ? 0.8 : 0.0;
158 };
159
160 if (headless) {
161 for (int step = 0; step < 600; ++step) {
162 ctrl_step();
163 mj_kdl::step(&arm1);
164 }
165
166 KDL::ChainFkSolverPos_recursive fk1(arm1.chain), fk2(arm2.chain);
167 KDL::JntArray q1(n), q2(n);
168 KDL::Frame ee1, ee2;
169 for (int i = 0; i < n; ++i) {
170 q1(i) = arm1.jnt_pos_msr[i];
171 q2(i) = arm2.jnt_pos_msr[i];
172 }
173 fk1.JntToCart(q1, ee1);
174 fk2.JntToCart(q2, ee2);
175 std::cout << "arm1 EE: [" << ee1.p.x() << ", " << ee1.p.y() << ", " << ee1.p.z() << "]\n";
176 std::cout << "arm2 EE: [" << ee2.p.x() << ", " << ee2.p.y() << ", " << ee2.p.z() << "]\n";
177 } else {
178 mj_kdl::Viewer viewer;
179 if (!mj_kdl::init_window_sim(&viewer, &arm1)) {
180 std::cerr << "init_window_sim() failed\n";
181 mj_kdl::cleanup(&arm1);
182 mj_kdl::cleanup(&arm2);
183 mj_kdl::destroy_scene(model, data);
184 return 1;
185 }
186
187 double prev_sim_time = data->time;
188 while (true) {
189 if (data->time < prev_sim_time - 1e-6) reset_to_home();
190 prev_sim_time = data->time;
191 ctrl_step();
192 if (!mj_kdl::step(&arm1)) break;
193 }
194
195 mj_kdl::cleanup(&viewer);
196 }
197
198 mj_kdl::cleanup(&arm1);
199 mj_kdl::cleanup(&arm2);
200 mj_kdl::destroy_scene(model, data);
201 return 0;
202}
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 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")
std::string asset(const fs::path &relative)
std::string menagerie_model(const fs::path &relative)
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
std::vector< RobotSpec > robots