2323from isaaclab_arena .metrics .success_rate import SuccessRateMetric
2424from isaaclab_arena .progress_tracking .progress_objective import ProgressObjective
2525from isaaclab_arena .tasks .common .mimic_default_params import MIMIC_DATAGEN_CONFIG_DEFAULTS
26+ from isaaclab_arena .tasks .predicates .object_on_destination_term import ObjectOnDestinationTerm
2627from isaaclab_arena .tasks .predicates .object_settling import objects_settled
27- from isaaclab_arena .tasks .predicates .spatial import object_is_above_height , object_on_destination , objects_in_proximity
28+ from isaaclab_arena .tasks .predicates .spatial import object_is_above_height , object_on_destination
2829from isaaclab_arena .tasks .task_base import TaskBase
2930from isaaclab_arena .tasks .task_transition import Relocate , TaskTransition
30- from isaaclab_arena .tasks .terminations import SuccessMode , check_success
3131from isaaclab_arena .utils .cameras import get_viewer_cfg_look_at_object
3232from isaaclab_arena .utils .configclass import make_configclass
3333
3434
3535@agent_ready
3636@register_task
3737class PickAndPlaceTask (TaskBase ):
38- """Pick-and-place task. Success fires when the pick-up object contacts the destination
39- with low velocity and, when ``max_separation`` is set, is within axis-aligned proximity
40- of the destination. Failure (object_dropped) fires when the object falls below the
41- background's ``object_min_z``.
38+ """Pick an object up and place it on or in a destination.
39+
40+ Success requires the object's bounds center over the destination footprint, upward support
41+ force, and low linear speed. Failure occurs when the object falls below the background.
42+
43+ Args:
44+ pick_up_object: Rigid object or rigid object set to pick up.
45+ destination_location: Destination whose live pose and spawned geometry define placement.
46+ It must be included in the environment scene.
47+ background_scene: Background whose minimum object height defines the drop failure.
48+ destination_object: Destination asset used by the default Mimic configuration.
49+ episode_length_s: Maximum episode duration in seconds.
50+ task_description: Natural-language task instruction. A description is generated when omitted.
51+ force_threshold: Minimum filtered normal force exerted on the object by the destination, in newtons.
52+ velocity_threshold: Object linear-speed threshold in meters per second. Speed must be below it.
53+ mimic_env_cfg_factory: Optional factory for a custom Mimic environment configuration.
54+ support_cone_half_angle_deg: Maximum angle in degrees between the filtered contact force and
55+ world +Z. Smaller values require the support force to be more vertical.
4256
43- The default Mimic cfg is ``PickPlaceMimicEnvCfg``. When a task needs a different cfg
44- shape (different arm subtask sequences, different per-subtask numerical knobs,
45- bespoke fields), pass ``mimic_env_cfg_factory`` to inject a custom ``MimicEnvCfg``::
46-
47- def _factory(arm_mode):
48- return MyCustomMimicEnvCfg(arm_mode=arm_mode, ...)
49-
50- PickAndPlaceTask(..., mimic_env_cfg_factory=_factory)
51-
52- The factory receives ``arm_mode`` from the env builder and returns a constructed cfg.
5357 """
5458
5559 def __init__ (
@@ -62,8 +66,8 @@ def __init__(
6266 task_description : str | None = None ,
6367 force_threshold : float = 0.1 ,
6468 velocity_threshold : float = 0.1 ,
65- max_separation : tuple [float , float , float ] | None = None ,
6669 mimic_env_cfg_factory : Callable [[ArmMode ], MimicEnvCfg ] | None = None ,
70+ support_cone_half_angle_deg : float = 45.0 ,
6771 ):
6872 super ().__init__ (episode_length_s = episode_length_s )
6973 self .pick_up_object = pick_up_object
@@ -73,10 +77,12 @@ def __init__(
7377 self .contact_sensor_name = f"contact_sensor_{ pick_up_object .name } "
7478 self .scene_config = self .make_scene_cfg ()
7579 self .force_threshold = force_threshold
80+ assert velocity_threshold >= 0.0 , f"velocity_threshold must be non-negative, got { velocity_threshold } "
7681 self .velocity_threshold = velocity_threshold
77- if max_separation is not None :
78- assert len (max_separation ) == 3 , f"max_separation must be (x, y, z), got { max_separation !r} "
79- self .max_separation = max_separation
82+ assert (
83+ 0.0 <= support_cone_half_angle_deg < 90.0
84+ ), f"support_cone_half_angle_deg must be in [0, 90), got { support_cone_half_angle_deg } "
85+ self .support_cone_half_angle_deg = support_cone_half_angle_deg
8086 self .mimic_env_cfg_factory = mimic_env_cfg_factory
8187 self .events_cfg = None
8288 self .termination_cfg = self .make_termination_cfg ()
@@ -107,39 +113,15 @@ def get_termination_cfg(self):
107113 return self .termination_cfg
108114
109115 def make_termination_cfg (self ):
110- predicates = [
111- TerminationTermCfg (
112- func = object_on_destination ,
113- params = {
114- "object_cfg" : SceneEntityCfg (self .pick_up_object .name ),
115- "contact_sensor_cfg" : SceneEntityCfg (self .contact_sensor_name ),
116- "force_threshold" : self .force_threshold ,
117- "velocity_threshold" : self .velocity_threshold ,
118- },
119- ),
120- ]
121- if self .max_separation is not None :
122- # TODO(qianl): replace objects_in_proximity with object_centroid_in_proximity
123- # for tighter container placement checks.
124- # TODO (qianl): current implementation doesn't support ObjectReference as target_object_cfg.
125- max_x_separation , max_y_separation , max_z_separation = self .max_separation
126- predicates .append (
127- TerminationTermCfg (
128- func = objects_in_proximity ,
129- params = {
130- "object_cfg" : SceneEntityCfg (self .pick_up_object .name ),
131- "target_object_cfg" : SceneEntityCfg (self .destination_location .name ),
132- "max_x_separation" : max_x_separation ,
133- "max_y_separation" : max_y_separation ,
134- "max_z_separation" : max_z_separation ,
135- },
136- )
137- )
138116 success = TerminationTermCfg (
139- func = check_success ,
117+ func = ObjectOnDestinationTerm ,
140118 params = {
141- "mode" : SuccessMode .ALL ,
142- "predicates" : predicates ,
119+ "object_cfg" : SceneEntityCfg (self .pick_up_object .name ),
120+ "destination_cfg" : SceneEntityCfg (self .destination_location .name ),
121+ "contact_sensor_cfg" : SceneEntityCfg (self .contact_sensor_name ),
122+ "force_threshold" : self .force_threshold ,
123+ "velocity_threshold" : self .velocity_threshold ,
124+ "support_cone_half_angle_deg" : self .support_cone_half_angle_deg ,
143125 },
144126 )
145127 object_dropped = TerminationTermCfg (
@@ -208,7 +190,7 @@ def get_viewer_cfg(self) -> ViewerCfg:
208190
209191 @classmethod
210192 def success_state_transition (cls , pick_up_object : str , destination_location : str , ** _ ) -> TaskTransition :
211- """Success (``object_on_destination``): the picked object ends up a relation with the destination ."""
193+ """Relate the picked object to the destination after geometric placement succeeds ."""
212194 # Note: with the current AABB-based object solver, placing an object ``on`` an open container
213195 # and letting it fall is equivalent to it being ``in`` the container, so a single ``on``
214196 # relation covers both surfaces and containers.
0 commit comments