Skip to content

Add G1 AGILE tabletop apple-to-plate environment#562

Open
lgulich wants to merge 12 commits intomainfrom
eng-agent/ad-8hb-g1-agile-tabletop-apple-plate
Open

Add G1 AGILE tabletop apple-to-plate environment#562
lgulich wants to merge 12 commits intomainfrom
eng-agent/ad-8hb-g1-agile-tabletop-apple-plate

Conversation

@lgulich
Copy link
Copy Markdown
Collaborator

@lgulich lgulich commented Apr 9, 2026

Problem

IsaacLab-Arena needs a tabletop manipulation task where the G1 robot uses the WBC-AGILE locomotion policy to pick up an apple and place it on a plate, while balancing in place.

Ref: ISAAC-12630

Solution

Add a new G1AgileTabletopAppleToPlateEnvironment that wires the G1WBCAgileJointEmbodiment (from PR #489) with the existing PickAndPlaceTask, a Seattle Lab table scene, and appropriate object assets.

Changes

  • isaaclab_arena_environments/g1_agile_tabletop_apple_to_plate_environment.py — New environment class: G1 robot at (-0.4, 0, 0) facing a table with an apple (pick object) and a clay plate (target). Uses G1WBCAgileJointEmbodiment for balance + upper body control. 30-second episodes. Supports --object, --embodiment, --teleop_device CLI args.
  • isaaclab_arena_environments/cli.py — Register the new environment in the ExampleEnvironments dict.
  • isaaclab_arena/tests/test_g1_agile_tabletop_apple_to_plate.py — Two tests: (1) initial state is not terminated (apple starts away from plate), (2) teleporting apple onto plate triggers success termination. Uses correct base-height command (0.75) to keep the robot stable.

Testing

  • New unit tests added (2 tests)
  • Linters pass locally (black, flake8, isort, pyupgrade, codespell, license headers)
  • CI pipeline (tests require Isaac Sim Docker with GPU)

Notes

  • Object positions (apple, plate, robot) are based on Seattle Lab table geometry and G1 arm reach. May need visual tuning in simulator.
  • No new task class needed — the existing PickAndPlaceTask handles contact-sensor success detection, object-dropped termination, and metrics.
  • Self-review caught and fixed a test issue: the initial-state test was sending zero base-height commands, which would cause the robot to squat. Fixed to use 0.75 (matching established pattern from test_g1_wbc_embodiment.py).

Generated by autodev — Claude Code

lgulich added 2 commits April 9, 2026 14:04
New environment where a G1 robot with WBC-AGILE policy stands at a
Seattle Lab table and moves an apple onto a plate. Reuses the existing
PickAndPlaceTask for contact-sensor success detection and object-dropped
termination.

- g1_agile_tabletop_apple_to_plate_environment.py: scene setup with
  table, apple, plate, and G1WBCAgileJoint embodiment
- cli.py: register the new environment in ExampleEnvironments
- test_g1_agile_tabletop_apple_to_plate.py: initial-state and
  teleport-success tests using run_simulation_app_function pattern

Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
The first test used step_zeros_and_call which sends zero actions
including a zero base-height command. For the G1 WBC embodiment this
causes the robot to squat to the ground, leading to physics instability.
Set actions[:, -4] = 0.75 consistent with the second test and the
existing G1 WBC test patterns in test_g1_wbc_embodiment.py.

Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Clean new environment addition that follows established patterns well. The G1AgileTabletopAppleToPlateEnvironment correctly wires G1WBCAgileJointEmbodiment with PickAndPlaceTask and the CLI/test integration is solid. Pre-commit and docs CI pass. Two minor suggestions below.

✅ What looks good

  • Pattern adherence: Environment class, CLI registration, and test structure all follow the established conventions (matches KitchenPickAndPlaceEnvironment, GalileoPickAndPlaceEnvironment, etc.)
  • Correct base-height command (0.75): Both tests properly set actions[:, -4] = 0.75 to keep the AGILE WBC robot standing, matching the pattern established in test_g1_wbc_embodiment.py
  • Good test design: The two-test approach (initial state not terminated + teleport-to-success) provides meaningful coverage of the task lifecycle without requiring full manipulation
  • Clean reuse: No new task class needed — PickAndPlaceTask handles everything

📝 Suggestions

1. Task description is hardcoded to "apple" but --object is configurable

File: g1_agile_tabletop_apple_to_plate_environment.py, line 66

The task_description always says "Pick up the apple from the table and place it onto the plate" even when a different object is passed via --object. Consider making it dynamic:

task_description=f"Pick up the {args_cli.object.replace('_', ' ')} from the table and place it onto the plate.",

Or if the description is only used for display/logging and the default object is always apple, this is fine as-is — just worth noting.

2. Test duplicates environment setup instead of reusing the environment class

File: test_g1_agile_tabletop_apple_to_plate.py

The test's get_test_environment() manually reconstructs the entire environment (assets, poses, task, embodiment) rather than instantiating G1AgileTabletopAppleToPlateEnvironment and calling get_env(). This means if positions or assets change in the environment class, the test won't reflect those changes.

That said, I see other tests in this repo follow the same pattern (e.g., test_g1_wbc_embodiment.py), so this may be an intentional convention to keep test setup explicit. If so, no change needed — just flagging the drift risk.

Verdict

LGTM with minor nits. This is a straightforward, well-structured environment addition. The two suggestions above are non-blocking. 👍

lgulich added 3 commits April 9, 2026 14:49
Add warmup steps (50) before checking termination in both tests.
The G1 AGILE WBC policy needs time to stabilise the robot's lower body;
during the first few dozen sim steps physics transients can nudge the
apple and trigger the object-dropped termination spuriously.

This mirrors the warmup pattern used in test_g1_agile_policy.py
(WARMUP_STEPS = 50) and extracts a shared _step_with_standing_actions
helper that both test functions now use.

Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
…ination

The G1 AGILE WBC policy's stabilisation during warmup physically knocks
the apple off the table, causing its root height to drop below the
-0.05m threshold and triggering the object_dropped termination before
the actual test assertions begin.

Add _replace_apple_at_initial_pose() which teleports the apple back to
its starting position with zero velocity after warmup completes. This
ensures test_initial_state_not_terminated validates steady-state
behaviour rather than warmup transients.

Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
…odiment

- Switch from Seattle Lab table to maple_table_robolab with On() relations
  for automatic object placement on table surface
- Add dome light (intensity 500) and correct object scales (apple 0.01, plate 0.5)
- Position robot at (1.2, 0, 0) facing the table
- Add G1WBCAgilePinkEmbodiment (g1_wbc_agile_pink) combining AGILE WBC
  with Pink IK upper body control for OpenXR teleoperation
- Add OpenXR retargeter for the new embodiment

Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Isaac Lab Review Bot

Reviewed 5 changed files. One bug and a couple of style nits:

🐛 Bug: Missing @register_asset decorator

isaaclab_arena/embodiments/g1/g1.pyG1WBCAgilePinkEmbodiment is missing the @register_asset decorator. Every other embodiment class (G1WBCJointEmbodiment, G1WBCPinkEmbodiment, G1WBCAgileJointEmbodiment) has it. Without it, asset_registry.get_asset_by_name("g1_wbc_agile_pink") will fail at runtime.

# Missing this:
@register_asset
class G1WBCAgilePinkEmbodiment(G1EmbodimentBase):

📝 Style nits (non-blocking)

  1. g1_agile_tabletop_apple_to_plate_environment.pyimport isaaclab.sim as sim_utils and from isaaclab_arena.utils.pose import Pose are placed mid-function between asset creation statements (lines ~36 and ~51). Move them to the top of get_env() with the other imports for readability.

  2. RetargeterG1WbcAgilePinkIsaacTeleopRetargeter imports the same _build_g1_pink_locomanipulation_pipeline as the non-AGILE retargeter. This is fine if the pipeline is truly the same for both, but worth a comment noting that the AGILE variant shares the same teleop pipeline.

Overall the environment follows existing patterns well. Tests are solid — the warmup + re-place strategy for the initial state test is a nice touch. Just needs the decorator fix.

The GPU physics pipeline reports spurious contact-sensor forces between
objects that are far apart, causing the object_on_destination success
termination to fire incorrectly.  Add an optional proximity guard
(destination_cfg + max_distance) that requires the pick-up object to be
within a configurable distance of the destination before the contact
force is considered valid.

Enable the guard in PickAndPlaceTask with max_distance=0.15 m to fix
the G1 AGILE tabletop apple-to-plate test failure where the apple was
being catapulted upward by robot vibrations yet phantom contact forces
still triggered the success termination.

Signed-off-by: Lionel Gulich <lgulich@nvidia.com>
@lgulich
Copy link
Copy Markdown
Collaborator Author

lgulich commented Apr 9, 2026

Fix: False success termination in pick-and-place tasks

Root Cause

The success termination was firing incorrectly — PhysX GPU pipeline reports phantom contact-sensor forces between objects that are far apart. The contact sensor reported 300-3700 N between the apple and plate even when they were 0.3-1.7m apart. When the apple had low velocity (e.g., at the peak of its trajectory), both conditions for success were satisfied, triggering false success termination.

Fix

Added a proximity guard to object_on_destination termination:

  • New optional parameters: destination_cfg (SceneEntityCfg) and max_distance (float)
  • When provided, verifies the pick-up object is within max_distance of the destination before contact force is considered valid
  • Backward compatible — existing callers see no behavior change
  • Enabled in PickAndPlaceTask with max_distance=0.15m

Test Results

  • test_initial_state_not_terminated: PASSED ✅ (was failing)
  • test_apple_on_plate_succeeds: PASSED ✅ (still passes)
  • test_agile_standing: PASSED ✅ (no regression)

Video

Environment recording attached — shows G1 robot standing at the table with the apple, plate, and table visible. AGILE WBC policy maintains balance.


Generated by autodev — Claude Code

lgulich and others added 6 commits April 9, 2026 21:49
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous black run used default line-length (88) instead of the
project's configured 120. Re-ran with correct settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The destination scene entity may be an XformPrimView (not a
RigidObject) and lacks a .data attribute. Fall back to
get_world_poses() for position retrieval when .data is unavailable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The proximity guard was breaking existing tests because it was
always-on for all PickAndPlaceTask instances. Make it opt-in via
success_proximity_max_distance parameter (default 0.0 = disabled).
Only the G1 tabletop environment enables it (0.15m) since that's
where the phantom contact force issue manifests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test creates its own PickAndPlaceTask instance separately from the
environment file, so it also needs success_proximity_max_distance=0.15
to prevent phantom contact forces from triggering false success.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lgulich lgulich requested a review from alexmillane April 9, 2026 23:32
@lgulich lgulich marked this pull request as ready for review April 9, 2026 23:32
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 9, 2026

Greptile Summary

This PR adds a new G1AgileTabletopAppleToPlateEnvironment that wires G1WBCAgileJointEmbodiment with the existing PickAndPlaceTask, a maple_table_robolab scene, and an apple-to-plate manipulation task. It also adds G1WBCAgilePinkEmbodiment, an OpenXR retargeter for the AGILE+Pink configuration, a proximity guard to object_on_destination to suppress spurious contact forces from the GPU physics pipeline, and two simulation tests for the new environment.

Confidence Score: 5/5

Safe to merge; all findings are P2 style/quality suggestions that do not block correct runtime behaviour.

No P0 or P1 defects were found. The proximity guard fix is backward-compatible and the new environment follows established patterns. The three P2 comments (apple scale, hardcoded re-placement position, simplified test scene) are informational quality improvements that should not block the PR.

isaaclab_arena_environments/g1_agile_tabletop_apple_to_plate_environment.py — verify the apple scale=(0.01,0.01,0.01) is correct for the USD asset's native units.

Important Files Changed

Filename Overview
isaaclab_arena_environments/g1_agile_tabletop_apple_to_plate_environment.py New environment class; apple is instantiated with scale=(0.01, 0.01, 0.01) — 100× smaller than the default — which may not match the actual USD asset units.
isaaclab_arena/tests/test_g1_agile_tabletop_apple_to_plate.py Two simulation tests added; helper hardcodes apple's initial position rather than deriving it from the asset, and the scene layout differs from the production environment.
isaaclab_arena/tasks/terminations.py Proximity guard added to object_on_destination; handles XformPrimView destinations defensively; logic is correct for the new environment's RigidObject destination.
isaaclab_arena/tasks/pick_and_place_task.py Proximity guard is now opt-in via success_proximity_max_distance; existing callers with default 0.0 are unaffected; clean backward-compatible change.
isaaclab_arena/embodiments/g1/g1.py G1WBCAgilePinkEmbodiment added alongside the existing G1WBCAgileJointEmbodiment; both follow established patterns.
isaaclab_arena/assets/retargeter_library.py New G1WbcAgilePinkIsaacTeleopRetargeter registered for the g1_wbc_agile_pink embodiment; follows existing retargeter pattern.
isaaclab_arena_environments/cli.py G1AgileTabletopAppleToPlateEnvironment correctly registered in ExampleEnvironments dict.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI / ArenaEnvBuilder
    participant Env as G1AgileTabletopAppleToPlateEnvironment
    participant Scene as Scene (maple_table_robolab + apple + plate)
    participant Task as PickAndPlaceTask
    participant Term as object_on_destination
    participant Prox as proximity guard

    CLI->>Env: get_env(args_cli)
    Env->>Scene: assemble assets + On(table_reference) relations
    Env->>Task: PickAndPlaceTask(success_proximity_max_distance=0.15)
    Task->>Term: register termination with destination_cfg + max_distance
    CLI->>Scene: ArenaEnvBuilder.make_registered()
    loop Each physics step
        Term->>Term: check contact force > 1 N
        Term->>Term: check object velocity < 0.1 m/s
        Term->>Prox: distance(apple, plate) < 0.15 m ?
        Prox-->>Term: close_enough
        Term-->>CLI: condition_met (success)
    end
Loading

Reviews (1): Last reviewed commit: "fix(test): enable proximity guard in tab..." | Re-trigger Greptile

from isaaclab_arena.tasks.pick_and_place_task import PickAndPlaceTask

background = self.asset_registry.get_asset_by_name("maple_table_robolab")()
pick_up_object = self.asset_registry.get_asset_by_name(args_cli.object)(scale=(0.01, 0.01, 0.01))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Verify apple scale is intentional

scale=(0.01, 0.01, 0.01) reduces the asset to 1% of its original size. No other environment in the codebase applies a scale this small to a pick-up object — the analogous PickAndPlaceMapleTableEnvironment uses the default scale of 1.0. If the apple_01_objaverse_robolab USD is already in meter units an apple would end up roughly 1 cm in diameter, which would make grasping and contact detection unreliable. If the asset is authored in centimetres then this conversion is correct; please add a brief comment explaining the intent so future readers don't wonder whether this is a typo.

Comment on lines +92 to +94
init_pos = torch.tensor([[0.15, 0.15, 0.05]], device=env.unwrapped.device)
init_quat = torch.tensor([[1.0, 0.0, 0.0, 0.0]], device=env.unwrapped.device)
apple_object.write_root_pose_to_sim(root_pose=torch.cat([init_pos, init_quat], dim=-1))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded re-placement position may silently drift from the test setup

init_pos is hardcoded to [0.15, 0.15, 0.05] instead of being derived from the apple asset configured in get_test_environment. If the initial pose on line 37 of that function is updated, these two values will silently diverge and the "re-placed" apple will be at the wrong location, potentially making the test pass or fail for the wrong reason. Consider reading the value from the apple object or from a shared constant:

# option: derive from apple's stored initial pose
init_pos = torch.tensor([apple.initial_pose.position_xyz], device=env.unwrapped.device)

Comment on lines +33 to +41
background = asset_registry.get_asset_by_name("table")()
apple = asset_registry.get_asset_by_name("apple_01_objaverse_robolab")()
plate = asset_registry.get_asset_by_name("clay_plates_hot3d_robolab")()

apple.set_initial_pose(Pose(position_xyz=(0.15, 0.15, 0.05), rotation_xyzw=(0.0, 0.0, 0.0, 1.0)))
plate.set_initial_pose(Pose(position_xyz=(0.15, -0.15, 0.02), rotation_xyzw=(0.0, 0.0, 0.0, 1.0)))

embodiment = G1WBCAgileJointEmbodiment(enable_cameras=ENABLE_CAMERAS)
embodiment.set_initial_pose(Pose(position_xyz=(-0.4, 0.0, 0.0), rotation_xyzw=(0.0, 0.0, 0.0, 1.0)))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test scene layout differs significantly from the production environment

The test uses the generic table background with the robot at (-0.4, 0, 0) facing +x (identity quaternion), whereas the production environment uses maple_table_robolab with the robot at (1.2, 0, 0) facing -x (180° z-rotation). The tests exercise the underlying component logic well, but they will not catch issues specific to the Seattle Lab table geometry, the actual G1 arm-reach envelope from that position, or the On(table_reference) spatial relation used in the environment. A short comment here explaining why a simplified layout is used would help future contributors understand the coverage gap.

@lgulich
Copy link
Copy Markdown
Collaborator Author

lgulich commented Apr 10, 2026

Hi @alexmillane — friendly reminder that this PR is ready for your review. All CI checks are passing (pre-commit, tests, GR00T policy tests, docs build). Let me know if you have any questions!


Generated by autodev — Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant