Skip to content

Commit c28b1a2

Browse files
committed
Align ArenaWorld frame naming
Signed-off-by: Clemens Volk <cvolk@nvidia.com>
1 parent 45e4426 commit c28b1a2

6 files changed

Lines changed: 124 additions & 115 deletions

File tree

isaaclab_arena/environments/arena_world.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,22 @@ class ArenaWorld:
2020

2121
def __init__(self, scene: InteractiveScene):
2222
self._scene: InteractiveScene | None = scene
23-
self._local_aabbs: dict[str, AxisAlignedBoundingBox] = {}
23+
self._aabbs_in_entity_frame: dict[str, AxisAlignedBoundingBox] = {}
2424
self._asset_base_cfg_pose_readers: dict[str, entity_access.AssetBaseCfgPoseReader] = {}
2525

2626
def get_pose_w(self, entity_name: str) -> torch.Tensor:
27-
"""Return an entity's current world pose for every environment."""
27+
"""Return ``T_W_E``, mapping the named entity frame ``E`` into world frame ``W``.
28+
29+
Poses have shape ``(num_envs, 7)`` and quaternion order ``(x, y, z, w)``.
30+
"""
2831
scene = self._get_scene()
2932
if entity_name in scene.rigid_objects:
30-
pose_w = scene.rigid_objects[entity_name].data.root_pose_w.torch
31-
assert pose_w.shape == (scene.num_envs, 7), (
32-
f"Rigid object '{entity_name}' returned pose shape {tuple(pose_w.shape)}; "
33-
f"expected ({scene.num_envs}, 7)."
34-
)
35-
return pose_w
33+
T_W_E = scene.rigid_objects[entity_name].data.root_pose_w.torch
34+
assert T_W_E.shape == (
35+
scene.num_envs,
36+
7,
37+
), f"Rigid object '{entity_name}' returned pose shape {tuple(T_W_E.shape)}; expected ({scene.num_envs}, 7)."
38+
return T_W_E
3639

3740
assert (
3841
entity_name in scene.extras
@@ -52,17 +55,17 @@ def get_linear_velocity_w(self, entity_name: str) -> torch.Tensor:
5255
)
5356
return linear_velocity_w
5457

55-
def get_local_aabb(self, entity_name: str) -> AxisAlignedBoundingBox:
56-
"""Return read-only cached geometry bounds in the entity's live pose frame."""
58+
def get_aabb_in_entity_frame(self, entity_name: str) -> AxisAlignedBoundingBox:
59+
"""Return cached geometry bounds expressed in the named entity frame ``E``."""
5760
scene = self._get_scene()
58-
if entity_name not in self._local_aabbs:
59-
local_aabb = entity_access.compute_spawned_geometry_bounds_in_entity_frame(scene, entity_name)
60-
self._local_aabbs[entity_name] = local_aabb
61-
return self._local_aabbs[entity_name]
61+
if entity_name not in self._aabbs_in_entity_frame:
62+
aabb_E = entity_access.compute_spawned_geometry_bounds_in_entity_frame(scene, entity_name)
63+
self._aabbs_in_entity_frame[entity_name] = aabb_E
64+
return self._aabbs_in_entity_frame[entity_name]
6265

6366
def close(self) -> None:
6467
"""Release cached geometry, pose readers, and the live scene reference."""
65-
self._local_aabbs.clear()
68+
self._aabbs_in_entity_frame.clear()
6669
self._asset_base_cfg_pose_readers.clear()
6770
self._scene = None
6871

isaaclab_arena/environments/arena_world_entity_access.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _find_single_rigid_body_prim_in_subtree(root_prim: Usd.Prim, entity_name: st
5858

5959

6060
def _compute_geometry_bounds_in_prim_frame(prim: Usd.Prim) -> AxisAlignedBoundingBox:
61-
"""Compute descendant geometry bounds in the prim frame."""
61+
"""Compute descendant geometry bounds expressed in the prim's local frame ``P``."""
6262
assert prim.IsValid(), "Prim must be valid."
6363

6464
time_code = Usd.TimeCode.Default()
@@ -79,12 +79,12 @@ def _compute_geometry_bounds_in_prim_frame(prim: Usd.Prim) -> AxisAlignedBoundin
7979
geometry_bounds = bounding_box_cache.ComputeWorldBound(geometry_prim)
8080
# Remove the prim's initial world translation and rotation while leaving spawned scale in the bounds.
8181
geometry_bounds.Transform(T_P_W)
82-
geometry_range = geometry_bounds.ComputeAlignedRange()
83-
if geometry_range.IsEmpty():
82+
geometry_range_P = geometry_bounds.ComputeAlignedRange()
83+
if geometry_range_P.IsEmpty():
8484
continue
8585

86-
lower_P = np.minimum(lower_P, np.asarray(geometry_range.GetMin(), dtype=np.float64))
87-
upper_P = np.maximum(upper_P, np.asarray(geometry_range.GetMax(), dtype=np.float64))
86+
lower_P = np.minimum(lower_P, np.asarray(geometry_range_P.GetMin(), dtype=np.float64))
87+
upper_P = np.maximum(upper_P, np.asarray(geometry_range_P.GetMax(), dtype=np.float64))
8888
found_geometry = True
8989

9090
prim_path = prim.GetPath()
@@ -99,17 +99,24 @@ def compute_spawned_geometry_bounds_in_entity_frame(
9999
scene: InteractiveScene,
100100
entity_name: str,
101101
) -> AxisAlignedBoundingBox:
102-
"""Build spawned geometry bounds in the entity's live pose frame."""
102+
"""Build spawned geometry bounds expressed in entity frame ``E``.
103+
104+
Args:
105+
scene: Interactive scene containing the spawned entity.
106+
entity_name: Scene entity whose prim-local coordinate system defines frame ``E``.
107+
108+
Returns:
109+
One batched AABB aligned with frame ``E`` and measured from its origin.
110+
The bounds do not change when the entity's live pose changes.
111+
"""
103112
assert (
104113
entity_name in scene.rigid_objects or entity_name in scene.extras
105114
), f"Scene entity '{entity_name}' must be a rigid object or an AssetBaseCfg scene entry."
106115
is_rigid_object = entity_name in scene.rigid_objects
107116
resolved_geometry_prim_path = getattr(scene.cfg, entity_name).prim_path.format(ENV_REGEX_NS=scene.env_regex_ns)
108117

109-
minimum_points_in_entity_frame_by_environment = torch.empty(
110-
(scene.num_envs, 3), dtype=torch.float32, device=scene.device
111-
)
112-
maximum_points_in_entity_frame_by_environment = torch.empty_like(minimum_points_in_entity_frame_by_environment)
118+
minimum_points_E_by_environment = torch.empty((scene.num_envs, 3), dtype=torch.float32, device=scene.device)
119+
maximum_points_E_by_environment = torch.empty_like(minimum_points_E_by_environment)
113120
coverage_count = [0] * scene.num_envs
114121

115122
for representative_prim, environment_ids in _get_spawned_entity_groups(
@@ -122,14 +129,10 @@ def compute_spawned_geometry_bounds_in_entity_frame(
122129
if is_rigid_object
123130
else representative_prim
124131
)
125-
geometry_bounds_in_entity_frame = _compute_geometry_bounds_in_prim_frame(entity_frame_prim).to(scene.device)
132+
geometry_bounds_E = _compute_geometry_bounds_in_prim_frame(entity_frame_prim).to(scene.device)
126133
environment_indices = torch.tensor(environment_ids, dtype=torch.long, device=scene.device)
127-
minimum_points_in_entity_frame_by_environment[environment_indices] = geometry_bounds_in_entity_frame.min_point[
128-
0
129-
]
130-
maximum_points_in_entity_frame_by_environment[environment_indices] = geometry_bounds_in_entity_frame.max_point[
131-
0
132-
]
134+
minimum_points_E_by_environment[environment_indices] = geometry_bounds_E.min_point[0]
135+
maximum_points_E_by_environment[environment_indices] = geometry_bounds_E.max_point[0]
133136
for environment_id in environment_ids:
134137
coverage_count[environment_id] += 1
135138

@@ -138,13 +141,13 @@ def compute_spawned_geometry_bounds_in_entity_frame(
138141
f" {coverage_count}."
139142
)
140143
return AxisAlignedBoundingBox(
141-
min_point=minimum_points_in_entity_frame_by_environment,
142-
max_point=maximum_points_in_entity_frame_by_environment,
144+
min_point=minimum_points_E_by_environment,
145+
max_point=maximum_points_E_by_environment,
143146
)
144147

145148

146149
class AssetBaseCfgPoseReader:
147-
"""Read current poses for one named AssetBaseCfg scene entry."""
150+
"""Read current ``T_W_E`` poses for an AssetBaseCfg scene entry defining frame ``E``."""
148151

149152
def __init__(self, scene: InteractiveScene, entity_name: str):
150153
assert entity_name in scene.extras, f"Scene entity '{entity_name}' must be an AssetBaseCfg scene entry."
@@ -172,11 +175,13 @@ def __init__(self, scene: InteractiveScene, entity_name: str):
172175
)
173176

174177
def get_pose_w(self) -> torch.Tensor:
175-
"""Return the entry's current world pose as ``(x, y, z, qx, qy, qz, qw)``."""
178+
"""Return ``T_W_E`` as ``(x, y, z, qx, qy, qz, qw)``."""
176179
position_w_buffer, orientation_w_buffer = self._frame_view.get_world_poses()
177-
pose_w = torch.cat((position_w_buffer.torch, orientation_w_buffer.torch), dim=-1)
178-
assert pose_w.shape == (self._num_envs, 7), (
179-
f"AssetBaseCfg scene entry '{self._entity_name}' returned pose shape {tuple(pose_w.shape)}; "
180+
t_W_E = position_w_buffer.torch
181+
q_W_E = orientation_w_buffer.torch
182+
T_W_E = torch.cat((t_W_E, q_W_E), dim=-1)
183+
assert T_W_E.shape == (self._num_envs, 7), (
184+
f"AssetBaseCfg scene entry '{self._entity_name}' returned pose shape {tuple(T_W_E.shape)}; "
180185
f"expected ({self._num_envs}, 7)."
181186
)
182-
return pose_w
187+
return T_W_E

isaaclab_arena/tasks/predicates/spatial.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,13 @@ def object_on_destination(
218218

219219
unwrapped_env = get_env(env)
220220
arena_world = unwrapped_env.arena_world
221-
object_pose_w = arena_world.get_pose_w(object_cfg.name)
222-
destination_pose_w = arena_world.get_pose_w(destination_cfg.name)
221+
T_W_O = arena_world.get_pose_w(object_cfg.name)
222+
T_W_D = arena_world.get_pose_w(destination_cfg.name)
223223
object_center_over_destination = object_bounds_center_over_destination(
224-
T_W_O=object_pose_w,
225-
object_bounds_center_O=arena_world.get_local_aabb(object_cfg.name).center,
226-
T_W_D=destination_pose_w,
227-
destination_bounds_D=arena_world.get_local_aabb(destination_cfg.name),
224+
T_W_O=T_W_O,
225+
object_bounds_center_O=arena_world.get_aabb_in_entity_frame(object_cfg.name).center,
226+
T_W_D=T_W_D,
227+
destination_bounds_D=arena_world.get_aabb_in_entity_frame(destination_cfg.name),
228228
)
229229

230230
contact_sensor: ContactSensor = unwrapped_env.scene[contact_sensor_cfg.name]

isaaclab_arena/tests/test_arena_world.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,24 @@ def _test_arena_world(_simulation_app) -> bool:
3636
arena_world = env.unwrapped.arena_world
3737
try:
3838
env.reset()
39-
initial_pose_w = arena_world.get_pose_w(sphere_name).clone()
40-
assert initial_pose_w.shape == (num_envs, 7)
39+
# S is the sphere frame.
40+
T_W_S_initial = arena_world.get_pose_w(sphere_name).clone()
41+
assert T_W_S_initial.shape == (num_envs, 7)
4142
assert arena_world.get_linear_velocity_w(sphere_name).shape == (num_envs, 3)
4243

43-
local_aabb = arena_world.get_local_aabb(sphere_name)
44-
assert local_aabb.min_point.shape == (num_envs, 3)
45-
assert local_aabb.max_point.shape == (num_envs, 3)
46-
assert arena_world.get_local_aabb(sphere_name) is local_aabb
44+
sphere_bounds_S = arena_world.get_aabb_in_entity_frame(sphere_name)
45+
assert sphere_bounds_S.min_point.shape == (num_envs, 3)
46+
assert sphere_bounds_S.max_point.shape == (num_envs, 3)
47+
assert arena_world.get_aabb_in_entity_frame(sphere_name) is sphere_bounds_S
4748

48-
moved_pose_w = initial_pose_w.clone()
49-
moved_pose_w[:, 0] += 0.25
50-
env.unwrapped.scene[sphere_name].write_root_pose_to_sim(moved_pose_w)
51-
torch.testing.assert_close(arena_world.get_pose_w(sphere_name), moved_pose_w)
52-
assert arena_world.get_local_aabb(sphere_name) is local_aabb
49+
T_W_S_moved = T_W_S_initial.clone()
50+
T_W_S_moved[:, 0] += 0.25
51+
env.unwrapped.scene[sphere_name].write_root_pose_to_sim(T_W_S_moved)
52+
torch.testing.assert_close(arena_world.get_pose_w(sphere_name), T_W_S_moved)
53+
assert arena_world.get_aabb_in_entity_frame(sphere_name) is sphere_bounds_S
5354

5455
env.reset()
55-
assert arena_world.get_local_aabb(sphere_name) is local_aabb
56+
assert arena_world.get_aabb_in_entity_frame(sphere_name) is sphere_bounds_S
5657
finally:
5758
env.close()
5859

0 commit comments

Comments
 (0)