Skip to content

Commit 45b8452

Browse files
committed
add bin packing example
1 parent 34729a3 commit 45b8452

File tree

3 files changed

+280
-0
lines changed

3 files changed

+280
-0
lines changed
503 KB
Loading

docs/source/overview/showroom.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,29 @@ A few quick showroom scripts to run and checkout:
193193
:alt: Multiple assets managed through the same simulation handles
194194

195195

196+
- Use the composition of MultiAssetSpawner and RigidObjectCollection spawn different number of varying assets for each individual environment:
197+
198+
.. tab-set::
199+
:sync-group: os
200+
201+
.. tab-item:: :icon:`fa-brands fa-linux` Linux
202+
:sync: linux
203+
204+
.. code:: bash
205+
206+
./isaaclab.sh -p scripts/demos/bin_packing.py
207+
208+
.. tab-item:: :icon:`fa-brands fa-windows` Windows
209+
:sync: windows
210+
211+
.. code:: batch
212+
213+
isaaclab.bat -p scripts\demos\bin_packing.py
214+
215+
.. image:: ../_static/demos/bin_packing.jpg
216+
:width: 100%
217+
:alt: Spawning random number of random asset per env_id using combination os MultiAssetSpawner and RigidObjectCollection
218+
196219

197220
- Use the interactive scene and spawn a simple parallel robot for pick and place:
198221

scripts/demos/bin_packing.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""This script demonstrates how to spawn different number of objects in multiple environments.
7+
8+
.. code-block:: bash
9+
10+
# Usage
11+
./isaaclab.sh -p scripts/demos/bin_packing.py --num_envs 2048
12+
13+
"""
14+
15+
from __future__ import annotations
16+
17+
"""Launch Isaac Sim Simulator first."""
18+
19+
20+
import argparse
21+
22+
from isaaclab.app import AppLauncher
23+
24+
# add argparse arguments
25+
parser = argparse.ArgumentParser(
26+
description="Demo on spawning different number of objects in multiple bin packing environments."
27+
)
28+
parser.add_argument("--num_envs", type=int, default=16, help="Number of environments to spawn.")
29+
# append AppLauncher cli args
30+
AppLauncher.add_app_launcher_args(parser)
31+
# parse the arguments
32+
args_cli = parser.parse_args()
33+
34+
# launch omniverse app
35+
app_launcher = AppLauncher(args_cli)
36+
simulation_app = app_launcher.app
37+
38+
"""Rest everything follows."""
39+
40+
import torch
41+
42+
import isaaclab.sim as sim_utils
43+
import isaaclab.utils.math as math_utils
44+
from isaaclab.assets import AssetBaseCfg, RigidObject, RigidObjectCfg, RigidObjectCollection, RigidObjectCollectionCfg
45+
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
46+
from isaaclab.sim import SimulationContext
47+
from isaaclab.utils import Timer, configclass
48+
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
49+
50+
##
51+
# Scene Configuration
52+
##
53+
54+
POSE_RANGE = {"roll": (-3.14, 3.14), "pitch": (-3.14, 3.14), "yaw": (-3.14, 3.14)}
55+
VELOCITY_RANGE = {"roll": (-0.76, 0.76), "pitch": (-0.76, 0.76), "yaw": (-0.76, 0.76)}
56+
57+
58+
RANDOM_YCB_RIGID_OBJECT_CFG = RigidObjectCfg(
59+
prim_path="/World/envs/env_.*/Object",
60+
spawn=sim_utils.MultiAssetSpawnerCfg(
61+
assets_cfg=[
62+
sim_utils.UsdFileCfg(
63+
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/YCB/Axis_Aligned_Physics/004_sugar_box.usd",
64+
rigid_props=sim_utils.RigidBodyPropertiesCfg(solver_position_iteration_count=4),
65+
),
66+
sim_utils.UsdFileCfg(
67+
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/YCB/Axis_Aligned_Physics/003_cracker_box.usd",
68+
rigid_props=sim_utils.RigidBodyPropertiesCfg(solver_position_iteration_count=4),
69+
),
70+
sim_utils.UsdFileCfg(
71+
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/YCB/Axis_Aligned_Physics/005_tomato_soup_can.usd",
72+
rigid_props=sim_utils.RigidBodyPropertiesCfg(solver_position_iteration_count=4),
73+
),
74+
sim_utils.UsdFileCfg(
75+
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/YCB/Axis_Aligned_Physics/006_mustard_bottle.usd",
76+
rigid_props=sim_utils.RigidBodyPropertiesCfg(solver_position_iteration_count=4),
77+
),
78+
# note: the placeholder, this allows the effect of having less objects in some env ids
79+
sim_utils.SphereCfg(
80+
radius=0.1,
81+
rigid_props=sim_utils.RigidBodyPropertiesCfg(disable_gravity=True),
82+
collision_props=sim_utils.CollisionPropertiesCfg(collision_enabled=False),
83+
visible=False
84+
),
85+
],
86+
random_choice=True,
87+
),
88+
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)),
89+
)
90+
91+
92+
@configclass
93+
class MultiObjectSceneCfg(InteractiveSceneCfg):
94+
"""Configuration for a multi-object scene."""
95+
96+
# ground plane
97+
ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
98+
99+
# lights
100+
dome_light = AssetBaseCfg(
101+
prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
102+
)
103+
104+
# rigid object
105+
object: RigidObjectCfg = RigidObjectCfg(
106+
prim_path="/World/envs/env_.*/Object",
107+
spawn=sim_utils.UsdFileCfg(
108+
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/KLT_Bin/small_KLT.usd",
109+
scale=(2.0, 2.0, 2.0),
110+
rigid_props=sim_utils.RigidBodyPropertiesCfg(
111+
solver_position_iteration_count=4, solver_velocity_iteration_count=0, kinematic_enabled=True
112+
),
113+
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
114+
),
115+
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 0.15)),
116+
)
117+
118+
# object collection
119+
object_collection: RigidObjectCollectionCfg = RigidObjectCollectionCfg(
120+
rigid_objects={
121+
"Object_A_Layer1": RANDOM_YCB_RIGID_OBJECT_CFG.replace(
122+
prim_path="/World/envs/env_.*/Object_A_Layer1",
123+
init_state=RigidObjectCfg.InitialStateCfg(pos=(-0.035, -0.06, 0.2)),
124+
),
125+
"Object_B_Layer1": RANDOM_YCB_RIGID_OBJECT_CFG.replace(
126+
prim_path="/World/envs/env_.*/Object_B_Layer1",
127+
init_state=RigidObjectCfg.InitialStateCfg(pos=(-0.035, 0.06, 0.2)),
128+
),
129+
"Object_C_Layer1": RANDOM_YCB_RIGID_OBJECT_CFG.replace(
130+
prim_path="/World/envs/env_.*/Object_C_Layer1",
131+
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.035, 0.06, 0.2)),
132+
),
133+
"Object_D_Layer1": RANDOM_YCB_RIGID_OBJECT_CFG.replace(
134+
prim_path="/World/envs/env_.*/Object_D_Layer1",
135+
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.035, -0.06, 0.2)),
136+
),
137+
"Object_A_Layer2": RANDOM_YCB_RIGID_OBJECT_CFG.replace(
138+
prim_path="/World/envs/env_.*/Object_A_Layer2",
139+
init_state=RigidObjectCfg.InitialStateCfg(pos=(-0.035, -0.06, 0.4)),
140+
),
141+
"Object_B_Layer2": RANDOM_YCB_RIGID_OBJECT_CFG.replace(
142+
prim_path="/World/envs/env_.*/Object_B_Layer2",
143+
init_state=RigidObjectCfg.InitialStateCfg(pos=(-0.035, 0.06, 0.4)),
144+
),
145+
"Object_C_Layer2": RANDOM_YCB_RIGID_OBJECT_CFG.replace(
146+
prim_path="/World/envs/env_.*/Object_C_Layer2",
147+
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.035, 0.06, 0.4)),
148+
),
149+
"Object_D_Layer2": RANDOM_YCB_RIGID_OBJECT_CFG.replace(
150+
prim_path="/World/envs/env_.*/Object_D_Layer2",
151+
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.035, -0.06, 0.4)),
152+
),
153+
}
154+
)
155+
156+
157+
def reset_object_collections(scene: InteractiveScene, view_ids: torch.Tensor):
158+
if len(view_ids) == 0:
159+
return
160+
rigid_object_collection: RigidObjectCollection = scene["object_collection"]
161+
default_state_w = rigid_object_collection.data.default_object_state.clone()
162+
default_state_w[..., :3] = default_state_w[..., :3] + scene.env_origins.unsqueeze(1)
163+
default_state_w_view = rigid_object_collection.reshape_data_to_view(default_state_w)[view_ids]
164+
range_list = [POSE_RANGE.get(key, (0.0, 0.0)) for key in ["x", "y", "z", "roll", "pitch", "yaw"]]
165+
ranges = torch.tensor(range_list, device=scene.device)
166+
samples = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(view_ids), 6), device=scene.device)
167+
168+
positions = default_state_w_view[:, :3] + samples[..., 0:3]
169+
orientations_delta = math_utils.quat_from_euler_xyz(samples[..., 3], samples[..., 4], samples[..., 5])
170+
orientations = math_utils.quat_mul(default_state_w_view[:, 3:7], orientations_delta)
171+
# velocities
172+
range_list = [VELOCITY_RANGE.get(key, (0.0, 0.0)) for key in ["x", "y", "z", "roll", "pitch", "yaw"]]
173+
ranges = torch.tensor(range_list, device=scene.device)
174+
samples = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(view_ids), 6), device=scene.device)
175+
176+
velocities = default_state_w_view[:, 7:13] + samples
177+
new_poses = torch.concat((positions, orientations), dim=-1)
178+
179+
new_poses[..., 3:] = math_utils.convert_quat(new_poses[..., 3:], to="xyzw")
180+
rigid_object_collection.root_physx_view.set_transforms(new_poses, indices=view_ids.view(-1, 1))
181+
rigid_object_collection.root_physx_view.set_velocities(velocities, indices=view_ids.view(-1, 1))
182+
183+
184+
185+
##
186+
# Simulation Loop
187+
##
188+
189+
190+
def run_simulator(sim: SimulationContext, scene: InteractiveScene):
191+
"""Runs the simulation loop."""
192+
# Extract scene entities
193+
# note: we only do this here for readability.
194+
rigid_object: RigidObject = scene["object"]
195+
rigid_object_collection: RigidObjectCollection = scene["object_collection"]
196+
view_indices = torch.arange(scene.num_envs * rigid_object_collection.num_objects, device=scene.device)
197+
# Define simulation stepping
198+
sim_dt = sim.get_physics_dt()
199+
count = 0
200+
# Simulation loop
201+
while simulation_app.is_running():
202+
# Reset
203+
if count % 250 == 0:
204+
# reset counter
205+
count = 0
206+
# reset the scene entities
207+
# object
208+
root_state = rigid_object.data.default_root_state.clone()
209+
root_state[:, :3] += scene.env_origins
210+
rigid_object.write_root_pose_to_sim(root_state[:, :7])
211+
rigid_object.write_root_velocity_to_sim(root_state[:, 7:])
212+
# object collection
213+
reset_object_collections(scene, view_indices)
214+
scene.reset()
215+
print("[INFO]: Resetting scene state...")
216+
217+
# Write data to sim
218+
scene.write_data_to_sim()
219+
# Perform step
220+
sim.step()
221+
object_pos_b = rigid_object_collection.data.object_pos_w - scene.env_origins.unsqueeze(1)
222+
object_pos_b_view = rigid_object_collection.reshape_data_to_view(object_pos_b)
223+
inbound_mask = (-1.0 < object_pos_b_view[:, 0]) & (object_pos_b_view[:, 0] < 1.0)
224+
inbound_mask &= (-1.0 < object_pos_b_view[:, 1]) & (object_pos_b_view[:, 1] < 1.0)
225+
reset_object_collections(scene, view_indices[~inbound_mask])
226+
# Increment counter
227+
count += 1
228+
# Update buffers
229+
scene.update(sim_dt)
230+
231+
232+
def main():
233+
"""Main function."""
234+
# Load kit helper
235+
sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
236+
sim = SimulationContext(sim_cfg)
237+
# Set main camera
238+
sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
239+
240+
# Design scene
241+
scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=1.0, replicate_physics=False)
242+
with Timer("[INFO] Time to create scene: "):
243+
scene = InteractiveScene(scene_cfg)
244+
245+
# Play the simulator
246+
sim.reset()
247+
# Now we are ready!
248+
print("[INFO]: Setup complete...")
249+
# Run the simulator
250+
run_simulator(sim, scene)
251+
252+
253+
if __name__ == "__main__":
254+
# run the main execution
255+
main()
256+
# close sim app
257+
simulation_app.close()

0 commit comments

Comments
 (0)