diff --git a/.github/workflows/rapier-ci-build.yml b/.github/workflows/rapier-ci-build.yml index 8f7bc5cfc..6b793fc52 100644 --- a/.github/workflows/rapier-ci-build.yml +++ b/.github/workflows/rapier-ci-build.yml @@ -146,8 +146,11 @@ jobs: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v4 + - run: sudo apt-get install -y cmake libxcb-composite0-dev + # Builds and verifies each crate the same way `cargo publish` does, + # catching bugs hidden by workspace feature unification. - name: publish dry-run - run: ./publish-all.sh + run: ./publish.sh --dry-run # Builds the docusaurus website (user guides + docs site). website-build: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 85eb6f4be..7fc1f097b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,15 @@ -## Unreleased +## v0.33.0 (05 June 2026) ### Added +- New `mjcf-rs` and `rapier3d-mjcf` crates: a pure-Rust (no `libmujoco`/FFI) loader for the MuJoCo + MJCF XML format. `mjcf-rs` parses MJCF into a typed AST — resolving ``, `` + class inheritance, angle units, and the several rotation specs (`quat`, `axisangle`, `euler`, + `xyaxes`, `zaxis`) — and `rapier3d-mjcf` converts a model into rigid-bodies, colliders, and + joints via `MjcfRobot`. Meshes load behind the `stl`/`wavefront`/`msh` features, and visual + meshes support per-vertex normals with optional smoothing ([#936](https://github.com/dimforge/rapier/pull/936), [#943](https://github.com/dimforge/rapier/pull/943)). +- Python bindings for Rapier 2D/3D (`rapier-py-*` crates under `python/`), generated and published + as installable wheels ([#938](https://github.com/dimforge/rapier/pull/938)). - New `PhysicsWorld` convenience wrapper bundling `RigidBodySet`, `ColliderSet`, the broad/narrow phases, islands, joints, the CCD solver, gravity, and integration parameters in a single struct, for simpler setup of basic simulations. It also exposes helpers for waking bodies (`wake_up`, @@ -42,6 +50,8 @@ ### Modified +- Unify version numbers for `rapier3d-urdf`, `rapier3d-meshloader`, and `rapier3d-mjcf` so they + follow the same versioning as the other libraries in the workspace. - **Breaking (CCD solver direct callers only):** `CCDSolver::find_first_impact` and `CCDSolver::predict_impacts_at_next_positions` now take an extra `hooks: &dyn PhysicsHooks` argument. `PhysicsPipeline::step` callers are unaffected. diff --git a/Cargo.toml b/Cargo.toml index 385166b55..9fc3adfc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ default-members = [ resolver = "2" [workspace.package] -version = "0.32.0" +version = "0.33.0" authors = ["Sébastien Crozet "] homepage = "https://rapier.rs" repository = "https://github.com/dimforge/rapier" diff --git a/README.md b/README.md index 492625054..527273df1 100644 --- a/README.md +++ b/README.md @@ -63,15 +63,15 @@ AI coding is extensively used for the implementation and maintenance of the foll `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. - - Changelogs generation. - - Tests generation. - - CI configuration and scripts. +- Documentation generation. +- Changelogs generation. +- Tests generation. +- CI configuration and scripts. We accept contributions involving AI coding as long as: - - They are verified to work properly by a human. - - The code quality is up to human-written code standards. - - Include non-regression tests whenever applicable (which itself can be AI-generated). +- They are verified to work properly by a human. +- The code quality is up to human-written code standards. +- Include non-regression tests whenever applicable (which itself can be AI-generated). ## Resources and discussions diff --git a/crates/rapier3d-meshloader/Cargo.toml b/crates/rapier3d-meshloader/Cargo.toml index f59c061c9..7b964de1a 100644 --- a/crates/rapier3d-meshloader/Cargo.toml +++ b/crates/rapier3d-meshloader/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "rapier3d-meshloader" -version = "0.13.0" +version.workspace = true authors = ["Sébastien Crozet "] description = "STL file loader for the 3D rapier physics engine." documentation = "https://docs.rs/rapier3d-meshloader" -homepage = "https://rapier.rs" -repository = "https://github.com/dimforge/rapier" +homepage.workspace = true +repository.workspace = true readme = "README.md" categories = [ "science", @@ -15,8 +15,8 @@ categories = [ "wasm", ] keywords = ["physics", "joints", "multibody", "robotics", "urdf"] -license = "Apache-2.0" -edition = "2024" +license.workspace = true +edition.workspace = true [features] default = ["stl", "collada", "wavefront"] @@ -29,4 +29,4 @@ thiserror = "2" profiling = "1.0" mesh-loader = "0.1.13" -rapier3d = { version = "0.32.0", path = "../rapier3d" } +rapier3d = { version = "0.33.0", path = "../rapier3d" } diff --git a/crates/rapier3d-mjcf/Cargo.toml b/crates/rapier3d-mjcf/Cargo.toml index cd0a4141b..335cd14e1 100644 --- a/crates/rapier3d-mjcf/Cargo.toml +++ b/crates/rapier3d-mjcf/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "rapier3d-mjcf" -version = "0.1.0" +version.workspace = true authors = ["Sébastien Crozet "] description = "MuJoCo MJCF loader for the 3D rapier physics engine." documentation = "https://docs.rs/rapier3d-mjcf" -homepage = "https://rapier.rs" -repository = "https://github.com/dimforge/rapier" +homepage.workspace = true +repository.workspace = true readme = "README.md" categories = [ "science", @@ -15,8 +15,8 @@ categories = [ "wasm", ] keywords = ["physics", "joints", "multibody", "robotics", "mjcf"] -license = "Apache-2.0" -edition = "2024" +license.workspace = true +edition.workspace = true [features] default = [] @@ -34,8 +34,8 @@ anyhow = "1" bitflags = "2" mjcf-rs = { version = "0.1.0", path = "../mjcf-rs" } -rapier3d = { version = "0.32.0", path = "../rapier3d" } -rapier3d-meshloader = { version = "0.13.0", path = "../rapier3d-meshloader", default-features = false, optional = true } +rapier3d = { version = "0.33.0", path = "../rapier3d" } +rapier3d-meshloader = { version = "0.33.0", path = "../rapier3d-meshloader", default-features = false, optional = true } [dev-dependencies] tempfile = "3" diff --git a/crates/rapier3d-urdf/Cargo.toml b/crates/rapier3d-urdf/Cargo.toml index 48fd5e7c8..1487cddbd 100644 --- a/crates/rapier3d-urdf/Cargo.toml +++ b/crates/rapier3d-urdf/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "rapier3d-urdf" -version = "0.13.0" +version.workspace = true authors = ["Sébastien Crozet "] description = "URDF file loader for the 3D rapier physics engine." documentation = "https://docs.rs/rapier3d-urdf" -homepage = "https://rapier.rs" -repository = "https://github.com/dimforge/rapier" +homepage.workspace = true +repository.workspace = true readme = "README.md" categories = [ "science", @@ -15,8 +15,8 @@ categories = [ "wasm", ] keywords = ["physics", "joints", "multibody", "robotics", "urdf"] -license = "Apache-2.0" -edition = "2024" +license.workspace = true +edition.workspace = true [features] stl = ["dep:rapier3d-meshloader", "rapier3d-meshloader/stl", "__meshloader_is_enabled"] @@ -31,5 +31,5 @@ anyhow = "1" bitflags = "2" urdf-rs = "0.9" -rapier3d = { version = "0.32.0", path = "../rapier3d" } -rapier3d-meshloader = { version = "0.13.0", path = "../rapier3d-meshloader", default-features = false, optional = true } +rapier3d = { version = "0.33.0", path = "../rapier3d" } +rapier3d-meshloader = { version = "0.33.0", path = "../rapier3d-meshloader", default-features = false, optional = true } diff --git a/crates/rapier_testbed2d-f64/Cargo.toml b/crates/rapier_testbed2d-f64/Cargo.toml index fd9966334..c2fff2751 100644 --- a/crates/rapier_testbed2d-f64/Cargo.toml +++ b/crates/rapier_testbed2d-f64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier_testbed2d-f64" -version = "0.32.0" +version = "0.33.0" authors = ["Sébastien Crozet "] description = "Testbed for the Rapier 2-dimensional physics engine in Rust." homepage = "http://rapier.rs" @@ -67,5 +67,5 @@ env_logger = "0.11" [dependencies.rapier] package = "rapier2d-f64" path = "../rapier2d-f64" -version = "0.32.0" +version = "0.33.0" features = ["serde-serialize", "debug-render", "profiler"] diff --git a/crates/rapier_testbed2d/Cargo.toml b/crates/rapier_testbed2d/Cargo.toml index d93c7ab89..b7da9e620 100644 --- a/crates/rapier_testbed2d/Cargo.toml +++ b/crates/rapier_testbed2d/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier_testbed2d" -version = "0.32.0" +version = "0.33.0" authors = ["Sébastien Crozet "] description = "Testbed for the Rapier 2-dimensional physics engine in Rust." homepage = "http://rapier.rs" @@ -67,5 +67,5 @@ env_logger = "0.11" [dependencies.rapier] package = "rapier2d" path = "../rapier2d" -version = "0.32.0" +version = "0.33.0" features = ["serde-serialize", "debug-render", "profiler"] diff --git a/crates/rapier_testbed3d-f64/Cargo.toml b/crates/rapier_testbed3d-f64/Cargo.toml index 21981b3a5..7cdf64f32 100644 --- a/crates/rapier_testbed3d-f64/Cargo.toml +++ b/crates/rapier_testbed3d-f64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier_testbed3d-f64" -version = "0.32.0" +version = "0.33.0" authors = ["Sébastien Crozet "] description = "Testbed for the Rapier 3-dimensional physics engine in Rust." homepage = "http://rapier.rs" @@ -68,5 +68,5 @@ env_logger = "0.11" [dependencies.rapier] package = "rapier3d-f64" path = "../rapier3d-f64" -version = "0.32.0" +version = "0.33.0" features = ["serde-serialize", "debug-render", "profiler"] diff --git a/crates/rapier_testbed3d/Cargo.toml b/crates/rapier_testbed3d/Cargo.toml index f708f8571..b0d298aa0 100644 --- a/crates/rapier_testbed3d/Cargo.toml +++ b/crates/rapier_testbed3d/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier_testbed3d" -version = "0.32.0" +version = "0.33.0" authors = ["Sébastien Crozet "] description = "Testbed for the Rapier 3-dimensional physics engine in Rust." homepage = "http://rapier.rs" @@ -69,5 +69,5 @@ env_logger = "0.11" [dependencies.rapier] package = "rapier3d" path = "../rapier3d" -version = "0.32.0" +version = "0.33.0" features = ["serde-serialize", "debug-render", "profiler"] diff --git a/examples2d/Cargo.toml b/examples2d/Cargo.toml index ed66c155d..882c20418 100644 --- a/examples2d/Cargo.toml +++ b/examples2d/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Sébastien Crozet "] edition = "2024" default-run = "all_examples2" +publish = false [features] parallel = ["rapier2d/parallel", "rapier_testbed2d/parallel"] diff --git a/examples3d-f64/Cargo.toml b/examples3d-f64/Cargo.toml index fb0976038..5f525337d 100644 --- a/examples3d-f64/Cargo.toml +++ b/examples3d-f64/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Sébastien Crozet "] edition = "2024" default-run = "all_examples3-f64" +publish = false [features] parallel = ["rapier3d-f64/parallel", "rapier_testbed3d-f64/parallel"] diff --git a/examples3d/Cargo.toml b/examples3d/Cargo.toml index 53760c4aa..b0fe5109f 100644 --- a/examples3d/Cargo.toml +++ b/examples3d/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Sébastien Crozet "] edition = "2024" default-run = "all_examples3" +publish = false [features] parallel = ["rapier3d/parallel", "rapier_testbed3d/parallel"] diff --git a/publish-all.sh b/publish-all.sh deleted file mode 100755 index b7a69853a..000000000 --- a/publish-all.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/bash - -if [[ "$PUBLISH_MODE" == 1 ]] -then - DRY_RUN="" ./scripts/publish-rapier.sh && - DRY_RUN="" ./scripts/publish-extra-formats.sh && - DRY_RUN="" ./scripts/publish-testbeds.sh -else - echo "Running in dry mode, re-run with \`PUBLISH_MODE=1 publish-all.sh\` to actually publish." - - DRY_RUN="--dry-run" ./scripts/publish-rapier.sh && - DRY_RUN="--dry-run" ./scripts/publish-extra-formats.sh && - DRY_RUN="--dry-run" ./scripts/publish-testbeds.sh -fi \ No newline at end of file diff --git a/publish.sh b/publish.sh new file mode 100755 index 000000000..57ea3752e --- /dev/null +++ b/publish.sh @@ -0,0 +1,97 @@ +#! /bin/bash +# +# Publishes every rapier crate to crates.io with a single +# `cargo publish --workspace` invocation: +# +# - rapier2d / rapier3d / rapier2d-f64 / rapier3d-f64 +# - rapier_testbed2d / rapier_testbed3d / rapier_testbed2d-f64 / rapier_testbed3d-f64 +# - rapier3d-meshloader / rapier3d-urdf / mjcf-rs / rapier3d-mjcf +# +# Cargo computes the dependency order itself and waits for each crate to become +# available on the registry before publishing the ones that depend on it. The +# example crates and the python-binding crates are marked `publish = false`, so +# `--workspace` skips them. +# +# Why this script exists +# ---------------------- +# The four main crates share a single source tree at `/src`, referenced +# from each manifest as `path = "../../src/lib.rs"`. The four testbed crates +# likewise share `/src_testbed` via `path = "../../src_testbed/lib.rs"`. +# Those paths point outside the crate directory, which `cargo publish` refuses +# to package. +# +# To work around it *only during publishing*, this script temporarily, for each +# affected crate: +# 1. rewrites the `[lib] path` to a crate-local one (e.g. `src/lib.rs`), and +# 2. creates a symlink inside the crate pointing at the shared source tree. +# +# Cargo follows the symlink and bundles the real source into each `.crate`. A +# trap restores the manifests and removes the symlinks on exit (including on +# error or Ctrl-C), leaving the tree exactly as it was. +# +# Extra arguments are forwarded to `cargo publish`, e.g.: +# ./publish.sh --dry-run +# ./publish.sh --token "$CARGO_TOKEN" +# +# Requires cargo >= 1.90 (for `cargo publish --workspace`). + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT" + +# Crates sharing `src/lib.rs`. +SRC_CRATES=(rapier2d rapier3d rapier2d-f64 rapier3d-f64) +# Crates sharing `src_testbed/lib.rs`. +TESTBED_CRATES=(rapier_testbed2d rapier_testbed3d rapier_testbed2d-f64 rapier_testbed3d-f64) + +# Refuse to run on a dirty tree: the only diff during publishing must be our own +# temporary edits, so the restore at the end is guaranteed to be correct. +if [ -n "$(git status --porcelain)" ]; then + echo "error: working tree is not clean. Commit or stash changes before publishing." >&2 + exit 1 +fi + +backup_dir="$(mktemp -d)" + +cleanup() { + for crate in "${SRC_CRATES[@]}" "${TESTBED_CRATES[@]}"; do + # Remove the symlink we created (only if it is in fact a symlink). + for link in "crates/$crate/src" "crates/$crate/src_testbed"; do + [ -L "$link" ] && rm -f "$link" + done + # Restore the original manifest. + if [ -f "$backup_dir/$crate.Cargo.toml" ]; then + cp "$backup_dir/$crate.Cargo.toml" "crates/$crate/Cargo.toml" + fi + done + rm -rf "$backup_dir" +} +trap cleanup EXIT INT TERM + +# Apply the temporary symlink layout: `shared_dir` lives at the repo root and is +# linked into each crate, while the manifest's `[lib] path` is made crate-local. +link_shared() { + local crate="$1" shared_dir="$2" + local manifest="crates/$crate/Cargo.toml" + + cp "$manifest" "$backup_dir/$crate.Cargo.toml" + + local tmp + tmp="$(mktemp)" + sed "s#path = \"\.\./\.\./$shared_dir/lib.rs\"#path = \"$shared_dir/lib.rs\"#" "$manifest" > "$tmp" + mv "$tmp" "$manifest" + + ln -s "../../$shared_dir" "crates/$crate/$shared_dir" +} + +for crate in "${SRC_CRATES[@]}"; do + link_shared "$crate" src +done +for crate in "${TESTBED_CRATES[@]}"; do + link_shared "$crate" src_testbed +done + +# Publish the whole workspace. `--allow-dirty` is required because our temporary +# edits make the tree dirty; the clean-tree check above keeps that safe. +cargo publish --workspace --allow-dirty "$@" diff --git a/python/rapier-testbed/pyproject.toml b/python/rapier-testbed/pyproject.toml index e7e79e57e..442153870 100644 --- a/python/rapier-testbed/pyproject.toml +++ b/python/rapier-testbed/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "rapier-testbed" -version = "0.32.0" +version = "0.33.0" description = "Panda3D-based visual testbed and example gallery for the Rapier Python bindings." readme = "README.md" license = { text = "Apache-2.0" } diff --git a/scripts/publish-extra-formats.sh b/scripts/publish-extra-formats.sh deleted file mode 100755 index af48e7773..000000000 --- a/scripts/publish-extra-formats.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -currdir=$(pwd) - -### Publish rapier3d-meshloader. -cd "crates/rapier3d-meshloader" && cargo publish $DRY_RUN || exit 1 -cd "$currdir" || exit 2 - -### Publish rapier3d-urdf. -cd "crates/rapier3d-urdf" && cargo publish $DRY_RUN || exit 1 -cd "$currdir" || exit 2 \ No newline at end of file diff --git a/scripts/publish-rapier.sh b/scripts/publish-rapier.sh deleted file mode 100755 index e70fbc250..000000000 --- a/scripts/publish-rapier.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# Publish each rapier crate. Because all four crates share a single `src/` -# directory (via `path = "../../src/lib.rs"`) which lives outside each crate -# root, `cargo publish` cannot package it directly. We work around this by -# temporarily copying the source into each crate directory, patching the lib -# path, publishing from the workspace (so workspace deps are resolved), and -# cleaning up afterwards. -# -# Always runs in dry-run mode unless DRY_RUN is explicitly set to empty -# (done by publish-all.sh when PUBLISH_MODE=1). - -set -euo pipefail - -DRY_RUN="${DRY_RUN---dry-run}" - -publish_crate() { - local crate_dir="$1" - - echo "Publishing $crate_dir..." - - # Copy source into crate directory - cp -r src "$crate_dir"/src - - # Patch lib path from ../../src to src - cp "$crate_dir"/Cargo.toml "$crate_dir"/Cargo.toml.bak - sed 's#\.\./\.\./src#src#g' "$crate_dir"/Cargo.toml.bak > "$crate_dir"/Cargo.toml - - # Publish from the workspace root (workspace deps get resolved) - local crate_name - crate_name=$(basename "$crate_dir") - cargo publish -p "$crate_name" --allow-dirty $DRY_RUN - - # Restore original Cargo.toml and remove copied source - mv "$crate_dir"/Cargo.toml.bak "$crate_dir"/Cargo.toml - rm -rf "$crate_dir"/src -} - -publish_crate crates/rapier2d -publish_crate crates/rapier3d -publish_crate crates/rapier2d-f64 -publish_crate crates/rapier3d-f64 diff --git a/scripts/publish-testbeds.sh b/scripts/publish-testbeds.sh deleted file mode 100755 index fe6790121..000000000 --- a/scripts/publish-testbeds.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -gsed -v >> /dev/null -if [ $? == 0 ]; then - gsed=gsed -else - # Hopefully installed sed is the gnu one. - gsed=sed -fi - -tmp=$(mktemp -d) - -echo "$tmp" - -cp -r src "$tmp"/. -cp -r src_testbed "$tmp"/. -cp -r crates "$tmp"/. -cp -r LICENSE README.md "$tmp"/. - -### Publish the 2D version. -$gsed 's#\.\./\.\./src#src#g' crates/rapier_testbed2d/Cargo.toml > "$tmp"/Cargo.toml -$gsed -i 's#\.\./rapier#./crates/rapier#g' "$tmp"/Cargo.toml -currdir=$(pwd) -cd "$tmp" && cargo publish $DRY_RUN -cd "$currdir" || exit - -### Publish the 3D version. -$gsed 's#\.\./\.\./src#src#g' crates/rapier_testbed3d/Cargo.toml > "$tmp"/Cargo.toml -$gsed -i 's#\.\./rapier#./crates/rapier#g' "$tmp"/Cargo.toml -cp -r LICENSE README.md "$tmp"/. -cd "$tmp" && cargo publish $DRY_RUN - -### Publish the 2D f64 version. -$gsed 's#\.\./\.\./src#src#g' crates/rapier_testbed2d-f64/Cargo.toml > "$tmp"/Cargo.toml -$gsed -i 's#\.\./rapier#./crates/rapier#g' "$tmp"/Cargo.toml -currdir=$(pwd) -cd "$tmp" && cargo publish $DRY_RUN -cd "$currdir" || exit - -### Publish the 3D f64 version. -$gsed 's#\.\./\.\./src#src#g' crates/rapier_testbed3d-f64/Cargo.toml > "$tmp"/Cargo.toml -$gsed -i 's#\.\./rapier#./crates/rapier#g' "$tmp"/Cargo.toml -cp -r LICENSE README.md "$tmp"/. -cd "$tmp" && cargo publish $DRY_RUN - -rm -rf "$tmp"