# 搭建 3D 场景

写一个场景文件，检查没有角色穿过道具，再在你的电脑上从它的机位画出一张静帧。

一个 3D 场景就是一个文件，里面有角色、道具、机位和时间线。你的 Agent 写它，MiniCut 检查它，并在你的电脑上把它画出来。距离的单位是米，地面是 0，转动的单位是度。

开始之前，先登录并建一个项目，见[快速开始](/zh-cn/docs/cli/quickstart)。这里的每条命令都要求这台电脑已经登录。在项目文件夹里运行，这里是 `~/.minicut/projects/rain-shop`。

## 写场景

1. 如果项目里还没有文件夹 `episodes/ep01/scenes`，就建一个。第 1 个镜头的场景叫 `c01`，第 2 个叫 `c02`，依此类推。
2. 在这个文件夹里写一个操作文件，`c01.scene.ops.json`。它是数据：一列场景操作和它们的参数，没有代码。

```json c01.scene.ops.json
{
  "ops": [
    { "op": "setWorld", "args": [{ "aspect": "9:16" }] },
    { "op": "addCharacter", "args": [{ "name": "Lin", "body": "female", "position": [-0.8, 0, 0] }], "as": "lin" },
    { "op": "addCharacter", "args": [{ "name": "Chen", "body": "male", "position": [0.8, 0, 0], "facing": "$lin" }] },
    { "op": "addProp", "args": [{ "name": "Table", "shape": "table", "position": [0, 0, 0.9] }] },
    { "op": "addCamera", "args": ["front_medium", { "name": "Front" }], "as": "cam" },
    { "op": "setCamera", "args": ["$cam", { "position": [0, 1.6, 6], "lookAt": [0, 1.4, 0], "fov": 40 }] },
    { "op": "setDuration", "args": [4] }
  ]
}
```

`as` 给一个操作做出的 id 起名，后面的操作用 `$lin` 来引用它。如果操作名不存在，`build` 会打印它接受的操作名。

3. 可选：如果你的 Agent 会写代码，运行 `minicut scene api`。它会打印一行给 Node.js 脚本用的导入语句。脚本最后写 `await api.save("episodes/ep01/scenes/c01.scene.json")`，它会检查场景并写出文件。MiniCut 从不运行这个脚本，由你的 Agent 用 `node` 运行。

## 生成场景文件

1. 生成文件：

```bash Terminal
minicut scene build episodes/ep01/scenes/c01.scene.ops.json
```

它会打印 `Wrote` 和 `c01.scene.json` 的路径、物体和机位的数量、时长，以及每个机位的 id 和名字。

2. 在输出里读出机位 id。每次生成都会做出新的 id，所以上一次生成的机位 id 不能再用。

`build` 在写文件之前先检查场景。如果检查没通过，它会打印每个问题，写上 `Not written`，并保持旧文件不变。含有代码的文件会被拒绝。

## 检查场景

1. 检查有没有角色穿过道具或地面：

```bash Terminal
minicut scene check episodes/ep01/scenes/c01.scene.json
```

```text Output
OK ~/.minicut/projects/rain-shop/episodes/ep01/scenes/c01.scene.json: 3 object(s), 1 camera(s), 4 s. Cameras: cam_a6tl (Front). No body goes through a prop or the floor.
```

2. 如果它打印了问题，就改操作文件，再生成一次。每一行都写明是哪个身体部位、哪个道具、穿进去多深、在第几秒，以及要移开多远。例如 `Lin's left thigh goes 9 cm into "Table" (p_kg3w), at 0 s`。
3. 要回到上一次生成之前的文件，运行 `minicut scene undo episodes/ep01/scenes/c01.scene.json`。它保留最近 20 个版本。

## 画一张静帧

1. 画出场景的中间那一秒：

```bash Terminal
minicut scene render episodes/ep01/scenes/c01.scene.json --still middle
```

命令会打印 `Drawing` 和机位、秒数，然后是 `Rendered` 和字节数，最后是路径。不加 `--camera` 时，用场景自己的机位。

2. 打开这张图。不加 `--project` 或 `--id` 时，它在你的 MiniCut 主文件夹的 `cache/stills` 文件夹里，命令会打印它的路径。旁边的 `labeled` 文件夹里有一份副本，每个资产上都带名牌，方便人看。
3. 可选：想把这张静帧作为场景存进你的素材库，加上 `--project rain-shop`。
4. 可选：想要小一点的图，加 `--draft`，每边缩到一半，或者加 `--size WIDTH`，宽度要是偶数。
5. 可选：想画出一集里每个镜头场景的中间那一秒，运行：

```bash Terminal
minicut scene render --project rain-shop --episode ep01 --still middle
```

它会依次画每一个 `cNN.scene.json`。最后一行会说画了几个、没画几个。

## 检查是否成功

静帧是一个 `.png` 文件，显示从机位看到的场景。没通过 `minicut scene check` 的场景不会被画出来，命令会打印 `Not rendered` 和问题。想画一段视频而不是静帧，见 [minicut scene render](/zh-cn/docs/cli/scene-and-music#minicut-scene-render)。

## 某一步失败时

| 信息 | 怎么做 |
|---|---|
| `ops[1].op: "NAME" is not a composer op.` | 用信息里列出的操作名之一 |
| `this text reads as code. An ops file holds names, numbers and text, never code.` | 直接写值本身，不要写生成它的代码 |
| `scene build reads data only, never a script.` | 生成一个名为 `NAME.scene.ops.json` 的文件 |
| `Not written: N problem(s).` | 先修好它上面打印的问题，再生成一次 |
| `No camera in c01.scene.json: add one to the scene.` | 加一个 `addCamera` 操作，再生成一次 |
| `--still is a second from 0 to 4 (the timeline).` | 给一个在场景时长之内的秒数 |
| `Use --draft or --size <width>, not both.` | 两个选一个 |
| `The render page is not on this computer ...` | 运行 `minicut update`，再画一次 |
| `No headless browser (chrome-headless-shell) on this computer.` | 运行 `minicut setup`，再画一次 |

## 下一步

[场景和音乐](/zh-cn/docs/cli/scene-and-music) [写音乐](/zh-cn/docs/cli/write-the-music) [检查和环节](/zh-cn/docs/cli/checks-and-loops)
