<mujoco model="so101">
<!--
MuJoCo physics model for SO101 robot arm — TEMPLATE FILE
Generated by mujoco_adapter._generate_robot_mujoco_xml() at launch time.
Placeholder substitutions (Python string replace before XML parse):
{{MESHES_DIR}} → absolute path to robot_description/meshes/lerobot/so101/
{{ROBOT_BASE_POS}} → "x y z" from layout.yaml robot_spawn (default: "0 0 0")
Camera convention:
Cameras are NOT defined here. They are injected dynamically from YAML peripherals
by mujoco_adapter._generate_robot_mujoco_xml(robot_spawn, peripherals).
Only cameras with driver="opencv" are injected.
Camera name convention: YAML name="top" → MJCF camera name="top_camera"
(must match _build_camera_remappings convention: f"{name}_camera")
Joint names "1"–"6" match the ros2_control joint names in so101_ros2_control.xacro.
Body hierarchy, joint origins, and axes are taken directly from the URDF:
IB_Robot/src/robot_description/urdf/lerobot/so101/so101_base.xacro
eulerseq="XYZ" (uppercase = extrinsic rotations) matches URDF's RPY convention:
URDF rpy=(r,p,y) = extrinsic XYZ: R = Rz(y)*Ry(p)*Rx(r)
MuJoCo eulerseq="XYZ": same formula → angles can be copied directly from URDF.
MuJoCo camera direction convention:
MuJoCo cameras look in -Z of their frame.
YAML transform stores Gazebo/URDF convention (camera looks in +Z).
mujoco_adapter applies: mj_roll = yaml_roll + pi (converting +Z to -Z)
-->
<compiler
meshdir="{{MESHES_DIR}}"
eulerseq="XYZ"
angle="radian" />
<!--
Physics solver settings — tuned for stable contact-rich grasping.
timestep="0.001": 1 ms step (was implicit 2 ms).
Keeps solref timeconst / timestep ≥ 10× (0.010/0.001), safely above the
≥4× stability floor. Controller still runs at 50 Hz (every 20 physics steps).
noslip_iterations="20": twenty Newton-smoothing passes on no-slip constraints.
Prevents micro-slip between gripper faces and grasped objects from
accumulating into macroscopic sliding / ejection.
Higher count (was 5) needed so gripper-banana no-slip converges even when
the arm receives a simultaneous contact impulse from the table or plate.
cone="elliptic": exact elliptic friction cone for condim=4 contacts.
The default pyramidal cone over-approximates friction in the diagonal
direction, causing numerical chatter in the torsional friction channel
(condim=4 adds one torsional DOF). Elliptic cone eliminates this.
-->
<option gravity="0 0 -9.81" integrator="implicitfast"
timestep="0.001" noslip_iterations="20" cone="elliptic">
<flag contact="enable" />
</option>
<!-- ================================================================
Mesh assets (STL files from robot_description package)
================================================================ -->
<asset>
<material name="mat_robot" specular="0.25" shininess="0.1"/>
<mesh name="base_motor_holder" file="base_motor_holder_so101_v1.stl" />
<mesh name="base_so101" file="base_so101_v2.stl" />
<mesh name="sts3215" file="sts3215_03a_v1.stl" />
<mesh name="waveshare" file="waveshare_mounting_plate_so101_v2.stl" />
<mesh name="motor_holder_base" file="motor_holder_so101_base_v1.stl" />
<mesh name="rotation_pitch" file="rotation_pitch_so101_v1.stl" />
<mesh name="upper_arm_mesh" file="upper_arm_so101_v1.stl" />
<mesh name="under_arm_mesh" file="under_arm_so101_v1.stl" />
<mesh name="motor_holder_wrist" file="motor_holder_so101_wrist_v1.stl" />
<mesh name="sts3215_no_horn" file="sts3215_03a_no_horn_v1.stl" />
<mesh name="wrist_roll_pitch" file="wrist_roll_pitch_so101_v2.stl" />
<mesh name="wrist_roll_follower" file="wrist_roll_follower_so101_v1.stl" />
<!-- CoACD decomposition of wrist_roll_follower (8 convex pieces) -->
<mesh name="wrist_roll_follower_col_0" file="wrist_roll_follower_col_0.stl" />
<mesh name="wrist_roll_follower_col_1" file="wrist_roll_follower_col_1.stl" />
<mesh name="wrist_roll_follower_col_2" file="wrist_roll_follower_col_2.stl" />
<mesh name="wrist_roll_follower_col_3" file="wrist_roll_follower_col_3.stl" />
<mesh name="wrist_roll_follower_col_4" file="wrist_roll_follower_col_4.stl" />
<mesh name="wrist_roll_follower_col_5" file="wrist_roll_follower_col_5.stl" />
<mesh name="wrist_roll_follower_col_6" file="wrist_roll_follower_col_6.stl" />
<mesh name="wrist_roll_follower_col_7" file="wrist_roll_follower_col_7.stl" />
<mesh name="moving_jaw" file="moving_jaw_so101_v1.stl" />
<!-- CoACD decomposition of moving_jaw (8 convex pieces) -->
<mesh name="moving_jaw_col_0" file="moving_jaw_col_0.stl" />
<mesh name="moving_jaw_col_1" file="moving_jaw_col_1.stl" />
<mesh name="moving_jaw_col_2" file="moving_jaw_col_2.stl" />
<mesh name="moving_jaw_col_3" file="moving_jaw_col_3.stl" />
<mesh name="moving_jaw_col_4" file="moving_jaw_col_4.stl" />
<mesh name="moving_jaw_col_5" file="moving_jaw_col_5.stl" />
<mesh name="moving_jaw_col_6" file="moving_jaw_col_6.stl" />
<mesh name="moving_jaw_col_7" file="moving_jaw_col_7.stl" />
</asset>
<!-- ================================================================
Default classes: separate visual (display-only) and
collision (physics) geoms.
================================================================ -->
<default>
<default class="visual">
<geom contype="0" conaffinity="0" group="1" rgba="1.0 0.5 0.0 1"
material="mat_robot" />
</default>
<default class="collision">
<geom contype="1" conaffinity="1" group="0" rgba="0 0 0 0" />
<!--
gripper_col: contact parameters for fixed jaw (wrist_roll_follower) and
moving jaw (moving_jaw) CoACD mesh pieces — 16 geoms total.
Change friction here to tune all of them at once.
friction="F 1.0 0.05": F=sliding (↑ = more grip), 1.0=torsional, 0.05=rolling.
Effective banana-gripper friction = min(gripper_F, banana_F).
-->
<default class="gripper_col">
<geom friction="10.0 1.0 0.05" condim="4"
solimp="0.99 0.999 0.001 0.5 2" solref="0.004 1"/>
</default>
</default>
</default>
<worldbody>
<!-- 补光由场景 headlight ambient 提供,不再额外加点光源 -->
<!-- Cameras are injected here at runtime by mujoco_adapter._generate_robot_mujoco_xml()
based on YAML peripherals with driver="opencv". See file header for convention. -->
<geom type="plane" size="1 1 0.01" rgba="0 0 0 0" contype="0" conaffinity="0" />
<!--
base body — fixed to the world (no joint).
Joint "base_joint" in URDF is a fixed joint (world→base, origin=0,0,0).
{{ROBOT_BASE_POS}} is substituted at runtime from layout.yaml robot_spawn.
-->
<body name="base" pos="{{ROBOT_BASE_POS}}">
<inertial
pos="0.020739 0.00204287 0.065966"
mass="0.147"
fullinertia="0.000136117 0.000114686 0.000130364 4.59787e-07 9.75275e-08 -4.97151e-06" />
<!-- Collision proxy -->
<geom class="collision" type="box" size="0.055 0.04 0.055"
pos="0.02 0.02 0.055" />
<!-- Visual meshes — origins taken from URDF visual elements -->
<geom class="visual" type="mesh" mesh="base_motor_holder"
pos="0.0206915 0.0221255 0.0300817" euler="1.5708 0 0" />
<geom class="visual" type="mesh" mesh="base_so101"
pos="0.0207909 0.0221255 0.0300817" euler="1.5708 0 0" />
<geom class="visual" type="mesh" mesh="sts3215" rgba="0.1 0.1 0.1 1"
pos="0.0207909 -0.0105745 0.0761817" euler="0 0 -1.5708" />
<geom class="visual" type="mesh" mesh="waveshare"
pos="0.0205915 0.0467435 0.0798817" euler="1.5708 0 0" />
<!--
body shoulder
URDF joint "1": origin xyz="0.0207909 -0.0230745 0.0948817"
rpy="-3.14159 0 1.5708"
axis xyz="0 0 1" → MuJoCo hinge axis="0 0 1"
-->
<body name="shoulder" pos="0.0207909 -0.0230745 0.0948817"
euler="-3.14159 0 1.5708">
<joint name="1" type="hinge" axis="0 0 1"
range="-2.0693 2.0709" damping="1.5" armature="0.005"/>
<inertial
pos="-0.0307604 -1.66727e-05 -0.0252713"
mass="0.100006"
fullinertia="8.3759e-05 8.10403e-05 2.39783e-05 7.55525e-08 -1.16342e-06 1.54663e-07" />
<!-- No collision geom for shoulder: joints 1-4 rely on joint limits and
damping for self-collision avoidance. In pick-and-place scenes the
arm should not contact objects; re-add box proxies here if needed. -->
<geom class="visual" type="mesh" mesh="sts3215" rgba="0.1 0.1 0.1 1"
pos="-0.0303992 0.000422241 -0.0417" euler="1.5708 1.5708 0" />
<geom class="visual" type="mesh" mesh="motor_holder_base"
pos="-0.0675992 -0.000177759 0.0158499" euler="1.5708 -1.5708 0" />
<geom class="visual" type="mesh" mesh="rotation_pitch"
pos="0.0122008 2.22413e-05 0.0464" euler="-1.5708 0 0" />
<!--
body upper_arm
URDF joint "2": origin xyz="-0.0303992 -0.0182778 -0.0542"
rpy="-1.5708 -1.5708 0"
axis xyz="0 0 1"
-->
<body name="upper_arm" pos="-0.0303992 -0.0182778 -0.0542"
euler="-1.5708 -1.5708 0">
<joint name="2" type="hinge" axis="0 0 1"
range="-1.92 1.92" damping="1.5" armature="0.005"/>
<inertial
pos="-0.0898471 -0.00838224 0.0184089"
mass="0.103"
fullinertia="4.08002e-05 0.000147318 0.000142487 -1.97819e-05 -4.03016e-08 8.97326e-09" />
<!-- No collision geom for upper_arm: see shoulder comment above. -->
<geom class="visual" type="mesh" mesh="sts3215" rgba="0.1 0.1 0.1 1"
pos="-0.11257 -0.0155 0.0187" euler="-3.14159 0 -1.5708" />
<geom class="visual" type="mesh" mesh="upper_arm_mesh"
pos="-0.065085 0.012 0.0182" euler="3.14159 0 0" />
<!--
body lower_arm
URDF joint "3": origin xyz="-0.11257 -0.028 0"
rpy="0 0 1.5708"
axis xyz="0 0 1"
-->
<body name="lower_arm" pos="-0.11257 -0.028 0"
euler="0 0 1.5708">
<joint name="3" type="hinge" axis="0 0 1"
range="-1.6813 1.6828" damping="1.5" armature="0.005"/>
<inertial
pos="-0.0980701 0.00324376 0.0182831"
mass="0.104"
fullinertia="2.87438e-05 0.000159844 0.00014529 7.41152e-06 1.26409e-06 -4.90188e-08" />
<!-- No collision geom for lower_arm: see shoulder comment above. -->
<geom class="visual" type="mesh" mesh="under_arm_mesh"
pos="-0.0648499 -0.032 0.0182" euler="-3.14159 0 0" />
<geom class="visual" type="mesh" mesh="motor_holder_wrist"
pos="-0.0648499 -0.032 0.018" euler="-3.14159 0 0" />
<geom class="visual" type="mesh" mesh="sts3215" rgba="0.1 0.1 0.1 1"
pos="-0.1224 0.0052 0.0187" euler="-3.14159 0 -3.14159" />
<!--
body wrist
URDF joint "4": origin xyz="-0.1349 0.0052 0"
rpy="0 0 -1.5708"
axis xyz="0 0 1"
-->
<body name="wrist" pos="-0.1349 0.0052 0"
euler="0 0 -1.5708">
<joint name="4" type="hinge" axis="0 0 1"
range="-1.65806 1.65806" damping="1.5" armature="0.005"/>
<inertial
pos="-0.000103312 -0.0386143 0.0281156"
mass="0.079"
fullinertia="3.68263e-05 2.5391e-05 2.1e-05 1.7893e-08 -5.28128e-08 3.6412e-06" />
<!-- No collision geom for wrist: see shoulder comment above. -->
<geom class="visual" type="mesh" mesh="sts3215_no_horn" rgba="0.1 0.1 0.1 1"
pos="0 -0.0424 0.0306" euler="1.5708 1.5708 0" />
<geom class="visual" type="mesh" mesh="wrist_roll_pitch"
pos="0 -0.028 0.0181" euler="-1.5708 -1.5708 0" />
<!--
body gripper
URDF joint "5": origin xyz="0 -0.0611 0.0181"
rpy="1.5708 0 3.14159"
axis xyz="0 0 1"
-->
<body name="gripper" pos="0 -0.0611 0.0181"
euler="1.5708 0 3.14159">
<joint name="5" type="hinge" axis="0 0 1"
range="-2.9115 2.9115" damping="1.5" armature="0.005"/>
<inertial
pos="0.000213627 0.000245138 -0.025187"
mass="0.087"
fullinertia="2.75087e-05 4.33657e-05 3.45059e-05 -3.35241e-07 -5.7352e-06 -5.17847e-08" />
<!-- Only finger mesh collisions — no arm boxes -->
<!-- Fixed jaw: CoACD 8-piece convex decomposition of wrist_roll_follower.
Same pos/euler as visual geom. -->
<geom class="gripper_col" type="mesh" mesh="wrist_roll_follower_col_0" pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0"/>
<geom class="gripper_col" type="mesh" mesh="wrist_roll_follower_col_1" pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0"/>
<geom class="gripper_col" type="mesh" mesh="wrist_roll_follower_col_2" pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0"/>
<geom class="gripper_col" type="mesh" mesh="wrist_roll_follower_col_3" pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0"/>
<geom class="gripper_col" type="mesh" mesh="wrist_roll_follower_col_4" pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0"/>
<geom class="gripper_col" type="mesh" mesh="wrist_roll_follower_col_5" pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0"/>
<geom class="gripper_col" type="mesh" mesh="wrist_roll_follower_col_6" pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0"/>
<geom class="gripper_col" type="mesh" mesh="wrist_roll_follower_col_7" pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0"/>
<geom class="visual" type="mesh" mesh="sts3215" rgba="0.1 0.1 0.1 1"
pos="0.0077 0.0001 -0.0234" euler="-1.5708 0 0" />
<geom class="visual" type="mesh" mesh="wrist_roll_follower"
pos="0 -0.000218214 0.000949706" euler="-3.14159 0 0" />
<!-- wrist_camera injected here by mujoco_adapter if YAML has
peripherals[name=wrist, driver=opencv, transform.parent_frame=gripper] -->
<!--
body jaw
URDF joint "6": origin xyz="0.0202 0.0188 -0.0234"
rpy="1.5708 0 0"
axis xyz="0 0 1"
-->
<body name="jaw" pos="0.0202 0.0188 -0.0234"
euler="1.5708 0 0">
<joint name="6" type="hinge" axis="0 0 1"
range="0.0 1.0" damping="1.5"
armature="0.028" frictionloss="0.052" />
<inertial
pos="-0.00157495 -0.0300244 0.0192755"
mass="0.012"
fullinertia="6.61427e-06 1.89032e-06 5.28738e-06 -3.19807e-07 -5.90717e-09 -1.09945e-07" />
<!-- Moving jaw: CoACD 8-piece convex decomposition.
Same pos/euler as visual geom. -->
<geom class="gripper_col" type="mesh" mesh="moving_jaw_col_0" pos="0 0 0.0189" euler="0 0 0"/>
<geom class="gripper_col" type="mesh" mesh="moving_jaw_col_1" pos="0 0 0.0189" euler="0 0 0"/>
<geom class="gripper_col" type="mesh" mesh="moving_jaw_col_2" pos="0 0 0.0189" euler="0 0 0"/>
<geom class="gripper_col" type="mesh" mesh="moving_jaw_col_3" pos="0 0 0.0189" euler="0 0 0"/>
<geom class="gripper_col" type="mesh" mesh="moving_jaw_col_4" pos="0 0 0.0189" euler="0 0 0"/>
<geom class="gripper_col" type="mesh" mesh="moving_jaw_col_5" pos="0 0 0.0189" euler="0 0 0"/>
<geom class="gripper_col" type="mesh" mesh="moving_jaw_col_6" pos="0 0 0.0189" euler="0 0 0"/>
<geom class="gripper_col" type="mesh" mesh="moving_jaw_col_7" pos="0 0 0.0189" euler="0 0 0"/>
<geom class="visual" type="mesh" mesh="moving_jaw"
pos="0 0 0.0189" euler="0 0 0" />
</body> <!-- jaw -->
</body> <!-- gripper -->
</body> <!-- wrist -->
</body> <!-- lower_arm -->
</body> <!-- upper_arm -->
</body> <!-- shoulder -->
</body> <!-- base -->
</worldbody>
<!-- Exclude self-collisions between adjacent links -->
<contact>
<exclude body1="base" body2="shoulder" />
<exclude body1="shoulder" body2="upper_arm" />
<exclude body1="upper_arm" body2="lower_arm" />
<exclude body1="lower_arm" body2="wrist" />
<exclude body1="wrist" body2="gripper" />
<exclude body1="gripper" body2="jaw" />
</contact>
<!-- Position actuators for ros2_control MujocoSystemInterface.
Actuator names match joint names so get_actuator_id() resolves them.
Joints 1-5: kp + dampratio=1.0 (MuJoCo auto-computes critical kv each timestep).
Joint 6 (gripper): kp + explicit kv; armature is nonzero so kv is fixed and
well-defined regardless of timestep. -->
<actuator>
<position name="1" joint="1" kp="998.22" dampratio="1.0" ctrlrange="-2.0693 2.0709" forcerange="-2.75 2.75"/>
<position name="2" joint="2" kp="998.22" dampratio="1.0" ctrlrange="-1.92 1.92" forcerange="-2.75 2.75"/>
<position name="3" joint="3" kp="998.22" dampratio="1.0" ctrlrange="-1.6813 1.6828" forcerange="-2.75 2.75"/>
<position name="4" joint="4" kp="998.22" dampratio="1.0" ctrlrange="-1.65806 1.65806" forcerange="-2.75 2.75"/>
<position name="5" joint="5" kp="998.22" dampratio="1.0" ctrlrange="-2.9115 2.9115" forcerange="-2.75 2.75"/>
<position name="6" joint="6" kp="748.66" kv="5.5" ctrlrange="0.0 1.0" forcerange="-2.75 2.75"/>
</actuator>
</mujoco>