> **本文回答**：`mapcadLayer.executeCommand` / `commandRegistry.repeatLast` / `lastCommandName` —— 启动 / 重复 / 取消命令。

## 一、基本调用

```js
mapcadLayer.executeCommand('LINE');                  // 启动
mapcadLayer.executeCommand('DRAWORDER', { action: 'back' }); // 带选项（内置命令仅 TEXTEDIT/INSERT/DRAWORDER 读 opts）
```

返回 `Promise<void>`。命令启动后异步等待用户输入；不需要 `await`，也可以 `await` 等结束。

## 二、带选项

选项被 `main(ctx, opts)` 的第二个参数接收：

```js
mapcadLayer.executeCommand('TEXTEDIT', { entity: someTextEnt });   // 跳过"选择实体"步骤
mapcadLayer.executeCommand('INSERT', { blockName: '灯具' });
mapcadLayer.executeCommand('DRAWORDER', { action: 'front' });
```

⚠️ **绝大多数内置命令不读 `opts`**——v1.0.1 中只有 `TEXTEDIT`（`opts.entity`）、`INSERT`（`opts.blockName`）、`DRAWORDER`（`opts.action`）三个消费它；`executeCommand('TEXT', { text, height })` 这类写法会被静默忽略。`opts` 主要给自定义命令用。

## 三、重复上一条

```js
mapcadLayer.commandRegistry.repeatLast();
const last = mapcadLayer.commandRegistry.lastCommandName;
// → 'LINE'
```

键盘：`repeatCommand` 快捷键**默认是 `null`（未绑定）**——回车默认是 `confirm` 动作不是 repeat。要回车重复上一条：`mapcadLayer.setShortcuts({ repeatCommand: 'enter' })`（会顶掉 confirm 的回车）。

## 四、取消命令

```js
mapcadLayer.inputManager.cancelCurrentInput();
```

对应的 `getPoint / getSelections / ...` Promise 会以 `status: 'cancel'` resolve，`main` 函数自然流程结束。

按 Esc 默认触发 cancel。**右键行为分输入类型**（右键只处理点输入与实体输入两种状态）：`getPoint` 等待中右键 = cancel；`getEntity({ multiple: true })`（MOVE/COPY/ERASE 等内置编辑命令的选集步骤）等待中右键 = **确认当前选择**（返回 `OK` + 已选实体，等同回车），单选 `getEntity` 右键 = cancel；**`getSelections` 等待中右键无反应**——用回车/空格，或单击空白处结束选择。

## 五、错误处理

```js
await mapcadLayer.executeCommand('UNKNOWN_CMD');
// 不抛异常：console 输出警告 `[vjmapext] Unknown command: UNKNOWN_CMD`，直接 return
```

注意：**命令名不存在不会 throw**（warn + return，Promise 正常 resolve）。想兜底：

```js
const names = mapcadLayer.commandRegistry.getAll().map(c => c.name);
if (!names.includes('MYCMD')) console.error('命令未注册');
```

- `main` 抛异常 → **也被吞掉**：`console.error('[vjmapext] Command XXX error:', err)`，Promise 照样 resolve；finally 里会 `endTransaction` + 清理 `_activeCommand`，**`command:ended` 照发**（实采）。`command:ended` 的 payload 只有 `{ name }`，成功/取消/异常一律在 finally 里发出，**无法据此判断结果**；要结果请在 `main` 里自行 try/catch、`eventBus.emit` 业务事件，或配合 `entity:added` 统计产出。
- **失败的命令不进历史**（实采）：`lastCommandName` 只在 `main` 正常结束时更新；`main` 抛错后 `repeatLast()` 重放的是上一条**成功**的命令。
- **browse 模式静默空转**（实采）：`setMode('browse')` 下 `executeCommand`/`repeatLastCommand` 直接 `resolve()`——不执行、不报错、不发 `command:started`。批量脚本里若误留 browse 态，命令全部静默跳过。

## 六、查看所有已注册命令

```js
// 低级 API
for (const cmd of mapcadLayer.commandRegistry.getAll()) {
  console.log(cmd.name, cmd.description);
}
```

或结合 `getShortcuts()`（返回**动作→键位**映射，见 [shortcuts](shortcuts.md)）：

```js
const shortcuts = mapcadLayer.getShortcuts();
// { undo: 'ctrl+z', cancel: 'escape', ... }
```

## 七、命令互斥

同一时刻只能有一条命令在执行。启动新命令会：

1. 取消旧命令（`cancelCurrentInput`）；
2. 清除旧命令残留的预览 / 高亮；
3. 启动新命令。

这是**特性不是 bug**：工具栏切换时不必手动清理。

## 八、延迟 / 定时启动

```js
setTimeout(() => mapcadLayer.executeCommand('LINE'), 100);
```

常见用途：UI 面板弹出后再启动对应绘图命令，避免 UI 动画吃掉第一次 click。

## 九、组合调用

```js
await mapcadLayer.executeCommand('LINE');        // 等画完一条线
await mapcadLayer.executeCommand('CIRCLE');      // 再画圆
// 注意：中间用户有可能取消；上面 await 只是等 command 的流程结束，并不等价于"成功"
```

## 十、常见坑

1. **await 里等不到用户输入**：命令内部有用户交互，`executeCommand` 不返回"用户确定"；`command:ended`（payload 只有 `{ name }`）也只说明"结束了"，成功与否要靠 `entity:added` 或命令自己 emit 的业务事件判断。
2. **不能并发**：两次 `executeCommand` 并发时，第二次会取消第一次。
3. **空闲按回车没触发**：需要 `mapcadLayer.getMapContainer()?.focus()` 聚焦容器。
4. **右键菜单多次执行 LINE**：注意菜单回调里的 `executeCommand` 会重复启动命令，取消当前再启动新的。


## 相关

- 内置绘图 → [builtin-draw](builtin-draw.md)
- 输入模块 → [input-manager](input-manager.md)
- 事件 → [event/bus-command-mode](../event/bus-command-mode.md)
