第 3 课
用 MjSpec 写代码搭场景,在 MuJoCo Studio 里热重载查看
用 MjSpec 从零添加灯光、地面、相机和三个自由下落的方块,编译仿真后存成 .xml 与 4.0 新增的 .mjz;读回文件改尺寸、改颜色、删物体再仿真;用 --gfx=web 启动 Studio Web 查看器并打开模型热重载,改文件后看到页面自动刷新;了解安全注意事项、独立桌面应用
课程目录第 3 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
图文教材
本课对照的官方页面:Model Editing(MjSpec) 、Python 绑定文档 、MuJoCo Studio 、Web Viewers 、Native Viewers 。测试环境同前两课:2026-11-11,Asia/Shanghai,Linux x86_64 云主机,Python 3.12.15,
mujoco4.0.0。对照结果均为真实输出,只做了节选。
前两课都是先写好 XML 再加载。真实项目里经常要「用代码生成场景」:随机摆放物体、批量换尺寸、程序化地增删零件。MuJoCo 的 MjSpec 就是做这件事的,它把模型变成可以在 Python 里增删改的对象,改完再编译成 MjModel。本课用它从零搭一个「三个方块掉落」的场景,存成文件,再读回来修改;最后用 4.0 新推出的 MuJoCo Studio 在浏览器和桌面应用里查看这个场景,并体验改文件后自动刷新的「热重载」。
第 1 步:用 MjSpec 从零搭场景
沿用前两课的环境。新建 build_scene.py:
import mujoco
import mediapy as media
spec = mujoco.MjSpec()
spec.modelname = 'falling boxes'
world = spec.worldbody
world.add_light(name='sun', pos=[0, 0, 3], dir=[0, 0, -1])
world.add_geom(name='floor', type=mujoco.mjtGeom.mjGEOM_PLANE,
size=[2, 2, 0.1], rgba=[0.3, 0.4, 0.5, 1])
world.add_camera(name='side', pos=[0, -2.5, 1.0], xyaxes=[1, 0, 0, 0, 0.4, 1])
colors = [[1, 0.3, 0.3, 1], [0.3, 0.8, 0.3, 1], [0.3, 0.5, 1, 1]]
for i, rgba in enumerate(colors):
box = world.add_body(name=f'box{i}', pos=[0.05 * i, 0, 0.3 + 0.35 * i])
box.add_freejoint()
box.add_geom(name=f'geom{i}', type=mujoco.mjtGeom.mjGEOM_BOX,
size=[0.1, 0.1, 0.1], rgba=rgba)
model = spec.compile()
data = mujoco.MjData(model)
print('bodies =', model.nbody, '| geoms =', model.ngeom, '| nq =', model.nq, '| nv =', model.nv)
mujoco.mj_step(model, data, nstep=1000) # 一次跑 1000 步 = 2 秒
print('t =', round(data.time, 3), 's')
for i in range(3):
print(f'box{i} 高度 = {data.body(f"box{i}").xpos[2]:.3f} m')
print('接触点数量 =', data.ncon)
with mujoco.Renderer(model, 480, 640) as renderer:
renderer.update_scene(data, camera='side')
media.write_image('falling_boxes.png', renderer.render())
spec.encode('falling_boxes.xml') # 存成 MJCF
spec.encode('falling_boxes.mjz', model=model) # 打包成 .mjz
print(spec.to_xml()[:400])
add_light、add_geom、add_camera、add_body 的参数名和 XML 属性一一对应,会写 XML 就会用 MjSpec。三个方块各带一个自由关节,从不同高度、略微错开的位置落下。mj_step 的 nstep 参数可以一次连跑多步,比在 Python 里写循环快。
第 2 步:运行并核对编译结果
export MUJOCO_GL=egl # 有桌面的电脑可省略
python build_scene.py
ls -l falling_boxes.*
对照结果:
bodies = 4 | geoms = 4 | nq = 21 | nv = 18
t = 2.0 s
box0 高度 = 0.100 m
box1 高度 = 0.299 m
box2 高度 = 0.499 m
接触点数量 = 12
<mujoco model="falling boxes">
<compiler angle="radian"/>
<worldbody>
<geom name="floor" size="2 2 0.1" type="plane" rgba="0.3 0.4 0.5 1"/>
<camera name="side" quat="0.828067 0.560629 0 0" pos="0 -2.5 1"/>
<light name="sun" pos="0 0 3"/>
<body name="box0" pos="0 0 0.3">
<joint type="free"/>
<geom name="geom0" size="0.1 0.1 0.1" type="box" rgba="1 0.3 0.3 1"/>
ls -l 会列出三个文件:falling_boxes.png、falling_boxes.xml(本机 742 字节)和 falling_boxes.mjz(本机 407 字节)。
怎么读:
bodies = 4是因为「世界」本身也算一个 body,再加 3 个方块。3 个自由关节,每个qpos7 个数、qvel6 个数,所以nq = 21、nv = 18。- 2 秒后三个方块的中心高度分别约为 0.1、0.3、0.5 米。方块边长 0.2 米,这说明它们已经稳稳地叠成了一摞,12 个接触点就是方块与方块、方块与地面之间的接触。
to_xml()输出的是规范化后的 MJCF:相机的xyaxes被换算成了四元数quat,dir等于默认值的灯光属性被省略了。.mjz是 4.0 新增的打包格式,可以把模型和它引用的网格、贴图打成一个压缩包,方便分享或拖进查看器。
第 3 步:读回文件,改尺寸、改颜色、删物体
新建 edit_scene.py:
import mujoco
spec = mujoco.MjSpec.from_file('falling_boxes.xml')
print('读回的 bodies:', [b.name for b in spec.bodies])
spec.geom('geom2').size = [0.15, 0.15, 0.15] # 顶上那块改大
spec.geom('geom2').rgba = [1, 0.8, 0.2, 1] # 改成黄色
spec.delete(spec.body('box1')) # 删掉中间那块
model = spec.compile()
data = mujoco.MjData(model)
mujoco.mj_step(model, data, nstep=1000)
print('bodies =', model.nbody)
print('box0 位置 =', data.body('box0').xpos.round(3))
print('box2 位置 =', data.body('box2').xpos.round(3))
spec.encode('falling_boxes.xml')
print('已覆盖保存 falling_boxes.xml')
cp falling_boxes.xml falling_boxes.v1.xml # 先备份第 1 版
python edit_scene.py
对照结果:
读回的 bodies: ['world', 'box0', 'box1', 'box2']
bodies = 3
box0 位置 = [-0.001 0. 0.1 ]
box2 位置 = [0.294 0. 0.15 ]
已覆盖保存 falling_boxes.xml
中间的方块删掉之后,大号的 box2 落在 box0 上,但两者错开了一点,它从 box0 上滑了下去,最后落在地板上:x 方向滑到约 0.29 米处,高度 0.15 米正好是它的半边长。这就是物理仿真的意义:改一处参数,结果要跑出来才知道。
第 4 步:用 Studio 网页查看器实时查看
MuJoCo Studio 是 4.0 新推出的查看器。官方提供了不需要显卡和桌面的 Web 查看器:物理计算在本机的 Python 里跑,三维画面交给浏览器绘制。在终端 1 里启动它,并打开「模型热重载」:
python -m mujoco.studio.viewer --model=falling_boxes.xml --gfx=web --hot_reload=model
对照结果:
+---------------------------------------------------------------------+
| MuJoCo Web Viewer running at: |
| |
| Remote: http://localhost:8080/ |
| Local: http://localhost:8080/ |
| |
| Ctrl+C to quit |
| |
| Warning: No authentication or encryption. Anyone who can reach this |
| port can connect and see your logs and file paths. |
+---------------------------------------------------------------------+
Model changed: the browser page reloads automatically.
用浏览器打开 http://localhost:8080/ 就能看到场景并控制仿真。端口默认是 8080 或之后第一个空闲端口,也可以用 --port 指定。在终端 2 里确认服务在工作:
curl -sI http://localhost:8080/ | head -5
对照结果:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 25864
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
最后两行是 Studio 自动加的跨源隔离响应头,浏览器里的 WebAssembly 客户端要靠它们才能多线程运行。
第 5 步:改文件,看查看器自动刷新
保持终端 1 的查看器开着,在终端 2 里把 box2 从黄色改回蓝色(也可以直接用编辑器改 rgba,保存即可):
sed -i 's/rgba="1 0.8 0.2 1"/rgba="0.2 0.8 1 1"/' falling_boxes.xml
grep geom2 falling_boxes.xml
对照结果:
<geom name="geom2" size="0.15 0.15 0.15" type="box" rgba="0.2 0.8 1 1"/>
几秒内终端 1 会再多打印一行:
Model changed: the browser page reloads automatically.
浏览器页面随之刷新,方块变成蓝色。用完在终端 1 按 Ctrl+C 退出。--hot_reload 还可以设成 plugins 或 all,配合 Studio 的 Python 插件做自定义界面,详见官方 Studio 文档的 Python Plugins 一节。
第 6 步:换成桌面版 Studio 或在线版
如果你的电脑有图形桌面,还有两种看法,二选一即可:
**方式一:独立桌面应用。**在 GitHub 4.0.0 发布页下载对应系统的包(macOS 是 .dmg,Windows 是 .zip,Linux 是 .tar.gz)。Linux 上解压后在包目录里运行,可直接打开包里自带的示例模型,也可以打开本课的文件:
tar -xzf mujoco-4.0.0-linux-x86_64.tar.gz
cd mujoco-4.0.0
./bin/mujoco_studio model/humanoid/humanoid.xml
./bin/mujoco_studio ~/mj-lab/falling_boxes.xml
窗口打开后,左侧 Options 面板里有 Pause / Run、Step Back / Step Fwd(单步后退、前进)、时间轴回放和 Physics、Rendering 等选项;右侧 Inspector 面板可以查看和拖动每个关节(Joints)、控制量(Controls)和传感器(Sensor)的数值。本课的封面截图就是用这个应用打开官方示例 cards/house_of_cards.xml 截的。Windows 用 bin\mujoco_studio.exe,macOS 直接双击 MuJoCoStudio.app。
**方式二:在线版 MuJoCo Live。**打开 https://live.mujoco.org/ ,把 .xml、.mjb 或 .mjz 文件直接拖进页面,整个仿真都在浏览器里运行,不用安装任何东西。
常见坑
- 在服务器上启动了 Web 查看器,用
http://服务器IP:8080打不开:浏览器只在localhost或 HTTPS 下才允许它需要的多线程能力。官方建议用 SSH 端口转发ssh -L 8080:localhost:8080 用户名@服务器,然后在本机打开http://localhost:8080。 - 安全提醒:Web 查看器没有登录和加密,并且监听所有网卡,能连上这个端口的人可以控制仿真、看到日志和文件路径。只在本机、可信网络或 SSH 隧道里使用,别在公网服务器上直接开放 8080 端口。
- 原生窗口打不开:
--gfx不写web时会打开原生窗口,需要图形桌面和可用的显卡驱动。本课测试用的是没有显卡的云主机,Python 原生查看器启动时报ValueError: Target not found,换成--gfx=web或独立桌面应用即可。 - 改了文件但查看器没有刷新:启动时要加
--hot_reload=model,默认值是空,表示什么都不自动重载。 spec.compile()报repeated name错误:MjSpec 里同类元素不能重名,例如两个 body 都叫a会报ValueError: Error: repeated name 'a' in body。批量生成物体时像本课一样给名字加上序号。- 改了
MjModel想存回文件:存文件要通过MjSpec。想把改动保存下来,就在spec上改,再spec.encode()。
小结
这一课用 MjSpec 在 Python 里从零搭了场景,编译、仿真、渲染之后存成 .xml 和 4.0 新增的 .mjz;又把文件读回来改尺寸、改颜色、删物体,看到改动怎样改变了仿真结果;最后用 MuJoCo Studio 的 Web 查看器配合热重载实时查看场景,并了解了桌面应用和在线版 MuJoCo Live 两种用法。到这里,「安装 → 建模 → 仿真 → 渲染 → 程序化编辑 → 交互查看」这条最常用的 MuJoCo 工作流已经完整走了一遍。接下来可以试试官方的 MuJoCo Menagerie 机器人模型库,或者在 Colab 里继续做官方教程后面的章节。
