Skip to main content

simcheck

Does your robot or object asset actually behave in simulation?

A URDF or MJCF file can load without a single warning and still be broken:

  • The drawer can be jammed inside a convex hull.
  • The mug can be a solid cylinder.
  • A door can swing through its own cabinet.
  • Joint limits can be silently ignored.
  • The inertia tensor can be thrown away by the engine.

simcheck loads the asset in MuJoCo and PyBullet and runs it. It reports what each engine actually does with the file, why, and how to fix it.

$ simcheck tests/assets/cabinet_mesh.urdf
tests/assets/cabinet_mesh.urdf
  FAIL  1 error, 4 warnings, 2 notes  (34 of 37 checks pass)   [object, fixed base; mujoco, pybullet; 0.4 s]
  ERRO engine.rest_contacts [mujoco]: 'carcass' and 'drawer' overlap by 45.0 mm at rest in mujoco; the authored geometry doesn't overlap there: it is mujoco's convex hull of 'carcass', so split that mesh into convex parts (CoACD); they are parent and child, but MuJoCo still collides them because the parent is welded to the world, so joint 'drawer_slide' is braked by this contact
  WARN engine.collision_shape [mujoco]: mujoco treats 100% of the empty space inside 'carcass' as solid (1.93e+05 cm^3); objects can't go in. Split the mesh into convex parts (CoACD, V-HACD).
  ...
$ simcheck fix tests/assets/cabinet_mesh.urdf -o fixed/cabinet.urdf
  fixed: link 'carcass': concave collision mesh (hull 5.6x its volume) split into 58 convex parts
wrote fixed/cabinet.urdf and meshes in fixed/cabinet_meshes
before: FAIL  1 error, 4 warnings
after:  PASS  0 errors, 0 warnings

Install

pip install "simcheck[all]"
simcheck doctor          # versions, engines, and a quick self-test
  • Requirements: Python 3.10 or newer, on Linux, macOS or Windows. MuJoCo, trimesh and the other dependencies install automatically.

  • Extras:

    • pybullet: the second engine.
    • fix: CoACD, used by simcheck fix for convex decomposition.
    • ros: xacro, for .xacro files.
    • all: all of them.
  • PyBullet: its last release only has prebuilt wheels for Linux x86_64 with Python 3.10 and 3.11. Elsewhere, [all] leaves PyBullet out and simcheck checks in MuJoCo only. To get both engines quickly, use Python 3.11 on Linux. See troubleshooting.

  • From source:

    git clone https://github.com/jashshah999/simcheck && pip install -e "simcheck[all]"
    

Use

simcheck robot.urdf                          # one file (URDF, xacro or MJCF)
simcheck assets/ --html report.html          # a folder, with an HTML report
simcheck model.xml --engines mujoco          # MJCF in one engine
simcheck mug.urdf --free-base --category mug # a loose object: also drop it on a floor
simcheck scanned_part.obj                    # a bare mesh, checked as a solid object (density of water)
simcheck explain engine.rest_contacts        # what a check means and how to fix it
simcheck fix cabinet.urdf                    # write cabinet.fixed.urdf and re-check it

ROS packages. simcheck expands .xacro files itself. Pass arguments with --xacro-arg name:=value. It finds package:// and $(find ...) packages in this order:

  • next to the file;
  • through ROS_PACKAGE_PATH;
  • under a ROS 2 install;
  • in folders you name with --package-path src/.

An SRDF next to the URDF (or --srdf) marks the link pairs MoveIt already ignores.

simcheck src/my_robot_description/urdf/robot.urdf.xacro --package-path src/

Datasets. scan checks thousands of files in parallel and can resume. select prints the ones worth keeping, and report turns results into HTML, Markdown, SARIF or JUnit:

simcheck scan generated/ -o results.jsonl                  # resumable; --retry-failed re-runs timeouts
simcheck select results.jsonl --ignore inertia.geometry > keep.txt
simcheck report results.jsonl --html results.html

CI. The GitHub Action checks only the assets a pull request changes, including through a mesh. A baseline means existing problems don't block anyone:

- uses: actions/checkout@v4
  with: { fetch-depth: 0 }
- uses: jashshah999/simcheck@v0.2.0
  with:
    paths: assets
    changed-since: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || '' }}
    baseline: simcheck-baseline.json   # from: simcheck check assets --write-baseline simcheck-baseline.json

It annotates the offending lines in the pull request and writes a summary to the job page. It can also write SARIF for code scanning. GitLab (JUnit), pre-commit hooks and caching are in docs/ci.md.

Python.

import simcheck

report = simcheck.check("cabinet.urdf", engines="mujoco")
print(report.verdict, [f.message for f in report.errors])
reports = simcheck.check_many(paths, jobs=8)        # under `if __name__ == "__main__":` on macOS/Windows

The full API is in docs/python-api.md.

Exit codes:

Code Meaning
0 Nothing at or above --fail-on.
1 An asset has an error (or a warning, with --fail-on warning).
2 A usage mistake.
3 The run is incomplete: a timeout, or a bug in simcheck.
130 Interrupted. Results checked so far are still written.

What it checks

Group Checks
File parses (expanding xacro); kinematic tree; meshes resolve and load; mesh file names that MuJoCo would merge
Mass and inertia every moving part has mass; inertia is positive definite and satisfies the triangle inequality; mass vs size (catches g-vs-kg and mm-vs-m, accepts hollow shells); inertia vs the part's own geometry (placeholders, impossible values, COM off the part)
Geometry plausible overall size (optionally against category priors); moving parts have collision geometry; collision sits where the visual is; mesh quality; concave collision meshes
Joints axis valid and unit length; limits ordered and sane; non-zero effort and velocity limits; mimic joints
Kinematics (no engine needed) moves each joint through its range and flags parts that pass through another part (a flipped axis, a hinge in the wrong place, wrong limits); parts that start inside each other; parts floating away from their parent; hinge axes that miss the part they turn
In each engine loads; keeps the joints and limits from the file; keeps the authored mass and inertia; starts without interpenetration; collision shape matches the mesh (measured by probing the engine with small spheres); drops and settles; stable under gravity; conserves energy; robust to the timestep; joint sweeps
Across engines MuJoCo and PyBullet load the same mass and land the drop in the same place

There are 35 checks in all. docs/checks.md explains each one and how to fix what it reports, and docs/how-it-works.md explains the method.

Robots and objects are judged slightly differently. A hollow arm link, or an arm that can fold into its own base, is normal for a robot but a defect in a cabinet. Each report says which simcheck decided ([robot, fixed base; ...]); --robot and --object override it.

What engines do silently

The engine checks exist because each of these behaviours makes a valid-looking file behave differently than written. Each one was reproduced on MuJoCo 3.15–3.16 and PyBullet 3.2.7.

MuJoCo:

  • Meshes collide as their convex hull, even on static bodies. Cabinets, drawers, mugs and bins become solid blocks.
  • Parent-child contacts are skipped, except when the parent is welded to the world. So a drawer inside a convexified fixed cabinet is pushed out at the first step, while the same drawer in a free cabinet silently passes through it.
  • A URDF joint with lower > upper or lower == upper becomes unlimited. PyBullet also leaves lower > upper unlimited, but locks lower == upper.
  • Two mesh files with the same file name in different folders become one mesh.
  • A URDF root link without a joint is fused into the world, so a loaded object cannot move.
  • It resets the simulation when it diverges. simcheck disables that, so blow-ups are visible.
  • COLLADA and glTF meshes can't be read. Visual ones are dropped; collision ones make loading fail.

PyBullet:

  • The URDF inertia tensor is ignored unless URDF_USE_INERTIA_FROM_FILE is set. It is recomputed from the bounding box instead.
  • A link without <inertial> gets 1 kg.
  • A non-physical inertia tensor is replaced with zero, with only a console message.
  • Every joint starts with a velocity motor that holds it in place.
  • Self-collision is off by default.
  • A file with very long lines doesn't parse. This happens with generated one-line URDFs. glTF meshes don't parse either, even when only visual.

simcheck runs each engine in an "honest" configuration that turns these conveniences off. It then reports where the defaults would change the result.

Fixing

simcheck fix asset.urdf writes a repaired copy (asset.fixed.urdf, with new meshes in asset.fixed_meshes/) and re-checks it. It works on URDF and xacro files. It changes only what it repairs: comments, formatting and package:// paths are kept, so the diff shows just the fix. It applies only mechanical fixes:

  • decompose: concave collision meshes are split into convex parts with CoACD. It refines until no other part of the asset sits inside the new shapes, within --max-time seconds per mesh (120 by default).
  • collision: collision geometry is added from the visual for parts that have none. Robots are skipped unless you ask with --only collision, since they often leave parts without collision on purpose.
  • inertia: missing, zero, non-physical or placeholder inertia is recomputed from geometry. It keeps the mass when it is valid, and otherwise uses --density.
  • limits: swapped limits are swapped back.
  • axes: joint axes are normalized.

Problems that need a human decision are listed under "left", not guessed. Examples are a wrong axis direction, a hinge in the wrong place, an implausible mass and a zero effort limit. fix exits 1 when the repaired file still has an error, or has a problem the original didn't (it says which).

On a robot, fix decomposes only the links whose shape causes a warning or an error; a concave link alone is just a note there. A xacro file is repaired into plain URDF.

Configuration

simcheck reads the nearest simcheck.toml, or a [tool.simcheck] table in pyproject.toml; simcheck init writes a commented one. You can set the engines, which checks run, robot or object, package paths, xacro arguments, thresholds, a baseline and a cache. You can also choose which findings don't count:

  • by path, check, part or pair of parts;
  • with a <!-- simcheck-disable: CHECK --> comment in the asset itself.

Everything is in docs/configuration.md.

Leaderboard

leaderboard/ scores 1,868 assets from 15 public sources (simcheck 0.1.0):

Dataset Kind Assets Pass Warn Fail
MuJoCo Menagerie human-authored robots (MJCF) 27 70% 22% 7%
robosuite objects human-authored objects (MJCF) 11 82% 18% 0%
RoboCasa kitchen objects human-authored objects (MJCF) 100 47% 53% 0%
RoboCasa kitchen fixtures human-authored articulated (MJCF) 49 0% 100% 0%
LIBERO articulated objects human-authored articulated (MJCF) 10 0% 30% 70%
robot_descriptions URDFs human-authored robots (URDF) 34 (+1 timed out) 3% 15% 82%
Google Scanned Objects (MJCF) scanned objects 80 0% 100% 0%
YCB (URDF) scanned objects 78 23% 77% 0%
YCB (MJCF) scanned objects 88 32% 68% 0%
EmbodiedGen generated: image/text-to-3D 130 0% 99% 1%
projectsim generated: commercial procedural 40 22% 32% 45%
Articraft-10K, full generated: LLM agent 301 1% 2% 97%
Articraft-10K, visual generated: LLM agent 699 0% 0% 100%
URDFormer outputs generated: image-to-URDF model 11 0% 0% 100%
LAM: claude-opus-4-6 generated: LLM (LAM pipeline) 30 0% 0% 100%
LAM: claude-sonnet-4 generated: LLM (LAM pipeline) 30 0% 0% 100%
LAM: gemini-2.5-pro generated: LLM (LAM pipeline) 30 0% 0% 100%
LAM: gemini-2.5-flash generated: LLM (LAM pipeline) 30 0% 0% 100%
LAM: o3 generated: LLM (LAM pipeline) 30 0% 0% 100%
LAM: gpt-5 generated: LLM (LAM pipeline) 30 0% 0% 100%
LAM: original pipeline generated: LLM (LAM pipeline) 30 0% 0% 100%

What it found:

  • Generated assets rarely carry real physics. Of 1,000 Articraft-10K assets, 699 have no collision geometry. Of the other 301, 292 have moving parts with no <inertial>, so MuJoCo invents a mass at the density of water and PyBullet uses 1 kg and 1 kg m^2. In LAM it is 267 of 271, across all seven LLM backbones.
  • Convex hulls create collisions nobody authored. 127 of 301 Articraft assets start with parts overlapping in MuJoCo. In 50 of them the authored meshes don't overlap; MuJoCo's convex hulls do. The same effect jams the drawers of 10 of the 11 URDFormer cabinets.
  • Placeholder inertia is common. 124 of 130 EmbodiedGen objects and 60 of 78 YCB URDFs use the same inertia on every axis whatever their size.
  • Visual meshes add mass. All 80 sampled Google Scanned Objects and all 49 RoboCasa fixtures get mass from visual-only geoms (one freezer drawer: 895 of its 956 kg).
  • Curated robots have real issues too. Kinova Gen3's base and shoulder overlap by 12 mm, which MuJoCo turns into a brake on joint 1. Talos's default pose puts its grippers inside its thighs.

Every finding category was audited by opening the flagged files. leaderboard/README.md has the method and how to reproduce it, and leaderboard/stats.py recomputes every number above from the results.

Benchmark

simcheck bench assets/ plants 17 kinds of defect into every asset and reports which ones are caught. Examples:

  • millimetre units
  • reversed axis
  • swapped limits
  • broken inertia
  • grams as kilograms
  • missing collision
  • offset collision
  • centre of mass off the part
  • broken mesh path
  • joint inside its parent

On 52 real assets (YCB, EmbodiedGen, robot_descriptions and the test fixtures), it caught 631 of 655 planted defects (96%): all 536 on objects and 95 of 119 on robots. Most robot misses are deliberate:

  • Missing or offset collision geometry is only a note on robots.
  • A reversed axis on a joint with symmetric limits is invisible geometrically.

See leaderboard/BENCH.md.

Limitations

  • Formats:
    • URDF, xacro and MJCF only; SDF and USD are not read yet.
    • PyBullet reads URDF only, so MJCF files are checked in MuJoCo alone.
  • Engines: Newton, Isaac and Genesis are not backends yet.
  • Platforms: CI runs the test suite on Linux (Python 3.10-3.13), macOS and Windows. On macOS and Windows, engines run in a fresh child process per asset (about a second more per engine), and PyBullet has no prebuilt wheels, so checks run in MuJoCo.
  • Speed: most assets take 1-10 s. Large robots with many detailed meshes can take a minute or more. check -j and scan run assets in parallel, and --cache skips unchanged ones.
  • What the checks test: the asset against itself and against physics, not against the real object. A cabinet that is internally consistent but twice the real size only shows up through a category size prior.
  • Heuristics: some checks are heuristics with thresholds you can change (pass_through_share, cavity_fill_share, shell_wall_warn; see configuration).
  • Gaps: soft bodies, cloth, cables, fluids, tendons and actuator tuning are not checked.

Documentation

  • NVIDIA SimReady: a spec and benchmark for USD assets in Isaac and PhysX.
  • simcompat: compares MuJoCo and PyBullet imports of URDF robots against Pinocchio, without contact.
  • simready-audit: checks rigid USD/MJCF objects in MuJoCo.
  • Static linters: check_urdf, urdf_parser_py, yourdfpy and others.

simcheck's focus:

  • articulated objects and robots in URDF and MJCF;
  • contact behaviour across engines;
  • measuring the collision shape each engine really uses;
  • a public leaderboard.

License

Apache-2.0

Metadata

Release files for simcheck 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for simcheck 0.2.0
File Size Uploaded Provenance Actions
simcheck-0.2.0.tar.gz 211.9 kB With provenance Details for simcheck-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for simcheck 0.2.0
File Interpreter ABI Platform Actions
simcheck-0.2.0-py3-none-any.whl Python 3 none any Details for simcheck-0.2.0-py3-none-any.whl

Total release size: 377.3 kB

Release files / simcheck-0.2.0.tar.gz

Download URL simcheck-0.2.0.tar.gz
Size 211.9 kB
Tags Source
SHA-256 checksum
How to use checksums
d00e7b38bdceba277d0eba5db6bbff3b485881c0db24d3ed8d48e779e2b42bb6
BLAKE2b-256 checksum
How to use checksums
eb85ac32298aa8a8d326d234afae1f5e0288d1971bb48ef140e13001204b1baf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / simcheck-0.2.0-py3-none-any.whl

Download URL simcheck-0.2.0-py3-none-any.whl
Size 165.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2cd499bc2191f1d6e838d5a9c824e4bea726088610eb46910cf671f7a9c2fda5
BLAKE2b-256 checksum
How to use checksums
999e598e6dfc4f3351e7c004af367a024fc0ee5ad63800f4e23b0d8903e18e64
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Security for simcheck 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for simcheck-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page