|
Mujoco KDL Wrapper
0.5.1
MuJoCo + KDL bridge for robot kinematics and dynamics
|
Most examples use the bundled Kinova GEN3 7-DOF arm under assets/kinova_gen3 and the bundled Robotiq 2F-85 gripper under assets/robotiq_2f85.
Build from the repo root:
Every example ends by itself. A C++ example (src/examples/) opens the Simulate UI unless given --headless; a Python one (python/mj_kdl_wrapper/examples/) runs headless unless given --gui. Either way it runs the same sequence or duration, prints its result, and exits; closing the window ends it early. Headless, each example checks its own goal (the limit is printed next to the measured value), prints PASS or FAIL, and exits 1 on a failure. The C++ headless runs are registered with CTest (ex_*_headless); the Python ones run under pytest (python/tests/test_examples.py). Recording: ex_table_pour --record [out.mp4] (C++ and Python) and ex_achd_pick_place --record (C++) also write an MP4.
Every example exists in both languages, with the same scene, gains, durations and limits.
The Python counterparts use the public Python wrapper and upstream PyKDL bindings for FK, IK, RNEA, and ACHD instead of re-binding KDL classes locally.
Control cycle. Every control loop does one update() per physics step:
update() reads the measurements and then writes the active mode's command in one call, like one exchange on a fieldbus, so a command reaches the actuators one control period after the state it was computed from. env.on_reset primes the first torque command (gravity at the reset pose), so the first step after a reset already holds the arm.
| Example | Scene | Main behavior | Headless check |
|---|---|---|---|
| ex_gravity_comp | arm only | hold home pose with KDL gravity compensation | EE drift |
| ex_joint_ctrl | arm only | POSITION motion to a target, then VELOCITY motion back home | joint error at the target, VELOCITY converges before the timeout |
| ex_table_pick_place | table + blue cube | joint-impedance pick-place, pushed while carrying the cube | cube placed, push deflection vs stiffness, spring-back, elbow height |
| ex_table_pour | table + transparent receiver | scripted pour from a gripper-held bottle into a tabletop vessel | balls in the receiver |
| ex_rnea_pick_place | two arms + grippers, table, two cubes, free objects, cameras | dual-arm pick-place with RNEA computed torque | both cubes placed, elbow heights, objects undisturbed, no arm contact |
| ex_achd_table_slide | arm without gripper, table | guarded ACHD approach of the wrist flange onto the table, then a slide along +X while pressing 10 N down through ACHD's external-force input | touchdown speed, table contact held, elbow height |
| ex_achd_pick_place | table + blue cube | ACHD Cartesian pick/place with 6D TCP regulation and half-arm support wrench | cube placed, elbow drop |
| ex_admittance_ft | table + wrist FT + gripper | admittance driven by a named force-torque sensor, RNEA task-space inner loop with a null-space posture | helix and push metrics, elbow height |
Scene: Kinova GEN3 arm (arm only, no gripper).
What it does:
Control law: CtrlMode::TORQUE - KDL gravity compensation.
Headless check: EE drift after 7500 steps: X.XXXX mm (limit 0.1000 mm); measured 0.0000 mm.
Scene: Kinova GEN3 arm (arm only, no gripper), with <joint>_velocity actuators (kv = 500) added through RobotSpec::modes.
What it does:
Control laws:
Headless check: the POSITION motion ends within 0.01 rad of the target (measured 0.0061) and the VELOCITY motion converges within 0.01 rad of home before its 5 s timeout (measured 2.66 s).
Scene: Kinova GEN3 + 2F-85 gripper mounted on a table, with a blue cube on the tabletop.
What it does:
Control law: CtrlMode::TORQUE - joint impedance (PD + KDL gravity).
With gravity compensated exactly, the quasi-static response to a force F at the pushed body's centre of mass is dq = K^-1 J_push^T F, which moves the TCP by J K^-1 J_push^T F (J the TCP Jacobian from ChainJntToJacSolver, J_push the same with its reference point moved to the centre of mass). The example compares the peak deflection along the push, relative to the tracking lag just before it and taken at full force, with that prediction.
Headless check (limit, measured C++ / Python): cube on the table within 5 mm of the place spot (1.56 / 1.61 mm); push deflection at least 10 mm (20.57 / 20.57 mm) and within 0.85..1.15 of the prediction (20.30 mm, ratio 1.013); deflection 0.5 s after the push at most 4 mm (1.72 mm); cube centre within 10 mm of the TCP while pushed (6.28 mm, the grasp offset); lowest elbow (forearm_link origin) at least 450 mm above the table (597 mm).
Scene: two Kinova GEN3 + 2F-85 arms on the bundled table (1.6 m x 1.2 m top at z = 0.7 m), facing each other: arm 1 at (-0.70, -0.12) facing +x, arm 2 at (0.70, 0.12) facing -x with every name prefixed r2_. Each arm's cube (cube, r2_cube) sits at the same spot in its own base frame, so the arms work in opposite halves of the table. Five free objects the arms must leave alone (three boxes, two spheres) stand on the table, and two scene cameras (overview, side) join each arm's own wrist / r2_wrist camera.
What it does:
Control law: CtrlMode::TORQUE - computed torque through ChainIdSolver_RNE, per arm.
Headless check (limit, measured C++ / Python): each cube on the table within 5 mm of its place spot (1.75 / 1.74 mm, both arms); each lowest elbow at least 450 mm above the table (598 mm); no free object moved more than 1 mm (0.37 mm); no contact between the two arms (0); RNEA never failed.
Scene: Kinova GEN3 + 2F-85 gripper mounted on a table, with a transparent receiver vessel and 36 small free spheres representing rice or pellets.
What it does:
Control law: CtrlMode::TORQUE - joint impedance (PD + KDL gravity), matching the pick examples.
Headless check: balls in transparent receiver: N/36, at least 24; measured 34 (C++) and 35 (Python).
The ACHD examples use ChainHdSolver_Vereshchagin to convert Cartesian task accelerations into constrained joint accelerations. For MuJoCo torque control, the examples command the full inverse-dynamics torque computed from that ACHD qddot:
Run headless:
Scene: Kinova GEN3 + wrist FT sensor + Robotiq 2F-85 gripper mounted on a table.
What it does: Admittance control for the whole run. Admittance is an outer force->position loop wrapped around an inner motion controller; here the inner loop is RNEA task-space computed torque (CtrlMode::TORQUE, ChainIkSolverVel_wdls + ChainIdSolver_RNE): a Cartesian PD on the TCP pose error becomes desired TCP acceleration, WDLS maps it to qddot, and RNEA maps that to torque. The 6-D TCP task leaves the 7-DOF arm's elbow free, so a joint-space PD toward the home posture is added in the task's null space with the same WDLS pseudo-inverse, qddot = J#(beta - J z) + z (Siciliano et al., Robotics: Modelling, Planning and Control, 2009, Sec. 3.5.1); without it the elbow sags about 445 mm during the run. It:
Outer admittance law:
Headless check (limit, measured): helix response > 0.10 m (0.120), helix tracking error < 6 mm (3.1), settle error < 6 mm (0.0), no false force at the handoff (0), push response
0.12 m (0.176), recovery error < 2 mm (0.0), hold drift after the push < 1 mm (0), lowest elbow (forearm_link origin) >= 0.64 m above the table (0.691, its start height; without the null-space term 0.246).
--record [out.mp4] runs an example headless and writes an H.264 MP4 through VideoRecorder (EGL offscreen rendering + ffmpeg pipe); no window is needed. A recorder that cannot start or write fails the run.
Requires: BUILD_RECORDER=ON (default) and ffmpeg in PATH.