mp_yam_code / source /bimanual /yam /tasks /pour_kettle.py
yqi19's picture
YAM bimanual task suite: env, solvers, tasks, converters
7399b6f verified
Raw
History Blame Contribute Delete
3.25 kB
"""Pour from a kettle into a cup: carry it over and rotate the wrist to tip the contents out.
The pour goes into a cup, which is small enough that the wrist rotation has to be aimed rather
than merely performed.
"""
from ..registry import register_task
from ..envs import YamTaskEnv
from ..solvers import pour
from .. import conditions as C
@register_task("pour_kettle")
class PourKettleTask(YamTaskEnv):
title = "POUR THE KETTLE INTO THE CUP"
tags = ["pouring", "trajectory", "wrist-rotation", "robotwin-asset"]
instruction = {"default": "Pour water from the kettle into the cup."}
rw_objects = {"rw_kettle": "091_kettle/base0.usd:0.052:0.12"}
# the +4.5 cm grip offset pushes the actual grip point away from the shoulder, so the
# kettle has to start closer than it looks: at (-0.03,0.11) the grip sat 0.39 m out
kettle_xy = (0.0, 0.03)
# pour INTO A CUP. The _mesh variant is required: the convex-decomposition build fills the
# cavity solid, so the beads would land on top of the cup instead of dropping into it.
cup = {"usd": "021_cup/base0_mesh.usd", "scale": 0.085, "rpy": (90, 0, 0), "xy": (0.02, -0.22)}
# No simulated contents, deliberately. A vessel that gets LIFTED has to be a convex dynamic
# body -- PhysX will not carry a hollow triangle mesh -- so the kettle's interior is solid and
# a bead placed "inside" is pushed out onto the table before the episode even starts (they
# were measured sitting at table height every run). Success is therefore the POUR itself:
# kettle over the cup, tipped past the angle its contents would leave it at.
pour_deg = 108.0
min_pour_tilt = 75.0
# Measured off the mesh, not guessed. Two separate problems, both invisible in the numbers:
# * the mesh origin is at its EDGE again -- the body's centroid is 4.5 cm off in y, so the
# jaws were closing beside the kettle ("did not rise")
# * the body is a smooth 7 cm shell that slips; the top 25% is a 1.5 x 3.5 cm neck, which
# is a real pinch
grip_offset = (0.0, 0.045)
# the 1.5 cm neck stalls the jaws but slips out under load; grip the 7.2 cm body
# instead, which the corrected y-offset now actually lands on
grip_top = None
gripper_effort, gripper_damping = 78, 86
def _load_scene(self):
self.scene.spawn_container({"name": "cup", **self.cup, "xy_jitter": (0.015, 0.015)})
self._placed = [self.scene.place_object(
{"name": "rw_kettle", "xy": self.kettle_xy, "xy_jitter": (0.015, 0.010)})]
def solve(self):
pos = self.scene.object_pos("rw_kettle")
print(f"[task] kettle {self.scene.object_size('rw_kettle').round(3)} at {pos.round(3)}",
flush=True)
return pour.solve(self, obj="rw_kettle", target="cup", pour_deg=self.pour_deg,
max_gap=0.048, jaw="x", grasp_offset=self.grip_offset,
grasp_top=self.grip_top)
def evaluate(self):
return self.check(
C.labelled("kettle lifted", C.object_lifted("rw_kettle", 0.05)),
C.labelled("kettle tipped out over the cup",
C.object_tipped_over("rw_kettle", "cup", min_tilt_deg=self.min_pour_tilt)))