Sim Env Builder
目标:根据一个描述,在尽可能少的用户交互轮次内交付一个可工作、物理合理、外观出色的模拟环境(理想情况:请求 -> 一次确认 -> 完成)。 质量标准:每次冒烟测试必须通过,你已经查看了渲染结果,并且它们与请求相匹配。
下方 SKILL_DIR = 此文件所在的目录。Python 辅助工具位于 SKILL_DIR/scripts/simkit。
阶段 0 - 设置(1 分钟)
PY=$(bash SKILL_DIR/scripts/setup_env.sh) # 包含 mujoco、trimesh、usd-core、gymnasium 的 Python 环境
$PY SKILL_DIR/scripts/check_platform.py # 检查操作系统、GPU、Isaac 是否可用
选择环境文件夹:./<短蛇形命名>/,放在当前目录下,除非用户指定了名称。
阶段 1 - 选择后端(阅读 reference/backend_choice.md)
Isaac Sim 需要具备 NVIDIA RTX GPU 的 Linux/Windows 系统。MuJoCo 可在任意平台上运行。
- MuJoCo:默认选项。适用于接触丰富的操作和运动,快速迭代,支持 CPU/笔记本电脑/macOS,适合小型到中型场景,基于模型的控制,可重复研究,以及支持 MJX/Warp 进行 GPU 强化学习。
- Isaac Sim:适用于照片级 RTX 渲染或合成数据(Replicator),高保真的 RTX 激光雷达/相机传感器,可在 Isaac Lab 中运行数千个并行环境,处理大型 USD/Omniverse 场景,且用户已在使用 Isaac。
- 如果 Isaac 更合适,但
isaac_runnable_here为假,则在确认中说明,并提供以下选项:在当前机器上使用 MuJoCo 构建并完全测试,同时导出一个 USD 阶段和 Isaac 冒烟测试脚本,供 GPU 机器使用(参考 reference/isaac.md)。切勿声称你未实际运行过的 Isaac 结果。
阶段 2 - 在构建之前与用户确认(参考 reference/confirmation.md)
- 首先进行一次快速的资产扫描,以使需求说明有依据:调用一次
find_assets(polypizza MCP),列出每个对象角色,每个角色使用 1-3 个查询。 - 发送一个简短的需求说明(模板在 reference/confirmation.md 中):包括后端及原因、场景中包含的内容、机器人/智能体及其控制接口、来自 Poly Pizza 还是程序化生成的资产、关键物理参数(重力、摩擦、时间步)、交付物,以及 2-4 个问题(每个问题都附带你推荐的默认设置)。
- 如果存在 AskUserQuestion 工具,则使用它来询问。否则,以你的需求说明结束当前回合作为消息,然后等待。在用户回答之前不要构建。
例外情况:用户已说明不要询问 / “直接构建” -> 将假设写入
SPEC.md,然后继续。 - 将确认的决策记录在
<env>/SPEC.md中(模板在 reference/confirmation.md)。
阶段 3 - 资产:首选 Poly Pizza,程序化回退(参考 reference/assets.md)
对于每个对象角色:
- 调用
find_assets/search_models-> 读取判断结果(找到 / 勉强 / 未找到)。 - 找到或勉强 -> 调用
preview_models([...ids])并查看:对象正确吗?风格一致吗?在写实场景中拒绝玩具、卡通、多对象捆绑包以及错误的比例类别。 - 接受 -> 调用
download_model(id, "<绝对 env 路径>/assets/polypizza", "<角色>")(积分会自动记录在assets/polypizza/CREDITS.md中)。 - 未找到、被拒绝或下载错误 -> 程序化生成:使用
simkit.procedural配方(树木、建筑物、家具、街道道具、大门、餐具、地形...)或使用基本体、lathe、extrude、rock自行组合。切勿遗漏请求的对象。 - 写入
<env>/assets/ASSETS.md:角色 | 来源(Poly Pizza id + 标题 + 许可,或程序化配方)| 真实大小 | 原因。如果模型的标题与角色不同(例如“公园长椅”用作野餐桌),则说明原因以及为什么仍然适合 - 否则重新搜索或使用程序化生成。 同时列出被拒绝的候选对象(id + 原因:“卡通风格”、“12 个对象的捆绑包”),以便搜索过程可追溯,即使所有内容最终都是程序化生成的。 Poly Pizza 模型是静态网格:关节机器人和车辆来自simkit.robots(你可以使用 Poly Pizza 网格作为视觉 geom 来为其添加外观)。
阶段 4 - 构建(参考 reference/robots.md、templates/)
mkdir -p <env> && cp -r SKILL_DIR/scripts/simkit <env>/simkit # vended:环境是自包含的
cp SKILL_DIR/templates/build_env.py SKILL_DIR/templates/env.py SKILL_DIR/templates/view.py <env>/ # 编辑前两个文件
build_env.py确定性地(带种子)重新生成所有内容 ->scene.xml。 使用simkit.scene.Scene(天空、地面、灯光、看向摄像机的相机、用于原始 MJCF 的节),simkit.assets.glb_to_mujoco(glb, env, name, target={"height": m}, yaw_deg=..)用于 Poly Pizza 模型(真实世界大小,Z 轴朝上,按颜色分区,盒子/包络/部件碰撞),simkit.procedural用于回退和地形,simkit.robots用于经过测试的机器人(四旋翼、小车、差动驱动、机械臂+夹爪、四足机器人)及其控制器。- 必须使用真实世界的比例(参考 reference/assets.md 中的大小表)。物体必须放置在其支撑面上。
- 用户命名的每一类对象在俯视视角中都必须清晰可见且大小可读(例如“岩石小路”需要可见的岩石,而不是 7 厘米的斑点;“粗糙地形”需要机器人尺度下可见的凸起)。冒烟测试会在
hints下列出斑点。 - 至少 2 个命名的相机:一个俯视视角和一个聚焦在主体上的视角(机器人/任务对象大致占据画面的 30-50%;如果适用,可安装在机器人上)。固定传感器相机不得被机器人的初始姿态遮挡(冒烟测试标记覆盖率 > 15%)。
world.py(当场景有自身动力学时):make_world(model)->world(model, data),在冒烟测试、检查器、展示器、env.py、view.py 和仪表板的每个物理步之前调用。浮力/阻力、风、传送带、交通灯循环、脚本化的行人及 NPC 车辆都放在这里,绝不能放在 controller.py 内(否则,被动测试或强化学习策略将会运行一个船只下沉、交通灯永不改变的 world)。任务检查(step the sim)也必须调用它。controller.py:一个脚本化的演示策略(make_controller(model)->ctrl(model, data)),展示环境的运作方式(无人机飞过门、小车沿路线行驶、机械臂抓取物体...)。在模块级别设置DEMO_SECONDS = <完整演示的长度>:冒烟测试随后会拍摄并检查整个演示过程(抓取、举起、放置 / 每个门),而不仅仅是前 4 秒。task_checks.py:函数check_<名称>(model, data) -> {"status", "value", "expect", "details"}用于任务级物理。每个用户声明或确认的成功标准都对应一个检查(以他们的词语命名:“feet in contact” ->check_feet_in_contact),外加初始状态检查(机器人从其支撑面开始)。至少 2 个。在首次运行之前将阈值和每个指标的精确定义(参考点、单位:“漫游车基地中心到着陆器中心,米”)写入 SPEC.md 并保持它们:轻微偏差意味着修复控制器/场景,绝不能放宽检查(任何阈值更改必须在“与已确认计划的变更”下列出)。通过检查但余量在 15% 以内的,应报告其余量(例如“倾斜 25.8 度 vs 30 度限制”)。你声称的任何行为(避障减速、交通灯周期、浮力、夹爪保持力)都需要一个检查来验证它,例如在路径中放置障碍物并断言机器人停止。场景的每个关键物理属性都需要一个带有物理目标的测量检查:球的滚动减速度、船的水线、漫游车的爬坡能力、低重力下的下落时间、夹爪保持力。所需的传感器也需要检查:在演示期间渲染机器人相机,并断言非空白图像显示任务(例如前方的树行);对于激光雷达,断言合理的距离范围。在脚本化滚转以及 2-3 个扰动滚转(其他种子、初始姿态、初始速度、相位)上运行每个任务检查——仅适用于一个精确滚转的检查证明力不足。抓取检查要求在提升和搬运期间,两个手指垫与物体之间都有接触(读取data.contact),而不仅仅是“物体向上移动了”。- 碰撞体:简单且诚实。树木:树干碰撞体,树冠仅视觉,除非用户希望树冠碰撞。多旋翼:臂部使用胶囊体或包络体,不使用会吞没螺旋桨的单个盒子。非凸起道具(显微镜、椅子、拱门、架子)使用
collision="parts"或几个基本体;单个凸包会堵塞明显为空的空间(检查 collision_sheet.png)。 env.py:Gymnasium 风格的reset/step/render,包含任务观察、奖励、终止函数。其__main__自检通过 env API 使用演示控制器(或参考策略)驱动环境,并打印任务目标是否达成——证明目标可达。- 调试/相机支架体和标记:使用 geom 组 4-5(从不渲染),不在数据相机中。
- 悬挂物体(天花板灯、悬挂标志、墙壁架子):将它们命名为
mounted_*或hanging_*,以便浮动对象检查跳过它们。 - 灯光:中性白色(无着色灯光);最多 2-3 个额外灯光;检查渲染中白色墙壁保持灰白色。
- 除了使场景正确可读之外的未请求装饰;在需求说明中列出可选的附加项,以便用户选择加入。
- 时间盒:开始构建时记录时间(
date)。奖励/延伸项(“折叠是一个奖励”)仅在所有必备项通过后才能进行,并且优先放弃。大约构建 60 分钟后,停止调优剩余内容:将 REPORT.md 和 README 的结果定为最终版本并报告(列出放弃的项)。从未报告结果的构建比已知缺口的构建更糟糕。 - 预算:一旦所有必备项都存在并且所有检查都通过,停止添加额外内容——完成视觉检查、自述文件和报告。额外内容作为想法放在 README 中。
- 每次更改后运行
$PY build_env.py;编译错误会显示行号。
阶段 5 - 冒烟测试(参考 reference/smoke_tests.md)。所有三项都必须执行。
cd <env> && $PY -m simkit.smoke scene.xml --out smoke --controller controller.py --task-checks task_checks.py
- 渲染测试 - 每个命名的相机 + 自动视角渲染出非空白、照明良好、细节丰富的帧;分割图显示哪些对象可见(未见的对象会被标记)。
- 物理检查器 - 使用场景自身设置进行求解器/接触/摩擦/重力探针测试,检查滚转稳定性、穿透、下坠、沉降、执行器阶跃响应、确定性、时间步敏感性、实时因子、布局(浮动/下沉/相互穿透的道具),以及你的任务检查。
- 视觉检查 - 你必须打开并查看
smoke/contact_sheet.png、smoke/filmstrip.png、smoke/collision_sheet.png以及smoke/renders/中的特写图像(使用读取工具),然后将检查清单填入smoke/visual_check.md,并附上具体观察结果。 修复每个 FAIL。“调查”每个 WARN(修复它,或者在 README 中解释为什么这是预期的)。 重新构建并重新运行,直到结果为 PASS / PASS_WITH_WARNINGS-explained 并且视觉检查全部为“是”。预计需要 2-3 次迭代;首次渲染很少正确。 同时运行$PY env.py(随机动作,有限观测)。
阶段 6 - Isaac Sim 交付物(仅当选择 Isaac 时;参考 reference/isaac.md)
$PY -m simkit.usd_export scene.xml isaac/scene.usda(导出 + 本地验证),将 SKILL_DIR/templates/isaac_smoke.py 复制到 isaac/,记录如何在 GPU 机器上运行它。明确说明已执行和未执行的操作。
阶段 7 - 交付
<env>/README.md:场景中包含什么,如何运行(构建、冒烟、环境、查看器:$PY view.py,它实时运行演示控制器;普通的 mujoco.viewer 仅显示被动物理效果),相机、执行器、控制器、任务检查、资产归属、已知限制。最终消息给用户:结论行、构建了什么、哪些资产来自 Poly Pizza vs 程序化生成、冒烟结果、路径指向 smoke/showcase.mp4(人们观看的视频)、联系表和胶片条、任何未完成或未验证的内容,以及“与已确认计划的变更”列表(替换的资产或许可证、更改的大小/数量、放弃的额外内容)——如果没有则为空,但始终存在。将每个数字(检查通过数、分数、错误数)从 smoke/smoke_report.json 和 physics_report.json 中复制过来;逐字粘贴其 summary_line(例如“Smoke PASS: examiner 19 pass / 0 warn / 0 fail; task checks 9/9 pass; render ok”),切勿凭记忆书写计数。
一旦首次冒烟运行完成,就开始编写 <env>/REPORT.md,并在每次修复后更新它,这样即使会话中断也能留下报告;最终消息是它的最后一个版本。
交付物布局(请严格保持;工具和仪表板依赖于此)
<env>/
SPEC.md README.md REPORT.md build_env.py scene.xml env.py view.py controller.py task_checks.py
world.py (仅当场景有自身动力学时才需要)
simkit/ vendored 辅助工具
assets/polypizza/*.glb assets/polypizza/CREDITS.md assets/polypizza/polypizza_assets.json
assets/meshes/ assets/procedural/ assets/ASSETS.md
smoke/ (showcase.mp4 contact_sheet.png filmstrip.png collision_sheet.png renders/ rollout.mp4
physics_report.json render_report.json smoke_report.json env_manifest.json
SMOKE.md visual_check.md)
isaac/ (仅用于 Isaac:scene.usda、isaac_smoke.py、README 节)
来之不易的规则(每一条都曾在实际构建中导致问题)
-
保持在已确认的范围内。演示展示用户同意的内容(例如,“站立就够了”意味着稳定的站立,而不是新的运动控制器)。将时间花在场景、资产、检查和视觉审查上;将延伸想法列在 README 中。
-
<visual>设置:sc.set_visual("map", znear=.., zfar=..)等。每个元素可能出现一次。 -
切勿手动计算相机
xyaxes:请使用Scene.camera(name, pos, target)/simkit.scene.lookat。 -
切勿手动编写关键帧 qpos 列表:设置
scene.home[joint] = value(机器人会自动执行此操作);Scene.write通过关节名称构建home关键帧,并设置位置执行器 ctrl 以匹配。 -
扁平/薄的网格需要使用
inertia="shell"(glb_to_mujoco 会自动处理)。视觉 geom:contype=0 conaffinity=0 group=2 mass=0;碰撞代理在 group 3 中。 -
焊接在 world 上的机器人会与自身基座碰撞:为基座/第一个连杆添加
<exclude>。
...
[已截断以进行翻译]