Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## v0.1.2 (2026-10-08)

### Feat

- **pour_marbles**: pouring direction in instruction
- configure in which cup the marbles spawn

### Fix

- **pour_marbles**: pass task config to wrapper
- **ball_maze**: typo in task instructions for ball maze
- ball maze instruction typo

## v0.1.1 (2026-10-06)

### Fix
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "duobench"
version = "0.1.1"
version = "0.1.2"
description="FR3 Duo Bimanual Manipulation Benchmark"
dependencies = [
"rcs-core>=0.7.3",
Expand Down Expand Up @@ -32,6 +32,7 @@ dev = [
"pytest==8.1.1",
"duckdb>=1.3.2",
"twine~=6.0",
"commitizen~=3.28.0",
]

[tool.ruff]
Expand Down
2 changes: 1 addition & 1 deletion src/duobench/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.1.1"
__version__ = "0.1.2"

import os
from pathlib import Path
Expand Down
31 changes: 25 additions & 6 deletions src/duobench/tasks/pour_marbles.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import math
from dataclasses import dataclass, field
from typing import Any
from typing import Any, Literal

import gymnasium as gym
import mujoco as mj
Expand Down Expand Up @@ -87,6 +87,18 @@ def __init__(self):
self.left_cup_upright = True
self.right_cup_upright = True

def set_pour_direction(self, source_cup: Literal["left", "right"], target_cup: Literal["left", "right"]):
self.source_cup = source_cup
self.target_cup = target_cup
self.instruction = (
f"grasp and lift both cups, then pour the marbles from the {source_cup} cup into the {target_cup} cup "
"and place the cups back to their original location inside the green square"
)
self.stage_to_subinstructions[3] = (
f"pour at least one marble from the {source_cup} cup into the {target_cup} cup"
)
self.stage_to_subinstructions[4] = f"pour all marbles from the {source_cup} cup into the {target_cup} cup"

def _ensure_collision_geoms(self, sim: Sim):
if self.collision_geoms is not None:
return
Expand Down Expand Up @@ -297,16 +309,22 @@ def info(self) -> dict[str, Any]:


class PourMarblesTaskWrapper(TaskStageWrapper):
def __init__(self, env: gym.Env, stage_tracker: PourMarblesStage):
def __init__(self, env: gym.Env, stage_tracker: PourMarblesStage, cfg: "PourMarblesTaskConfig"):
super().__init__(env, stage_tracker)
self.stage_tracker: PourMarblesStage = stage_tracker
self.sim = self.get_wrapper_attr("sim")
self.marble_spawn_cup = cfg.marble_spawn_cup

def reset(self, *, seed: int | None = None, options: dict[str, Any] | None = None):
obs, info = super().reset(seed=seed, options=options)

self.stage_tracker.target_cup = "left" if self.np_random.random() > 0.5 else "right"
self.stage_tracker.source_cup = "right" if self.stage_tracker.target_cup == "left" else "left"
if self.marble_spawn_cup == "random":
source_cup: Literal["left", "right"] = "left" if self.np_random.random() > 0.5 else "right"
else:
source_cup = self.marble_spawn_cup

target_cup: Literal["left", "right"] = "right" if source_cup == "left" else "left"
self.stage_tracker.set_pour_direction(source_cup, target_cup)
lc = self.sim.data.body("leftteacup_body")
rc = self.sim.data.body("rightteacup_body")
tf_W_LC = RigidTransform.from_components(lc.xpos, Rotation.from_matrix(lc.xmat.reshape((3, 3))))
Expand Down Expand Up @@ -343,6 +361,7 @@ def reset(self, *, seed: int | None = None, options: dict[str, Any] | None = Non
@dataclass(kw_only=True)
class PourMarblesTaskConfig(BaseTaskConfig):
task_id: str = "pour_marbles"
marble_spawn_cup: Literal["random", "left", "right"] = "random"
cup_xml = rcs.OBJECT_PATHS["teacup"]
marble_xml = rcs.OBJECT_PATHS["marble"]
marbles_to_mug: rcs.common.Pose = field(
Expand Down Expand Up @@ -401,8 +420,8 @@ def add_task_env(
cfg: PourMarblesTaskConfig, env: gym.Env, simulation: Sim, env_cfg: SimEnvCreatorConfig
) -> gym.Env:
"""Add task-specific wrappers to the environment."""
_ = cfg, simulation, env_cfg
return PourMarblesTaskWrapper(env, PourMarblesStage())
_ = simulation, env_cfg
return PourMarblesTaskWrapper(env, PourMarblesStage(), cfg)


rcs.TASKS["pour_marbles"] = PourMarblesTask
Expand Down
Loading