# 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: ```bash 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. ```python @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: ```bash 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.