Skip to content

Commit 36c6f54

Browse files
Sphere and initial object velocity (#479)
## Summary New sphere in object library and ObjectBase has now optional initial velocity ## Detailed description Now it can be spawn spheres, and objects like the sphere can be initialized with linear velocity.
1 parent 869535c commit 36c6f54

File tree

5 files changed

+169
-7
lines changed

5 files changed

+169
-7
lines changed

isaaclab_arena/assets/object_base.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(
4242
self.prim_path = prim_path
4343
self.object_type = object_type
4444
self.initial_pose: Pose | PoseRange | None = None
45+
self.initial_velocity: tuple[float, float, float] | None = None
4546
self.object_cfg = None
4647
self.event_cfg = None
4748
self.relations: list[RelationBase] = []
@@ -80,6 +81,21 @@ def set_initial_pose(self, pose: Pose | PoseRange) -> None:
8081
self.object_cfg.init_state.rot = initial_pose.rotation_wxyz
8182
self.event_cfg = self._init_event_cfg()
8283

84+
def set_initial_linear_velocity(self, velocity: tuple[float, float, float]) -> None:
85+
"""Set / override the initial linear velocity and rebuild derived configs.
86+
87+
The velocity is applied as the ``init_state.lin_vel`` on the underlying
88+
config (``RigidObjectCfg`` or ``ArticulationCfg``) and is also restored
89+
on every environment reset via the reset event.
90+
91+
Args:
92+
velocity: Linear velocity ``(vx, vy, vz)`` in the world frame.
93+
"""
94+
self.initial_velocity = velocity
95+
if self.object_cfg is not None and hasattr(self.object_cfg.init_state, "lin_vel"):
96+
self.object_cfg.init_state.lin_vel = velocity
97+
self.event_cfg = self._init_event_cfg()
98+
8399
def _requires_reset_pose_event(self) -> bool:
84100
"""Whether a reset-event for the initial pose should be generated.
85101
@@ -91,7 +107,7 @@ def _requires_reset_pose_event(self) -> bool:
91107
)
92108

93109
def _init_event_cfg(self) -> EventTermCfg | None:
94-
"""Build the ``EventTermCfg`` for resetting this object's pose."""
110+
"""Build the ``EventTermCfg`` for resetting this object's pose and velocity."""
95111
if not self._requires_reset_pose_event():
96112
return None
97113

@@ -106,13 +122,15 @@ def _init_event_cfg(self) -> EventTermCfg | None:
106122
},
107123
)
108124
else:
125+
params: dict = {
126+
"pose": initial_pose,
127+
"asset_cfg": SceneEntityCfg(self.name),
128+
"velocity": self.initial_velocity,
129+
}
109130
return EventTermCfg(
110131
func=set_object_pose,
111132
mode="reset",
112-
params={
113-
"pose": initial_pose,
114-
"asset_cfg": SceneEntityCfg(self.name),
115-
},
133+
params=params,
116134
)
117135

118136
def get_relations(self) -> list[RelationBase]:

isaaclab_arena/assets/object_library.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
if TYPE_CHECKING:
1212
from isaaclab_arena.assets.hdr_image import HDRImage
13+
from isaaclab.assets import RigidObjectCfg
1314
from isaaclab.sim.spawners.from_files.from_files_cfg import GroundPlaneCfg
1415
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR
1516

@@ -419,6 +420,61 @@ def __init__(
419420
super().__init__(instance_name=instance_name, prim_path=prim_path, initial_pose=initial_pose)
420421

421422

423+
@register_asset
424+
class Sphere(LibraryObject):
425+
"""
426+
A sphere with rigid body physics (dynamic by default).
427+
"""
428+
429+
name = "sphere"
430+
tags = ["object"]
431+
object_type = ObjectType.SPAWNER
432+
scale = (1.0, 1.0, 1.0)
433+
default_spawner_cfg = sim_utils.SphereCfg(
434+
radius=0.1,
435+
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.8, 0.2, 0.2)),
436+
collision_props=sim_utils.CollisionPropertiesCfg(),
437+
rigid_props=sim_utils.RigidBodyPropertiesCfg(
438+
solver_position_iteration_count=16,
439+
solver_velocity_iteration_count=1,
440+
max_angular_velocity=1000.0,
441+
max_linear_velocity=1000.0,
442+
max_depenetration_velocity=5.0,
443+
disable_gravity=False,
444+
),
445+
mass_props=sim_utils.MassPropertiesCfg(mass=0.25),
446+
)
447+
448+
def __init__(
449+
self,
450+
instance_name: str | None = None,
451+
prim_path: str | None = None,
452+
initial_pose: Pose | None = None,
453+
scale: tuple[float, float, float] | None = None,
454+
spawner_cfg: sim_utils.SphereCfg = default_spawner_cfg,
455+
):
456+
self.spawner_cfg = spawner_cfg
457+
super().__init__(
458+
instance_name=instance_name,
459+
prim_path=prim_path,
460+
initial_pose=initial_pose,
461+
scale=scale,
462+
)
463+
464+
def _generate_spawner_cfg(self) -> RigidObjectCfg:
465+
object_cfg = RigidObjectCfg(
466+
prim_path=self.prim_path,
467+
spawn=self.spawner_cfg,
468+
)
469+
object_cfg = self._add_initial_pose_to_cfg(object_cfg)
470+
if self.initial_velocity is not None:
471+
object_cfg.init_state.lin_vel = self.initial_velocity
472+
return object_cfg
473+
474+
def _requires_reset_pose_event(self) -> bool:
475+
return self.get_initial_pose() is not None and self.reset_pose
476+
477+
422478
@register_asset
423479
class DomeLight(LibraryObject):
424480
"""A dome light, optionally textured with an HDR image environment map.

isaaclab_arena/terms/events.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def set_object_pose(
1616
env_ids: torch.Tensor,
1717
asset_cfg: SceneEntityCfg,
1818
pose: Pose,
19+
velocity: tuple[float, float, float] | None = None,
1920
) -> None:
2021
if env_ids is None:
2122
return
@@ -27,7 +28,12 @@ def set_object_pose(
2728
pose_t_xyz_q_wxyz[:, :3] += env.scene.env_origins[env_ids]
2829
# Set the pose and velocity
2930
asset.write_root_pose_to_sim(pose_t_xyz_q_wxyz, env_ids=env_ids)
30-
asset.write_root_velocity_to_sim(torch.zeros(1, 6, device=env.device), env_ids=env_ids)
31+
if velocity is not None:
32+
vel = torch.zeros(num_envs, 6, device=env.device)
33+
vel[:, :3] = torch.tensor(velocity, device=env.device)
34+
asset.write_root_velocity_to_sim(vel, env_ids=env_ids)
35+
else:
36+
asset.write_root_velocity_to_sim(torch.zeros(1, 6, device=env.device), env_ids=env_ids)
3137

3238

3339
def set_object_pose_per_env(

isaaclab_arena/tests/test_detect_object_type.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def _test_detect_object_type_for_all_objects(simulation_app):
7272
# the simple RIGID/ARTICULATION classification:
7373
# - For example, the "peg" and "hole" assets have both RigidBodyAPI and ArticulationRootAPI
7474
# applied simultaneously, sometimes in different prim layers.
75-
if object_asset.name not in ("hole", "peg", "small_gear", "medium_gear", "large_gear", "gear_base"):
75+
if object_asset.name not in ("hole", "peg", "small_gear", "medium_gear", "large_gear", "gear_base", "sphere"):
7676
print(f"Automatically classifying: {object_asset.name}")
7777
detected_object_type = detect_object_type(usd_path=object_asset.usd_path)
7878
print(f"database object type: {object_asset.object_type}")

isaaclab_arena/tests/test_events.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
HEADLESS = True
1313
INITIAL_POSITION_EPS = 0.1 # The cracker box falls slightly.
1414

15+
# For object initial velocity test: min displacement in velocity direction
16+
OBJECT_VELOCITY_MIN_DISPLACEMENT = 0.05 # minimum movement in meters
17+
1518

1619
def _test_set_object_pose_per_env_event(simulation_app):
1720
"""Returns a scene which we use for these tests."""
@@ -107,6 +110,76 @@ def _test_set_object_pose_per_env_event(simulation_app):
107110
return True
108111

109112

113+
def _test_object_moves_with_initial_velocity(simulation_app):
114+
"""Test that a sphere moves with the given initial velocity after reset."""
115+
import isaaclab.sim as sim_utils
116+
117+
from isaaclab_arena.assets.asset_registry import AssetRegistry
118+
from isaaclab_arena.cli.isaaclab_arena_cli import get_isaaclab_arena_cli_parser
119+
from isaaclab_arena.environments.arena_env_builder import ArenaEnvBuilder
120+
from isaaclab_arena.environments.isaaclab_arena_environment import IsaacLabArenaEnvironment
121+
from isaaclab_arena.scene.scene import Scene
122+
from isaaclab_arena.utils.pose import Pose
123+
124+
asset_registry = AssetRegistry()
125+
no_gravity_cfg = sim_utils.SphereCfg(
126+
radius=0.1,
127+
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.8, 0.2, 0.2)),
128+
collision_props=sim_utils.CollisionPropertiesCfg(),
129+
rigid_props=sim_utils.RigidBodyPropertiesCfg(
130+
solver_position_iteration_count=16,
131+
solver_velocity_iteration_count=1,
132+
max_angular_velocity=1000.0,
133+
max_linear_velocity=1000.0,
134+
max_depenetration_velocity=5.0,
135+
disable_gravity=True,
136+
),
137+
mass_props=sim_utils.MassPropertiesCfg(mass=0.25),
138+
)
139+
sphere = asset_registry.get_asset_by_name("sphere")(spawner_cfg=no_gravity_cfg)
140+
141+
initial_velocity = (-0.5, 0.0, 0.0) # There is a wall in +x
142+
sphere.set_initial_pose(Pose(position_xyz=(0.0, 0.0, 0.5), rotation_wxyz=(1.0, 0.0, 0.0, 0.0)))
143+
sphere.set_initial_linear_velocity(initial_velocity)
144+
145+
scene = Scene(assets=[sphere])
146+
isaaclab_arena_environment = IsaacLabArenaEnvironment(
147+
name="object_initial_velocity_test",
148+
scene=scene,
149+
)
150+
151+
args_cli = get_isaaclab_arena_cli_parser().parse_args([])
152+
args_cli.num_envs = 1
153+
env_builder = ArenaEnvBuilder(isaaclab_arena_environment, args_cli)
154+
env = env_builder.make_registered()
155+
env.reset()
156+
157+
try:
158+
initial_position = env.unwrapped.scene[sphere.name].data.root_pose_w[0, :3].clone()
159+
initial_position[:3] -= env.unwrapped.scene.env_origins[0]
160+
161+
for _ in tqdm.tqdm(range(NUM_STEPS)):
162+
with torch.inference_mode():
163+
actions = torch.zeros(env.action_space.shape, device=env.unwrapped.device)
164+
env.step(actions)
165+
166+
final_position = env.unwrapped.scene[sphere.name].data.root_pose_w[0, :3].clone()
167+
final_position[:3] -= env.unwrapped.scene.env_origins[0]
168+
displacement = final_position - initial_position
169+
assert (
170+
displacement[0].item() < -OBJECT_VELOCITY_MIN_DISPLACEMENT # - x because initial velocity is in -x
171+
), f"Object did not move with given velocity: displacement in x was {displacement[0].item()}"
172+
173+
except Exception as e:
174+
print(f"Error: {e}")
175+
return False
176+
177+
finally:
178+
env.close()
179+
180+
return True
181+
182+
110183
def test_set_object_post_per_env_event():
111184
result = run_simulation_app_function(
112185
_test_set_object_pose_per_env_event,
@@ -115,5 +188,14 @@ def test_set_object_post_per_env_event():
115188
assert result, f"Test {test_set_object_post_per_env_event.__name__} failed"
116189

117190

191+
def test_object_moves_with_initial_velocity():
192+
result = run_simulation_app_function(
193+
_test_object_moves_with_initial_velocity,
194+
headless=HEADLESS,
195+
)
196+
assert result, f"Test {test_object_moves_with_initial_velocity.__name__} failed"
197+
198+
118199
if __name__ == "__main__":
119200
test_set_object_post_per_env_event()
201+
test_object_moves_with_initial_velocity()

0 commit comments

Comments
 (0)