Skip to content

Commit b382b4d

Browse files
committed
Add coverage for frame transformer source with duplicate body names
1 parent 527edfd commit b382b4d

File tree

1 file changed

+59
-27
lines changed

1 file changed

+59
-27
lines changed

source/isaaclab/test/sensors/test_frame_transformer.py

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,8 @@ def test_sensor_print(sim):
592592

593593

594594
@pytest.mark.isaacsim_ci
595-
def test_frame_transformer_duplicate_body_names(sim):
595+
@pytest.mark.parametrize("source_robot", ["Robot", "Robot_1"])
596+
def test_frame_transformer_duplicate_body_names(sim, source_robot):
596597
"""Test tracking bodies with same leaf name at different hierarchy levels.
597598
598599
This test verifies that bodies with the same leaf name but different paths
@@ -612,6 +613,11 @@ def test_frame_transformer_duplicate_body_names(sim):
612613
`idx = target_frame_names.index("RF_SHANK")`. However, when multiple bodies share
613614
the same leaf name, this results in duplicate frame names. The transforms are
614615
still distinct because internal body tracking uses full relative paths.
616+
617+
Args:
618+
source_robot: The robot to use as the source frame ("Robot" or "Robot_1").
619+
This tests that both source frames work correctly when there are
620+
duplicate body names.
615621
"""
616622

617623
# Create a custom scene config with two robots
@@ -630,31 +636,35 @@ class MultiRobotSceneCfg(InteractiveSceneCfg):
630636
init_state=ANYMAL_C_CFG.init_state.replace(pos=(2.0, 0.0, 0.6)),
631637
)
632638

633-
# Frame transformer tracking same-named bodies from both robots
634-
frame_transformer: FrameTransformerCfg = FrameTransformerCfg(
635-
prim_path="{ENV_REGEX_NS}/Robot/base",
636-
target_frames=[
637-
# Explicit frame names (recommended when bodies share the same leaf name)
638-
FrameTransformerCfg.FrameCfg(
639-
name="Robot_LF_SHANK",
640-
prim_path="{ENV_REGEX_NS}/Robot/LF_SHANK",
641-
),
642-
FrameTransformerCfg.FrameCfg(
643-
name="Robot_1_LF_SHANK",
644-
prim_path="{ENV_REGEX_NS}/Robot_1/LF_SHANK",
645-
),
646-
# Implicit frame names (backward compatibility)
647-
FrameTransformerCfg.FrameCfg(
648-
prim_path="{ENV_REGEX_NS}/Robot/RF_SHANK",
649-
),
650-
FrameTransformerCfg.FrameCfg(
651-
prim_path="{ENV_REGEX_NS}/Robot_1/RF_SHANK",
652-
),
653-
],
654-
)
639+
# Frame transformer will be set after config creation (needs source_robot parameter)
640+
frame_transformer: FrameTransformerCfg = None # type: ignore
655641

656642
# Spawn things into stage
657643
scene_cfg = MultiRobotSceneCfg(num_envs=2, env_spacing=10.0, lazy_sensor_update=False)
644+
645+
# Frame transformer tracking same-named bodies from both robots
646+
# Source frame is parametrized to test both Robot/base and Robot_1/base
647+
scene_cfg.frame_transformer = FrameTransformerCfg(
648+
prim_path=f"{{ENV_REGEX_NS}}/{source_robot}/base",
649+
target_frames=[
650+
# Explicit frame names (recommended when bodies share the same leaf name)
651+
FrameTransformerCfg.FrameCfg(
652+
name="Robot_LF_SHANK",
653+
prim_path="{ENV_REGEX_NS}/Robot/LF_SHANK",
654+
),
655+
FrameTransformerCfg.FrameCfg(
656+
name="Robot_1_LF_SHANK",
657+
prim_path="{ENV_REGEX_NS}/Robot_1/LF_SHANK",
658+
),
659+
# Implicit frame names (backward compatibility)
660+
FrameTransformerCfg.FrameCfg(
661+
prim_path="{ENV_REGEX_NS}/Robot/RF_SHANK",
662+
),
663+
FrameTransformerCfg.FrameCfg(
664+
prim_path="{ENV_REGEX_NS}/Robot_1/RF_SHANK",
665+
),
666+
],
667+
)
658668
scene = InteractiveScene(scene_cfg)
659669

660670
# Play the simulator
@@ -681,11 +691,17 @@ class MultiRobotSceneCfg(InteractiveSceneCfg):
681691
assert len(rf_shank_indices) == 2, f"Expected 2 RF_SHANK indices, got {rf_shank_indices}"
682692

683693
# Acquire ground truth body indices
694+
robot_base_body_idx = scene.articulations["robot"].find_bodies("base")[0][0]
695+
robot_1_base_body_idx = scene.articulations["robot_1"].find_bodies("base")[0][0]
684696
robot_lf_shank_body_idx = scene.articulations["robot"].find_bodies("LF_SHANK")[0][0]
685697
robot_1_lf_shank_body_idx = scene.articulations["robot_1"].find_bodies("LF_SHANK")[0][0]
686698
robot_rf_shank_body_idx = scene.articulations["robot"].find_bodies("RF_SHANK")[0][0]
687699
robot_1_rf_shank_body_idx = scene.articulations["robot_1"].find_bodies("RF_SHANK")[0][0]
688700

701+
# Determine expected source frame based on parameter
702+
expected_source_robot = "robot" if source_robot == "Robot" else "robot_1"
703+
expected_source_base_body_idx = robot_base_body_idx if source_robot == "Robot" else robot_1_base_body_idx
704+
689705
# Define simulation stepping
690706
sim_dt = sim.get_physics_dt()
691707

@@ -721,15 +737,31 @@ class MultiRobotSceneCfg(InteractiveSceneCfg):
721737
scene.update(sim_dt)
722738

723739
# Get frame transformer data
724-
target_pos_w = scene.sensors["frame_transformer"].data.target_pos_w
740+
frame_transformer_data = scene.sensors["frame_transformer"].data
741+
source_pos_w = frame_transformer_data.source_pos_w
742+
source_quat_w = frame_transformer_data.source_quat_w
743+
target_pos_w = frame_transformer_data.target_pos_w
725744

726-
# Get ground truth positions
745+
# Get ground truth positions and orientations (after scene.update() so they're current)
727746
robot_lf_pos_w = scene.articulations["robot"].data.body_pos_w[:, robot_lf_shank_body_idx]
728747
robot_1_lf_pos_w = scene.articulations["robot_1"].data.body_pos_w[:, robot_1_lf_shank_body_idx]
729748
robot_rf_pos_w = scene.articulations["robot"].data.body_pos_w[:, robot_rf_shank_body_idx]
730749
robot_1_rf_pos_w = scene.articulations["robot_1"].data.body_pos_w[:, robot_1_rf_shank_body_idx]
731750

732-
# TEST 1: Explicit named frames (LF_SHANK) should have DIFFERENT positions
751+
# Get expected source frame positions and orientations (after scene.update() so they're current)
752+
expected_source_base_pos_w = scene.articulations[expected_source_robot].data.body_pos_w[
753+
:, expected_source_base_body_idx
754+
]
755+
expected_source_base_quat_w = scene.articulations[expected_source_robot].data.body_quat_w[
756+
:, expected_source_base_body_idx
757+
]
758+
759+
# TEST 1: Verify source frame is correctly resolved
760+
# The source_pos_w should match the expected source robot's base world position
761+
torch.testing.assert_close(source_pos_w, expected_source_base_pos_w, rtol=1e-5, atol=1e-5)
762+
torch.testing.assert_close(source_quat_w, expected_source_base_quat_w, rtol=1e-5, atol=1e-5)
763+
764+
# TEST 2: Explicit named frames (LF_SHANK) should have DIFFERENT world positions
733765
lf_pos_difference = torch.norm(target_pos_w[:, robot_lf_idx] - target_pos_w[:, robot_1_lf_idx], dim=-1)
734766
assert torch.all(lf_pos_difference > 1.0), (
735767
f"Robot_LF_SHANK and Robot_1_LF_SHANK should have different positions (got diff={lf_pos_difference}). "
@@ -740,7 +772,7 @@ class MultiRobotSceneCfg(InteractiveSceneCfg):
740772
torch.testing.assert_close(target_pos_w[:, robot_lf_idx], robot_lf_pos_w)
741773
torch.testing.assert_close(target_pos_w[:, robot_1_lf_idx], robot_1_lf_pos_w)
742774

743-
# TEST 2: Implicit named frames (RF_SHANK) should also have DIFFERENT positions
775+
# TEST 3: Implicit named frames (RF_SHANK) should also have DIFFERENT world positions
744776
# Even though they have the same frame name, internal body tracking uses full paths
745777
rf_pos_difference = torch.norm(
746778
target_pos_w[:, rf_shank_indices[0]] - target_pos_w[:, rf_shank_indices[1]], dim=-1

0 commit comments

Comments
 (0)