Skip to content

feat(jmex): add AgileMaster full-body plugin and G1 teleop example - #1250

Open
jmex-sw wants to merge 1 commit into
NVIDIA:mainfrom
jmex-sw:jmex-plugin-full-body
Open

jmex-sw wants to merge 1 commit into
NVIDIA:mainfrom
jmex-sw:jmex-plugin-full-body

Conversation

@jmex-sw

@jmex-sw jmex-sw commented Oct 13, 2026 •

Copy link
Copy Markdown

Description

Adds support for the j-mex AgileMaster motion capture suit: a device plugin that publishes the
operator's body pose as FullBodyPose under a new body.jmex vendor, the core registration that
lets Isaac Teleop read it, and an Isaac Lab example that drives a Unitree G1 from it. None of this
is upstream yet; the plugin, the example and their tests are all new.

AgileMaster has three parts: the Mocap Suit the operator wears; MOXI Player, which solves the suit
onto a skeleton and streams it on a numbered channel; and the MOXI Receiver SDK, which the plugin
links against.

The plugin (src/plugins/jmex/) receives one Player channel and fills the 24 BodyJoint slots
from the standard human skeleton loaded in Player, one slot per joint, in OpenXR axes. It pushes one
FullBodyPose per frame, on the jmex_full_body collection by default. A slot with no joint, or
with no usable value in a frame, goes out with is_valid false; nothing is inferred from
neighboring joints. The raw device clock carries the time Player stamped on the frame
(--device-clock=local uses the pickup time instead). If the CloudXR runtime restarts underneath
it, the plugin re-establishes its session and carries on. It needs MOXI Receiver SDK 0.8.0 or newer;
without it, configure prints Skipping jmex plugin build: MOXIReceiverSDK 0.8.0 or newer not found
and the rest of the tree builds as before.

Where the conversion happens. Player streams its own skeleton. The plugin maps it onto the 24
slots and into OpenXR axes (body_joint_mapping, openxr_convention); Player does not emit
FullBodyPose. There are two reasons for this. The change of axes was measured in this tree and is
pinned by the plugin's tests, next to the code that consumes the slots. Done in Player, those tests
would live where this repository's CI never runs them. And Player does not have to produce Isaac
Teleop's internal schema, so a change to FullBodyPose stays in this tree instead of waiting on a
Player release.

Registration in core. Outside the new directories, each change adds an entry to an existing
list; no existing line is changed or removed.

Where What
src/core/live_trackers/cpp/live_full_body_tracker_jmex_impl.{hpp,cpp} New: the live tracker for body.jmex. It reads the plugin's collection and takes two vendor parameters, collection_id and max_flatbuffer_size
src/core/live_trackers/cpp/CMakeLists.txt Those two files in the library's source list
live_deviceio_factory.{hpp,cpp} A factory method and its try_create_* function; one row in k_tracker_dispatch, after the existing full-body vendors, so FullBodyTracker's default vendor is unchanged; one branch in validate_vendor_selections
tests/cpp/core/live_trackers/test_vendor_validation.cpp Five SECTIONs for body.jmex, beside the existing vendors'
src/plugins/CMakeLists.txt add_subdirectory(jmex) in the ungated list; the plugin skips itself without the SDK
tests/cpp/plugins/CMakeLists.txt, tests/python/examples/CMakeLists.txt add_subdirectory(jmex) for the plugin's and the example's tests, with a one-line note in the first
cmake/ClangFormat.cmake An SDK unpacked into the plugin directory is excluded from the format check
docs/source/_data/devices.yaml A row for the Ecosystem page

The example (examples/jmex/, package isaaccapture_examples.jmex) has two parts:

  • jmex_g1_tasks registers Isaac-PickPlace-Locomanipulation-G1-Jmex-Abs-v0, which inherits Isaac
    Lab's LocomanipulationG1EnvCfg and takes its action from the operator; nothing in Isaac Lab is
    modified. It is upper-body teleop. Each arm is rebuilt segment by segment onto the G1's own
    shoulder, so the operator's arm angles carry over at the robot's arm lengths, and the elbows and
    shoulders get IK targets from that same chain. The wrist's orientation is the operator's hand,
    carried through a forearm frame. The operator's torso turns the robot's shoulders about its waist,
    so leaning, bending sideways and twisting come through; their heading does not, so the operator
    can turn to face a screen without moving the robot. Only a frame that carries all 24 slots drives
    the robot.
  • record_jmex_full_body records the same stream to a standard full-body MCAP
    (core.FullBodyPoseRecord on the full_body channel), which the shared replay_full_body script
    plays back unmodified.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Testing

The tests in this PR need no SDK, so CI builds and runs them. ctest -L jmex selects 15: the
plugin's 14 Catch2 cases (slot mapping, change of axes, device clock choice, one frame becoming one
FullBodyPose, session recovery after a failed push), which compile the plugin's SDK-free sources
directly; and the example's Python test, whose 91 pytest cases cover its action source, including
the geometry of the rebuilt arm and the orientation carried through the forearm frame. The Python
test is registered when BUILD_EXAMPLES and BUILD_VIZ are on, as in the ubuntu and Windows jobs.
The body.jmex sections of the vendor validation test run under ctest -R 'vendor validation'.

Local gate, Ubuntu x86_64. We ran the six x64 entries of build-ubuntu (Python 3.11, 3.12 and
3.13, each in Debug and Release), configured as that job does and without the SDK, each from an empty
tree, twice: once with CI's toolchain (GCC 11.4, CMake 3.31.6, uv 0.10.6, clang-format 14.0.0) and
once with GCC 13.3. In all twelve:

  • configure skipped the plugin with the line above;
  • the build compiled 566 objects, and its clang-format check passed over 335 files;
  • the 15 jmex tests and the 3 vendor validation cases passed;
  • the 8 of this change's 10 .cpp files that build without the SDK compiled without warnings (the
    other two are covered below);
  • of the 507 tests in the full ctest, 495 passed, 9 skipped and 3 failed. The three are upstream
    GPU tests (VkContext with an explicit device index, and two camera_viz tests) that fail on this
    workstation's GPU setup and are unrelated to this change; CI's runners have no GPU and skip them.

The two plugin sources that compile only with the SDK were built against SDK 0.8.0 with GCC 13 and
syntax-checked with GCC 11, without warnings. With CI's toolchain we also repeated the Release
install with auditwheel repair, the pip-editable-install job (its import check, and its
wheel-content tests: 6 passed) and the configure of test-viz-sanitizers.
SKIP=check-copyright-year pre-commit run --all-files, make current-docs (-W),
scripts/check_doc_refs.py --all, ruff, and clang-format 14 on the changed files all pass. Windows
and arm64 were not built locally.

Since this PR comes from a fork, its workflows wait for a maintainer's approval. The same commit has
already been through this repository's CI on our fork, where all twelve build-ubuntu entries (x64
and arm64) passed, along with DCO, pre-commit and the docs build:
https://github.com/jmex-sw/IsaacTeleop/actions/runs/38352128694

With the device. Driven live by an operator in the suit (Isaac Lab 3.0 on Isaac Sim 6.0.1, XR
running), the G1's waist followed lean, side bend and twist with a correlation of 0.99, 90–270 ms
behind, and turning on the spot through ±90° moved its yaw less than 12°. Replayed headless from a
recording, the waist followed all three with a correlation of 1.00, one or two physics steps behind.

Running it yourself. Isaac Lab needs this repository's isaaccapture installed in its Python:
no released package carries the body.jmex vendor yet, and Isaac Lab pins the package under its
previous name. The example README has the steps. The SDK and MOXI Player are not distributed with
this PR. A maintainer validation package can be provided through an agreed private channel on
request.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the linter and formatter with SKIP=check-copyright-year pre-commit run --all-files
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix/feature works (or explained why not)
  • I have signed off all my commits (git commit -s) per the DCO

Summary by CodeRabbit

  • New Features
    • Added support for streaming j-mex AgileMaster full-body motion capture into Isaac Teleop.
    • Added a G1 teleoperation example that retargets captured body poses, with options to record and replay sessions.
    • Added device setup guidance, prerequisites, and troubleshooting documentation.
  • Bug Fixes
    • Improved recovery when pose publishing encounters connection failures, and validation of incoming pose data.
  • Tests
    • Added coverage for motion-capture data handling, pose retargeting, and publishing recovery.

Adds `jmex`, a plugin for the j-mex AgileMaster motion capture suit, and an example, in two parts,
that consumes what it publishes.

The plugin reads one MOXI Player channel through the MOXI Receiver SDK (0.8.0 or newer, obtained
from j-mex; without it the plugin is skipped and the rest of the tree builds as before). It fills
the 24 `FullBodyPose` slots from the standard human skeleton Player has loaded, one slot per joint,
already in OpenXR axes, and pushes them under the `body.jmex` vendor on every frame. A slot with no
joint, or with no usable value in a frame, goes out invalid; nothing is inferred from its
neighbors. The raw device clock carries the time Player stamped on the frame (`--device-clock=local`
uses the pickup time instead), and if the CloudXR runtime restarts underneath the plugin, the plugin
reconnects rather than ending the run.

Isaac Teleop learns `body.jmex` as a full-body vendor: a live tracker that reads the plugin's
collection, with `collection_id` and `max_flatbuffer_size` parameters, its entry in the DeviceIO
factory, the vendor validation cases, and a row in `devices.yaml`. An SDK unpacked into the plugin
directory is kept out of the clang-format check.

`isaaccapture_examples.jmex.jmex_g1_tasks` registers a task that inherits Isaac Lab's
`LocomanipulationG1EnvCfg` and takes its action from the operator; nothing in Isaac Lab is
modified. It is upper-body teleop. Each arm is rebuilt segment by segment onto the robot's own
shoulder, so the operator's arm angles survive while the lengths stay the robot's, and the elbows
and shoulders get IK targets of their own from that same chain. The wrist's orientation is the
operator's hand, carried across through a forearm frame. The operator's torso turns the robot's
shoulders about its waist, so leaning, bending sideways and twisting come through; their heading
does not, so the operator can turn to face a screen without moving the robot. Only a frame that
carries all 24 slots drives the robot, and slots that stay missing are named on the console.

`isaaccapture_examples.jmex.record_jmex_full_body` writes the same stream to a standard full-body
MCAP.

Tests cover the plugin's slot mapping, its change of axes, the device clock choice and the session
rebuild after a failed push, the vendor's parameters, and the example's action source, including
the geometry of the rebuilt arm and the orientation carried through the forearm frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: j-mex <jmex.dev@jmex.com.tw>
@github-actions

Copy link
Copy Markdown
Contributor

📝 Docs preview is not auto-deployed for fork PRs.

A maintainer with write access to NVIDIA/IsaacCapture can deploy a preview by
commenting /preview-docs on this PR. Once deployed, the preview
will live at:

https://nvidia.github.io/IsaacCapture/preview/pr-1250/

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: NVIDIA/IsaacCapture/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: b150d31f-05b8-4673-9d90-291312365719
📥 Commits

Reviewing files that changed from the base of the PR and between 2fe1bbe and a91f3af.

📒 Files selected for processing (41)
  • cmake/ClangFormat.cmake
  • docs/source/_data/devices.yaml
  • examples/jmex/.gitignore
  • examples/jmex/README.md
  • examples/jmex/pyproject.toml
  • examples/jmex/python/isaaccapture_examples/jmex/__init__.py
  • examples/jmex/python/isaaccapture_examples/jmex/jmex_g1_retargeting.py
  • examples/jmex/python/isaaccapture_examples/jmex/jmex_g1_tasks.py
  • examples/jmex/python/isaaccapture_examples/jmex/record_jmex_full_body.py
  • src/core/live_trackers/cpp/CMakeLists.txt
  • src/core/live_trackers/cpp/inc/live_trackers/live_deviceio_factory.hpp
  • src/core/live_trackers/cpp/live_deviceio_factory.cpp
  • src/core/live_trackers/cpp/live_full_body_tracker_jmex_impl.cpp
  • src/core/live_trackers/cpp/live_full_body_tracker_jmex_impl.hpp
  • src/plugins/CMakeLists.txt
  • src/plugins/jmex/.gitignore
  • src/plugins/jmex/CMakeLists.txt
  • src/plugins/jmex/README.md
  • src/plugins/jmex/app/CMakeLists.txt
  • src/plugins/jmex/app/main.cpp
  • src/plugins/jmex/core/CMakeLists.txt
  • src/plugins/jmex/core/body_joint_mapping.cpp
  • src/plugins/jmex/core/device_clock.cpp
  • src/plugins/jmex/core/full_body_publisher.cpp
  • src/plugins/jmex/core/inc/jmex/body_joint_mapping.hpp
  • src/plugins/jmex/core/inc/jmex/device_clock.hpp
  • src/plugins/jmex/core/inc/jmex/full_body_publisher.hpp
  • src/plugins/jmex/core/inc/jmex/receiver_session.hpp
  • src/plugins/jmex/core/openxr_convention.cpp
  • src/plugins/jmex/core/openxr_convention.h
  • src/plugins/jmex/core/receiver_session.cpp
  • src/plugins/jmex/plugin.yaml
  • tests/cpp/core/live_trackers/test_vendor_validation.cpp
  • tests/cpp/plugins/CMakeLists.txt
  • tests/cpp/plugins/jmex/CMakeLists.txt
  • tests/cpp/plugins/jmex/test_jmex_plugin_core.cpp
  • tests/python/examples/CMakeLists.txt
  • tests/python/examples/jmex/CMakeLists.txt
  • tests/python/examples/jmex/conftest.py
  • tests/python/examples/jmex/pyproject.toml
  • tests/python/examples/jmex/test_jmex_g1_action_source.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

📝 Walkthrough

This change adds a j-mex plugin that receives AgileMaster full-body poses and publishes them through OpenXR. It adds body.jmex live-tracker support and a G1 teleoperation example that converts received poses into robot action targets. It also adds an MCAP recording example, build and test configuration, and documentation for the plugin and examples.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PluginMain
  participant ReceiverSession
  participant FullBodyPublisher
  participant OpenXRTransport
  PluginMain->>ReceiverSession: Poll for a new frame
  ReceiverSession-->>PluginMain: Return mapped full-body frame
  PluginMain->>FullBodyPublisher: Publish frame
  FullBodyPublisher->>OpenXRTransport: Push serialized pose
Loading
Merge Risk: ⚪ Minimal · up to a91f3

This change adds an optional j-mex full-body plugin, a body.jmex tracker, and examples. No concrete defects are established, and builds without the SDK skip the plugin, so the change appears ready to merge.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 50.20% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 249 functions across 23 files. (18 skippe… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main changes: adding the AgileMaster full-body plugin and the G1 teleoperation example.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 50.20% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 249 functions across 23 files. (18 skipped: 18 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@ivany-nv ivany-nv self-assigned this Oct 13, 2026
@ivany-nv
ivany-nv self-requested a review October 13, 2026 18:16
@ivany-nv

Copy link
Copy Markdown
Contributor

/preview-docs

@github-actions

Copy link
Copy Markdown
Contributor

✅ Preview deployed: https://NVIDIA.github.io/IsaacCapture/preview/pr-1250/

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants