每个仿真环境独立隔离在 sim/venvs/<name>/ 下,彻底消除依赖冲突。对外提供统一观测结构、gymnasium 注册入口、MCP 远程控制服务和 Web Dashboard。
# 一键安装 (任选)
bash scripts/setup_envs.sh metaworld
bash scripts/setup_envs.sh maniskill
bash scripts/setup_envs.sh metaworld,maniskill,libero
# 启动 MCP 服务 + Web Dashboard
python -m sim.mcp_server
# 或: python sim/mcp_server/server.py
# 浏览器打开 http://localhost:8765/直接使用 gymnasium:
source sim/venvs/maniskill/bin/activate
python -c "
import gymnasium as gym
import sim.env_registry
env = gym.make('openeta/maniskill_PickCube-v1-v0', render_mode='rgb_array')
obs, _ = env.reset()
print(obs['cameras'].keys(), obs['proprio'].keys(), obs['task_description'])
"只有已安装的 bench 才会注册环境 — 切换 venv 就切换可用环境集。
所有后端返回相同的 obs 字典:
obs = {
"cameras": {
"<name>": {
"rgb": np.ndarray # (H, W, 3) uint8
"depth": np.ndarray # (H, W) float32 | None # 线性米 (metres)
"intrinsics": dict | None
"extrinsics": dict | None
},
},
"proprio": {
"joint_positions": np.ndarray # (N,) float32
"joint_velocities": np.ndarray # (N,) float32 | None
"ee_pose": np.ndarray # (7,) float32 | None # xyz + xyzw
"gripper_open": float | None
},
"task_description": str, # 所有后端都保证存在
"objects": [{"name": str, "position": [x,y,z]}, ...],
"metadata": {...},
}depth 已经是线性的、以米为单位的度量深度,无需任何客户端换算:
-
ManiSkill (SAPIEN):原生 int16 毫米 →
/1000→ 米。 -
MuJoCo (MetaWorld / LIBERO / FrankaSim / D4RL):原始 z-buffer ∈ [0, 1], 按 robosuite 公式线性化,并用
model.stat.extent缩放后的裁剪面转成米:near = model.vis.map.znear * extent far = model.vis.map.zfar * extent z_m = near / (1 - z_buf * (1 - near / far)) -
BEHAVIOR-1K (OmniGibson):只使用米制 optical-Z
depth_linear;径向 distance-to-camera 的depth不进入该链路。
所有后端在 UnifiedEnv 边界把 NaN、Inf 和负深度转换成 0.0(无效像素),
避免非有限值进入 JSON;有效像素始终保留米制,不做逐帧归一化。
经 MCP 传输时编码为 uint16 PNG(毫米),解码用 depth_m = pixel / 1000.0。
新标准 RoboCasa / BEHAVIOR 包显式附带 depth_encoding="uint16_png" 与
depth_scale=1000.0(每米的编码单位数);LIBERO 为保持既有 wire shape 不新增
这两个字段。
intrinsics = {
"fx", "fy", # 焦距 (像素)
"cx", "cy", # 主点 (像素)
"width", "height",
"znear", "zfar", # MuJoCo: 度量裁剪面 (米),界定有效深度范围
}
extrinsics = { # 相机在 world 坐标系中的位姿 (非相对末端)
"pos": [x, y, z], # 相机位置 (米)
# 3×3 旋转矩阵 camera→world,行主序 (row-major) 展平
"mat": [m00, m01, m02, m10, m11, m12, m20, m21, m22],
"camera_to_world": [[...], [...], [...], [0, 0, 0, 1]],
"matrix_layout": "row_major",
"frame_transform": "camera_to_world",
"camera_frame": "opencv", # +X 右、+Y 下、+Z 前
"image_origin": "top_left",
"normalized_from": "mujoco_opengl", # 可选调试来源
# ManiSkill: 四元数 camera→world (已由 SAPIEN 原生 wxyz 重排为 xyzw)
"quat_xyzw": [x, y, z, w], # camera_frame="ros",沿局部 +X 观察
}RoboCasa 和 BEHAVIOR 的 agent-facing DirectEnv 会在仿真 adapter 边界把
MuJoCo/OpenGL 或 OmniGibson/USD 相机位姿归一成与 RGB、线性 depth、intrinsics
一致的 OpenCV optical camera_to_world。其 R 可直接用于:
R = np.array(mat).reshape(3, 3)
p_world = R @ p_opencv + pos
LIBERO 为保持既有成功率复现,继续发布原有 camera_frame="opengl" 的 v1
自描述外参,不改变 observation、planner context 或控制数值。MetaWorld 等旧
MuJoCo 路径同样仍可能发布 OpenGL;ManiSkill 发布 camera_frame="ros"。
消费者必须逐包读取 camera_frame,不能根据 backend 名称或矩阵形状猜测。
camera_pose_to_world 兼容显式
input_camera_convention / extrinsics_camera_convention 参数(以及既有
input_camera_frame / camera_to_world_frame 名称);标准 DirectEnv 包通常无需
覆盖,因为 producer 已写入 camera_frame="opencv"。
针孔反投影得到 OpenCV optical 点(X 右、Y 下、Z 前):
x = (u - cx) * d / fx # d = depth_m (线性米)
y = (v - cy) * d / fy
p_opencv = np.array([x, y, d])
# 新 RoboCasa / BEHAVIOR 标准包:
p_world = np.array(mat).reshape(3, 3) @ p_opencv + pos
# LIBERO / 旧 MuJoCo OpenGL 包:
p_opengl = np.diag([1, -1, -1]) @ p_opencv
p_world = np.array(mat).reshape(3, 3) @ p_opengl + posBEHAVIOR 只使用 OmniGibson depth_linear(distance-to-image-plane)作为
optical-Z;不会把径向 depth(distance-to-camera)冒充针孔 Z 深度。
| 后端 | 相机名 | 深度 | 说明 |
|---|---|---|---|
dummy |
dummy_front |
合成 | 合成 RGBD |
metaworld |
view |
✅ 米 | MuJoCo rgbd_tuple 渲染 (默认 480×480) |
maniskill |
<sensor_name> |
✅ 米 | MS3 原生传感器 (int16 毫米 → 米) |
libero |
agentview, wrist |
✅ 米 | 固定视角 + 腕部相机 |
genesis |
head |
— | 头部相机 |
behavior |
zed_head, wrist_left, wrist_right |
米(契约测试) | DirectEnv:RGB + depth_linear + OpenCV camera_to_world |
robocasa |
agentview_left, agentview, wrist, agentview_right |
米(契约测试) | DirectEnv:同相机 RGB-D,OpenGL 位姿在 adapter 内归一成 OpenCV |
d4rl |
(无) | — | 纯状态 |
✅ 表示已安装并验证深度为正确的线性度量米值(无 NaN/inf)。已验证环境:
metaworld_50_assembly-v3、maniskill_PickCube-v1、libero_libero_10_task0。
RoboCasa/BEHAVIOR 的 vendored RLinf vector wrapper 保留训练兼容职责:它们是
batched policy 接口,不是 agent-facing MCP RGB-D 几何接口。MCP registry 使用
各自 DirectEnv;不要把 vector batch 直接作为单个 EnvObservation,也不要在
缺少逐行 live calibration 时调用 SAM→抓取→世界坐标链路。
from sim.env_registry import list_envs, search, get_env_spec, hot_activate, hot_list_available
# 查看已安装的 bench
hot_list_available() # {"metaworld": True, "libero": True, ...}
# 按类型过滤
list_envs(env_type="maniskill") # → list[EnvSpec]
# 语义搜索
search("pick up") # → list[EnvSpec]
# 获取单个环境的详情
get_env_spec("openeta/maniskill_PickCube-v1-v0").task_description
# 运行时动态激活某个 bench 的 venv
hot_activate("libero") # → True (成功) / False (未安装)python -m sim.mcp_server # 默认 SSE 模式,端口 8765
python -m sim.mcp_server --transport stdio # stdio 模式 (本地 Claude)
python -m sim.mcp_server --port 9000 # 自定义端口打开 http://localhost:8765/ 即可交互式操控仿真环境:
- 左侧面板搜索 / 选择环境
- DPAD 按钮控制末端平移 (X/Y/Z)
- 夹爪开关、手动 action 输入
- Auto step+render 实时画面
- Speed multiplier 调节步长
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/benches |
列出已安装 bench |
GET |
/api/envs?type=&q= |
搜索/过滤环境 |
POST |
/api/envs |
创建环境 → {handle, action_dim, backend} |
POST |
/api/envs/{handle}/reset |
重置 → {image_base64, width, height, proprio, ...} |
POST |
/api/envs/{handle}/step |
执行动作 → 同 reset 结构 + {reward, terminated, truncated} |
POST |
/api/envs/{handle}/observe |
返回最近一次观测 |
POST |
/api/envs/{handle}/render |
渲染当前帧 |
DELETE |
/api/envs/{handle} |
关闭/销毁环境 |
GET |
/api/sessions |
列出活跃 session |
GET |
/session/{sid} |
Session 实时 Dashboard |
GET |
/session/{sid}/stream[/{handle}] |
SSE 实时画面流 |
Claude Code / Claude Desktop 通过 SSE 连接后自动发现:
| 工具 | 参数 | 说明 |
|---|---|---|
hot_activate |
bench |
运行时激活 bench venv |
list_available_benches |
— | 返回已安装 bench 列表 |
list_envs |
env_type |
按类型列环境 |
search_envs |
query |
语义搜索任务 |
create_env |
env_id, render_mode, seed, task, image_width/height, include_objects |
创建环境 → handle + session_id |
reset_env |
handle, seed |
重置并返回初始观测 |
step_env |
handle, action, num_steps |
执行动作并返回观测 |
move_to |
handle, x, y, z, roll/pitch/yaw, enable_collision_check |
闭环移动末端到绝对位姿 (含 cuRobo 碰撞检测) |
gripper_open |
handle |
打开夹爪 |
gripper_close |
handle |
关闭夹爪 |
observe_env |
handle |
返回最近观测 (不 step) |
render_env |
handle |
渲染当前帧 (完整多相机观测) |
close_env |
handle |
关闭环境 |
list_active_envs |
— | 列出活跃 handle |
观测中的
cameras[].depth_base64为 uint16 PNG(线性米 × 1000),intrinsics/extrinsics语义见上文「深度」「相机内参 / 外参」。move_to/ gripper 使用sim/mcp_server/action_codecs.py的显式 backend codec;未知 backend 或未声明的动作布局会返回结构化错误,不会猜测动作槽位。 BEHAVIOR DirectEnv 默认把双臂切换为有界 delta-pose IK,并在创建环境时 返回实际 arm/gripper action indices。enable_collision_check当前只在已接入 checker 的 LIBERO / ManiSkill 上执行;其他后端会明确标记 unavailable。close_env幂等,显式关闭和 TTL cleanup 都会释放 worker 引用。
Claude Code 配置 (.mcp.json,项目根目录已有):
{
"mcpServers": {
"openeta": {
"url": "http://localhost:8765/sse"
}
}
}远程连接:
ngrok http 8765
# 把 url 换成 ngrok 的公网地址即可env = gym.make("openeta/maniskill_PickCube-v1-v0", render_mode="human") # 弹窗
env = gym.make("openeta/maniskill_PickCube-v1-v0", render_mode="rgb_array") # numpy
frame = env.render() # rgb_array → (H,W,3); human → Nonesim/adapter.py 将 UnifiedEnv 桥接到 OpenETA 协议 (adapter/protocol.py):
from sim.adapter import make_sim_adapter
adapter = make_sim_adapter("openeta/metaworld_50_assembly-v3-v0")
obs = adapter.reset(task="assembly") # → EnvObservation
result = adapter.step(EnvAction(...)) # → StepResult
adapter.close()EnvObservation 包含 CameraFrame 列表、RobotState (joint positions/velocities, EE pose, gripper)、对象摘要和 metadata,可直接喂给 AgentSimBridge。
from adapter import AgentSimBridge
from logger import EpisodeLogger
bridge = AgentSimBridge(simulator=adapter, agent=agent_adapter)
outcome = bridge.run_episode(
task="pick and place the mug",
seed=0,
max_steps=100,
logger=EpisodeLogger("artifacts/episodes"),
environment="robocasa",
)
bridge.close()每个 episode 目录包含 episode.json、steps.jsonl 和 media/。JSONL 只保存
RGB/depth 相对路径,RGB 为 PNG,depth 为 uint16 毫米 PNG;每轮记录 observation、
plan、safety/checker 信息、EnvAction、StepResult 和 latency。response 不会被送入
仿真的 Box action space。导出回放视频:
uv run openeta-replay artifacts/episodes/EPISODE_ID \
--camera agentview --output artifacts/episodes/EPISODE_ID/replay.mp4数量为对应 venv 中实际
gym.registry注册数(随 ManiSkill/MetaWorld 版本浮动)。
| 环境 | 数量 | 机器人 | 安装命令 | 状态 |
|---|---|---|---|---|
dummy |
2 | — | built-in | ✅ |
metaworld |
96 | Sawyer | setup_envs.sh metaworld |
✅ 深度已验证 |
maniskill |
52 | 13 种² | setup_envs.sh maniskill |
✅ 深度已验证 |
libero |
130 | Franka Panda | setup_envs.sh libero |
✅ 深度已验证 |
robocasa |
634¹ | PandaOmron | setup_envs.sh robocasa |
✅ RoboCasa365 benchmark |
genesis |
1 | Franka | setup_envs.sh genesis |
|
behavior |
1,016 BDDL definitions (50-task RLinf eval subset) | R1Pro | OmniGibson 3.9 / Isaac Sim 5.1 | integrated; GPU worker required |
d4rl |
9 | — | setup_envs.sh d4rl |
TODO |
calvin |
— | Franka | 需 clone + dataset | TODO |
robotwin |
— | 多种 | 需 clone | TODO |
roboverse |
— | 多种 | 需 clone + MetaSim | TODO |
habitat |
— | — | 需 conda | TODO |
isaaclab |
— | 多种 | 需 Isaac Sim | TODO |
polaris |
— | 多种 | 需 Isaac Sim + PolaRiS | TODO |
embodichain |
— | — | pip install | TODO |
frankasim |
— | Franka | Repo not found | TODO |
¹ RoboCasa 的 317 个官方任务分别注册 pretrain/target 两个 split,因此是 634 个 env ID。 ² ManiSkill 机器人: panda, SO100, widowxai, allegro_hand, dclaw, tri_finger, ant, hopper, humanoid, cartpole, anymal_c, unitree_g1/h1/go2 ³ Genesis 需 CUDA 12.x 驱动 (nvJitLink 与 CUDA 13.2 驱动不兼容),代码已有但未测试
| 后端 | 维数 | 说明 |
|---|---|---|
metaworld |
4 | (dx, dy, dz, gripper), 范围 [-1, 1] |
maniskill |
7 | (dx, dy, dz, roll, pitch, yaw, gripper), delta, 范围 [-1, 1] |
libero |
7 | (dx, dy, dz, rx, ry, rz, gripper), OSC_POSE, 范围 [-1, 1] |
robocasa |
12 | PandaOmron: arm [0:6], gripper 6, base [7:10], torso 10, mode 11 |
behavior |
运行时声明 | R1Pro controller 顺序;MCP 读取 DirectEnv 返回的 IK / gripper 实际槽位,不硬编码维度 |
dummy |
dict | {"action_type": "code_policy", "code": "..."} |
RoboCasa 使用独立的 Python 3.11 venv;它的官方 LeRobot 依赖固定
gymnasium<1.0,不能和 OpenETA 主环境的 gymnasium>=1.0 混装。
./scripts/setup_envs.sh robocasa
source sim/venvs/robocasa/bin/activate
source sim/venvs/robocasa/activate_extra.sh
# 读取官方 task sets,并生成 317 tasks × 50 scenarios 的目标域清单
python -m scripts.robocasa_benchmark task-sets
python -m scripts.robocasa_benchmark manifest \
--task-set all_tasks --split target --scenarios-per-task 50 --seed 0 \
--output runs/robocasa/all_tasks_target.json完整的环境 ID、评测恢复、policy runner 接口和 MCP 控制说明见 ROBOCASA.md。
sim/
├── README.md # 本文件
├── SETUP.md # 各环境详细安装说明 (参考用)
├── __init__.py # 公开 UnifiedEnv, UnifiedSimulatorAdapter
├── env_registry.py # EnvSpec, gymnasium 注册, list_envs, search, hot_activate
├── env_config.py # 各后端的 DictConfig 构建器
├── unified_env.py # 统一 obs 结构 + render 接口
├── adapter.py # SimulatorAdapter — 桥接到 adapter/protocol.py
├── mcp_server/ # MCP 服务 + Web Dashboard + REST API (拆分为子包)
│ ├── __init__.py / __main__.py
│ ├── server.py # FastMCP + CLI 入口
│ ├── session.py # session 状态存储与生命周期
│ ├── worker_mgr.py # per-bench 子进程 worker 管理
│ ├── rest_api.py # REST API handler + SSE 直播
│ └── dashboard_html.py # Web Dashboard HTML 模板
├── envs/ # 按后端分隔的纯 gym.Env 封装
│ ├── metaworld.py
│ ├── maniskill.py
│ ├── libero.py
│ ├── genesis.py
│ └── ...
├── venvs/ # 每个 bench 独立 uv venv (gitignored)
│ ├── metaworld/
│ ├── maniskill/
│ └── libero/
- 在
sim/envs/下创建newbench.py,实现_make_newbench_direct(cfg)函数和_register_newbench()注册函数 - 在
sim/env_config.py中添加build_newbench_cfg()DictConfig 构建器 - 在
sim/unified_env.py中添加_normalise_newbench()归一化逻辑 - 在
sim/env_registry.py的hot_activate()和_init_registry()中注册 - 在
scripts/setup_envs.sh中添加newbench)case