Level 2 and 5 (after Lessons 2.1 to 2.4 and 5.1 to 5.3) · Time 3 to 4 hours · Difficulty 2 of 5
Write an MJCF model of a three-joint arm with a two-finger gripper, joint limits, position servos, a workspace camera and a keyframe, starting from arm3.xml, which is empty. Do not copy a course model: the point is to make every modelling decision yourself and to be able to defend each number.
angle setting.arm3.xml: your model. It starts as an empty <mujoco> element.starter.py: POSES, a dictionary of at least three named full qpos vectors, and model_xml(), which reads arm3.xml (leave it as it is).The tests find the parts by type, so names are yours to choose, with two exceptions: the camera is called workspace and the keyframe home.
The model must:
mujoco.set_mju_user_warning and require it to stay silent);home keyframe under gravity for 5 s with every arm joint within 1 degree of its start, with all of mjData.warning’s counters at zero;<!-- m; kg -->, or unitless).The reference model holds its keyframe to 0.21 degree. Its shoulder carries a static load of 2.73 N m with the arm horizontal, measured as qfrc_bias at that pose; with $k_p$ = 500 N m/rad the predicted steady error is 2.73/500 = 0.0055 rad (0.31 degree). Size your servos the same way: work out the worst gravity load, then pick $k_p$ for the error you can accept.
MJC_IMPL=starter pytest projects/p03_arm3
pytest projects/p03_arm3
Six tests: a warning-free compile, the required parts, the 5 s hold, contact-free poses, the gripper response, and the unit comments. Each common failure below is a mutant in mutants.json. python tools/mutate_project.py p03_arm3 applies each one to the reference and checks that the intended test fails; a control mutant (swapped fromto ends) must pass.
range="-90 90" under angle="radian" is a range of ±90 rad; the joint is effectively unlimited. The compiler’s default is degrees, so state the unit explicitly in <compiler>.fromto ends changes nothing, because a capsule is symmetric. Pointing it the wrong way does: fromto="0 0 0 0 0 -0.25" on the upper arm, with the elbow body still at $+0.25$, buries the link in the base. The pose test then reports contacts and the hold test fails.<contact> and say why.<position> defaults to $k_p$ = 1. A servo whose stiffness cannot carry the gravity load sags until the error times $k_p$ equals the load.mujoco.MjSpec in Python and test that the compiled models have identical body_mass, jnt_range, actuator_gainprm and geom_size.gravcomp on the links and actuatorgravcomp on the joints, and measure the hold error again.arm3_solution.xml (about 60 lines) and solution.py, which names its poses.