Robot Arm: Completed Reference to Scaffold

Compare the pinned completed reference (left, removals in red) with the learning-path scaffold (right, additions in green).

9 files changed, 202 insertions(+), 395 deletions(-). All changed files are included; unchanged regions are omitted.

Source: isaac-sim/IsaacLabTutorial ยท committed local branch tips
reference: 9a4286b4bc8a5720ac97f87e5e0140f7542c22d9
scaffold: 798c28c553d471ee6afc6172ec2d2c618230a7c5

Files changed (9) hide show
  1. README.md +18 -0
  2. src/isaaclab_tutorial/tasks/place_vial/config/so101/__init__.py +10 -17
  3. src/isaaclab_tutorial/tasks/place_vial/config/so101/agents/rsl_rl_distillation_cfg.py +3 -37
  4. src/isaaclab_tutorial/tasks/place_vial/config/so101/agents/rsl_rl_ppo_cfg.py +3 -32
  5. src/isaaclab_tutorial/tasks/place_vial/config/so101/camera_env_cfg.py +15 -90
  6. src/isaaclab_tutorial/tasks/place_vial/config/so101/env_cfg.py +50 -217
  7. src/isaaclab_tutorial/tasks/place_vial/config/so101/inspection_env_cfg.py +34 -0
  8. src/isaaclab_tutorial/tasks/place_vial/config/so101/lesson_scaffold.py +61 -0
  9. tests/test_config.py +8 -2
README.md CHANGED
@@ -1,3 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # SO-101 vial placement
2
 
3
  <p align="center"><img src="media/demo.gif" alt="SO-101 workshop demonstration" width="100%"></p>
 
1
+ # Isaac Lab Tutorial Learning Path
2
+
3
+ This branch is the exercise scaffold for the completed code on `main`.
4
+ Follow the **Train a Robot Arm** module, applying its marked regions in lesson order.
5
+ Imports, workshop assets, the reset dataset, and supporting utilities are supplied.
6
+ The initial scene intentionally has no robot. Lesson 02 makes inspection runnable;
7
+ lesson 04 adds actions, lesson 05 the state teacher, and lesson 06 camera distillation.
8
+ Run the upstream tests after completing lesson 06, not against unfinished exercises.
9
+
10
+ Compare with the completed reference without switching branches:
11
+
12
+ ```bash
13
+ git diff main -- src/
14
+ ```
15
+
16
+ The reference documentation below describes the completed task and includes
17
+ optional upstream workflows outside the course.
18
+
19
  # SO-101 vial placement
20
 
21
  <p align="center"><img src="media/demo.gif" alt="SO-101 workshop demonstration" width="100%"></p>
src/isaaclab_tutorial/tasks/place_vial/config/so101/__init__.py CHANGED
@@ -4,25 +4,18 @@ import gymnasium as gym
4
 
5
  _PACKAGE = "isaaclab_tutorial.tasks.place_vial.config.so101"
6
 
7
- gym.register(
8
- id="IsaacTutorial-Place-Vial-SO101",
9
- entry_point="isaaclab.envs:ManagerBasedRLEnv",
10
- disable_env_checker=True,
11
- kwargs={
12
- "env_cfg_entry_point": f"{_PACKAGE}.env_cfg:SO101VialEnvCfg",
13
- "rsl_rl_cfg_entry_point": f"{_PACKAGE}.agents.rsl_rl_ppo_cfg:SO101StatePPORunnerCfg",
14
- "default_agent": "rsl_rl",
15
- },
 
 
 
16
- )
 
17
 
18
  gym.register(
19
- id="IsaacTutorial-Place-Vial-SO101-Camera",
20
  entry_point="isaaclab.envs:ManagerBasedRLEnv",
21
  disable_env_checker=True,
22
- kwargs={
23
- "env_cfg_entry_point": f"{_PACKAGE}.camera_env_cfg:SO101VialCameraEnvCfg",
24
- "rsl_rl_cfg_entry_point": f"{_PACKAGE}.agents.rsl_rl_distillation_cfg:SO101CameraDistillationRunnerCfg",
25
- "rsl_rl_ppo_cfg_entry_point": f"{_PACKAGE}.agents.rsl_rl_ppo_cfg:SO101CameraPPORunnerCfg",
26
- "default_agent": "rsl_rl",
27
- },
28
  )
 
4
 
5
  _PACKAGE = "isaaclab_tutorial.tasks.place_vial.config.so101"
6
 
 
 
 
7
+ # BEGIN lesson-01-registration
 
 
 
8
+ # Complete task registration in the matching lesson.
9
+ # END lesson-01-registration
10
+
11
+ # BEGIN lesson-06-camera-registration
12
+ # Complete task registration in the matching lesson.
13
+ # END lesson-06-camera-registration
14
+
15
 
16
  gym.register(
17
+ id="IsaacTutorial-Inspect-SO101",
18
  entry_point="isaaclab.envs:ManagerBasedRLEnv",
19
  disable_env_checker=True,
 
20
+ kwargs={"env_cfg_entry_point": f"{_PACKAGE}.inspection_env_cfg:SO101InspectionEnvCfg"},
 
 
 
 
21
  )
src/isaaclab_tutorial/tasks/place_vial/config/so101/agents/rsl_rl_distillation_cfg.py CHANGED
@@ -23,42 +23,8 @@ class BoundedTeacherDistillation(Distillation):
23
  return actions
24
 
25
 
 
26
  @configclass
27
  class SO101CameraDistillationRunnerCfg(RslRlDistillationRunnerCfg):
28
- """DAgger-style distillation: the student acts, the loaded state teacher labels every visited state."""
29
-
30
- seed = 42
31
- num_steps_per_env = 32
32
- max_iterations = 1600
33
- save_interval = 100
34
- experiment_name = "so101_vial_camera_distillation"
35
- run_name = ""
36
- obs_groups = {"student": ["wrist_rgb", "proprioception"], "teacher": ["teacher_state"]}
37
- clip_actions = 1.0
38
- student = RslRlCNNModelCfg(
39
- # Compact encoder for the 64 x 48 wrist image.
40
- cnn_cfg=RslRlCNNModelCfg.CNNCfg(
41
- output_channels=[16, 32, 32], kernel_size=[5, 3, 3], stride=[2, 2, 2], activation="elu"
42
- ),
43
- hidden_dims=[256, 128],
44
- activation="elu",
45
- obs_normalization=True,
46
- distribution_cfg=RslRlCNNModelCfg.GaussianDistributionCfg(init_std=0.05, std_type="log"),
47
- )
48
- # Must mirror the state actor so its checkpoint loads; the teacher always acts deterministically.
49
- teacher = RslRlMLPModelCfg(
50
- hidden_dims=[256, 256, 128],
51
- activation="elu",
52
- obs_normalization=True,
53
- distribution_cfg=RslRlMLPModelCfg.GaussianDistributionCfg(init_std=0.2, std_type="log", std_range=(0.05, 0.3)),
54
- )
55
- algorithm = RslRlDistillationAlgorithmCfg(
56
- class_name=(
57
- "isaaclab_tutorial.tasks.place_vial.config.so101.agents.rsl_rl_distillation_cfg:BoundedTeacherDistillation"
58
- ),
59
- num_learning_epochs=4,
60
- learning_rate=5.0e-4,
61
- gradient_length=1,
62
- max_grad_norm=1.0,
63
- loss_type="mse",
64
- )
 
23
  return actions
24
 
25
 
26
+ # BEGIN lesson-06-distillation
27
  @configclass
28
  class SO101CameraDistillationRunnerCfg(RslRlDistillationRunnerCfg):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
30
+ # END lesson-06-distillation
 
 
 
 
 
 
 
 
 
src/isaaclab_tutorial/tasks/place_vial/config/so101/agents/rsl_rl_ppo_cfg.py CHANGED
@@ -5,40 +5,11 @@ from isaaclab.utils.configclass import configclass
5
  from isaaclab_rl.rsl_rl import RslRlCNNModelCfg, RslRlMLPModelCfg, RslRlOnPolicyRunnerCfg, RslRlPpoAlgorithmCfg
6
 
7
 
 
8
  @configclass
9
  class SO101StatePPORunnerCfg(RslRlOnPolicyRunnerCfg):
10
- """State teacher: fully observed actor, privileged critic."""
11
-
12
- seed = 42
13
- num_steps_per_env = 64
14
- max_iterations = 800
15
- save_interval = 50
16
- experiment_name = "so101_vial_state"
17
- run_name = ""
18
- obs_groups = {"actor": ["policy"], "critic": ["critic"]}
19
- clip_actions = 1.0
20
- actor = RslRlMLPModelCfg(
21
- hidden_dims=[256, 256, 128],
22
- activation="elu",
23
- obs_normalization=True,
24
- # Clamp the std: with clipped actions and an entropy bonus, PPO otherwise grows it without bound.
25
- distribution_cfg=RslRlMLPModelCfg.GaussianDistributionCfg(init_std=0.2, std_type="log", std_range=(0.05, 0.3)),
26
- )
27
- critic = RslRlMLPModelCfg(hidden_dims=[256, 256, 128], activation="elu", obs_normalization=True)
28
- algorithm = RslRlPpoAlgorithmCfg(
29
- value_loss_coef=1.0,
30
- use_clipped_value_loss=True,
31
- clip_param=0.2,
32
- entropy_coef=0.005,
33
- num_learning_epochs=5,
34
- num_mini_batches=8,
35
- learning_rate=3.0e-4,
36
- schedule="adaptive",
37
- gamma=0.995,
38
- lam=0.95,
39
- desired_kl=0.01,
40
- max_grad_norm=1.0,
41
- )
42
 
43
 
44
  @configclass
 
5
  from isaaclab_rl.rsl_rl import RslRlCNNModelCfg, RslRlMLPModelCfg, RslRlOnPolicyRunnerCfg, RslRlPpoAlgorithmCfg
6
 
7
 
8
+ # BEGIN lesson-05-ppo
9
  @configclass
10
  class SO101StatePPORunnerCfg(RslRlOnPolicyRunnerCfg):
 
 
 
 
 
 
 
 
 
11
+ pass
 
 
 
 
 
 
 
 
 
12
+ # END lesson-05-ppo
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
 
15
  @configclass
src/isaaclab_tutorial/tasks/place_vial/config/so101/camera_env_cfg.py CHANGED
@@ -48,98 +48,32 @@ _HORIZONTAL_APERTURE = 20.955 * 80 / 64
48
  WRIST_FOCAL_LENGTH_PIXELS = 80 * _FOCAL_LENGTH / _HORIZONTAL_APERTURE
49
 
50
 
 
51
  @configclass
52
  class SO101CameraSceneCfg(SO101SceneCfg):
53
- """SO-101 scene with the rectified pinhole wrist camera and handles to the randomized visual materials."""
54
-
55
- wrist_camera = CameraCfg(
56
- prim_path="{ENV_REGEX_NS}/Robot/gripper/wowrobo_2MP_camera/pinhole",
57
- spawn=PinholeCameraCfg(focal_length=_FOCAL_LENGTH, horizontal_aperture=_HORIZONTAL_APERTURE),
58
- offset=CameraCfg.OffsetCfg(convention="opengl"),
59
- data_types=["rgb"],
60
- width=80,
61
- height=60,
62
- update_period=1.0 / 30.0,
63
- update_latest_camera_pose=True,
64
- renderer_cfg=_camera_renderer_cfg(),
65
- background_color=(0.3, 0.3, 0.3),
66
- )
67
-
68
- # The Newton renderer only reads material color.
69
- robot_visual = VisualMaterialCfg(prim_path="{ENV_REGEX_NS}/Robot/Looks/material_a_d_printed", spawn=None)
70
- desk_visual = VisualMaterialCfg(prim_path="{ENV_REGEX_NS}/Desk/Looks/Wood", spawn=None)
71
- rack_visual = VisualMaterialCfg(prim_path="{ENV_REGEX_NS}/Rack/WorkshopVisual/Looks/OmniPBR", spawn=None)
72
- vial_body_visual = VisualMaterialCfg(prim_path="{ENV_REGEX_NS}/Vial/Looks/Vial_plastic", spawn=None)
73
- vial_cap_visual = VisualMaterialCfg(prim_path="{ENV_REGEX_NS}/Vial/Looks/Vial_cap", spawn=None)
74
- vial_label_visual = VisualMaterialCfg(prim_path="{ENV_REGEX_NS}/Vial/Looks/Label", spawn=None)
75
-
76
- def __post_init__(self):
77
- self.robot.spawn.hide_camera_housing = True
78
- self.vial.spawn.usd_path = str(VIAL_LABELED_USD)
79
 
80
 
 
81
  @configclass
82
  class WristImageCfg(ObsGroup):
83
- """Two-frame wrist RGB history with episode-consistent photometric (and, for sim2real, intrinsics) variation."""
84
-
85
- image = ObsTerm(
86
- func=mdp.wrist_rgb,
87
- params={
88
- "sensor_cfg": SceneEntityCfg("wrist_camera"),
89
- "exposure_range": (0.75, 1.25),
90
- "contrast_range": (0.85, 1.15),
91
- "white_balance_range": (0.90, 1.10),
92
- "brightness_range": (-0.05, 0.05),
93
- "gamma_range": preset(default=(1.0, 1.0), sim2real=(0.85, 1.15)),
94
- "blur_range": preset(default=(0.0, 0.0), sim2real=(0.0, 0.5)),
95
- "history_length": 2,
96
- "projection_size": POLICY_IMAGE_SIZE,
97
- "focal_length_pixels": WRIST_FOCAL_LENGTH_PIXELS,
98
- "focal_scale_range": preset(default=(1.0, 1.0), sim2real=(0.95, 1.05)),
99
- "principal_point_pixels": preset(default=0.0, sim2real=1.5),
100
- "radial_distortion_range": preset(default=(0.0, 0.0), sim2real=(-0.04, 0.04)),
101
- },
102
- noise=UniformNoiseCfg(n_min=-0.025, n_max=0.025),
103
- )
104
-
105
- def __post_init__(self):
106
- self.enable_corruption = True
107
 
108
 
 
109
  @configclass
110
  class ProprioceptionCfg(ObsGroup):
111
- """Proprioceptive observations available on the real robot, in the encoders' (possibly biased) coordinates."""
112
-
113
- joint_pos = ObsTerm(
114
- func=mdp.encoder_joint_pos,
115
- params={"asset_cfg": SceneEntityCfg("robot")},
116
- noise=UniformNoiseCfg(n_min=-0.01, n_max=0.01),
117
- )
118
- joint_vel = ObsTerm(
119
- func=mdp.joint_vel,
120
- params={"asset_cfg": SceneEntityCfg("robot")},
121
- noise=UniformNoiseCfg(n_min=-0.02, n_max=0.02),
122
- )
123
- joint_target = ObsTerm(
124
- func=mdp.encoder_joint_target,
125
- params={"asset_cfg": SceneEntityCfg("robot")},
126
- noise=UniformNoiseCfg(n_min=-0.005, n_max=0.005),
127
- )
128
- previous_action = ObsTerm(func=mdp.last_action)
129
-
130
- def __post_init__(self):
131
- self.enable_corruption = True
132
- self.concatenate_terms = True
133
 
134
 
 
135
  @configclass
136
  class CameraObservationsCfg:
137
- """Deployable student inputs, the state teacher's inputs (distillation labels), and the privileged critic."""
138
-
139
- wrist_rgb: WristImageCfg = WristImageCfg()
140
- proprioception: ProprioceptionCfg = ProprioceptionCfg()
141
- teacher_state: PolicyStateGroupCfg = PolicyStateGroupCfg()
142
- critic: CriticStateGroupCfg = CriticStateGroupCfg()
143
 
144
 
145
  def _color_event(materials: str, color_range) -> EventTerm:
@@ -173,17 +107,8 @@ class CameraSim2RealEventsCfg(Sim2RealEventsCfg):
173
  )
174
 
175
 
 
176
  @configclass
177
  class SO101VialCameraEnvCfg(SO101VialEnvCfg):
178
- """Vial placement from wrist RGB and proprioception, distilled from the state teacher."""
179
-
180
- scene: SO101CameraSceneCfg = SO101CameraSceneCfg(num_envs=1024, env_spacing=0.9, replicate_physics=True)
181
- observations: CameraObservationsCfg = CameraObservationsCfg()
182
- events: EventsCfg = preset(default=EventsCfg(), sim2real=CameraSim2RealEventsCfg())
183
-
184
- def play_mode(self):
185
- from isaaclab_tutorial.utils import evaluation
186
-
187
- super().play_mode()
188
- if not evaluation.EXACT_EVALUATION_ACTIVE:
189
- self.scene.num_envs = min(self.scene.num_envs, 8)
 
48
  WRIST_FOCAL_LENGTH_PIXELS = 80 * _FOCAL_LENGTH / _HORIZONTAL_APERTURE
49
 
50
 
51
+ # BEGIN lesson-06-SO101CameraSceneCfg
52
  @configclass
53
  class SO101CameraSceneCfg(SO101SceneCfg):
54
+ pass
 
55
+ # END lesson-06-SO101CameraSceneCfg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
 
58
+ # BEGIN lesson-06-WristImageCfg
59
  @configclass
60
  class WristImageCfg(ObsGroup):
 
 
 
 
61
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
+ # END lesson-06-WristImageCfg
 
63
 
64
 
65
+ # BEGIN lesson-06-ProprioceptionCfg
66
  @configclass
67
  class ProprioceptionCfg(ObsGroup):
 
 
68
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
+ # END lesson-06-ProprioceptionCfg
 
70
 
71
 
72
+ # BEGIN lesson-06-CameraObservationsCfg
73
  @configclass
74
  class CameraObservationsCfg:
75
+ pass
 
76
+ # END lesson-06-CameraObservationsCfg
 
 
 
77
 
78
 
79
  def _color_event(materials: str, color_range) -> EventTerm:
 
107
  )
108
 
109
 
110
+ # BEGIN lesson-06-SO101VialCameraEnvCfg
111
  @configclass
112
  class SO101VialCameraEnvCfg(SO101VialEnvCfg):
 
 
 
 
 
 
113
+ pass
 
 
 
114
+ # END lesson-06-SO101VialCameraEnvCfg
 
src/isaaclab_tutorial/tasks/place_vial/config/so101/env_cfg.py CHANGED
@@ -14,7 +14,10 @@ from pxr import Gf, Sdf, Usd, UsdGeom, UsdPhysics, UsdShade
14
 
15
  import isaaclab.sim as sim_utils
16
  from isaaclab.assets import AssetBaseCfg, RigidObjectCfg
17
- from isaaclab.envs import ManagerBasedRLEnvCfg
 
 
 
18
  from isaaclab.envs.mdp.actions.actions_cfg import RelativeJointPositionActionCfg
19
  from isaaclab.managers import EventTermCfg as EventTerm
20
  from isaaclab.managers import ObservationGroupCfg as ObsGroup
@@ -45,6 +48,14 @@ from isaaclab_tutorial.assets import DESK_USD, RACK_USD, RESET_DATASET, VIAL_USD
45
  from isaaclab_tutorial.tasks.place_vial import mdp
46
  from isaaclab_tutorial.tasks.place_vial.reset.curriculum import ALL_PHASES, CANONICAL_START
47
 
 
 
 
 
 
 
 
 
48
  JOINTS = ["shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll", "gripper"]
49
  ARM_JOINTS = JOINTS[:-1]
50
 
@@ -438,77 +449,14 @@ def _jaw_support_sensor(link: str) -> ContactSensorCfg:
438
  class SO101SceneCfg(InteractiveSceneCfg):
439
  """One SO-101, one vial, one rack, and a desk; sim2real adds the gray mat and jaw/support contact sensing."""
440
 
 
441
- robot = SO101_CFG.replace(
442
- prim_path="{ENV_REGEX_NS}/Robot",
443
- init_state=SO101_CFG.init_state.replace(
444
- pos=preset(default=(-0.05, 0.0, 0.0), sim2real=(-0.05, 0.0, BASE_HEIGHT_CORRECTION)),
445
- # +90 degrees about world Z (XYZW).
446
- rot=(0.0, 0.0, 0.7071068, 0.7071068),
447
- joint_pos=dict(zip(JOINTS, WORKSHOP_INITIAL_JOINT_POSITION, strict=True)),
448
- ),
449
- soft_joint_pos_limit_factor=0.98,
450
- )
451
 
452
- vial = RigidObjectCfg(
453
- prim_path="{ENV_REGEX_NS}/Vial",
454
- spawn=VialSpawnCfg(
455
- usd_path=str(VIAL_USD),
456
- rigid_props=preset(
457
- default=None,
458
- physx=PhysxRigidBodyCfg(solver_position_iteration_count=PHYSX_SOLVER_POSITION_ITERATIONS),
459
- ),
460
- activate_contact_sensors=True,
461
- randomize_diameters=preset(default=False, sim2real=True),
462
- ),
463
- init_state=RigidObjectCfg.InitialStateCfg(
464
- pos=TABLETOP_VIAL_POSITION,
465
- # Horizontal vial: +90 degrees about world Y (XYZW).
466
- rot=(0.0, 0.7071068, 0.0, 0.7071068),
467
- ),
468
- )
469
-
470
- rack = RigidObjectCfg(
471
- prim_path="{ENV_REGEX_NS}/Rack",
472
- spawn=sim_utils.UsdFileCfg(usd_path=str(RACK_USD), func=_spawn_usd_with_contact_material),
473
- init_state=RigidObjectCfg.InitialStateCfg(pos=(0.18, 0.08, 0.04)),
474
- )
475
-
476
- desk = AssetBaseCfg(
477
- prim_path="{ENV_REGEX_NS}/Desk",
478
- spawn=sim_utils.UsdFileCfg(usd_path=str(DESK_USD), func=_spawn_usd_with_contact_material),
479
- init_state=AssetBaseCfg.InitialStateCfg(pos=(0.22, 0.0, 0.032), rot=(0.0, 0.0, 0.7071068, 0.7071068)),
480
- )
481
-
482
- support = preset(
483
- default=None,
484
- sim2real=RigidObjectCfg(
485
- prim_path="{ENV_REGEX_NS}/Support",
486
- spawn=SupportSpawnCfg(
487
- size=(0.39, 0.39, 0.005),
488
- rigid_props=sim_utils.UsdPhysicsRigidBodyCfg(kinematic_enabled=True),
489
- collision_props=sim_utils.UsdPhysicsCollisionCfg(),
490
- mass_props=sim_utils.MassCfg(mass=1.0),
491
- physics_material=WORKSHOP_CONTACT_MATERIAL,
492
- visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.07, 0.07, 0.07)),
493
- ),
494
- init_state=RigidObjectCfg.InitialStateCfg(pos=(0.22, 0.02, 0.0325)),
495
- ),
496
- )
497
-
498
- # Unfiltered: OV PhysX cannot build a filtered contact view for the gripper link in a cloned scene.
499
- fixed_jaw_contact = ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/gripper", history_length=4)
500
- moving_jaw_contact = ContactSensorCfg(
501
- prim_path="{ENV_REGEX_NS}/Robot/moving_jaw_so101_v1",
502
- filter_prim_paths_expr=["{ENV_REGEX_NS}/Vial"],
503
- history_length=4,
504
- )
505
- vial_rack_contact = ContactSensorCfg(
506
- prim_path="{ENV_REGEX_NS}/Vial",
507
- filter_prim_paths_expr=["{ENV_REGEX_NS}/Rack"],
508
- history_length=4,
509
- )
510
- fixed_jaw_support = preset(default=None, sim2real=_jaw_support_sensor("gripper"))
511
- moving_jaw_support = preset(default=None, sim2real=_jaw_support_sensor("moving_jaw_so101_v1"))
512
 
513
  light = AssetBaseCfg(
514
  prim_path="/World/Light",
@@ -521,108 +469,39 @@ class SO101SceneCfg(InteractiveSceneCfg):
521
  )
522
 
523
 
 
524
  @configclass
525
  class ActionsCfg:
526
- """Joint targets relative to the measured position, matching the real SO-101 position interface."""
527
-
528
- arm_action = RelativeJointPositionActionCfg(
529
- asset_name="robot",
530
- joint_names=ARM_JOINTS,
531
- preserve_order=True,
532
- # Small offsets stall the real shoulder and elbow, so sim2real gives them larger steps.
533
- scale=preset(
534
- default=0.033,
535
- sim2real={
536
- "shoulder_pan": 0.033,
537
- "shoulder_lift": 0.12,
538
- "elbow_flex": 0.07,
539
- "wrist_flex": 0.033,
540
- "wrist_roll": 0.033,
541
- },
542
- ),
543
- use_zero_offset=True,
544
- )
545
- gripper_action = RelativeJointPositionActionCfg(
546
- asset_name="robot",
547
- joint_names=["gripper"],
548
- # Avoid opening a grasp rapidly from a small policy bias.
549
- scale=0.02,
550
- use_zero_offset=True,
551
- )
552
 
553
 
 
554
  @configclass
555
  class PolicyStateGroupCfg(ObsGroup):
556
- """Fully observed state actor inputs."""
557
-
558
- joint_pos = ObsTerm(func=mdp.joint_pos, params={"asset_cfg": SceneEntityCfg("robot", joint_names=JOINTS)})
559
- joint_vel = ObsTerm(func=mdp.joint_vel, params={"asset_cfg": SceneEntityCfg("robot", joint_names=JOINTS)})
560
- joint_target = ObsTerm(func=mdp.joint_target, params={"asset_cfg": SceneEntityCfg("robot", joint_names=JOINTS)})
561
- previous_action = ObsTerm(func=mdp.last_action)
562
- end_effector = ObsTerm(
563
- func=mdp.body_state_b,
564
- params={
565
- "body_asset_cfg": SceneEntityCfg("robot", body_names="gripper"),
566
- "base_asset_cfg": SceneEntityCfg("robot"),
567
- },
568
- )
569
- vial = ObsTerm(
570
- func=mdp.body_state_b,
571
- params={"body_asset_cfg": SceneEntityCfg("vial"), "base_asset_cfg": SceneEntityCfg("robot")},
572
- )
573
- vial_in_rack = ObsTerm(func=mdp.vial_in_rack_frame)
574
- placement = ObsTerm(func=mdp.placement_features)
575
- # Latched milestones make the once-per-episode milestone rewards Markov.
576
- progress = ObsTerm(func=mdp.progress_flags)
577
-
578
- def __post_init__(self):
579
- self.enable_corruption = False
580
- self.concatenate_terms = True
581
 
582
 
 
583
  @configclass
584
  class CriticStateGroupCfg(PolicyStateGroupCfg):
585
- """Privileged training critic inputs."""
586
-
587
- contact = ObsTerm(func=mdp.jaw_contact_flags)
588
 
589
 
 
590
  @configclass
591
  class ObservationsCfg:
592
- policy: PolicyStateGroupCfg = PolicyStateGroupCfg()
593
- critic: CriticStateGroupCfg = CriticStateGroupCfg()
594
 
595
 
 
596
  @configclass
597
  class EventsCfg:
598
- """Task-horizon resets from the physics-validated dataset plus modest vial variation."""
599
-
600
- vial_material = EventTerm(
601
- func=mdp.randomize_rigid_body_material,
602
- mode="startup",
603
- params={
604
- "asset_cfg": SceneEntityCfg("vial"),
605
- "static_friction_range": preset(default=VIAL_FRICTION_RANGE, physx=PHYSX_VIAL_FRICTION_RANGE),
606
- "dynamic_friction_range": preset(default=VIAL_FRICTION_RANGE, physx=PHYSX_VIAL_FRICTION_RANGE),
607
- "restitution_range": (0.0, 0.02),
608
- "num_buckets": 32,
609
- },
610
- )
611
- vial_mass = EventTerm(
612
- func=mdp.randomize_rigid_body_mass,
613
- mode="startup",
614
- params={
615
- "asset_cfg": SceneEntityCfg("vial"),
616
- # Newton cannot reliably infer mass from the detailed mesh.
617
- "mass_distribution_params": (0.015, 0.025),
618
- "operation": "abs",
619
- },
620
- )
621
- reset_from_dataset = EventTerm(
622
- func=mdp.ResetFromDataset,
623
- mode="reset",
624
- params={"dataset_path": str(RESET_DATASET), "sequential": False, "phase_weights": ALL_PHASES},
625
- )
626
 
627
 
628
  def _material_event(asset_cfg: SceneEntityCfg, friction_range: tuple[float, float]) -> EventTerm:
@@ -702,43 +581,18 @@ class Sim2RealEventsCfg(EventsCfg):
702
  )
703
 
704
 
 
705
  @configclass
706
  class RewardsCfg:
707
- """Sparse physical milestones, a success bonus, two dense shaping terms, and light regularization."""
708
-
709
- # Sim2real aims the pad centre slightly above the cap, favouring a shallower pinch clear of the mat.
710
- approach_progress = RewTerm(
711
- func=mdp.approach_progress, weight=1.0, params={"height_offset": preset(default=0.0, sim2real=0.008)}
712
- )
713
- held_goal = RewTerm(func=mdp.held_goal, weight=0.1)
714
- milestones = RewTerm(func=mdp.milestones, weight=10.0)
715
- success = RewTerm(func=mdp.success, weight=200.0)
716
- vial_lost = RewTerm(
717
- func=mdp.is_terminated_term, weight=-50.0, params={"term_keys": ["vial_out_of_bounds", "vial_dropped"]}
718
- )
719
- action_rate = RewTerm(func=mdp.action_rate_l2, weight=-0.002)
720
- joint_velocity = RewTerm(func=mdp.joint_vel_l2, weight=-0.0002)
721
- joint_limits = RewTerm(func=mdp.joint_pos_limits, weight=-1.0)
722
- jaw_support_force = preset(
723
- default=None,
724
- sim2real=RewTerm(func=mdp.jaw_support_force, weight=-8.0, params={"free_force": 0.25, "force_span": 2.0}),
725
- )
726
 
727
 
 
728
  @configclass
729
  class TerminationsCfg:
730
- success = DoneTerm(func=mdp.placement_success)
731
- vial_dropped = DoneTerm(func=mdp.vial_dropped)
732
- # The robot base frame is yawed +90 degrees, so these bounds are 0.5 m to its sides and 0.62 m ahead/behind.
733
- vial_out_of_bounds = DoneTerm(
734
- func=mdp.out_of_bound,
735
- params={
736
- "asset_cfg": SceneEntityCfg("vial"),
737
- "in_bound_range": {"x": (-0.55, 0.45), "y": (-0.62, 0.62), "z": (-0.01, 1.0)},
738
- },
739
- )
740
- unstable_robot = DoneTerm(func=mdp.abnormal_robot_state)
741
- time_out = DoneTerm(func=mdp.time_out, time_out=True)
742
 
743
 
744
  @configclass
@@ -768,44 +622,23 @@ class PhysicsCfg(PresetCfg):
768
  default = newton_mjwarp
769
 
770
 
 
771
  @configclass
772
  class SO101VialEnvCfg(ManagerBasedRLEnvCfg):
773
- """State task trained from physics-validated reset poses."""
774
 
775
- scene: SO101SceneCfg = SO101SceneCfg(num_envs=4096, env_spacing=0.9, replicate_physics=True)
776
- actions: ActionsCfg = ActionsCfg()
777
- observations: ObservationsCfg = ObservationsCfg()
778
- events: EventsCfg = preset(default=EventsCfg(), sim2real=Sim2RealEventsCfg())
779
- rewards: RewardsCfg = RewardsCfg()
780
- terminations: TerminationsCfg = TerminationsCfg()
 
 
781
 
782
  def __post_init__(self):
783
  self.decimation = 4
784
- # The measured shoulder response is slower, so sim2real attempts get more time.
785
- self.episode_length_s = preset(default=20.0, sim2real=30.0)
786
- self.is_finite_horizon = False
787
  self.sim.dt = 1.0 / 120.0
788
  self.sim.render_interval = self.decimation
789
  self.sim.physics = PhysicsCfg()
790
  self.sim.default_visualizer_cfg = VisualizerCfg(eye=(0.64, 0.0, 0.36), lookat=(0.19, 0.02, 0.075))
791
-
792
- def play_mode(self):
793
- """Play and evaluate complete episodes from the home pose, in dataset order.
794
-
795
- The nominal task pins the vial to its workshop material and mass; sim2real keeps its physics randomization.
796
- """
797
- from isaaclab_tutorial.utils import evaluation
798
-
799
- requested_num_envs = self.scene.num_envs
800
- super().play_mode()
801
- if evaluation.EXACT_EVALUATION_ACTIVE:
802
- self.scene.num_envs = min(requested_num_envs, evaluation.EVALUATION_EPISODES)
803
- else:
804
- self.scene.num_envs = min(self.scene.num_envs, 16)
805
- self.events.reset_from_dataset.params["sequential"] = True
806
- self.events.reset_from_dataset.params["phase_weights"] = CANONICAL_START
807
- if not isinstance(self.events, Sim2RealEventsCfg):
808
- self.events.vial_material.params.update(
809
- static_friction_range=(0.7, 0.7), dynamic_friction_range=(0.7, 0.7), restitution_range=(0.0, 0.0)
810
- )
811
- self.events.vial_mass.params["mass_distribution_params"] = (0.020, 0.020)
 
14
 
15
  import isaaclab.sim as sim_utils
16
  from isaaclab.assets import AssetBaseCfg, RigidObjectCfg
17
+ from isaaclab.envs import (
18
+ ManagerBasedRLEnvCfg,
19
+ VideoRecorderCfg, # noqa: F401 -- used by the lesson 04 replacement
20
+ )
21
  from isaaclab.envs.mdp.actions.actions_cfg import RelativeJointPositionActionCfg
22
  from isaaclab.managers import EventTermCfg as EventTerm
23
  from isaaclab.managers import ObservationGroupCfg as ObsGroup
 
48
  from isaaclab_tutorial.tasks.place_vial import mdp
49
  from isaaclab_tutorial.tasks.place_vial.reset.curriculum import ALL_PHASES, CANONICAL_START
50
 
51
+ from .lesson_scaffold import ( # noqa: F401 -- supplied for the lesson 01-04 replacements
52
+ AgentObservationGroupsCfg,
53
+ EmptyActionsCfg,
54
+ InspectionObservationsCfg,
55
+ LessonEventsCfg,
56
+ LessonTerminationsCfg,
57
+ )
58
+
59
  JOINTS = ["shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll", "gripper"]
60
  ARM_JOINTS = JOINTS[:-1]
61
 
 
449
  class SO101SceneCfg(InteractiveSceneCfg):
450
  """One SO-101, one vial, one rack, and a desk; sim2real adds the gray mat and jaw/support contact sensing."""
451
 
452
+ # BEGIN lesson-02-robot
453
+ robot = None
 
 
 
454
+ # END lesson-02-robot
 
 
 
 
 
455
 
456
+ # BEGIN lesson-03-objects
 
 
 
 
 
 
 
 
 
 
 
 
457
+ # Add the vial, rack, desk, and contact sensors in lesson 03.
 
458
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
+ # END lesson-03-objects
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
  light = AssetBaseCfg(
462
  prim_path="/World/Light",
 
469
  )
470
 
471
 
472
+ # BEGIN lesson-04-actions
473
  @configclass
474
  class ActionsCfg:
 
 
 
 
475
+ pass
476
+ # END lesson-04-actions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
 
478
 
479
+ # BEGIN lesson-05-policy-observations
480
  @configclass
481
  class PolicyStateGroupCfg(ObsGroup):
 
 
 
 
 
 
 
 
482
+ pass
 
 
 
 
 
 
 
 
 
483
+ # END lesson-05-policy-observations
 
 
 
 
 
 
484
 
485
 
486
+ # BEGIN lesson-05-critic-observations
487
  @configclass
488
  class CriticStateGroupCfg(PolicyStateGroupCfg):
489
+ pass
 
490
+ # END lesson-05-critic-observations
491
 
492
 
493
+ # BEGIN lesson-05-observation-groups
494
  @configclass
495
  class ObservationsCfg:
496
+ pass
497
+ # END lesson-05-observation-groups
498
 
499
 
500
+ # BEGIN lesson-05-dataset-resets
501
  @configclass
502
  class EventsCfg:
 
 
 
 
 
503
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
+ # END lesson-05-dataset-resets
 
 
 
 
505
 
506
 
507
  def _material_event(asset_cfg: SceneEntityCfg, friction_range: tuple[float, float]) -> EventTerm:
 
581
  )
582
 
583
 
584
+ # BEGIN lesson-05-rewards
585
  @configclass
586
  class RewardsCfg:
 
 
 
587
+ pass
 
 
 
 
 
588
+ # END lesson-05-rewards
 
 
 
 
 
 
 
 
 
589
 
590
 
591
+ # BEGIN lesson-05-terminations
592
  @configclass
593
  class TerminationsCfg:
 
 
 
 
 
594
+ pass
 
 
 
 
 
595
+ # END lesson-05-terminations
596
 
597
 
598
  @configclass
 
622
  default = newton_mjwarp
623
 
624
 
625
+ # BEGIN lesson-environment
626
  @configclass
627
  class SO101VialEnvCfg(ManagerBasedRLEnvCfg):
628
+ """Inspection scaffold; lesson 04 adds actions and lesson 05 adds training."""
629
 
630
+ scene: SO101SceneCfg = SO101SceneCfg(num_envs=1, env_spacing=0.9, replicate_physics=True)
631
+ actions: EmptyActionsCfg = EmptyActionsCfg()
632
+ observations: InspectionObservationsCfg = InspectionObservationsCfg()
 
 
633
+ events: LessonEventsCfg = LessonEventsCfg()
634
+ rewards = None
635
+ terminations = None
636
 
637
  def __post_init__(self):
638
  self.decimation = 4
 
 
639
+ self.episode_length_s = 20.0
640
  self.sim.dt = 1.0 / 120.0
641
  self.sim.render_interval = self.decimation
642
  self.sim.physics = PhysicsCfg()
643
  self.sim.default_visualizer_cfg = VisualizerCfg(eye=(0.64, 0.0, 0.36), lookat=(0.19, 0.02, 0.075))
 
 
 
 
 
 
 
 
 
644
+ # END lesson-environment
 
 
 
 
 
 
 
 
 
 
 
src/isaaclab_tutorial/tasks/place_vial/config/so101/inspection_env_cfg.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Inspect the robot and scene before adding policy controls."""
2
+
3
+ from isaaclab.envs import ManagerBasedRLEnvCfg, VideoRecorderCfg
4
+ from isaaclab.utils.configclass import configclass
5
+ from isaaclab.visualizers import VisualizerCfg
6
+
7
+ from isaaclab_tasks.utils import preset
8
+
9
+ from .env_cfg import PhysicsCfg, SO101SceneCfg
10
+ from .lesson_scaffold import EmptyActionsCfg, InspectionObservationsCfg, LessonEventsCfg
11
+
12
+
13
+ @configclass
14
+ class SO101InspectionEnvCfg(ManagerBasedRLEnvCfg):
15
+ scene: SO101SceneCfg = SO101SceneCfg(num_envs=1, env_spacing=0.9, replicate_physics=True)
16
+ actions: EmptyActionsCfg = EmptyActionsCfg()
17
+ observations: InspectionObservationsCfg = InspectionObservationsCfg()
18
+ events: LessonEventsCfg = LessonEventsCfg()
19
+ rewards = None
20
+ terminations = None
21
+
22
+ def __post_init__(self):
23
+ self.episode_length_s = 20.0
24
+ self.decimation = 4
25
+ self.sim.dt = 1.0 / 120.0
26
+ self.sim.render_interval = self.decimation
27
+ self.sim.physics = PhysicsCfg()
28
+ self.sim.default_visualizer_cfg = VisualizerCfg(eye=(0.64, 0.0, 0.36), lookat=(0.19, 0.02, 0.075))
29
+ self.video_recorders = preset(
30
+ default=[],
31
+ record_video=[VideoRecorderCfg(
32
+ source="visualizer:newton", output_dir="videos/inspection", video_length=120, fps=30,
33
+ )],
34
+ )
src/isaaclab_tutorial/tasks/place_vial/config/so101/lesson_scaffold.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Small runnable scene scaffold used before the teacher configuration is assembled."""
2
+
3
+ # Imports are supplied for the lesson snippets, including unfinished regions.
4
+ # ruff: noqa: F401
5
+
6
+ from isaaclab.managers import EventTermCfg as EventTerm
7
+ from isaaclab.managers import ObservationGroupCfg as ObsGroup
8
+ from isaaclab.managers import ObservationTermCfg as ObsTerm
9
+ from isaaclab.managers import SceneEntityCfg
10
+ from isaaclab.managers import TerminationTermCfg as DoneTerm
11
+ from isaaclab.utils.configclass import configclass
12
+
13
+ from isaaclab_tutorial.tasks.place_vial import mdp
14
+
15
+
16
+ @configclass
17
+ class EmptyActionsCfg:
18
+ pass
19
+
20
+
21
+ @configclass
22
+ class JointObservationsCfg(ObsGroup):
23
+ joint_pos = ObsTerm(func=mdp.joint_pos, params={"asset_cfg": SceneEntityCfg("robot")})
24
+ joint_vel = ObsTerm(func=mdp.joint_vel, params={"asset_cfg": SceneEntityCfg("robot")})
25
+
26
+ def __post_init__(self):
27
+ self.enable_corruption = False
28
+ self.concatenate_terms = True
29
+
30
+
31
+ @configclass
32
+ class InspectionObservationsCfg:
33
+ policy: JointObservationsCfg = JointObservationsCfg()
34
+
35
+
36
+ # BEGIN lesson-04-AgentObservationsCfg
37
+ @configclass
38
+ class AgentObservationsCfg(JointObservationsCfg):
39
+ pass
40
+ # END lesson-04-AgentObservationsCfg
41
+
42
+
43
+ @configclass
44
+ class AgentObservationGroupsCfg:
45
+ policy: AgentObservationsCfg = AgentObservationsCfg()
46
+
47
+
48
+ @configclass
49
+ class LessonEventsCfg:
50
+ reset_scene = EventTerm(func=mdp.reset_scene_to_default, mode="reset", params={"reset_joint_targets": True})
51
+
52
+ # BEGIN lesson-03-vial-mass
53
+ pass
54
+ # END lesson-03-vial-mass
55
+
56
+
57
+ # BEGIN lesson-04-LessonTerminationsCfg
58
+ @configclass
59
+ class LessonTerminationsCfg:
60
+ pass
61
+ # END lesson-04-LessonTerminationsCfg
tests/test_config.py CHANGED
@@ -42,8 +42,14 @@ def _camera(*presets):
42
 
43
  def test_tasks_are_registered_with_rsl_rl_entry_points():
44
  ids = sorted(task for task in gym.registry if task.startswith("IsaacTutorial-"))
 
 
 
45
- assert ids == ["IsaacTutorial-Place-Vial-SO101", "IsaacTutorial-Place-Vial-SO101-Camera"]
 
46
- state, camera = (gym.spec(task).kwargs for task in ids)
 
 
47
  assert state["env_cfg_entry_point"].endswith("env_cfg:SO101VialEnvCfg")
48
  assert state["rsl_rl_cfg_entry_point"].endswith("rsl_rl_ppo_cfg:SO101StatePPORunnerCfg")
49
  assert camera["env_cfg_entry_point"].endswith("camera_env_cfg:SO101VialCameraEnvCfg")
 
42
 
43
  def test_tasks_are_registered_with_rsl_rl_entry_points():
44
  ids = sorted(task for task in gym.registry if task.startswith("IsaacTutorial-"))
45
+ assert ids == [
46
+ "IsaacTutorial-Inspect-SO101",
47
+ "IsaacTutorial-Place-Vial-SO101",
48
+ "IsaacTutorial-Place-Vial-SO101-Camera",
49
+ ]
50
+ inspection, state, camera = (gym.spec(task).kwargs for task in ids)
51
+ assert inspection["env_cfg_entry_point"].endswith("inspection_env_cfg:SO101InspectionEnvCfg")
52
+ assert "rsl_rl_cfg_entry_point" not in inspection
53
  assert state["env_cfg_entry_point"].endswith("env_cfg:SO101VialEnvCfg")
54
  assert state["rsl_rl_cfg_entry_point"].endswith("rsl_rl_ppo_cfg:SO101StatePPORunnerCfg")
55
  assert camera["env_cfg_entry_point"].endswith("camera_env_cfg:SO101VialCameraEnvCfg")