Mujoco KDL Wrapper  0.5.1
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_rnea_pick_place.cpp
Go to the documentation of this file.
1/* ex_rnea_pick_place.cpp
2 * Two Kinova GEN3 + Robotiq 2F-85 arms face each other across the table; each picks its own cube
3 * and places it 0.24 m to its left, with computed torque through KDL ChainIdSolver_RNE in TORQUE
4 * mode:
5 *
6 * qddot_des[i] = Kp[i] * (q_des[i] - q[i]) - Kd[i] * qdot[i]
7 * tau = RNEA(q, qdot, qddot_des) = M(q) qddot_des + C(q, qdot) qdot + g(q)
8 *
9 * The second arm's names carry the prefix "r2_". Each arm has its own Robot, KDL chain and
10 * solver; both see their cube at the same base-frame spot, so one IK solve serves both, and one
11 * update() per step drives both. Free objects the arms must leave alone stand on the table; the
12 * "overview" and "side" scene cameras join each arm's own "wrist" camera.
13 *
14 * Usage:
15 * ex_rnea_pick_place [--headless]
16 *
17 * --headless skips the viewer and exits 1 unless each cube rests on the table within kMaxPlaceErr
18 * of its place spot, each elbow stayed above kMinElbowHeight, no free object moved more than
19 * kMaxDisturb, the arms never touched and RNEA never failed. */
20
21#include "common.hpp"
22#include "example_paths.hpp"
23
24#include <kdl/chainidsolver_recursive_newton_euler.hpp>
25
26#include <algorithm>
27#include <array>
28#include <cmath>
29#include <deque>
30#include <iomanip>
31#include <iostream>
32#include <string>
33#include <vector>
34
35namespace ex = mj_kdl_examples;
36
37static constexpr double kMaxPlaceErr = 0.005; // [m] in the table plane
38static constexpr double kMinElbowHeight = 0.45; // [m] forearm_link origin above the table
39static constexpr double kMaxDisturb = 0.001; // [m] free-object displacement
40
41// Acceleration gains: the closed loop is qddot = Kp e - Kd qdot [1/s^2], [1/s].
42static constexpr double kKp[7] = { 100, 200, 100, 200, 100, 200, 100 };
43static constexpr double kKd[7] = { 20, 28, 20, 28, 20, 28, 20 };
44
45// Base x, y [m] and yaw [rad] on the tabletop: facing each other, each arm in its own half.
46static constexpr double kBase[2][3] = { { -0.70, -0.12, 0.0 }, { 0.70, 0.12, M_PI } };
47static constexpr const char *kPrefix[2] = { "", "r2_" };
48
49static KDL::Frame world_T_base(int arm)
50{
51 return KDL::Frame(
52 KDL::Rotation::RotZ(kBase[arm][2]), KDL::Vector(kBase[arm][0], kBase[arm][1], ex::kTableZ)
53 );
54}
55
56// A cube's centre resting on the table at xy in the arm's base frame, in the world frame.
57static KDL::Vector cube_spot(int arm, const double xy[2])
58{
59 return world_T_base(arm) * KDL::Vector(xy[0], xy[1], ex::kCubeHS);
60}
61
63{
64 const char *name;
66 double x, y, half; // [m] on the table; half-size or radius
67 float rgb[3];
68};
69
70// clang-format off
71static const FreeObject kFreeObjects[] = {
72 { "red_box", mj_kdl::Shape::BOX, 0.00, 0.42, 0.030, { 1.0f, 0.20f, 0.2f } },
73 { "green_box", mj_kdl::Shape::BOX, 0.00, -0.42, 0.030, { 0.2f, 1.00f, 0.2f } },
74 { "yellow_box", mj_kdl::Shape::BOX, -0.45, 0.42, 0.040, { 1.0f, 0.85f, 0.1f } },
75 { "orange_sphere", mj_kdl::Shape::SPHERE, 0.45, -0.42, 0.035, { 1.0f, 0.55f, 0.0f } },
76 { "purple_sphere", mj_kdl::Shape::SPHERE, 0.00, 0.00, 0.025, { 0.7f, 0.00f, 0.9f } },
77};
78// clang-format on
79
80static mj_kdl::SceneObject scene_object(const FreeObject &f)
81{
83 o.name = f.name;
84 o.shape = f.shape;
85 for (int k = 0; k < 3; ++k) o.size[k] = f.shape == mj_kdl::Shape::BOX || k == 0 ? f.half : 0.0;
86 o.pos[0] = f.x;
87 o.pos[1] = f.y;
88 o.pos[2] = ex::kTableZ + f.half;
89 std::copy(f.rgb, f.rgb + 3, o.rgba);
90 o.rgba[3] = 1.0f;
91 o.mass = 0.1;
92 const double friction[3] = { 1.0, 0.005, 0.0001 }; // MuJoCo's geom default
93 std::copy(friction, friction + 3, o.friction);
94 return o;
95}
96
98 camera(const char *name, const double pos[3], const double quat[4], double fovy)
99{
101 cam.name = name;
102 std::copy(pos, pos + 3, cam.pos);
103 std::copy(quat, quat + 4, cam.quat);
104 cam.fovy = fovy;
105 return cam;
106}
107
108// RNEA computed torque for one arm; the solvers keep a reference to robot.chain.
110{
111 RneaArm(mj_kdl::Robot &r, double gravity_z)
112 : robot(r), n(r.chain.getNrOfJoints()), rnea(r.chain, KDL::Vector(0.0, 0.0, gravity_z)),
113 dyn(r.chain, KDL::Vector(0.0, 0.0, gravity_z)), q(n), qdot(n), qddot(n), tau(n),
114 f_ext(r.chain.getNrOfSegments(), KDL::Wrench::Zero())
115 {}
116
117 bool control(const KDL::JntArray &q_des)
118 {
120 for (unsigned i = 0; i < n; ++i) qddot(i) = kKp[i] * (q_des(i) - q(i)) - kKd[i] * qdot(i);
121 if (rnea.CartToJnt(q, qdot, qddot, f_ext, tau) < 0) return false;
122 for (unsigned i = 0; i < n; ++i) robot.jnt_trq_cmd[i] = tau(i);
123 return true;
124 }
125
127 unsigned n;
132};
133
134int main(int argc, char *argv[])
135{
136 const bool headless = ex::parse_args(argc, argv).headless;
137
139 for (int a = 0; a < 2; ++a) {
141 spec.path = ex::asset("kinova_gen3/gen3.xml");
142 spec.prefix = kPrefix[a];
143 spec.pos[0] = kBase[a][0];
144 spec.pos[1] = kBase[a][1];
145 spec.pos[2] = ex::kTableZ;
146 spec.quat[2] = std::sin(kBase[a][2] / 2.0);
147 spec.quat[3] = std::cos(kBase[a][2] / 2.0);
148 spec.attachments.push_back(ex::gripper_attachment(ex::asset("robotiq_2f85/2f85.xml")));
149 scene.robots.push_back(spec);
150 }
151 scene.objects.push_back(ex::table_object(ex::asset("table.xml"), ex::kTableZ));
152 for (int a = 0; a < 2; ++a) {
153 const KDL::Vector p = cube_spot(a, ex::kPickXY);
155 cube.name = std::string(kPrefix[a]) + "cube";
156 scene.objects.push_back(cube);
157 }
158 for (const FreeObject &f : kFreeObjects) scene.objects.push_back(scene_object(f));
159 // Both look at the table centre: overview from -y above, side from +y.
160 const double overview_pos[3] = { 0.0, -1.3, 1.9 };
161 const double overview_q[4] = { 0.410747, 0.0, 0.0, 0.911749 };
162 const double side_pos[3] = { 0.0, 1.5, 1.15 };
163 const double side_q[4] = { 0.0, 0.633989, 0.773342, 0.0 };
164 scene.cameras.push_back(camera("overview", overview_pos, overview_q, 55.0));
165 scene.cameras.push_back(camera("side", side_pos, side_q, 50.0));
166
167 mj_kdl::Env env;
168 if (!mj_kdl::init_env(&env, &scene)) return 1;
169 std::cout << "cameras:";
170 for (int i = 0; i < env.model->ncam; ++i)
171 std::cout << " " << mj_id2name(env.model, mjOBJ_CAMERA, i);
172 std::cout << "\n";
173
175 std::array<mj_kdl::Robot, 2> robots;
176 std::vector<ex::PhaseArm> arms;
177 std::vector<mj_kdl::SceneFreeBodySlot *> cubes, free_bodies;
178 std::deque<RneaArm> ctl;
179 std::array<int, 2> roots{};
180 for (int a = 0; a < 2; ++a) {
181 const std::string pre = kPrefix[a];
183 &robots[a], &env, "base_link", "bracelet_link", pre.c_str(), &tool
184 ))
185 return 1;
186 if (!mj_kdl::set_control_mode(&robots[a], mj_kdl::CtrlMode::TORQUE)) return 1;
187 arms.push_back(
188 { &robots[a],
189 mj_kdl::bind_scene_actuator(&env.scene, (pre + "g_fingers_actuator").c_str()) }
190 );
191 cubes.push_back(mj_kdl::bind_scene_free_body(&env.scene, (pre + "cube").c_str()));
192 if (!arms.back().gripper || !cubes.back()) return 1;
193 ctl.emplace_back(robots[a], scene.gravity_z);
194 const int base = mj_name2id(env.model, mjOBJ_BODY, (pre + "base_link").c_str());
195 roots[a] = env.model->body_rootid[base];
196 }
197 for (const FreeObject &f : kFreeObjects) {
198 free_bodies.push_back(mj_kdl::bind_scene_free_body(&env.scene, f.name));
199 if (!free_bodies.back()) return 1;
200 }
201
202 // Same waypoints for both arms: each target is in its own base frame.
204 if (!ex::solve_pick_place(robots[0], wp)) return 1;
205
206 bool restart = false, solver_ok = true;
207 std::array<double, 2> elbow_min{};
208 double disturb = 0.0;
209 int arm_contacts = 0;
210
211 env.on_reset = [&](mj_kdl::ResetContext *ctx) {
212 for (int a = 0; a < 2; ++a) {
213 mj_kdl::set_joint_pos(&robots[a], wp.home);
214 const KDL::Vector p = cube_spot(a, ex::kPickXY);
215 const double pos[3] = { p.x(), p.y(), p.z() };
216 mj_kdl::set_body_pose(&env, cubes[a]->name.c_str(), pos);
217 ctx->data->ctrl[arms[a].gripper->ctrl_id] = 0.0;
218 ex::prime_gravity(robots[a], ctl[a].dyn, wp.home);
219 elbow_min[a] = INFINITY;
220 }
221 disturb = 0.0;
222 arm_contacts = 0;
223 restart = true;
224 };
225 mj_kdl::reset(&env);
226 if (!headless && !mj_kdl::open_viewer(&env)) return 1;
227
228 const auto control = [&](std::size_t a, const KDL::JntArray &q_des) {
229 if (!ctl[a].control(q_des)) solver_ok = false;
230 };
231 const auto after_step = [&](const ex::Phase &, double) {
232 for (int a = 0; a < 2; ++a) {
233 KDL::Frame elbow;
235 &env, (std::string(kPrefix[a]) + "forearm_link").c_str(), &elbow
236 );
237 elbow_min[a] = std::min(elbow_min[a], elbow.p.z());
238 }
239 for (std::size_t i = 0; i < free_bodies.size(); ++i) {
240 const FreeObject &f = kFreeObjects[i];
241 const KDL::Vector rest(f.x, f.y, ex::kTableZ + f.half);
242 disturb = std::max(disturb, (free_bodies[i]->pose.p - rest).Norm());
243 }
244 for (int c = 0; c < env.data->ncon; ++c) {
245 const mjContact &con = env.data->contact[c];
246 const int r0 = env.model->body_rootid[env.model->geom_bodyid[con.geom[0]]];
247 const int r1 = env.model->body_rootid[env.model->geom_bodyid[con.geom[1]]];
248 if ((r0 == roots[0] && r1 == roots[1]) || (r0 == roots[1] && r1 == roots[0]))
249 ++arm_contacts;
250 }
251 };
252 const bool completed =
253 ex::run_phases(env, arms, ex::pick_place_phases(wp), restart, control, after_step);
254 mj_kdl::update(&env);
255 if (!solver_ok) std::cerr << "RNEA failed during the run\n";
256
257 bool ok = completed && solver_ok;
258 std::cout << std::fixed << std::setprecision(4);
259 for (int a = 0; a < 2; ++a) {
260 const KDL::Vector c = cubes[a]->pose.p;
261 const KDL::Vector spot = cube_spot(a, ex::kPlaceXY);
262 const double place_xy = std::hypot(c.x() - spot.x(), c.y() - spot.y());
263 const bool on_table = std::abs(c.z() - spot.z()) < 0.002;
264 const double elbow_h = elbow_min[a] - ex::kTableZ;
265 std::cout << "arm " << a + 1 << ": cube at [" << c.x() << ", " << c.y() << ", " << c.z()
266 << "] place error " << place_xy * 1000.0 << " mm (limit " << kMaxPlaceErr * 1000.0
267 << " mm)" << (on_table ? "" : ", not on the table")
268 << "; lowest elbow above the table " << elbow_h * 1000.0 << " mm (limit "
269 << kMinElbowHeight * 1000.0 << " mm)\n";
270 ok = ok && on_table && place_xy <= kMaxPlaceErr && elbow_h >= kMinElbowHeight;
271 }
272 std::cout << "largest free-object displacement: " << disturb * 1000.0 << " mm (limit "
273 << kMaxDisturb * 1000.0 << " mm)\narm-to-arm contacts: " << arm_contacts
274 << " (limit 0)\n";
275 mj_kdl::cleanup(&env);
276 ok = ok && disturb <= kMaxDisturb && arm_contacts == 0;
277 return headless ? ex::verdict(ok, "both arms placed their cubes and left the rest alone") : 0;
278}
static Rotation RotZ(double angle)
double z() const
double x() const
double y() const
int main(int argc, char *argv[])
ResetInfo reset(Env *env, const ResetOptions *options=nullptr)
void set_body_pose(Env *env, const char *body_name, const double pos[3], const double *quat=nullptr)
Status init_env(Env *env, const SceneSpec *spec)
void update(Env *env)
Status set_control_mode(Robot *r, CtrlMode mode)
Status init_robot_from_mjcf(Robot *r, Env *env, 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)
void cleanup(Robot *r)
bool get_body_frame(Env *env, const char *body_name, KDL::Frame *out)
SceneActuatorSlot * bind_scene_actuator(SceneState *s, const char *name)
SceneFreeBodySlot * bind_scene_free_body(SceneState *s, const char *body_name)
Status open_viewer(Env *env, const char *title="MuJoCo")
std::vector< Wrench > Wrenches
std::string asset(const fs::path &relative)
bool solve_pick_place(const mj_kdl::Robot &robot, PickPlaceWaypoints &w)
Definition common.hpp:248
int verdict(bool ok, const std::string &goal)
Definition common.hpp:58
mj_kdl::SceneSpec scene_spec()
Definition common.hpp:138
mj_kdl::ToolFrameSpec gripper_tool(const std::string &prefix="")
Definition common.hpp:159
constexpr double kCubeHS
Definition common.hpp:29
std::vector< Phase > pick_place_phases(const PickPlaceWaypoints &w)
Definition common.hpp:292
constexpr double kTableZ
Definition common.hpp:28
constexpr double kPickXY[2]
Definition common.hpp:30
mj_kdl::AttachmentSpec gripper_attachment(const std::string &path, const std::string &site="pinch_site")
Definition common.hpp:149
constexpr double kPlaceXY[2]
Definition common.hpp:31
void read_q(const mj_kdl::Robot &robot, KDL::JntArray &q)
Definition common.hpp:80
mj_kdl::SceneObject table_object(const std::string &path, double surface_z)
Definition common.hpp:168
void prime_gravity(mj_kdl::Robot &robot, KDL::ChainDynParam &dyn, const KDL::JntArray &q)
Definition common.hpp:131
bool run_phases(mj_kdl::Env &env, const std::vector< PhaseArm > &arms, const std::vector< Phase > &phases, bool &restart, const std::function< void(std::size_t, const KDL::JntArray &)> &control, const std::function< void(const Phase &, double t_rel)> &after_step={})
Definition common.hpp:319
Args parse_args(int argc, char **argv, const std::string &default_record_path="")
Definition common.hpp:41
mj_kdl::SceneObject cube_object(double x, double y, double surface_z)
Definition common.hpp:178
const char * name
mj_kdl::Shape shape
KDL::JntArray qdot
RneaArm(mj_kdl::Robot &r, double gravity_z)
KDL::JntArray q
KDL::JntArray qddot
bool control(const KDL::JntArray &q_des)
KDL::ChainIdSolver_RNE rnea
KDL::JntArray tau
mj_kdl::Robot & robot
KDL::ChainDynParam dyn
KDL::Wrenches f_ext
ResetHook on_reset
SceneState scene
std::vector< AttachmentSpec > attachments
std::vector< RobotSpec > robots
std::vector< SceneObject > objects
std::vector< CameraSpec > cameras