Skip to content

Commit 8a23ce5

Browse files
authored
chore: add example (#7)
* chore: add example * update around CI and README * fix CI * update CI * update CI * update CI * update CI * fix CI * update CI * update CI * fix warnings * update CI
1 parent d0abb85 commit 8a23ce5

File tree

7 files changed

+204
-17
lines changed

7 files changed

+204
-17
lines changed

.github/workflows/CI.yml

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
branches: [main, v*]
77

88
jobs:
9-
CI:
9+
build:
1010
runs-on: ubuntu-latest
1111

1212
strategy:
@@ -48,3 +48,40 @@ jobs:
4848
echo "[DEBUG] bindgen.rs content:" && cat ./src/bindgen.rs
4949
exit 1
5050
fi
51+
52+
test:
53+
runs-on: ubuntu-latest
54+
55+
strategy:
56+
matrix:
57+
toolchain: [stable, nightly]
58+
59+
steps:
60+
- uses: actions/checkout@v4
61+
62+
- name: setup
63+
run: |
64+
rustup update
65+
rustup default ${{ matrix.toolchain }}
66+
rustup component add rustfmt ### required for the build script to work ###
67+
mkdir -p $HOME/.mujoco
68+
cd $HOME/.mujoco
69+
wget https://github.com/google-deepmind/mujoco/releases/download/3.3.2/mujoco-3.3.2-linux-x86_64.tar.gz
70+
tar -xzf mujoco-3.3.2-linux-x86_64.tar.gz
71+
echo "MUJOCO_DIR=$HOME/.mujoco/mujoco-3.3.2" >> $GITHUB_ENV
72+
echo "LD_LIBRARY_PATH=$HOME/.mujoco/mujoco-3.3.2/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV
73+
74+
- name: setup additional dependencies for examples
75+
run: |
76+
sudo apt update && sudo apt install -y cmake build-essential libwayland-dev libxkbcommon-x11-dev libgl1-mesa-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev
77+
git clone https://github.com/glfw/glfw.git
78+
mkdir -p glfw/build && cd glfw/build
79+
cmake .. && make && sudo make install
80+
81+
- name: run tests
82+
run: |
83+
cargo test --lib
84+
cargo test --doc
85+
cargo test --tests
86+
cargo test --benches
87+
cargo test --examples

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rusty_mujoco"
3-
version = "0.0.1"
3+
version = "0.1.0"
44
edition = "2024"
55
authors = ["kanarus <[email protected]>"]
66
documentation = "https://docs.rs/rusty_mujoco"
@@ -14,3 +14,6 @@ categories = ["api-bindings", "science::robotics", "simulation"]
1414

1515
[build-dependencies]
1616
bindgen = "0.72"
17+
18+
[dev-dependencies]
19+
glfw = "0.59.0"

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,17 @@
2525
and expanded **as it is** (don't rename or partially move the files)
2626
- `MUJOCO_DIR` environment variable set to the path of the MuJoCo directory (e.g. `$HOME/.mujoco/mujoco-3.3.2`)
2727

28-
## Note & Tips
28+
### Note & Tips
2929

30+
- Example commands to download & expand MuJoCo 3.3.2:
31+
```sh
32+
wget https://github.com/google-deepmind/mujoco/releases/download/3.3.2/mujoco-3.3.2-<YOUR OPTION>
33+
tar -xzf mujoco-3.3.2-<YOUR OPTION>
34+
```
35+
Replace `<YOUR OPTION>` depending on your system.
36+
3037
- One way to setup is to install MuJoCo to _a default standard path_ like `/usr/local/lib/`
31-
(or a folder in _PATH_ on Windows), if needed create symlink to `mujoco-3.3.2/lib/libmujoco.so` there,
38+
(or a folder in _PATH_ on Windows), then if needed create symlink to `mujoco-3.3.2/lib/libmujoco.so` there,
3239
and insert to your shell config file:
3340
```sh
3441
# example on Linux with /usr/local/lib/
@@ -40,6 +47,7 @@
4047
export MUJOCO_DIR="$HOME/.mujoco/mujoco-3.3.2"
4148
export LD_LIBRARY_PATH="$MUJOCO_DIR/lib:$LD_LIBRARY_PATH"
4249
```
50+
4351
- Depending on your setting, be sure to specify `$MUJOCO_DIR/lib` as shared library path
4452
when executing your app (for example `LD_LIBRARY_PATH=$MUJOCO_DIR/lib cargo run` on Linux)
4553

@@ -48,7 +56,7 @@
4856
*Cargo.toml*
4957
```toml
5058
[dependencies]
51-
rusty_mujoco = "0.0.1"
59+
rusty_mujoco = "0.1.0"
5260
```
5361

5462
*src/main.rs*
@@ -58,6 +66,8 @@ fn main() {
5866
}
5967
```
6068

69+
See the [examples](./examples) directory for working examples.
70+
6171
## License
6272

6373
rusty_mujoco is licensed under [MIT LICENSE](https://github.com/rust-control/rusty_mujoco/blob/main/LICENSE).

examples/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## `visualize_left_object.rs`
2+
3+
This example leaves a single object in the scene and visualizes it.
4+
5+
### Requirements
6+
7+
- Of course [`rusty_mujoco` Requirements](https://github.com/rust-control/rusty_mujoco?tab=readme-ov-file#requirements)
8+
- Additionally [`glfw` Prerequisites](https://github.com/PistonDevelopers/glfw-rs?tab=readme-ov-file#prerequisites)
9+
10+
### Usage
11+
12+
Pass the path to a MuJoCo model XML file as an argument. For example,
13+
you can use the `humanoid.xml` model provided by MuJoCo:
14+
15+
```sh
16+
cargo run --example visualize_left_object -- $MUJOCO_DIR/model/humanoid/humanoid.xml
17+
```
18+
19+
Options:
20+
21+
- `--camera <camera name>`: Specify the name of camera to use for visualization (optional).
22+
If not provided, the default camera will be used.
23+
- example: `--camera side` for the humanoid model
24+
25+
Depending on your system, you may need to give:
26+
27+
- `MUJOCO_DIR` environment variable, to the MuJoCo directory path (e.g. `$HOME/.mujoco/mujoco-3.3.2`)
28+
- `LD_LIBRARY_PATH` (Linux), `DYLD_LIBRARY_PATH` (macOS), or `PATH` (Windows) configuration
29+
for searching the MuJoCo library path
30+
31+
like `LD_LIBRARY_PATH="$MUJOCO_DIR/lib" cargo run --example visualize_left_object -- $MUJOCO_DIR/model/humanoid.xml`

examples/visualize_left_object.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use rusty_mujoco::{mj_loadXML, mj_makeData, mj_step};
2+
use rusty_mujoco::{mjr_makeContext, mjr_render, mjrContext, mjrRect, mjtCatBit, mjtFontScale, mjv_makeScene, mjv_updateScene, mjvCamera, mjvOption, mjvScene};
3+
4+
struct Args {
5+
xml_path: String,
6+
camera_name: Option<String>,
7+
}
8+
impl Args {
9+
fn from_env() -> Self {
10+
const USAGE: &str = "\
11+
Usage: visualize_left_object <path_to_xml_file> [options]\n\
12+
\n\
13+
Options:\n\
14+
\t--camera <camera name>\tSpecify the name of camera to use for rendering (optional)\n\
15+
";
16+
17+
let mut args = std::env::args().skip(1);
18+
let xml_path = args.next().expect(USAGE);
19+
20+
let (mut camera_name,) = (None,);
21+
while let Some(arg) = args.next() {
22+
match &*arg {
23+
"--camera" => {
24+
camera_name = Some(args.next().expect("Expected a camera name after --camera"));
25+
}
26+
_ => {
27+
eprintln!("Unknown argument: {arg}");
28+
eprintln!("{}", USAGE);
29+
std::process::exit(1);
30+
}
31+
}
32+
}
33+
34+
Self { xml_path, camera_name }
35+
}
36+
}
37+
38+
fn main() {
39+
let Args { xml_path, camera_name } = Args::from_env();
40+
41+
let model = mj_loadXML(xml_path).expect("Failed to load XML file");
42+
let mut data = mj_makeData(&model);
43+
44+
let mut glfw = glfw::init(glfw::fail_on_errors).expect("Failed to initialize GLFW");
45+
let (mut window, events) = glfw
46+
.create_window(1200, 900, "Acrobot Simulation", glfw::WindowMode::Windowed)
47+
.expect("Failed to create GLFW window");
48+
window.set_size_polling(true);
49+
glfw::Context::make_current(&mut *window);
50+
51+
let opt = mjvOption::default();
52+
let mut scn = mjvScene::default();
53+
let mut con = mjrContext::default();
54+
let mut cam = mjvCamera::default();
55+
mjv_makeScene(&model, &mut scn, 2000);
56+
mjr_makeContext(&model, &mut con, mjtFontScale::X150);
57+
camera_name.map(|name| cam.set_fixedcamid(model.object_id(&name).expect("No camera of such name in the model")));
58+
59+
while !window.should_close() {
60+
while data.time() < glfw.get_time() {
61+
mj_step(&model, &mut data);
62+
}
63+
64+
let viewport = {
65+
let (width, height) = window.get_framebuffer_size();
66+
mjrRect::new(0, 0, width as u32, height as u32)
67+
};
68+
69+
mjv_updateScene(
70+
&model,
71+
&mut data,
72+
&opt,
73+
None, /* No perturbation */
74+
&mut cam,
75+
mjtCatBit::ALL,
76+
&mut scn,
77+
);
78+
mjr_render(viewport, &mut scn, &con);
79+
glfw::Context::swap_buffers(&mut *window);
80+
81+
glfw.poll_events();
82+
for (_, event) in glfw::flush_messages(&events) {
83+
match event {
84+
glfw::WindowEvent::Close => {
85+
window.set_should_close(true);
86+
}
87+
glfw::WindowEvent::Size(width, height) => {
88+
window.set_size(width, height);
89+
}
90+
_ => (),
91+
}
92+
}
93+
}
94+
}

src/functions/support.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,16 +312,16 @@ pub fn mj_angmomMat(
312312
/// ## Example
313313
///
314314
/// ```
315-
/// use rusty_mujoco::obj;
315+
/// use rusty_mujoco::{mj_loadXML, mj_name2id, obj};
316316
///
317-
/// # fn main() {
318-
/// let model = rusty_mujoco::mj_loadXML(
317+
/// # #[cfg(false)]
318+
/// # fn f() {
319+
/// let model = mj_loadXML(
319320
/// "path/to/model.xml"
320321
/// ).unwrap();
321-
/// let body_id = rusty_mujoco::mj_name2id::<obj::Body>(
322-
/// &model,
323-
/// "body_name"
324-
/// );
322+
/// # }
323+
/// # fn example(model: &rusty_mujoco::mjModel) {
324+
/// let body_id = mj_name2id::<obj::Body>(&model, "body_name");
325325
/// println!("Body ID: {:?}", body_id);
326326
/// # }
327327
/// ```

src/types/mjdata.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ macro_rules! buffer_slices_depending_on_model {
192192
#[doc = "\n"]
193193
#[doc = "**SAFETY**: `model` must be exactly the same model as the one used to create this `mjData`."]
194194
pub unsafe fn $name(&self, model: &mjModel) -> &[$T] {
195-
#[cfg(debug_assertions/* size check */)] let _: $T = unsafe { std::mem::transmute(*self.$name) };
195+
#[cfg(debug_assertions/* size check */)] {
196+
#[allow(unnecessary_transmutes)]
197+
let _: $T = unsafe { std::mem::transmute(*self.$name) };
198+
}
196199
unsafe { std::slice::from_raw_parts(self.$name as *const $T, model.$size()$(* $lit)?$(* model.$var())?) }
197200
}
198201
)*
@@ -206,7 +209,10 @@ macro_rules! buffer_slices_depending_on_model {
206209
#[doc = "\n"]
207210
#[doc = "**SAFETY**: `model` must be exactly the same model as the one used to create this `mjData`."]
208211
pub unsafe fn $name(&self, model: &mjModel) -> &[$T] {
209-
#[cfg(debug_assertions/* size check */)] let _: $T = unsafe { std::mem::transmute(*self.$name) };
212+
#[cfg(debug_assertions/* size check */)] {
213+
#[allow(unnecessary_transmutes)]
214+
let _: $T = unsafe { std::mem::transmute(*self.$name) };
215+
}
210216
unsafe { std::slice::from_raw_parts(self.$name as *const $T, model.$size()$(* $mul)?) }
211217
}
212218

@@ -215,7 +221,10 @@ macro_rules! buffer_slices_depending_on_model {
215221
#[doc = "\n"]
216222
#[doc = "**SAFETY**: `model` must be exactly the same model as the one used to create this `mjData`."]
217223
pub unsafe fn $mut_name(&mut self, model: &mjModel) -> &mut [$T] {
218-
#[cfg(debug_assertions/* size check */)] let _: $T = unsafe { std::mem::transmute(*self.$name) };
224+
#[cfg(debug_assertions/* size check */)] {
225+
#[allow(unnecessary_transmutes)]
226+
let _: $T = unsafe { std::mem::transmute(*self.$name) };
227+
}
219228
unsafe { std::slice::from_raw_parts_mut(self.$name as *mut $T, model.$size()$(* $mul)?) }
220229
}
221230
)*
@@ -384,15 +393,18 @@ buffer_slices_depending_on_model! {
384393
dof_island: [i32; nv * 1] = "island id of this dof; -1: none (nv x 1)";
385394
}
386395

387-
// transmute: `i32` -> `mjtConstraint`, `mjtConstraintState`
396+
// transmute: `i32` -> `mjtConstraint`, `mjtConstraintState`, `mjContact`
388397
macro_rules! buffer_slices {
389398
($($name:ident : [$T:ty; $size:ident $(* $lit:literal)?] = $description:literal;)*) => {
390399
#[allow(non_snake_case)]
391400
impl mjData {
392401
$(
393402
#[doc = $description]
394403
pub fn $name(&self) -> &[$T] {
395-
#[cfg(debug_assertions/* size check */)] let _: $T = unsafe {std::mem::transmute(*self.$name)};
404+
#[cfg(debug_assertions/* size check */)] {
405+
#[allow(unnecessary_transmutes)]
406+
let _: $T = unsafe {std::mem::transmute(*self.$name)};
407+
}
396408
unsafe { std::slice::from_raw_parts(self.$name as *const $T, self.$size()) }
397409
}
398410
)*

0 commit comments

Comments
 (0)