Skip to content

Commit 35f8221

Browse files
committed
Derive stand native height from USD bbox with measured fallback
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
1 parent d02be42 commit 35f8221

2 files changed

Lines changed: 40 additions & 8 deletions

File tree

isaaclab_arena/embodiments/droid/droid.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
# SPDX-License-Identifier: Apache-2.0
55

66

7+
import functools
78
import torch
9+
import warnings
810
from abc import ABC
911
from dataclasses import MISSING
1012
from typing import Any
@@ -49,10 +51,37 @@
4951
_DEFAULT_STAND_HEIGHT_SCALE: float = 1.7
5052

5153
# The stand's top is pinned at the robot base and it extends downward to the floor, so scaling its
52-
# height moves the floor contact, not the mount. Measured native (scale=1.0) stand height in meters;
53-
# used to lift the robot base by the same amount the stand grows, keeping the stand's bottom on the
54-
# floor while the robot rides higher.
55-
_STAND_UNIT_HEIGHT_M: float = 0.795
54+
# height moves the floor contact, not the mount. We lift the robot base by the amount the stand
55+
# grows (native height x scale delta), keeping the stand's bottom on the floor while the robot rides
56+
# higher. The native height is read from the stand USD; this measured value is the fallback used when
57+
# the asset can't be opened (e.g. no asset resolver / network outside a running SimulationApp).
58+
_FALLBACK_STAND_UNIT_HEIGHT_M: float = 0.795
59+
60+
61+
@functools.lru_cache(maxsize=None)
62+
def _stand_unit_height_m(usd_path: str) -> float:
63+
"""Native (scale=1.0) z-height of the stand USD in meters, cached per asset path.
64+
65+
Falls back to ``_FALLBACK_STAND_UNIT_HEIGHT_M`` if the asset cannot be opened or measured.
66+
"""
67+
try:
68+
from pxr import Usd, UsdGeom
69+
70+
stage = Usd.Stage.Open(usd_path)
71+
assert stage is not None, f"could not open stand USD: {usd_path}"
72+
# Bound the asset's default prim (its geometry root), not the pseudo-root, so stray stage
73+
# prims (lights, cameras, guides) can't inflate the measured height.
74+
root_prim = stage.GetDefaultPrim() or stage.GetPseudoRoot()
75+
bound = UsdGeom.BBoxCache(Usd.TimeCode.Default(), [UsdGeom.Tokens.default_]).ComputeWorldBound(root_prim)
76+
height = bound.ComputeAlignedRange().GetSize()[2]
77+
assert height > 0.0, f"non-positive stand height {height} from {usd_path}"
78+
return height
79+
except Exception as exc: # noqa: BLE001 - any failure falls back to the measured constant
80+
warnings.warn(
81+
f"Falling back to {_FALLBACK_STAND_UNIT_HEIGHT_M} m for the stand height; "
82+
f"could not measure {usd_path}: {exc!r}"
83+
)
84+
return _FALLBACK_STAND_UNIT_HEIGHT_M
5685

5786

5887
class DroidEmbodimentBase(EmbodimentBase, ABC):
@@ -78,7 +107,8 @@ def __init__(
78107
self.scene_config = DroidSceneCfg()
79108
self.scene_config.stand.spawn.scale = (*_STAND_FOOTPRINT_SCALE_XY, stand_height)
80109
# Lift the robot base (and stand) so a taller/shorter stand keeps its bottom on the floor.
81-
self._robot_base_z_offset = _STAND_UNIT_HEIGHT_M * (stand_height - _DEFAULT_STAND_HEIGHT_SCALE)
110+
stand_unit_height = _stand_unit_height_m(self.scene_config.stand.spawn.usd_path)
111+
self._robot_base_z_offset = stand_unit_height * (stand_height - _DEFAULT_STAND_HEIGHT_SCALE)
82112
self.scene_config.robot.init_state.pos = self._lift_z(self.scene_config.robot.init_state.pos)
83113
self.scene_config.stand.init_state.pos = self._lift_z(self.scene_config.stand.init_state.pos)
84114
self.action_config = None

isaaclab_arena/tests/test_droid_stand_scale.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,21 @@ def _test_droid_stand_scale(simulation_app) -> bool:
1818
from isaaclab_arena.assets.registries import AssetRegistry
1919
from isaaclab_arena.embodiments.droid.droid import (
2020
_DEFAULT_STAND_HEIGHT_SCALE,
21-
_STAND_UNIT_HEIGHT_M,
2221
DroidAbsoluteJointPositionEmbodiment,
22+
_stand_unit_height_m,
2323
)
2424
from isaaclab_arena.utils.pose import Pose
2525

26-
expected_offset = _STAND_UNIT_HEIGHT_M * (_CUSTOM_STAND_HEIGHT - _DEFAULT_STAND_HEIGHT_SCALE)
27-
2826
try:
2927
# The default leaves the stand at its nominal scale and the robot base at z=0.
3028
default_emb = DroidAbsoluteJointPositionEmbodiment()
3129
assert tuple(default_emb.scene_config.stand.spawn.scale) == _DEFAULT_STAND_SCALE
3230
assert default_emb.scene_config.robot.init_state.pos[2] == 0.0
3331

32+
# The lift is driven by the stand's native height (read from its USD, with a fallback).
33+
unit_height = _stand_unit_height_m(default_emb.scene_config.stand.spawn.usd_path)
34+
expected_offset = unit_height * (_CUSTOM_STAND_HEIGHT - _DEFAULT_STAND_HEIGHT_SCALE)
35+
3436
# An override changes only the z-scale (x/y footprint and robot mesh untouched)...
3537
custom_emb = DroidAbsoluteJointPositionEmbodiment(stand_height=_CUSTOM_STAND_HEIGHT)
3638
assert tuple(custom_emb.scene_config.stand.spawn.scale) == _EXPECTED_CUSTOM_SCALE

0 commit comments

Comments
 (0)