mp_yam_code / source /bimanual /yam /README.md
yqi19's picture
YAM bimanual task suite: env, solvers, tasks, converters
7399b6f verified
|
Raw
History Blame Contribute Delete
4.75 kB

YAM task suite

Scripted manipulation tasks for the YAM bimanual arm in Isaac Lab, laid out the way ManiSkill lays out its tasks: one registered class per task file, with the environment, the solvers and the motion planner as separate layers.

yam/
  motion/    how the robot MOVES        arm.py · planner.py · recorder.py
  envs/      the environment a task runs in   base_env.py · scene.py
  solvers/   scripted skills, one module each  pick_place · multi_pick · insert · stack · dual_lift
  tasks/     ONE FILE PER TASK, registered by name
  registry.py · conditions.py

Run one:

export ROBOTWIN_USD=/home/yu/internship_yu/robotwin_usd
python scripts/yam_task.py --list
python scripts/yam_task.py --task grape_box --seed 3
python scripts/yam_task.py --task grape_box --no-randomize      # nominal poses

Writing a task

A task declares what is in the scene and what counts as done. It never touches action vectors, IK or the video writer.

@register_task("grape_box")
class GrapeBoxTask(YamTaskEnv):
    title = "GRAPE -> BOX"
    tags = ["pick-place"]
    grape_spawn = (-0.03, 0.10)              # task constants as class attrs
    grape_spawn_jitter = (0.025, 0.015)      # randomization range
    gripper_effort, gripper_damping = 75, 85

    def _load_scene(self):
        self.scene.build_box({"name": "box", "xy": (-0.02, -0.26), "span": 0.26, "wall_h": 0.05})
        self._placed = [self.scene.place_object(
            {"name": "grape", "xy": self.grape_spawn, "xy_jitter": self.grape_spawn_jitter})]

    def solve(self):
        return pick_place.solve(self, obj="grape", target="box")

    def evaluate(self):
        return self.check(C.labelled("grape in box", C.object_in_region("grape", "box")),
                          C.labelled("grape lifted", C.object_lifted("grape", 0.05)))

Add the module to tasks/__init__.py so the registry sees it. Lifecycle is _load_scene() -> _initialize_episode() -> solve() -> evaluate().

Randomization: any object/container/marker accepts xy_jitter and yaw_jitter; --seed reproduces an episode exactly. Nominal poses with --no-randomize.

Asset gotchas this framework handles for you

Every one of these cost a debugging session; they are now enforced in envs/scene.py rather than repeated per task.

Symptom Cause Handled by
cup upside-down, basket on its side RoboTwin GLBs are authored Y-up pass rpy: (90,0,0); the container default
container spawns 1.9 m wide the GLB->USD converter reads model_data scale but does not bake it pass scale: at spawn
tall object lands on its side objects are written in above the table and drop reseat_objects() seats each at its measured height
arm never moves, huge tracking error a tall prop sits on the arm's home pose height-aware warning at build time
container asset missing the _mesh variant was never converted explicit error printing the exact convert command
container swallows objects / they roll off it convex decomposition fills the cavity convert containers with --collision none --suffix _mesh

Grasping rules the solvers apply

  • Position from physics, size from the bbox. object_pos() is live; object_size() is the authored bbox and is only valid for extents — it does not follow the object once it moves or is rotated.
  • Close across the narrow axis. The jaw opens 9.4 cm. A bottle lying on its side is 18 cm long and 5 cm across; jaw="auto" picks the axis that fits.
  • A stall is not a grasp. ArmController.grasp() rejects a stall at an implausibly wide gap (that is the jaw resting on the body) and then verifies the object actually rises. Without both checks an episode mimes the whole sequence with an empty hand and still reports success.
  • Clamp force is object-dependent. Thin-walled vessels get crushed through above ~50; heavy solids slip below ~70. Set gripper_effort per task.
  • Two arms move on one profile. env.move_both() commands both and steps once; driving them in sequence parks the first arm in the second's path.

Verification

The numeric success check only inspects final object poses, and it will happily pass an episode where the cup landed upside-down or one gripper hung empty in the air. Render a contact sheet and have a vision agent judge it:

python scripts/yam_agent_loop.py evaluate            # contact sheets + review prompts
python scripts/yam_agent_loop.py update              # verdicts -> concrete parameter fixes
python scripts/yam_agent_loop.py propose             # uncovered skill axes from the asset library

Only numeric=SUCCESS and visual=CONFIRMED counts as done.