88.. code-block:: bash
99
1010 # Usage
11- ./isaaclab.sh -p scripts/demos/bin_packing.py --num_envs 2048
11+ ./isaaclab.sh -p scripts/demos/bin_packing.py --num_envs 32
1212
1313"""
1414
2222from isaaclab .app import AppLauncher
2323
2424# add argparse arguments
25- parser = argparse .ArgumentParser (
26- description = "Demo on spawning different number of objects in multiple bin packing environments."
27- )
25+ parser = argparse .ArgumentParser (description = "Demo usage of RigidObjectCollection through bin packing example" )
2826parser .add_argument ("--num_envs" , type = int , default = 16 , help = "Number of environments to spawn." )
2927# append AppLauncher cli args
3028AppLauncher .add_app_launcher_args (parser )
3735
3836"""Rest everything follows."""
3937
38+ import math
4039import torch
4140
4241import isaaclab .sim as sim_utils
4342import isaaclab .utils .math as math_utils
44- from isaaclab .assets import AssetBaseCfg , RigidObject , RigidObjectCfg , RigidObjectCollection , RigidObjectCollectionCfg
43+ from isaaclab .assets import AssetBaseCfg , RigidObjectCfg , RigidObjectCollection , RigidObjectCollectionCfg
4544from isaaclab .scene import InteractiveScene , InteractiveSceneCfg
4645from isaaclab .sim import SimulationContext
4746from isaaclab .utils import Timer , configclass
5150# Scene Configuration
5251##
5352
53+ MAX_NUM_OBJECTS = 24
54+ MAX_OBJECTS_PER_BIN = 24
55+ MIN_OBJECTS_PER_BIN = 1
56+ NUM_OBJECTS_PER_LAYER = 4
57+ CACHE_HEIGHT = 2.5
5458POSE_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 )}
59+ VELOCITY_RANGE = {"roll" : (- 0.2 , 1.0 ), "pitch" : (- 0.2 , 1.0 ), "yaw" : (- 0.2 , 1.0 )}
5660
61+ # Object layout configuration
5762
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 ,
63+ GROCERIES = {
64+ "OBJECT_A" : sim_utils .UsdFileCfg (
65+ usd_path = f"{ ISAAC_NUCLEUS_DIR } /Props/YCB/Axis_Aligned_Physics/004_sugar_box.usd" ,
66+ rigid_props = sim_utils .RigidBodyPropertiesCfg (solver_position_iteration_count = 4 ),
67+ ),
68+ "OBJECT_B" : sim_utils .UsdFileCfg (
69+ usd_path = f"{ ISAAC_NUCLEUS_DIR } /Props/YCB/Axis_Aligned_Physics/003_cracker_box.usd" ,
70+ rigid_props = sim_utils .RigidBodyPropertiesCfg (solver_position_iteration_count = 4 ),
71+ ),
72+ "OBJECT_C" : sim_utils .UsdFileCfg (
73+ usd_path = f"{ ISAAC_NUCLEUS_DIR } /Props/YCB/Axis_Aligned_Physics/005_tomato_soup_can.usd" ,
74+ rigid_props = sim_utils .RigidBodyPropertiesCfg (solver_position_iteration_count = 4 ),
8775 ),
88- init_state = RigidObjectCfg .InitialStateCfg (pos = (0.0 , 0.0 , 2.0 )),
89- )
76+ "OBJECT_D" : sim_utils .UsdFileCfg (
77+ usd_path = f"{ ISAAC_NUCLEUS_DIR } /Props/YCB/Axis_Aligned_Physics/006_mustard_bottle.usd" ,
78+ rigid_props = sim_utils .RigidBodyPropertiesCfg (solver_position_iteration_count = 4 ),
79+ )
80+ }
9081
9182
9283@configclass
@@ -115,82 +106,103 @@ class MultiObjectSceneCfg(InteractiveSceneCfg):
115106 init_state = RigidObjectCfg .InitialStateCfg (pos = (0.0 , 0.0 , 0.15 )),
116107 )
117108
118- # object collection
119- object_collection : RigidObjectCollectionCfg = RigidObjectCollectionCfg (
109+ groceries : RigidObjectCollectionCfg = RigidObjectCollectionCfg (
120110 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- ),
111+ f"Object_{ label } _Layer{ layer } " : RigidObjectCfg (
112+ prim_path = f"/World/envs/env_.*/Object_{ label } _Layer{ layer } " ,
113+ init_state = RigidObjectCfg .InitialStateCfg (
114+ pos = (x , y , 0.2 + (layer ) * 0.2 )
115+ ),
116+ spawn = GROCERIES .get (f"OBJECT_{ label } " )
117+ )
118+ for layer in range (MAX_NUM_OBJECTS // NUM_OBJECTS_PER_LAYER )
119+ for label , (x , y ) in zip (["A" , "B" , "C" , "D" ], [(- 0.035 , - 0.1 ), (- 0.035 , 0.1 ), (0.035 , 0.1 ), (0.035 , - 0.1 )])
153120 }
154121 )
155122
156- def reset_object_collections (scene : InteractiveScene , view_ids : torch .Tensor ):
157- if len (view_ids ) == 0 :
158- return
159- rigid_object_collection : RigidObjectCollection = scene ["object_collection" ]
160- default_state_w = rigid_object_collection .data .default_object_state .clone ()
161- default_state_w [..., :3 ] = default_state_w [..., :3 ] + scene .env_origins .unsqueeze (1 )
162- default_state_w_view = rigid_object_collection .reshape_data_to_view (default_state_w )[view_ids ]
163- range_list = [POSE_RANGE .get (key , (0.0 , 0.0 )) for key in ["x" , "y" , "z" , "roll" , "pitch" , "yaw" ]]
164- ranges = torch .tensor (range_list , device = scene .device )
165- samples = math_utils .sample_uniform (ranges [:, 0 ], ranges [:, 1 ], (len (view_ids ), 6 ), device = scene .device )
166123
124+ def reset_object_collections (
125+ scene : InteractiveScene , collection_name : str , view_states : torch .Tensor , view_ids : torch .Tensor , randomize : bool = False
126+ ):
127+ rigid_object_collection : RigidObjectCollection = scene [collection_name ]
128+ sel_view_states = view_states [view_ids ]
129+ positions = sel_view_states [:, :3 ]
130+ orientations = sel_view_states [:, 3 :7 ]
167131 # poses
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- new_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" )
132+ if randomize :
133+ range_list = [POSE_RANGE .get (key , (0.0 , 0.0 )) for key in ["x" , "y" , "z" , "roll" , "pitch" , "yaw" ]]
134+ ranges = torch .tensor (range_list , device = scene .device )
135+ samples = math_utils .sample_uniform (ranges [:, 0 ], ranges [:, 1 ], (len (view_ids ), 6 ), device = scene .device )
136+ positions += samples [..., 0 :3 ]
180137
181- num_objects = rigid_object_collection .num_instances * rigid_object_collection .num_objects
138+ orientations_delta = math_utils .quat_from_euler_xyz (samples [..., 3 ], samples [..., 4 ], samples [..., 5 ])
139+ orientations = math_utils .convert_quat (orientations , to = "wxyz" )
140+ orientations = math_utils .quat_mul (orientations , orientations_delta )
141+ orientations = math_utils .convert_quat (orientations , to = "xyzw" )
182142
183- if len (view_ids ) != num_objects :
184- poses = torch .zeros ((num_objects , 7 ), device = scene .device )
185- poses [view_ids , :] = new_poses
186- velocities = torch .zeros ((num_objects , 6 ), device = scene .device )
187- velocities [view_ids , :] = new_velocities
143+ # velocities
144+ new_velocities = sel_view_states [:, 7 :13 ]
145+ if randomize :
146+ range_list = [VELOCITY_RANGE .get (key , (0.0 , 0.0 )) for key in ["x" , "y" , "z" , "roll" , "pitch" , "yaw" ]]
147+ ranges = torch .tensor (range_list , device = scene .device )
148+ samples = math_utils .sample_uniform (ranges [:, 0 ], ranges [:, 1 ], (len (view_ids ), 6 ), device = scene .device )
149+ new_velocities += samples
188150 else :
189- poses = new_poses
190- velocities = new_velocities
191-
192- rigid_object_collection .root_physx_view .set_transforms (poses , indices = view_ids .view (- 1 , 1 ))
193- rigid_object_collection .root_physx_view .set_velocities (velocities , indices = view_ids .view (- 1 , 1 ))
151+ new_velocities [:] = 0.0
152+
153+ view_states [view_ids , :7 ] = torch .concat ((positions , orientations ), dim = - 1 )
154+ view_states [view_ids , 7 :] = new_velocities
155+
156+ rigid_object_collection .root_physx_view .set_transforms (view_states [:, :7 ], indices = view_ids )
157+ rigid_object_collection .root_physx_view .set_velocities (view_states [:, 7 :], indices = view_ids )
158+
159+
160+ def build_grocery_defaults (
161+ num_envs : int ,
162+ device : str = "cpu" ,
163+ z_spacing : float = 0.1 ,
164+ cache_spacing : float = 0.25 ,
165+ num_objects_per_layer : int = NUM_OBJECTS_PER_LAYER ,
166+ max_objects_per_bin : int = MAX_OBJECTS_PER_BIN ,
167+ max_num_objects : int = MAX_NUM_OBJECTS ,
168+ cache_height : float = CACHE_HEIGHT
169+ ):
170+
171+ # The bin has a size of 0.2 x 0.3 x 0.15 m
172+ bin_x_dim , bin_y_dim , bin_z_dim = 0.2 , 0.3 , 0.15
173+ # First, we calculate the number of layers and objects per layer
174+ num_layers = math .ceil (max_objects_per_bin / num_objects_per_layer )
175+ num_x_objects = math .ceil (math .sqrt (num_objects_per_layer ))
176+ num_y_objects = math .ceil (num_objects_per_layer / num_x_objects )
177+ total_objects = num_x_objects * num_y_objects * num_layers
178+ # Then, we create a 3D grid that allows for IxJxN objects to be placed ontop of the bin.
179+ x = torch .linspace (- bin_x_dim * (2 / 6 ), bin_x_dim * (2 / 6 ), num_x_objects , device = device )
180+ y = torch .linspace (- bin_y_dim * (2 / 6 ), bin_y_dim * (2 / 6 ), num_y_objects , device = device )
181+ z = torch .linspace (0 , z_spacing * (num_layers - 1 ), num_layers , device = device ) + bin_z_dim * 2
182+ grid_z , grid_y , grid_x = torch .meshgrid (z , y , x , indexing = "ij" ) # Note Z first, allowing to naturally stack the layers.
183+ # Using this grid plus a reference quaternion, we can create the poses for the groceries to be spawned above the bin.
184+ ref_quat = torch .tensor ([[0.0 , 0.0 , 0.0 , 1.0 ]], device = device ).repeat (total_objects , 1 )
185+ positions = torch .stack ((grid_x .flatten (), grid_y .flatten (), grid_z .flatten ()), dim = - 1 )
186+ poses = torch .cat ((positions , ref_quat ), dim = - 1 )
187+ # Duplicate across environments, cap at max_num_objects
188+ active_spawn_poses = poses .unsqueeze (0 ).repeat (num_envs , 1 , 1 )[:, :max_num_objects , :]
189+
190+ # We'll also create a buffer for the cached groceries. They'll be spawned below the bin so they can't be seen.
191+ num_x_objects = math .ceil (math .sqrt (max_num_objects ))
192+ num_y_objects = math .ceil (max_num_objects / num_x_objects )
193+ # We create a XY grid only and fix the Z height for the cache.
194+ x = cache_spacing * torch .arange (num_x_objects , device = device )
195+ y = cache_spacing * torch .arange (num_y_objects , device = device )
196+ grid_y , grid_x = torch .meshgrid (y , x , indexing = "ij" )
197+ grid_z = cache_height * torch .ones_like (grid_x )
198+ # We can then create the poses for the cached groceries.
199+ ref_quat = torch .tensor ([[1.0 , 0.0 , 0.0 , 0.0 ]], device = device ).repeat (num_x_objects * num_y_objects , 1 )
200+ positions = torch .stack ((grid_x .flatten (), grid_y .flatten (), grid_z .flatten ()), dim = - 1 )
201+ poses = torch .cat ((positions , ref_quat ), dim = - 1 )
202+ # Duplicate across environments, cap at max_num_objects
203+ cached_spawn_poses = poses .unsqueeze (0 ).repeat (num_envs , 1 , 1 )[:, :max_num_objects , :]
204+
205+ return active_spawn_poses , cached_spawn_poses
194206
195207
196208##
@@ -202,24 +214,38 @@ def run_simulator(sim: SimulationContext, scene: InteractiveScene):
202214 """Runs the simulation loop."""
203215 # Extract scene entities
204216 # note: we only do this here for readability.
205- rigid_object : RigidObject = scene ["object" ]
206- rigid_object_collection : RigidObjectCollection = scene ["object_collection" ]
217+ rigid_object_collection : RigidObjectCollection = scene ["groceries" ]
207218 view_indices = torch .arange (scene .num_envs * rigid_object_collection .num_objects , device = scene .device )
219+ default_state_w = rigid_object_collection .data .default_object_state .clone ()
220+ default_state_w [..., :3 ] = default_state_w [..., :3 ] + scene .env_origins .unsqueeze (1 )
208221 # Define simulation stepping
209222 sim_dt = sim .get_physics_dt ()
210223 count = 0
224+
225+ active_spawn_poses , cached_spawn_poses = build_grocery_defaults (scene .num_envs , scene .device , max_num_objects = rigid_object_collection .num_objects )
226+ active_spawn_poses [..., :3 ] += scene .env_origins .view (- 1 , 1 , 3 )
227+ cached_spawn_poses [..., :3 ] += scene .env_origins .view (- 1 , 1 , 3 )
228+ active_spawn_poses = rigid_object_collection .reshape_data_to_view (active_spawn_poses )
229+ cached_spawn_poses = rigid_object_collection .reshape_data_to_view (cached_spawn_poses )
230+ spawning_state_w_view_xyzw = rigid_object_collection .reshape_data_to_view (default_state_w ).clone ()
231+
232+ groceries_mask_helper = torch .arange (rigid_object_collection .num_objects * scene .num_envs , device = scene .device ) % rigid_object_collection .num_objects
211233 # Simulation loop
212234 while simulation_app .is_running ():
213235 # Reset
214236 if count % 250 == 0 :
215237 # reset counter
216238 count = 0
217- # reset the scene entities
218- # object
219- root_state = rigid_object .data .default_root_state .clone ()
220- root_state [:, :3 ] += scene .env_origins
221- # object collection
222- reset_object_collections (scene , view_indices )
239+ num_active_groceries = torch .randint (MIN_OBJECTS_PER_BIN , rigid_object_collection .num_objects , (scene .num_envs , 1 ), device = scene .device )
240+ groceries_mask = (groceries_mask_helper .view (scene .num_envs , - 1 ) < num_active_groceries ).view (- 1 , 1 )
241+ spawning_state_w_view_xyzw [:, :7 ] = cached_spawn_poses * (~ groceries_mask ) + active_spawn_poses * groceries_mask
242+ # Retrieve positions
243+ reset_object_collections (scene , "groceries" , spawning_state_w_view_xyzw , view_indices [~ groceries_mask .view (- 1 )])
244+ reset_object_collections (scene , "groceries" , spawning_state_w_view_xyzw , view_indices [groceries_mask .view (- 1 )], randomize = True )
245+ # reset_object_collections(scene, "groceries", spawning_state_w_view_xyzw, view_indices)
246+ random_masses = torch .rand (rigid_object_collection .num_instances * rigid_object_collection .num_objects , device = scene .device ) * 0.2 + 0.2
247+ rigid_object_collection .root_physx_view .set_masses (random_masses .cpu (), view_indices .cpu ())
248+ rigid_object_collection .root_physx_view .set_disable_gravities ((~ groceries_mask ).cpu (), indices = view_indices .cpu ())
223249 scene .reset ()
224250 print ("[INFO]: Resetting scene state..." )
225251
@@ -231,7 +257,9 @@ def run_simulator(sim: SimulationContext, scene: InteractiveScene):
231257 object_pos_b_view = rigid_object_collection .reshape_data_to_view (object_pos_b )
232258 inbound_mask = (- 0.2 < object_pos_b_view [:, 0 ]) & (object_pos_b_view [:, 0 ] < 0.2 )
233259 inbound_mask &= (- 0.3 < object_pos_b_view [:, 1 ]) & (object_pos_b_view [:, 1 ] < 0.3 )
234- reset_object_collections (scene , view_indices [~ inbound_mask ])
260+ out_bound_indices = view_indices [~ inbound_mask ]
261+ if len (out_bound_indices ) > 0 :
262+ reset_object_collections (scene , "groceries" , spawning_state_w_view_xyzw , view_indices [out_bound_indices ])
235263 # Increment counter
236264 count += 1
237265 # Update buffers
0 commit comments