URDF to MJCF Conversion
Warning
This tool is hacky and highly experimental! Expect things to be broken.
As MuJoCo does not ingest URDFs, we have written a helper tool for converting URDF to MJCF to assist with converting a robot description to an MJCF. This can either be done offline or at runtime; refer to demo 2 for an example.
As noted in the warning above, but reiterating here, these tools are highly experimental! They are intended to be used for assistance and getting started, but do not expect things to work for all possible inputs, nor to work immediately out of the box.
Additional cleanup, documentation, and tips and tricks are a work in progress.
Usage
The current tool that is available is make_mjcf_from_robot_description, which is runnable with:
ros2 run mujoco_ros2_control make_mjcf_from_robot_description.py
(or)
ros2 run mujoco_ros2_control robot_description_to_mjcf.sh
When robot_description_to_mjcf.sh is first executed, it creates a Python virtual environment at $ROS_HOME/ros2_control and installs all necessary dependencies.
Once set up, the script sources the environment and runs make_mjcf_from_robot_description.py.
On subsequent runs, it reuses the existing virtual environment.
By default, the tool will pull a URDF from the /robot_description topic.
However, this is configurable at execution time.
A complete list of options is available from the argument parser:
$ ros2 run mujoco_ros2_control make_mjcf_from_robot_description.py -h
usage: make_mjcf_from_robot_description.py [-h] [-u URDF] [-r ROBOT_DESCRIPTION] [-m MUJOCO_INPUTS] [-o OUTPUT] [-p PUBLISH_TOPIC] [-c] [-s] [-f]
[--fuse | --no-fuse] [-a ASSET_DIR] [--scene SCENE]
Convert a full URDF to MJCF for use in MuJoCo
options:
-h, --help show this help message and exit
-u URDF, --urdf URDF Optionally pass an existing URDF file (default: None)
-r ROBOT_DESCRIPTION, --robot_description ROBOT_DESCRIPTION
Optionally pass the robot description string (default: None)
-m MUJOCO_INPUTS, --mujoco_inputs MUJOCO_INPUTS
Optionally specify a defaults xml for default settings, actuators, options, and additional sensors (default: None)
-o OUTPUT, --output OUTPUT
Generated output path (default: mjcf_data)
-p PUBLISH_TOPIC, --publish_topic PUBLISH_TOPIC
Optionally specify the topic to publish the MuJoCo model (default: None)
-c, --convert_stl_to_obj
If we should convert .stls to .objs (default: False)
-s, --save_only Save files permanently on disk; without this flag, files go to a temporary directory (default: False)
-f, --add_free_joint Adds a free joint before the root link of the robot in the urdf before conversion (default: False)
--fuse, --no-fuse Allows MuJoCo to merge static bodies. Use --no-fuse to prevent merging. (default: True)
-a ASSET_DIR, --asset_dir ASSET_DIR
Optionally pass an existing folder with pre-generated OBJ meshes. (default: None)
--scene SCENE Optionally pass an existing xml for the scene (default: None)
A sample URDF and inputs file are provided in test_robot.urdf and test_inputs.xml.
To convert the URDF, run the following from the repo root:
# Dependencies are installed on the fly, if needed
ros2 run mujoco_ros2_control robot_description_to_mjcf.sh \
--save_only \
-u mujoco_ros2_control_demos/demo_resources/robot/test_robot.urdf \
-m mujoco_ros2_control_demos/demo_resources/mjcf_generation/test_inputs.xml \
-o /tmp/output/
The /tmp/output/ directory will contain all necessary assets and MJCF files that can be copied into the relevant locations in a config package.
They can also be adjusted as needed after the fact.
ros2 run mujoco_vendor simulate /tmp/output/mujoco_description_formatted.xml
Of note, the test robot has a good chunk of supported functionality, and we recommend using it as a guide.
Note
The make_mjcf_from_robot_description.py script requires trimesh, mujoco, coacd, and obj2mjcf.
These must either be installed system-wide or available within a virtual environment that is sourced before running the command.
Note
This page focuses on generating MJCFs for robots. Please see additional documentation on modeling and generating MJCFs for objects.
Notes
Note
This has some heavy non-ROS dependencies that could probably be cleaned up:
MuJoCo Python API
trimesh — Python library for loading and using triangular meshes.
obj2mjcf — A tool for converting Wavefront OBJ files to multiple MuJoCo meshes grouped by material.
xml.dom (not sure if this is already available)
Rough outline of the automated conversion process
Reads a robot description URDF.
Adds in mujoco tag that provides necessary info for conversion.
Replaces package names from
package://to absolute filepaths.Note
Duplicate mesh files will have an
_Nappended to them to avoid conflicts in the outputassetsfolder. For instance, if running multiple types of UR robots in one sim, there will be multipleshoulder.daefiles.Reads absolute filepaths of all meshes and converts either
.daeor.stlto.objusing trimesh.Puts all meshes into an
assets/folder undermjcf_data/relative to current working dir.Modifies filepaths again in URDF to point to the
assets/folder.Decomposes large meshes into multiple components to ensure convex hulls.
Publishes the new formatted robot description XML file that can be used for conversion.
Converts the new robot description URDF file.
Runs the MuJoCo conversion tool to get the MJCF version.
Copies in a default
scene.xmlfile which gives better camera and scene info.Adds remaining sites, items, and any other custom inputs.
Embedding MuJoCo Inputs inside URDF
You can embed MuJoCo-specific information directly inside a URDF (typically inside a xacro) so the URDF → MJCF conversion script can pick it up and inject the corresponding tags into the generated MJCF. See test_robot.urdf for a complete example.
Top-level container
Use a <mujoco_inputs> element inside your xacro/URDF. The converter looks for this element
and copies or processes its children into the MJCF.
Main sub-elements
raw_inputsArbitrary MJCF XML fragments that will get copied verbatim into the generated MJCF. Use this for elements that don’t require conversion (for example
option,default,actuator,tendon,equality, simplesensordefinitions, etc.). Seetest_robot.urdf.Can also exclude contacts between bodies using the contact/exclude tag.
processed_inputsConvenience tags that the converter understands and processes into valid MJCF entries. Use these when the converter must transform or generate MJCF elements (for example, cameras, lidar rangefinders, mesh decomposition hints, or targeted modifications). Common processed tags (supported by the demo converter):
decompose_mesh(attributes:mesh_name,threshold) — requests mesh decomposition when running theobj2mjcf/decompose step; useful when large meshes must be split for more robust collision hull generation. Particularly useful for gripper fingers and anything a robot will interact with, like object handles.camera(attributes:site,name,fovy,mode,resolution) — instructs the converter to add a camera in the MJCF attached to the given URDF site (frame). The converter will fill position/quaternion from the URDF link pose.resolutionis two integers separated by a space (e.g.640 480). Thenamemust match thesensorname declared in theros2_controlblock if you plan to publish images to ROS topics.lidar(attributes:ref_site,sensor_name,min_angle,max_angle,angle_increment) — generates a set of MJCFrangefindersensors placed aroundref_site. The converter rotates rangefinders about the replicate frame’s Y axis betweenmin_angleandmax_angleat the step sizeangle_increment. The generated rangefinders will be named by thesensor_namebase (e.g.rf-01,rf-02, …); ROS-facingsensorentries in theros2_controlsection should use the same base name.modify_element(attributes:type,name, …any MJCF attributes…) — finds the generated MJCF element bytype(for examplejointorbody) andnameand sets or overwrites the provided attributes. Useful to tweak physics properties likefrictionloss,stiffness,damping,gravcomp, etc.
sceneScene-level MJCF fragments such as
asset,worldbody,visual, and small scene parameters. If present the converter will merge/insert it into the MJCF scene (camera lighting, ground textures, skybox definitions, etc.). Ascene.xmlcan also be passed to the script using the--sceneargument to generate the model including the scene configuration.Gravity can also be changed in the MuJoCo
scene. For example:Earth gravity:
<option gravity="0 0 -9.81"> <flag contact="enable" /> </option>
Lunar gravity (one-sixth Earth gravity):
<option gravity="0 0 -1.63"> <flag contact="enable" /> </option>
Note
Changing the simulated gravity does not affect gravity compensation in the
processed_inputsdescribed above.
Sensor and ROS mapping
Define ROS-facing sensors inside the
ros2_controltag (or anywhere in the URDF that your robot description consumers expect). For cameras, thesensorname in the URDF must match thecameranameplaced inprocessed_inputsso the plugin can map the MJCF camera to a ROS topic (seetest_robot.urdf).Lidar: the converter produces multiple MJCF
rangefindersensors. The URDFsensorfor the lidar should provideangle_increment,min_angle,max_angle,range_min,range_max, andlaserscan_topicparameters. The hardware interface will combine the set of generated rangefinders into a single ROSLaserScanmessage.
Practical tips and conventions
Use URDF/xacro frames (links) as reference
sitelocations for cameras and sensors; the converter will read the link pose and attach MJCF objects accordingly.Keep
raw_inputsminimal and preferprocessed_inputsfor things that need conversion or generation (meshes, cameras, rangefinders) — processed inputs document intent and are easier to maintain than dropping raw MJCF fragments into the URDF.When matching sensors: make sure MJCF sensor names and URDF
sensornames align — this is how runtime mapping and topics are resolved.
Where to look:
Example usage in repo: test_robot.urdf.
The conversion inputs file used by the demo: test_inputs.xml.
Minimal example (camera + lidar)
Insert the following snippet inside a xacro/URDF where you want to describe MuJoCo inputs. It
demonstrates a small mujoco_inputs block plus the matching ros2_control sensor entries that
map MJCF sensors to ROS topics.
<!-- MuJoCo inputs embedded in URDF/xacro -->
<mujoco_inputs>
<raw_inputs>
<!-- simple actuator copied verbatim into MJCF -->
<actuator>
<position name="joint1" joint="joint1" kp="1000"/>
</actuator>
</raw_inputs>
<processed_inputs>
<!-- add a camera attached to the URDF frame 'camera_frame' -->
<camera site="camera_frame" name="camera" fovy="58" mode="fixed" resolution="640 480"/>
<!-- generate a set of rangefinders attached to 'lidar_frame' -->
<lidar ref_site="lidar_frame" sensor_name="rf" min_angle="-0.3" max_angle="0.3" angle_increment="0.025"/>
</processed_inputs>
</mujoco_inputs>
<!-- ROS-facing sensor mappings (example inside ros2_control or globally) -->
<ros2_control name="MujocoSystem" type="system">
<!-- camera sensor: name must match processed_inputs camera 'name' -->
<sensor name="camera">
<param name="frame_name">camera_color_mujoco_frame</param>
<param name="image_topic">/camera/color/image_raw</param>
<param name="info_topic">/camera/color/camera_info</param>
</sensor>
<!-- lidar sensor: base name must match processed_inputs 'sensor_name' -->
<sensor name="lidar">
<param name="frame_name">lidar_frame</param>
<param name="angle_increment">0.025</param>
<param name="min_angle">-0.3</param>
<param name="max_angle">0.3</param>
<param name="range_min">0.05</param>
<param name="range_max">10.0</param>
<param name="laserscan_topic">/scan</param>
</sensor>
</ros2_control>
Processed inputs attribute reference
The following lists the supported processed_inputs tags and their attributes. These are the
attributes the demo converter recognizes; converters may extend this list.
decompose_mesh
Required:
mesh_name(string)Optional:
threshold(float) — convex decomposition threshold; smaller values produce finer decomposition. Example:<decompose_mesh mesh_name="shoulder_link" threshold="0.05"/>.
camera
Required:
site(string) — URDF link/frame name to attach the camera to.Required:
name(string) — camera name in MJCF; must match URDFsensorname used for ROS mapping.Optional:
fovy(int/float) — vertical field of view in degrees (example:58).Optional:
mode(string) — MJCF camera mode (commonlyfixed).Optional:
resolution(string) — two integers"<width> <height>"(example:640 480).Optional: any other args supported by the MJCF camera tag.
Notes: Converter fills transform (position + quaternion) from the URDF link pose.
lidar
Required:
ref_site(string) — URDF frame used as reference for placing rangefinders.Required:
sensor_name(string) — base name for generated MJCF rangefinders (e.g.rfwill producerf-01,rf-02, …). URDFsensorentries and ROS mapping should reference the same base name.Required:
min_angle(float) — start angle in radians.Required:
max_angle(float) — end angle in radians.Required:
angle_increment(float) — angular step between generated rangefinders.Notes: The converter creates multiple MJCF
rangefindersensors across the angle range; the hardware interface merges them into a single ROSLaserScan.
modify_element
Required:
type(string) — MJCF element type to target (e.g.joint,body).Required:
name(string) — name attribute of the MJCF element to modify.Additional attributes: any MJCF attributes you want to set or overwrite (for example
frictionloss,damping,gravcomp,solimp,solref, …).Example:
<modify_element type="joint" name="joint1" frictionloss="1.0" damping="2.0"/>.