From d1fd4cbdd30a83905d48a1a7f857f01228590f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20J=C3=BClg?= Date: Thu, 23 Jul 2026 22:43:03 -0700 Subject: [PATCH 1/7] fix: ball maze instruction typo --- src/duobench/tasks/ball_maze.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duobench/tasks/ball_maze.py b/src/duobench/tasks/ball_maze.py index a9df0af..17741fb 100644 --- a/src/duobench/tasks/ball_maze.py +++ b/src/duobench/tasks/ball_maze.py @@ -25,7 +25,7 @@ class BallMazeTaskConfig(BaseTaskConfig): board_number = 2 object_body = "board" include_rotation: bool = True - task_instructions = "pick up the board and tilt it so the ball roles onto the red square" + task_instructions = "pick up the board and tilt it so the ball rolls onto the red square" hard_reset = False x_width = 0.3 y_width = 0.3 From 610eeccd67437d4c40bea3ff6165473b2dcc0183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20J=C3=BClg?= Date: Thu, 23 Jul 2026 22:47:08 -0700 Subject: [PATCH 2/7] feat: configure in which cup the marbles spawn --- src/duobench/tasks/pour_marbles.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/duobench/tasks/pour_marbles.py b/src/duobench/tasks/pour_marbles.py index 3baad11..0a038fa 100644 --- a/src/duobench/tasks/pour_marbles.py +++ b/src/duobench/tasks/pour_marbles.py @@ -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 @@ -301,12 +301,19 @@ def __init__(self, env: gym.Env, stage_tracker: PourMarblesStage): super().__init__(env, stage_tracker) self.stage_tracker: PourMarblesStage = stage_tracker self.sim = self.get_wrapper_attr("sim") + task_cfg = self.get_wrapper_attr("task_cfg") + self.marble_spawn_cup: Literal["random", "left", "right"] = task_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 + + self.stage_tracker.source_cup = source_cup + self.stage_tracker.target_cup = "right" if source_cup == "left" else "left" 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)))) @@ -343,6 +350,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"] = "right" cup_xml = rcs.OBJECT_PATHS["teacup"] marble_xml = rcs.OBJECT_PATHS["marble"] marbles_to_mug: rcs.common.Pose = field( From 13f50c7901cf94633fdaec77fb1fc73f01d05faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20J=C3=BClg?= Date: Fri, 24 Jul 2026 09:59:49 -0700 Subject: [PATCH 3/7] feat(pour_marbles): pouring direction in instruction --- src/duobench/tasks/pour_marbles.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/duobench/tasks/pour_marbles.py b/src/duobench/tasks/pour_marbles.py index 0a038fa..682757c 100644 --- a/src/duobench/tasks/pour_marbles.py +++ b/src/duobench/tasks/pour_marbles.py @@ -87,6 +87,16 @@ 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 @@ -312,8 +322,8 @@ def reset(self, *, seed: int | None = None, options: dict[str, Any] | None = Non else: source_cup = self.marble_spawn_cup - self.stage_tracker.source_cup = source_cup - self.stage_tracker.target_cup = "right" if source_cup == "left" else "left" + 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)))) From 3d462d40f42990f1780f7a6fc7b448d81bda10cf Mon Sep 17 00:00:00 2001 From: Tobias Juelg Date: Mon, 5 Oct 2026 06:53:08 +0200 Subject: [PATCH 4/7] chore: pour marble direction default random like data --- src/duobench/tasks/pour_marbles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duobench/tasks/pour_marbles.py b/src/duobench/tasks/pour_marbles.py index 682757c..3c411ed 100644 --- a/src/duobench/tasks/pour_marbles.py +++ b/src/duobench/tasks/pour_marbles.py @@ -360,7 +360,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"] = "right" + 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( From fe3ec67ee740a9f0c8a34b8f0224c774042f7838 Mon Sep 17 00:00:00 2001 From: Tobias Juelg Date: Wed, 7 Oct 2026 11:07:27 -0700 Subject: [PATCH 5/7] style: python formatting --- src/duobench/tasks/pour_marbles.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/duobench/tasks/pour_marbles.py b/src/duobench/tasks/pour_marbles.py index 3c411ed..becb6c0 100644 --- a/src/duobench/tasks/pour_marbles.py +++ b/src/duobench/tasks/pour_marbles.py @@ -94,7 +94,9 @@ def set_pour_direction(self, source_cup: Literal["left", "right"], target_cup: L 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[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): From cdab04527393623e8ea9f2c9ca3408ef34c05514 Mon Sep 17 00:00:00 2001 From: Tobias Juelg Date: Thu, 8 Oct 2026 17:31:13 +0000 Subject: [PATCH 6/7] fix(pour_marbles): pass task config to wrapper --- src/duobench/tasks/pour_marbles.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/duobench/tasks/pour_marbles.py b/src/duobench/tasks/pour_marbles.py index becb6c0..9ecc42d 100644 --- a/src/duobench/tasks/pour_marbles.py +++ b/src/duobench/tasks/pour_marbles.py @@ -309,12 +309,11 @@ 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") - task_cfg = self.get_wrapper_attr("task_cfg") - self.marble_spawn_cup: Literal["random", "left", "right"] = task_cfg.marble_spawn_cup + 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) @@ -421,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 From e93155aa2a48688bfa3cbf74de692658cd01e6bc Mon Sep 17 00:00:00 2001 From: Tobias Juelg Date: Thu, 8 Oct 2026 11:10:41 -0700 Subject: [PATCH 7/7] =?UTF-8?q?bump:=20version=200.1.1=20=E2=86=92=200.1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 13 +++++++++++++ pyproject.toml | 3 ++- src/duobench/__init__.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a29d52..e6fd715 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 90003d1..18f171a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", @@ -32,6 +32,7 @@ dev = [ "pytest==8.1.1", "duckdb>=1.3.2", "twine~=6.0", + "commitizen~=3.28.0", ] [tool.ruff] diff --git a/src/duobench/__init__.py b/src/duobench/__init__.py index 807164e..3da5dbc 100644 --- a/src/duobench/__init__.py +++ b/src/duobench/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" import os from pathlib import Path