> **本文回答**：vjcad 提供两条内置脚本命令——`EXECSTR`（执行 CAD 命令脚本）和 `EXECJS`（执行 JavaScript 代码）。代码侧有两个层级：`Engine.editor.automate(scriptString)` 是**裸命令喂行器**（只认命令名行+点输入行），`new vjcad.ExecuteStrCommand().executeString(script)` 才支持 `SET`/`$(VAR)`/`REPEAT` 等语法糖。

## 一、`Engine.editor.automate(script)`：裸命令喂行器

语义（源码 `UD` 类 + `getPoint` 自动化分支）：

- **主循环只执行命令名行**：逐行 `CommandRegistry.item(行)` 查找，非命令行（含 `SET`、`$(…)`、垃圾文本、空行、注释）**静默跳过**——不报错、不执行。
- **命令别名在脚本里无效**：主循环只查 `CommandRegistry.item`，不经 `CommandAliasManager` 解析，所以 `L` / `C` / `M` 这类别名行同样被静默跳过（交互命令行 `executerWithOp` 才解析别名）。脚本里一律写全名 `LINE` / `CIRCLE` / `MOVE`。
- **只有 `getPoint` 会消费脚本行**：命令执行中每个点提示从 `scriptRowStack` 读下一非空行，跳过 `;`/`//` 注释与空行；`"x,y"` 解析为坐标，单数字+`useBasePoint` 解析为相对偏移，命中 `keywords` 则按关键字，否则 **Cancel 结束当前命令**。
- **`getString`/`getKeyword`/`getReal`/`getEntity` 不读脚本行**——命令走到这类提示时会**回到交互模式等待真实输入**，`automate()` 的 Promise 挂起不返回。
- **顶层 `getInteger`/`getCorner` 同样不吃 `automate`**：`getInteger(opts)` 挂起后 `automate("42\n")` 只是把 `"42"` 当命令名查表跳过，promise 永挂；`getCorner` 同理。这两类只能真实 UI 输入或派发 Escape 取消：命令进行中用 `await Engine.editor.awaitDispatchEscapeEvent()`（派发 Escape 并等待命令结束；`Editor` 上**没有** `dispatchEscapeKey`，那是右键菜单的私有方法）；不在命令里的顶层调用，`awaitDispatchEscapeEvent` 要么不派发、要么等不到命令结束，改为直接 `document.dispatchEvent(new KeyboardEvent('keydown', { code: 'Escape' }))`。vjcad **没有** vjmapext `inputManager.resolve*Value` 那样的程序化落地通道。
- **空行≠回车**：空行被点输入读取循环跳过。`LINE`/`PLINE` 这类需要 Enter 收尾的命令，靠**下一条命令名行被当无效点读入→Cancel** 收尾（已画段会提交）。所以两条 LINE 要写成 `LINE p1 p2 LINE LINE p3 p4 <收尾行>` 形态。
- **脚本末尾不能留着待输入的命令**：栈空时 `getPoint` 落回交互等待，`automate()` Promise 永不 resolve（静默挂死）。

```js
// 可直接运行：两条 CIRCLE（圆心+半径两段自动完成，无需收尾行）
await Engine.editor.automate(`CIRCLE
5000,5000
30
CIRCLE
5200,5000
40
`);

// LINE 需要"下一条命令名"收尾——这里第一条 CIRCLE 行既给 LINE Cancel 收尾、
// 第二条 CIRCLE 行才真正启动画圆
await Engine.editor.automate(`LINE
5000,5000
5100,5000
CIRCLE
CIRCLE
6000,5000
20
`);
```

MCP 参考：`vjcad/example/command/05executescript.js`（⚠️ 示例里的矩形脚本以 `LINE`+空行收尾——最后一条 LINE 会等不到输入而挂起，且空行并不产生回车）。

## 二、`ExecuteStrCommand.executeString(script)`：带语法糖的脚本

`EXECSTR` 命令类（导出为 `vjcad.ExecuteStrCommand`）内部用 `GD` 解析器把脚本编译成可执行行序列，再喂给 `automate`——**`SET`/`$(VAR)`/`%VAR%`/`$(expr)`/`REPEAT n…ENDREPEAT`/`GOTO`/`:label`/`IF … GOTO` 都在这一层生效**，裸 `automate` 里它们只是被静默跳过的普通行。

```js
await new vjcad.ExecuteStrCommand().executeString(`SET Y=8000
REPEAT 3
CIRCLE
9000,$(Y)
$(10*2)
ENDREPEAT
`);
// REPEAT 3 展开三次 → CIRCLE +3；$(Y)→8000、$(10*2)→20
```

⚠️ REPEAT 展开的是**原样的行序列**——块内若放 `LINE` 这类需收尾命令，块首的 `LINE` 行会沦为上一轮 LINE 的 Cancel 输入，循环只产出 1 条。块内用自完成命令（CIRCLE/RECTANG 等两点即完的）才稳。

语法表（ExecuteStrCommand 层）：

| 元素 | 含义 |
|---|---|
| `;` 或 `//` 开头 | 注释（解析器与 automate 两层都会跳过） |
| `SET VAR=value` | 变量赋值（值先经 `$(…)`/`%…%` 展开，可解析成数字则存数字） |
| `$(VAR)` / `%VAR%` | 引用变量 |
| `$(expr)` | 表达式：`$(100+50*2)`、`$(PI)`、`$(CLAYER)`、`$(CECOLOR)`、`$(LASTPOINT_X/Y)` |
| `REPEAT n … ENDREPEAT` | 循环展开（`n` 可写表达式，嵌套 REPEAT 支持） |
| `GOTO :label` / `IF cond GOTO :label` | 跳转/条件跳转（`:label` 行定义标签） |
| 空行 | 编译为 `""` 行进入 automate，再被点输入跳过——**不是回车** |

## 二、`EXECSTR`：弹脚本编辑对话框

```js
await Engine.editor.executerWithOp('EXECSTR');
```

会弹出脚本输入对话框，用户在里面写脚本然后执行。MCP 参考：`vjcad/example/command/05executescript.js`（⚠️ 示例里打印的 `EXECUTESTR` 是过期名，真实注册名 `EXECSTR`；`EXECJS` 同理）。

## 三、`EXECJS`：弹 JS 代码对话框，跑一段 JS

```js
await Engine.editor.executerWithOp('EXECJS');
```

执行上下文（vjcad@1.0.16 源码）：`ExecuteJsCommand.executeCode` 把 `window.vjcad` 的全部导出按名字注入 `AsyncFunction` 参数——即**所有 vjcad 顶层导出都可用**，如 `Engine`、`Point2D`、`LineEnt`、`CircleEnt`、`ArcEnt`、`PolylineEnt`、`TextEnt`、`MTextEnt`、`writeMessage`、`message`、`regen` 等。

⚠️ 官方示例 `06executejs.js` 自述的 `doc` / `log` / `executeCommand` / `addEntity` 四个全局在 vjcad@1.0.16 源码中**并不存在**（它们不是包导出）；等价写法是 `Engine.currentDoc`、`message.info(...)`/`writeMessage(...)`、`Engine.editor.executerWithOp('CMD')`、`Engine.addEntities(ent)`。示例模板里 `addEntity(circle)` / `executeCommand(...)` 的写法在对话框里会 `ReferenceError`。

MCP 参考：`vjcad/example/command/06executejs.js`。

## 四、对比 `automate` vs `executerWithOp`

| 想做的 | 用 |
|---|---|
| 跑一段已有的命令脚本 | `Engine.editor.automate(scriptString)` |
| 跑一条命令（字符串名） | `Engine.editor.executerWithOp('CMDNAME')` |
| 弹给用户编辑脚本 / JS | `Engine.editor.executerWithOp('EXECSTR')` / `'EXECJS'` |

## 反例

- 给 `automate` 传含 `SET`/`$(…)`/`REPEAT` 的脚本：这些行在 automate 层**全部静默跳过**——`$(R)` 喂给点输入会得到 NaN→Cancel。要用 `new vjcad.ExecuteStrCommand().executeString(...)`。
- 脚本以"还在等输入的命令"结尾：栈空 → `getPoint` 落回交互等待 → `automate()` 挂起。收尾用一条自完成命令（如 `CIRCLE` 两段即完）或让下一条命令名吃掉它。
- 脚本里写中文标点：`100，50` 不是合法参数（应是英文 `,`）。
- `automate` 无法喂 `getString`/`getKeyword`/`getReal`/`getEntity` 类输入——命令走到这类提示会静默转交互、Promise 不返回。只能驱动纯点输入链路的命令。

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/command/index.md`
- 示例：`vjcad/example/command/05executescript.js`、`vjcad/example/command/06executejs.js`
