From 1c3771e4e40db18bac3541ded1de5ff48c92a25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 28 May 2026 12:09:30 +0200 Subject: [PATCH 1/4] feat: generate python bindings --- .cargo/config.toml | 13 +- .github/workflows/python-bindings.yml | 79 + .github/workflows/python-wheels.yml | 82 + .gitignore | 20 +- Cargo.toml | 27 + README.md | 11 +- crates/mjcf-rs/src/lib.rs | 2 +- crates/rapier3d-mjcf/README.md | 5 +- crates/rapier3d-mjcf/docs/01-overview.md | 122 - .../docs/02-coverage-and-mapping.md | 402 -- .../rapier3d-mjcf/docs/03-mjcf-rs-design.md | 173 - crates/rapier3d-mjcf/docs/04-out-of-scope.md | 45 - crates/rapier3d-mjcf/docs/README.md | 50 - .../docs/phase-1-core-kinematics.md | 111 - .../docs/phase-2-defaults-and-meshes.md | 161 - .../phase-3-contact-filtering-and-equality.md | 145 - .../docs/phase-4-springs-dampers-armature.md | 75 - .../phase-5-actuators-sensors-keyframes.md | 173 - python/README.md | 108 + python/docs/Makefile | 11 + python/docs/api/controllers.rst | 38 + python/docs/api/debug_render.rst | 22 + python/docs/api/dynamics.rst | 56 + python/docs/api/events_hooks.rst | 31 + python/docs/api/geometry.rst | 82 + python/docs/api/joints.rst | 75 + python/docs/api/loaders.rst | 28 + python/docs/api/math.rst | 49 + python/docs/api/pipeline.rst | 48 + python/docs/api/serde.rst | 37 + python/docs/api/world.rst | 52 + python/docs/changelog.rst | 38 + python/docs/conf.py | 115 + python/docs/dim_scalar.rst | 80 + python/docs/getting_started.rst | 78 + python/docs/index.rst | 90 + python/docs/limitations.rst | 60 + python/docs/repackaging/00-target-layout.md | 84 + .../docs/repackaging/01-restructure-crates.md | 65 + .../docs/repackaging/02-pure-python-split.md | 56 + .../repackaging/03-tests-examples-testbed.md | 48 + python/docs/repackaging/04-ci-build-matrix.md | 69 + python/docs/repackaging/05-publishing-pypi.md | 72 + python/docs/repackaging/06-docs-cleanup.md | 39 + python/docs/repackaging/README.md | 66 + python/examples/.gitkeep | 0 python/examples/character/stairs.py | 73 + python/examples/hello_world.py | 33 + python/examples/joints/pendulum.py | 57 + python/examples/joints/six_dof_motor.py | 46 + python/examples/parity/balls3.py | 72 + python/examples/perf/many_bodies.py | 42 + .../examples/render/matplotlib_animation.py | 76 + python/examples/serde/snapshot_restore.py | 41 + python/examples/urdf/load_simple.py | 62 + python/examples/vehicle/drive.py | 67 + python/rapier-py-2d-f64/Cargo.toml | 29 + python/rapier-py-2d-f64/README.md | 32 + python/rapier-py-2d-f64/pyproject.toml | 39 + .../python/rapier2d_f64/__init__.py | 338 ++ .../python/rapier2d_f64/_debug_render.py | 56 + .../python/rapier2d_f64/_event_handler.py | 110 + .../python/rapier2d_f64/_math_helpers.py | 27 + .../python/rapier2d_f64/_pickle_setup.py | 46 + .../python/rapier2d_f64/_rapier2d_f64.pyi | 713 +++ .../python/rapier2d_f64/py.typed | 0 python/rapier-py-2d-f64/src/lib.rs | 37 + python/rapier-py-2d/Cargo.toml | 29 + python/rapier-py-2d/README.md | 32 + python/rapier-py-2d/pyproject.toml | 39 + .../rapier-py-2d/python/rapier2d/__init__.py | 343 ++ .../rapier-py-2d/python/rapier2d/__init__.pyi | 20 + .../python/rapier2d/_debug_render.py | 56 + .../python/rapier2d/_event_handler.py | 110 + .../python/rapier2d/_math_helpers.py | 27 + .../python/rapier2d/_pickle_setup.py | 46 + .../python/rapier2d/_rapier2d.pyi | 713 +++ python/rapier-py-2d/python/rapier2d/math.py | 15 + python/rapier-py-2d/python/rapier2d/py.typed | 0 python/rapier-py-2d/src/lib.rs | 38 + python/rapier-py-3d-f64/Cargo.toml | 29 + python/rapier-py-3d-f64/README.md | 32 + python/rapier-py-3d-f64/pyproject.toml | 39 + .../python/rapier3d_f64/__init__.py | 352 ++ .../python/rapier3d_f64/_debug_render.py | 56 + .../python/rapier3d_f64/_event_handler.py | 110 + .../python/rapier3d_f64/_math_helpers.py | 27 + .../python/rapier3d_f64/_pickle_setup.py | 46 + .../python/rapier3d_f64/_rapier3d_f64.pyi | 976 ++++ .../python/rapier3d_f64/py.typed | 0 python/rapier-py-3d-f64/src/lib.rs | 37 + python/rapier-py-3d/Cargo.toml | 44 + python/rapier-py-3d/README.md | 32 + python/rapier-py-3d/build.rs | 27 + python/rapier-py-3d/pyproject.toml | 39 + .../rapier-py-3d/python/rapier3d/__init__.py | 356 ++ .../rapier-py-3d/python/rapier3d/__init__.pyi | 22 + .../python/rapier3d/_debug_render.py | 56 + .../python/rapier3d/_event_handler.py | 110 + .../python/rapier3d/_math_helpers.py | 27 + .../python/rapier3d/_pickle_setup.py | 46 + .../python/rapier3d/_rapier3d.pyi | 2032 +++++++ .../python/rapier3d/loaders/__init__.py | 19 + .../python/rapier3d/loaders/mesh.py | 18 + .../python/rapier3d/loaders/mjcf.py | 41 + .../python/rapier3d/loaders/urdf.py | 35 + python/rapier-py-3d/python/rapier3d/math.py | 15 + python/rapier-py-3d/python/rapier3d/math.pyi | 12 + python/rapier-py-3d/python/rapier3d/py.typed | 0 python/rapier-py-3d/src/lib.rs | 67 + python/rapier-py-core/Cargo.toml | 37 + python/rapier-py-core/src/controllers.rs | 1828 ++++++ python/rapier-py-core/src/conv.rs | 302 + python/rapier-py-core/src/debug_render.rs | 1244 ++++ python/rapier-py-core/src/dynamics.rs | 3493 ++++++++++++ python/rapier-py-core/src/errors.rs | 94 + python/rapier-py-core/src/events_hooks.rs | 995 ++++ python/rapier-py-core/src/geometry.rs | 4999 +++++++++++++++++ python/rapier-py-core/src/joints.rs | 4003 +++++++++++++ python/rapier-py-core/src/lib.rs | 38 + python/rapier-py-core/src/loaders.rs | 251 + python/rapier-py-core/src/math.rs | 1896 +++++++ python/rapier-py-core/src/mjcf.rs | 530 ++ python/rapier-py-core/src/pipeline.rs | 2455 ++++++++ python/rapier-py-core/src/serde_io.rs | 134 + python/rapier-py-core/src/serde_macros.rs | 679 +++ python/rapier-py-core/src/urdf.rs | 673 +++ python/rapier-testbed/README.md | 17 + python/rapier-testbed/pyproject.toml | 38 + .../rapier-testbed/rapier_testbed/__init__.py | 205 + .../rapier-testbed/rapier_testbed/__main__.py | 8 + .../rapier-testbed/rapier_testbed/_meshes.py | 1097 ++++ .../rapier-testbed/rapier_testbed/_picker.py | 359 ++ .../rapier_testbed/_registry.py | 94 + .../rapier-testbed/rapier_testbed/_testbed.py | 1282 +++++ .../rapier_testbed/examples2/__init__.py | 1 + .../rapier_testbed/examples2/add_remove2.py | 84 + .../rapier_testbed/examples2/ccd2.py | 89 + .../examples2/character_controller2.py | 156 + .../examples2/collision_groups2.py | 71 + .../examples2/convex_polygons2.py | 73 + .../rapier_testbed/examples2/damping2.py | 50 + .../examples2/debug_box_ball2.py | 39 + .../examples2/debug_compression2.py | 69 + .../examples2/debug_intersection2.py | 43 + .../examples2/debug_total_overlap2.py | 31 + .../examples2/debug_vertical_column2.py | 46 + .../rapier_testbed/examples2/drum2.py | 72 + .../rapier_testbed/examples2/heightfield2.py | 63 + .../rapier_testbed/examples2/inv_pyramid2.py | 46 + .../examples2/inverse_kinematics2.py | 62 + .../examples2/joint_motor_position2.py | 69 + .../rapier_testbed/examples2/joints2.py | 58 + .../examples2/locked_rotations2.py | 51 + .../examples2/one_way_platforms2.py | 105 + .../examples2/pin_slot_joint2.py | 76 + .../rapier_testbed/examples2/platform2.py | 88 + .../rapier_testbed/examples2/polyline2.py | 63 + .../rapier_testbed/examples2/pyramid2.py | 47 + .../rapier_testbed/examples2/restitution2.py | 49 + .../rapier_testbed/examples2/rope_joints2.py | 68 + .../rapier_testbed/examples2/s2d_arch.py | 97 + .../examples2/s2d_ball_and_chain.py | 88 + .../rapier_testbed/examples2/s2d_bridge.py | 72 + .../examples2/s2d_card_house.py | 78 + .../rapier_testbed/examples2/s2d_confined.py | 59 + .../examples2/s2d_far_pyramid.py | 51 + .../examples2/s2d_high_mass_ratio_1.py | 62 + .../examples2/s2d_high_mass_ratio_2.py | 66 + .../examples2/s2d_high_mass_ratio_3.py | 61 + .../examples2/s2d_joint_grid.py | 66 + .../rapier_testbed/examples2/s2d_pyramid.py | 44 + .../rapier_testbed/examples2/sensor2.py | 64 + .../examples2/stress_tests/__init__.py | 1 + .../examples2/stress_tests/balls2.py | 41 + .../examples2/stress_tests/boxes2.py | 64 + .../examples2/stress_tests/capsules2.py | 68 + .../stress_tests/convex_polygons2.py | 76 + .../examples2/stress_tests/heightfield2.py | 63 + .../examples2/stress_tests/joint_ball2.py | 53 + .../examples2/stress_tests/joint_fixed2.py | 73 + .../stress_tests/joint_prismatic2.py | 63 + .../examples2/stress_tests/pyramid2.py | 47 + .../stress_tests/vertical_stacks2.py | 54 + .../rapier_testbed/examples2/trimesh2.py | 65 + .../examples2/utils/__init__.py | 1 + .../examples2/utils/character.py | 10 + .../rapier_testbed/examples2/utils/svg.py | 45 + .../rapier_testbed/examples2/voxels2.py | 56 + .../rapier_testbed/examples3/__init__.py | 2 + .../rapier_testbed/examples3/ccd3.py | 102 + .../examples3/character_controller3.py | 198 + .../examples3/collision_groups3.py | 90 + .../rapier_testbed/examples3/compound3.py | 98 + .../examples3/convex_decomposition3.py | 33 + .../examples3/convex_polyhedron3.py | 81 + .../rapier_testbed/examples3/damping3.py | 57 + .../examples3/debug_add_remove_collider3.py | 61 + .../examples3/debug_articulations3.py | 97 + .../rapier_testbed/examples3/debug_balls3.py | 69 + .../examples3/debug_big_colliders3.py | 53 + .../rapier_testbed/examples3/debug_boxes3.py | 51 + .../examples3/debug_chain_high_mass_ratio3.py | 82 + .../examples3/debug_cube_high_mass_ratio3.py | 84 + .../examples3/debug_cylinder3.py | 53 + .../examples3/debug_deserialize3.py | 33 + .../examples3/debug_disabled3.py | 66 + .../examples3/debug_dynamic_collider_add3.py | 101 + .../examples3/debug_eccentric_boxes3.py | 69 + .../examples3/debug_friction3.py | 59 + .../examples3/debug_infinite_fall3.py | 53 + .../examples3/debug_internal_edges3.py | 76 + .../examples3/debug_long_chain3.py | 68 + .../debug_multibody_ang_motor_pos3.py | 57 + .../rapier_testbed/examples3/debug_pop3.py | 45 + .../examples3/debug_prismatic3.py | 89 + .../examples3/debug_rollback3.py | 79 + .../examples3/debug_shape_modification3.py | 140 + .../examples3/debug_sleeping_kinematic3.py | 74 + .../examples3/debug_thin_cube_on_mesh3.py | 44 + .../examples3/debug_triangle3.py | 43 + .../examples3/debug_trimesh3.py | 72 + .../examples3/debug_two_cubes3.py | 35 + .../rapier_testbed/examples3/domino3.py | 78 + .../examples3/dynamic_trimesh3.py | 68 + .../rapier_testbed/examples3/fountain3.py | 81 + .../rapier_testbed/examples3/gyroscopic3.py | 44 + .../examples3/harness_capsules3.py | 61 + .../rapier_testbed/examples3/heightfield3.py | 92 + .../examples3/inverse_kinematics3.py | 64 + .../examples3/joint_motor_position3.py | 78 + .../rapier_testbed/examples3/joints3.py | 416 ++ .../rapier_testbed/examples3/keva3.py | 107 + .../examples3/locked_rotations3.py | 56 + .../examples3/newton_cradle3.py | 55 + .../examples3/one_way_platforms3.py | 73 + .../rapier_testbed/examples3/platform3.py | 93 + .../rapier_testbed/examples3/primitives3.py | 87 + .../rapier_testbed/examples3/restitution3.py | 48 + .../rapier_testbed/examples3/rope_joints3.py | 84 + .../rapier_testbed/examples3/sensor3.py | 74 + .../examples3/spring_joints3.py | 64 + .../examples3/stress_tests/__init__.py | 1 + .../examples3/stress_tests/balls3.py | 47 + .../examples3/stress_tests/boxes3.py | 54 + .../examples3/stress_tests/capsules3.py | 55 + .../examples3/stress_tests/ccd3.py | 71 + .../examples3/stress_tests/compound3.py | 63 + .../stress_tests/convex_polyhedron3.py | 69 + .../examples3/stress_tests/heightfield3.py | 69 + .../examples3/stress_tests/joint_ball3.py | 54 + .../examples3/stress_tests/joint_fixed3.py | 60 + .../stress_tests/joint_prismatic3.py | 65 + .../examples3/stress_tests/joint_revolute3.py | 69 + .../examples3/stress_tests/keva3.py | 53 + .../stress_tests/many_kinematics3.py | 74 + .../examples3/stress_tests/many_pyramids3.py | 68 + .../examples3/stress_tests/many_sleep3.py | 47 + .../examples3/stress_tests/many_static3.py | 47 + .../examples3/stress_tests/pyramid3.py | 68 + .../examples3/stress_tests/ray_cast3.py | 32 + .../examples3/stress_tests/stacks3.py | 115 + .../examples3/stress_tests/trimesh3.py | 69 + .../rapier_testbed/examples3/trimesh3.py | 131 + .../rapier_testbed/examples3/urdf3.py | 83 + .../examples3/utils/__init__.py | 5 + .../examples3/utils/character.py | 90 + .../examples3/vehicle_controller3.py | 133 + .../examples3/vehicle_joints3.py | 152 + .../rapier_testbed/examples3/voxels3.py | 31 + python/tests/test_controllers.py | 460 ++ python/tests/test_controllers_2d.py | 105 + python/tests/test_debug_render.py | 323 ++ python/tests/test_debug_render_2d.py | 110 + python/tests/test_deferred_bindings.py | 233 + python/tests/test_dynamics.py | 221 + python/tests/test_dynamics_2d.py | 129 + python/tests/test_events_hooks.py | 336 ++ python/tests/test_events_hooks_2d.py | 114 + python/tests/test_examples.py | 68 + python/tests/test_geometry.py | 385 ++ python/tests/test_geometry_2d.py | 116 + python/tests/test_joints.py | 261 + python/tests/test_joints_2d.py | 120 + python/tests/test_loaders.py | 361 ++ python/tests/test_math.py | 281 + python/tests/test_math_2d.py | 95 + python/tests/test_multibody.py | 135 + python/tests/test_new_bindings.py | 200 + python/tests/test_pipeline.py | 260 + python/tests/test_pipeline_2d.py | 112 + python/tests/test_queries.py | 297 + python/tests/test_serde.py | 271 + python/tests/test_serde_2d.py | 93 + python/tests/test_shapes.py | 136 + python/tests/test_smoke.py | 54 + 296 files changed, 51290 insertions(+), 1465 deletions(-) create mode 100644 .github/workflows/python-bindings.yml create mode 100644 .github/workflows/python-wheels.yml delete mode 100644 crates/rapier3d-mjcf/docs/01-overview.md delete mode 100644 crates/rapier3d-mjcf/docs/02-coverage-and-mapping.md delete mode 100644 crates/rapier3d-mjcf/docs/03-mjcf-rs-design.md delete mode 100644 crates/rapier3d-mjcf/docs/04-out-of-scope.md delete mode 100644 crates/rapier3d-mjcf/docs/README.md delete mode 100644 crates/rapier3d-mjcf/docs/phase-1-core-kinematics.md delete mode 100644 crates/rapier3d-mjcf/docs/phase-2-defaults-and-meshes.md delete mode 100644 crates/rapier3d-mjcf/docs/phase-3-contact-filtering-and-equality.md delete mode 100644 crates/rapier3d-mjcf/docs/phase-4-springs-dampers-armature.md delete mode 100644 crates/rapier3d-mjcf/docs/phase-5-actuators-sensors-keyframes.md create mode 100644 python/README.md create mode 100644 python/docs/Makefile create mode 100644 python/docs/api/controllers.rst create mode 100644 python/docs/api/debug_render.rst create mode 100644 python/docs/api/dynamics.rst create mode 100644 python/docs/api/events_hooks.rst create mode 100644 python/docs/api/geometry.rst create mode 100644 python/docs/api/joints.rst create mode 100644 python/docs/api/loaders.rst create mode 100644 python/docs/api/math.rst create mode 100644 python/docs/api/pipeline.rst create mode 100644 python/docs/api/serde.rst create mode 100644 python/docs/api/world.rst create mode 100644 python/docs/changelog.rst create mode 100644 python/docs/conf.py create mode 100644 python/docs/dim_scalar.rst create mode 100644 python/docs/getting_started.rst create mode 100644 python/docs/index.rst create mode 100644 python/docs/limitations.rst create mode 100644 python/docs/repackaging/00-target-layout.md create mode 100644 python/docs/repackaging/01-restructure-crates.md create mode 100644 python/docs/repackaging/02-pure-python-split.md create mode 100644 python/docs/repackaging/03-tests-examples-testbed.md create mode 100644 python/docs/repackaging/04-ci-build-matrix.md create mode 100644 python/docs/repackaging/05-publishing-pypi.md create mode 100644 python/docs/repackaging/06-docs-cleanup.md create mode 100644 python/docs/repackaging/README.md create mode 100644 python/examples/.gitkeep create mode 100644 python/examples/character/stairs.py create mode 100644 python/examples/hello_world.py create mode 100644 python/examples/joints/pendulum.py create mode 100644 python/examples/joints/six_dof_motor.py create mode 100644 python/examples/parity/balls3.py create mode 100644 python/examples/perf/many_bodies.py create mode 100644 python/examples/render/matplotlib_animation.py create mode 100644 python/examples/serde/snapshot_restore.py create mode 100644 python/examples/urdf/load_simple.py create mode 100644 python/examples/vehicle/drive.py create mode 100644 python/rapier-py-2d-f64/Cargo.toml create mode 100644 python/rapier-py-2d-f64/README.md create mode 100644 python/rapier-py-2d-f64/pyproject.toml create mode 100644 python/rapier-py-2d-f64/python/rapier2d_f64/__init__.py create mode 100644 python/rapier-py-2d-f64/python/rapier2d_f64/_debug_render.py create mode 100644 python/rapier-py-2d-f64/python/rapier2d_f64/_event_handler.py create mode 100644 python/rapier-py-2d-f64/python/rapier2d_f64/_math_helpers.py create mode 100644 python/rapier-py-2d-f64/python/rapier2d_f64/_pickle_setup.py create mode 100644 python/rapier-py-2d-f64/python/rapier2d_f64/_rapier2d_f64.pyi create mode 100644 python/rapier-py-2d-f64/python/rapier2d_f64/py.typed create mode 100644 python/rapier-py-2d-f64/src/lib.rs create mode 100644 python/rapier-py-2d/Cargo.toml create mode 100644 python/rapier-py-2d/README.md create mode 100644 python/rapier-py-2d/pyproject.toml create mode 100644 python/rapier-py-2d/python/rapier2d/__init__.py create mode 100644 python/rapier-py-2d/python/rapier2d/__init__.pyi create mode 100644 python/rapier-py-2d/python/rapier2d/_debug_render.py create mode 100644 python/rapier-py-2d/python/rapier2d/_event_handler.py create mode 100644 python/rapier-py-2d/python/rapier2d/_math_helpers.py create mode 100644 python/rapier-py-2d/python/rapier2d/_pickle_setup.py create mode 100644 python/rapier-py-2d/python/rapier2d/_rapier2d.pyi create mode 100644 python/rapier-py-2d/python/rapier2d/math.py create mode 100644 python/rapier-py-2d/python/rapier2d/py.typed create mode 100644 python/rapier-py-2d/src/lib.rs create mode 100644 python/rapier-py-3d-f64/Cargo.toml create mode 100644 python/rapier-py-3d-f64/README.md create mode 100644 python/rapier-py-3d-f64/pyproject.toml create mode 100644 python/rapier-py-3d-f64/python/rapier3d_f64/__init__.py create mode 100644 python/rapier-py-3d-f64/python/rapier3d_f64/_debug_render.py create mode 100644 python/rapier-py-3d-f64/python/rapier3d_f64/_event_handler.py create mode 100644 python/rapier-py-3d-f64/python/rapier3d_f64/_math_helpers.py create mode 100644 python/rapier-py-3d-f64/python/rapier3d_f64/_pickle_setup.py create mode 100644 python/rapier-py-3d-f64/python/rapier3d_f64/_rapier3d_f64.pyi create mode 100644 python/rapier-py-3d-f64/python/rapier3d_f64/py.typed create mode 100644 python/rapier-py-3d-f64/src/lib.rs create mode 100644 python/rapier-py-3d/Cargo.toml create mode 100644 python/rapier-py-3d/README.md create mode 100644 python/rapier-py-3d/build.rs create mode 100644 python/rapier-py-3d/pyproject.toml create mode 100644 python/rapier-py-3d/python/rapier3d/__init__.py create mode 100644 python/rapier-py-3d/python/rapier3d/__init__.pyi create mode 100644 python/rapier-py-3d/python/rapier3d/_debug_render.py create mode 100644 python/rapier-py-3d/python/rapier3d/_event_handler.py create mode 100644 python/rapier-py-3d/python/rapier3d/_math_helpers.py create mode 100644 python/rapier-py-3d/python/rapier3d/_pickle_setup.py create mode 100644 python/rapier-py-3d/python/rapier3d/_rapier3d.pyi create mode 100644 python/rapier-py-3d/python/rapier3d/loaders/__init__.py create mode 100644 python/rapier-py-3d/python/rapier3d/loaders/mesh.py create mode 100644 python/rapier-py-3d/python/rapier3d/loaders/mjcf.py create mode 100644 python/rapier-py-3d/python/rapier3d/loaders/urdf.py create mode 100644 python/rapier-py-3d/python/rapier3d/math.py create mode 100644 python/rapier-py-3d/python/rapier3d/math.pyi create mode 100644 python/rapier-py-3d/python/rapier3d/py.typed create mode 100644 python/rapier-py-3d/src/lib.rs create mode 100644 python/rapier-py-core/Cargo.toml create mode 100644 python/rapier-py-core/src/controllers.rs create mode 100644 python/rapier-py-core/src/conv.rs create mode 100644 python/rapier-py-core/src/debug_render.rs create mode 100644 python/rapier-py-core/src/dynamics.rs create mode 100644 python/rapier-py-core/src/errors.rs create mode 100644 python/rapier-py-core/src/events_hooks.rs create mode 100644 python/rapier-py-core/src/geometry.rs create mode 100644 python/rapier-py-core/src/joints.rs create mode 100644 python/rapier-py-core/src/lib.rs create mode 100644 python/rapier-py-core/src/loaders.rs create mode 100644 python/rapier-py-core/src/math.rs create mode 100644 python/rapier-py-core/src/mjcf.rs create mode 100644 python/rapier-py-core/src/pipeline.rs create mode 100644 python/rapier-py-core/src/serde_io.rs create mode 100644 python/rapier-py-core/src/serde_macros.rs create mode 100644 python/rapier-py-core/src/urdf.rs create mode 100644 python/rapier-testbed/README.md create mode 100644 python/rapier-testbed/pyproject.toml create mode 100644 python/rapier-testbed/rapier_testbed/__init__.py create mode 100644 python/rapier-testbed/rapier_testbed/__main__.py create mode 100644 python/rapier-testbed/rapier_testbed/_meshes.py create mode 100644 python/rapier-testbed/rapier_testbed/_picker.py create mode 100644 python/rapier-testbed/rapier_testbed/_registry.py create mode 100644 python/rapier-testbed/rapier_testbed/_testbed.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/__init__.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/add_remove2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/ccd2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/character_controller2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/collision_groups2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/convex_polygons2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/damping2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/debug_box_ball2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/debug_compression2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/debug_intersection2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/debug_total_overlap2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/debug_vertical_column2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/drum2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/heightfield2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/inv_pyramid2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/inverse_kinematics2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/joint_motor_position2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/joints2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/locked_rotations2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/one_way_platforms2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/pin_slot_joint2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/platform2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/polyline2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/pyramid2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/restitution2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/rope_joints2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_arch.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_ball_and_chain.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_bridge.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_card_house.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_confined.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_far_pyramid.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_high_mass_ratio_1.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_high_mass_ratio_2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_high_mass_ratio_3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_joint_grid.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/s2d_pyramid.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/sensor2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/__init__.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/balls2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/boxes2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/capsules2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/convex_polygons2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/heightfield2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/joint_ball2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/joint_fixed2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/joint_prismatic2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/pyramid2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/stress_tests/vertical_stacks2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/trimesh2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/utils/__init__.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/utils/character.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/utils/svg.py create mode 100644 python/rapier-testbed/rapier_testbed/examples2/voxels2.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/__init__.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/ccd3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/character_controller3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/collision_groups3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/compound3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/convex_decomposition3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/convex_polyhedron3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/damping3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_add_remove_collider3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_articulations3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_balls3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_big_colliders3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_boxes3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_chain_high_mass_ratio3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_cube_high_mass_ratio3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_cylinder3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_deserialize3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_disabled3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_dynamic_collider_add3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_eccentric_boxes3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_friction3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_infinite_fall3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_internal_edges3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_long_chain3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_multibody_ang_motor_pos3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_pop3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_prismatic3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_rollback3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_shape_modification3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_sleeping_kinematic3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_thin_cube_on_mesh3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_triangle3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_trimesh3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/debug_two_cubes3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/domino3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/dynamic_trimesh3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/fountain3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/gyroscopic3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/harness_capsules3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/heightfield3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/inverse_kinematics3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/joint_motor_position3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/joints3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/keva3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/locked_rotations3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/newton_cradle3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/one_way_platforms3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/platform3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/primitives3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/restitution3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/rope_joints3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/sensor3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/spring_joints3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/__init__.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/balls3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/boxes3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/capsules3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/ccd3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/compound3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/convex_polyhedron3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/heightfield3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/joint_ball3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/joint_fixed3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/joint_prismatic3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/joint_revolute3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/keva3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/many_kinematics3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/many_pyramids3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/many_sleep3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/many_static3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/pyramid3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/ray_cast3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/stacks3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/stress_tests/trimesh3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/trimesh3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/urdf3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/utils/__init__.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/utils/character.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/vehicle_controller3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/vehicle_joints3.py create mode 100644 python/rapier-testbed/rapier_testbed/examples3/voxels3.py create mode 100644 python/tests/test_controllers.py create mode 100644 python/tests/test_controllers_2d.py create mode 100644 python/tests/test_debug_render.py create mode 100644 python/tests/test_debug_render_2d.py create mode 100644 python/tests/test_deferred_bindings.py create mode 100644 python/tests/test_dynamics.py create mode 100644 python/tests/test_dynamics_2d.py create mode 100644 python/tests/test_events_hooks.py create mode 100644 python/tests/test_events_hooks_2d.py create mode 100644 python/tests/test_examples.py create mode 100644 python/tests/test_geometry.py create mode 100644 python/tests/test_geometry_2d.py create mode 100644 python/tests/test_joints.py create mode 100644 python/tests/test_joints_2d.py create mode 100644 python/tests/test_loaders.py create mode 100644 python/tests/test_math.py create mode 100644 python/tests/test_math_2d.py create mode 100644 python/tests/test_multibody.py create mode 100644 python/tests/test_new_bindings.py create mode 100644 python/tests/test_pipeline.py create mode 100644 python/tests/test_pipeline_2d.py create mode 100644 python/tests/test_queries.py create mode 100644 python/tests/test_serde.py create mode 100644 python/tests/test_serde_2d.py create mode 100644 python/tests/test_shapes.py create mode 100644 python/tests/test_smoke.py diff --git a/.cargo/config.toml b/.cargo/config.toml index 3b5738184..0b4dc4bb6 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,15 @@ [target.wasm32-unknown-unknown] runner = "wasm-server-runner" # Needed for getrandom/uuid: https://github.com/uuid-rs/uuid/issues/792 -rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] \ No newline at end of file +rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] + +# macOS: PyO3 cdylibs built with the `extension-module` feature don't link +# against libpython (Python provides those symbols at runtime). `cargo build` +# doesn't know that, so we tell the linker not to fail on the unresolved +# Python symbols. (Maturin handles this for us; this only affects bare +# `cargo build` invocations.) +[target.'cfg(target_os = "macos")'] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] \ No newline at end of file diff --git a/.github/workflows/python-bindings.yml b/.github/workflows/python-bindings.yml new file mode 100644 index 000000000..05d525bda --- /dev/null +++ b/.github/workflows/python-bindings.yml @@ -0,0 +1,79 @@ +name: Python bindings + +on: + push: + pull_request: + +env: + CARGO_TERM_COLOR: always + +# The four engine packages live under python/rapier-py-{2,3}d{,-f64}; the +# pure-Python testbed under python/rapier-testbed; the shared parity test +# suite under python/tests (imports all four engine packages at once). +jobs: + lint-rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-lint-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-lint- + ${{ runner.os }}-cargo- + - name: Cargo fmt + run: cargo fmt --check + - name: Cargo clippy (Python bindings) + run: | + cargo clippy --no-deps \ + -p rapier-py-core \ + -p rapier-py-2d -p rapier-py-2d-f64 \ + -p rapier-py-3d -p rapier-py-3d-f64 \ + -- -D warnings + + test: + needs: lint-rust + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-14, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-py-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-py- + ${{ runner.os }}-cargo- + - name: Install tooling + run: | + python -m pip install --upgrade pip + python -m pip install maturin pytest pytest-timeout hypothesis numpy matplotlib + # Build + install all four engine packages into the runner's environment + # so the cross-flavor parity suite can import every variant. + - name: Build & install engine packages + shell: bash + run: | + for crate in rapier-py-2d rapier-py-2d-f64 rapier-py-3d rapier-py-3d-f64; do + python -m pip install --no-build-isolation "./python/$crate" + done + - name: Install testbed (no deps; engine packages already present) + shell: bash + run: python -m pip install --no-deps ./python/rapier-testbed + - name: Smoke (import surface) + run: python -m pytest python/tests/test_smoke.py python/tests/test_math.py python/tests/test_math_2d.py -v + - name: Full parity test suite + run: python -m pytest python/tests/ -q --timeout=120 diff --git a/.github/workflows/python-wheels.yml b/.github/workflows/python-wheels.yml new file mode 100644 index 000000000..5f054782a --- /dev/null +++ b/.github/workflows/python-wheels.yml @@ -0,0 +1,82 @@ +name: Python wheels + +# Build abi3 wheels for the four engine packages across every supported +# platform and (on a `python-v*` tag) publish them to PyPI via trusted +# publishing. +# +# abi3-py39 means ONE wheel per (package, platform, arch) covers Python 3.9+, +# so there is deliberately no Python-version axis. +# +# NOTE: no sdist is published. The rapier2d/3d engine crates share their +# source via the repo-root `src/` tree (`[lib] path = "../../src/lib.rs"`), +# which lives outside any vendored crate directory, and maturin's sdist +# packer cannot reach parent paths (`..` is rejected in `include`). A source +# build would therefore be incomplete. The wheel matrix below covers all +# mainstream platforms; source builds use a full git checkout of the repo. +# Restoring an sdist is tracked as a follow-up (needs an upstream layout +# change or registry-based engine deps). +on: + push: + tags: ["python-v*"] + pull_request: + paths: + - "python/**" + - ".github/workflows/python-wheels.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: wheel ${{ matrix.package.name }} ${{ matrix.platform.target }} + runs-on: ${{ matrix.platform.runner }} + strategy: + fail-fast: false + matrix: + package: + - { dir: rapier-py-3d, name: rapier3d } + - { dir: rapier-py-3d-f64, name: rapier3d-f64 } + - { dir: rapier-py-2d, name: rapier2d } + - { dir: rapier-py-2d-f64, name: rapier2d-f64 } + platform: + - { runner: ubuntu-latest, target: x86_64-unknown-linux-gnu, manylinux: auto } + - { runner: ubuntu-latest, target: aarch64-unknown-linux-gnu, manylinux: auto } + - { runner: ubuntu-latest, target: x86_64-unknown-linux-musl, manylinux: musllinux_1_2 } + - { runner: ubuntu-latest, target: aarch64-unknown-linux-musl, manylinux: musllinux_1_2 } + - { runner: macos-13, target: x86_64-apple-darwin } + - { runner: macos-14, target: aarch64-apple-darwin } + - { runner: windows-latest, target: x86_64-pc-windows-msvc } + steps: + - uses: actions/checkout@v4 + - name: Build wheel + uses: PyO3/maturin-action@v1 + with: + command: build + target: ${{ matrix.platform.target }} + manylinux: ${{ matrix.platform.manylinux }} + args: --release --out dist -m python/${{ matrix.package.dir }}/Cargo.toml + sccache: "true" + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.package.name }}-${{ matrix.platform.target }} + path: dist + + publish: + name: Publish to PyPI + needs: [build] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/python-v') + environment: pypi + permissions: + id-token: write # OIDC for trusted publishing + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + pattern: "wheels-*" + merge-multiple: true + - name: Publish all four packages + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist diff --git a/.gitignore b/.gitignore index 64e4fb7e8..d3617f5f6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,22 @@ package-lock.json .history .vscode/ *.autosave.json -.claude \ No newline at end of file +.claude + +# Python bindings: compiled extension modules that `maturin develop` stages +# next to each package's Python sources (e.g. `_rapier3d.abi3.so`) and the +# Sphinx build output. +python/rapier-py-*/python/**/_rapier*.so +python/rapier-py-*/python/**/_rapier*.pyd +python/rapier-py-*/python/**/_rapier*.dylib +python/docs/_build/ +**/__pycache__/ +*.pyc +*.egg-info/ +dist/ +build/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.venv/ +venv/ diff --git a/Cargo.toml b/Cargo.toml index e0ee6515b..385166b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,33 @@ members = [ "crates/rapier3d-meshloader", "crates/mjcf-rs", "crates/rapier3d-mjcf", + "python/rapier-py-core", + "python/rapier-py-2d", + "python/rapier-py-2d-f64", + "python/rapier-py-3d", + "python/rapier-py-3d-f64", +] +# Bare `cargo build` / `cargo test` / `cargo clippy` only touches the +# pure-Rust crates. The Python-binding crates need a Python build env +# (pyo3 `extension-module`) and are exercised by the dedicated +# `.github/workflows/python-bindings.yml` workflow via explicit +# `-p rapier-py-*` invocations. +default-members = [ + "crates/rapier2d", + "crates/rapier2d-f64", + "crates/rapier_testbed2d", + "crates/rapier_testbed2d-f64", + "examples2d", + "crates/rapier3d", + "crates/rapier3d-f64", + "crates/rapier_testbed3d", + "crates/rapier_testbed3d-f64", + "examples3d", + "examples3d-f64", + "crates/rapier3d-urdf", + "crates/rapier3d-meshloader", + "crates/mjcf-rs", + "crates/rapier3d-mjcf", ] resolver = "2" diff --git a/README.md b/README.md index 983955f11..492625054 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,18 @@ The easiest way to get started with Rapier is to: Their source code are available on the `examples2d/` and `examples3d/` directory. 3. Don't hesitate to ask for help on [Discord](https://discord.gg/vt9DJSW), or by opening an issue on GitHub. +## Python bindings + +Python bindings are under development. They ship as four PyPI packages — +`rapier2d`, `rapier3d`, `rapier2d-f64`, `rapier3d-f64` — one per +(dimension, scalar) flavor. See [`python/README.md`](python/README.md) for how +to build the bindings, the docs, and the testbed from a checkout, and +[`python/docs/`](python/docs/) for the API documentation. + ## AI coding disclaimer and policy -AI coding is extensively used for the implementation and maintenance of the following crates: `mjcf-rs`, `rapier3d-mjcf`. +AI coding is extensively used for the implementation and maintenance of the following crates: `mjcf-rs`, +`rapier3d-mjcf`, as well as the Python bindings (`python/rapier-py*`), including their tests, examples, and docs. We actively use AI assistance (with human reviews) for the following tasks: - Documentation generation. diff --git a/crates/mjcf-rs/src/lib.rs b/crates/mjcf-rs/src/lib.rs index 3f58e780f..3d1cf9d94 100644 --- a/crates/mjcf-rs/src/lib.rs +++ b/crates/mjcf-rs/src/lib.rs @@ -32,7 +32,7 @@ //! //! - Simulate anything. Pair this with `rapier3d-mjcf` for that. //! - Cover MJCF features that are out of scope for `rapier3d-mjcf`. See the -//! `rapier3d-mjcf` planning docs for the complete list. +//! `rapier3d-mjcf` README for the complete list. #![warn(missing_docs)] diff --git a/crates/rapier3d-mjcf/README.md b/crates/rapier3d-mjcf/README.md index ff8e25358..3da17d864 100644 --- a/crates/rapier3d-mjcf/README.md +++ b/crates/rapier3d-mjcf/README.md @@ -42,8 +42,7 @@ robot.insert_using_impulse_joints(&mut bodies, &mut colliders, &mut impulse_join ## Feature matrix This crate is rolled out in phases. The table tracks what each phase -delivers and the current implementation status. See [`docs/`](docs/) for -the planning rationale. +delivers and the current implementation status. Legend: ✅ supported · ⚠️ partial / approximated · 📦 preserved as metadata · ❌ out of scope. @@ -172,8 +171,6 @@ convention. (Tracked for a future polish pass.) - `` (deprecated MJCF feature). - MJCF write-back (the parser is read-only). -See [`docs/04-out-of-scope.md`](docs/04-out-of-scope.md) for rationale. - ## Limitations The mappings noted with ⚠️ above are deliberate trade-offs. In particular: diff --git a/crates/rapier3d-mjcf/docs/01-overview.md b/crates/rapier3d-mjcf/docs/01-overview.md deleted file mode 100644 index 40195b9e6..000000000 --- a/crates/rapier3d-mjcf/docs/01-overview.md +++ /dev/null @@ -1,122 +0,0 @@ -# 01 — Overview - -## Goals - -1. Let users load a MuJoCo XML model and turn it into a set of rapier - `RigidBody`, `Collider`, and `GenericJoint`/multibody-joint instances. -2. Match the API shape of `rapier3d-urdf` (`Robot::from_file`, `from_str`, - `from_robot`, `insert_using_impulse_joints`, `insert_using_multibody_joints`) - so users moving between the two crates feel at home. -3. Preserve the original MJCF AST alongside the converted rapier objects, so - downstream code can inspect MJCF-only metadata (sites, actuators, sensors, - names, classes, …) that rapier doesn't simulate. -4. Pure Rust. No `libmujoco`, no C bindings. - -## Non-goals - -- We do **not** aim to **simulate** MJCF — only to load the kinematic / dynamic - description into rapier. Anything MJCF-specific that rapier cannot represent - is either (a) approximated, (b) preserved as metadata for the caller, or - (c) explicitly skipped. See [`04-out-of-scope.md`](04-out-of-scope.md). -- We do not aim to round-trip MJCF (load + edit + write). The parser may grow a - serializer later, but it isn't on the roadmap. - -## Two-crate split - -Mirrors the URDF stack: - -``` -crates/ -├── mjcf-rs/ ← XML → typed MJCF AST (no rapier deps) -└── rapier3d-mjcf/ ← MJCF AST → rapier rigid bodies / colliders / joints -``` - -Why split: - -- The parser has zero rapier dependency, so it can be reused (e.g. by other - Dimforge crates, by users who want to read MJCF without using rapier). -- It keeps the rapier-side conversion logic small and focused. -- It mirrors `urdf-rs` / `rapier3d-urdf`, so the codebase stays consistent. - -## Why MJCF differs structurally from URDF - -The two formats look superficially similar but model the world differently: - -| Concept | URDF | MJCF | -| ---------------------- | ---------------------------------------- | ----------------------------------------------------- | -| Topology | Flat list of `` + `` (joint names parent + child by name) | Nested `` tree (joint sits **inside** the child body and connects it to its parent) | -| Default angle units | radians | **degrees** (configurable via ``) | -| Default geom type | n/a (each shape has its own tag) | `sphere` (with `size` interpretation depending on `type`) | -| Joints per link | exactly 1 | **0..N** (multiple joints in one body = serial DoFs) | -| Default class system | none | `` with nested classes and `class` / `childclass` attributes | -| File composition | flat, single file | `` recursively inlines other files | -| Geom origin | shape-local | shape-local, but the body's **inertial frame may differ from the body frame** | -| Coordinate convention | right-handed, X-forward | right-handed, **Z-up** | -| Mesh formats | STL/OBJ/Collada via robot description | STL / OBJ / `.msh` (custom MuJoCo binary) | - -Concrete consequences for the loader: - -- **Multiple joints per body**: rapier's multibody / impulse-joint model - permits at most one joint per body pair. The loader has to insert massless - intermediate rigid-bodies for the joints "between" the parent and the - declared body. (Phase 1 covers the single-joint case; Phase 1.5 / Phase 2 - handles multi-joint bodies.) -- **``**: the body's parent is the world and there is no joint — - emit a free dynamic rigid-body. No constraint to insert. -- **0 joints, parent = world**: the body is welded to the world; force-fixed - rigid-body, just like the URDF "empty-fixed-link → squeeze" fix-up. -- **0 joints, non-world parent**: the body is welded to its parent → emit a - fixed joint between the two. -- **`compiler/angle="degree"`** is the default, so we must convert to - radians before handing values to rapier (URDF doesn't have this trap). -- **`compiler/eulerseq`** changes the convention used for `<… euler="…">` - attributes (default `"xyz"`). The default differs from `glamx`'s default - parser convention, so we handle each case explicitly. - -## API shape (target) - -```rust -pub struct MjcfLoaderOptions { - pub create_colliders_from_collision_shapes: bool, // geom group/contype-aware (see Phase 3) - pub create_colliders_from_visual_shapes: bool, // geoms with contype=conaffinity=0 - pub apply_imported_mass_props: bool, - pub enable_joint_collisions: bool, - pub make_roots_fixed: bool, - pub trimesh_flags: TriMeshFlags, - pub mesh_converter: Option, - pub shift: Pose, - pub scale: Real, - pub collider_blueprint: ColliderBuilder, - pub rigid_body_blueprint: RigidBodyBuilder, - pub squeeze_empty_fixed_bodies: bool, // analogue of squeeze_empty_fixed_links -} - -pub struct MjcfRobot { - pub bodies: Vec, // one entry per , plus implicit world at index 0 - pub joints: Vec, // one entry per joint reachable in the kinematic tree - pub equality: Vec, // mapped to extra impulse joints -} - -impl MjcfRobot { - pub fn from_file(path, options, asset_dir) -> anyhow::Result<(Self, mjcf_rs::Model)>; - pub fn from_str(str, options, asset_dir) -> anyhow::Result<(Self, mjcf_rs::Model)>; - pub fn from_model(model, options, asset_dir) -> Self; - pub fn insert_using_impulse_joints(self, ...) -> MjcfRobotHandles; - pub fn insert_using_multibody_joints(self, ..., MjcfMultibodyOptions) -> MjcfRobotHandles>; - pub fn append_transform(&mut self, transform: &Pose); -} -``` - -Same `MjcfMultibodyOptions::JOINTS_ARE_KINEMATIC | DISABLE_SELF_CONTACTS` -flags as the URDF loader. - -## Cargo features - -Same shape as `rapier3d-urdf`: - -- `stl` — load `.stl` meshes via `rapier3d-meshloader` -- `wavefront` — load `.obj` meshes via `rapier3d-meshloader` -- `msh` — parse MuJoCo's custom `.msh` mesh format (pure Rust, written - in `mjcf-rs` since the format is MJCF-specific). See Phase 2. - -(Collada is intentionally absent — MJCF doesn't use it.) diff --git a/crates/rapier3d-mjcf/docs/02-coverage-and-mapping.md b/crates/rapier3d-mjcf/docs/02-coverage-and-mapping.md deleted file mode 100644 index 242b7bd95..000000000 --- a/crates/rapier3d-mjcf/docs/02-coverage-and-mapping.md +++ /dev/null @@ -1,402 +0,0 @@ -# 02 — Full MJCF Coverage and Rapier Mapping - -This document enumerates every MJCF top-level element, its children and -attributes, and how each maps (or doesn't) to a rapier construct. It is the -authoritative reference for what the parser and loader must support. - -Legend for the **Status** column: - -| Symbol | Meaning | -| ------ | ---------------------------------------------------------------- | -| ✅ | Fully mapped to a rapier feature. | -| ⚠️ | Partially mapped or approximated (lossy). | -| 📦 | Preserved in the AST as metadata; no runtime effect in rapier. | -| ❌ | Out of scope. See [`04-out-of-scope.md`](04-out-of-scope.md). | - -Each row also notes the **Phase** in which it lands. - ---- - -## Top-level structure - -```xml - - ← simulation-input parsing knobs - -``` - -| Element | Status | Phase | Notes | -| ------------- | ------ | ----- | ----- | -| `` | ✅ | 1 | Root. `model` attribute → `MjcfRobot::name`. | -| `` | ⚠️ | 1/2 | See dedicated table below. | -| `