> **本文回答**：vjmapext 命令系统总览 —— CommandRegistry / CommandContext、40+ 内置命令、自定义命令、预览 / 快捷键 / Undo。

# 绘图命令主题 (vjmapext)

vjmapext 用**命令模式**封装所有交互式绘图 / 编辑操作。每条命令是一个独立的异步流程，通过 `CommandRegistry` 注册、通过 `executeCommand(name)` 执行。

## 架构

```
UI / 快捷键 / 右键菜单
        │ mapcadLayer.executeCommand('LINE')
        ▼
CommandRegistry.execute → CommandClass.main(ctx)
                                 │
        ┌────────────────────────┼─────────────────────────┐
        ▼          ▼          ▼        ▼        ▼        ▼
     store   inputManager  eventBus  preview  gripMgr   undoMgr
   (实体集)  (点/选/数字)  (广播)   (橡皮筋)  (夹点)   (撤销)
```

## 子项索引

| 文件 | 主题 |
|---|---|
| [execute-command](execute-command.md) | `executeCommand` / `repeatLastCommand` / `getLastCommandName`（`commandRegistry` 上为 `repeatLast`/`lastCommandName`） |
| [builtin-draw](builtin-draw.md) | LINE/PLINE/POINT/CIRCLE/ARC/ELLIPSE/SPLINE/FREEHAND/RECTPOLY/DLINE/REVCLOUD |
| [builtin-edit](builtin-edit.md) | MOVE/COPY/ERASE/MIRROR/OFFSET/SCALE/STRETCH/BREAK/TRIM/EXTEND/FILLET/ARRAY/EXPLODE/DRAWORDER |
| [input-manager](input-manager.md) | `getPoint` / `getSelections` / `getReal` / `getInteger` / `getKeyword` / `getString` |
| [snap](snap.md) | `SnapManager` + 10 种捕捉模式（END/MID/CEN/NOD/QUA/INT/INS/PER/TAN/NEA 位标志） |
| [grip](grip.md) | 夹点编辑：GripManager / GripPointDef / GripType |
| [preview](preview.md) | `PreviewManager.setPreviewEntities` 橡皮筋实时预览 |
| [undo-redo](undo-redo.md) | `undoManager.beginTransaction` / `undo` / `redo` |
| [serialize](serialize.md) | `mapcadLayer.toJSON / fromJSON` / `DrawingDefaults` |
| [custom-command](custom-command.md) | `commandRegistry.register`：完整自定义命令 |
| [shortcuts](shortcuts.md) | `setShortcuts` / `getShortcuts` / `setModeToggleShortcut`（动作→键位映射） |
| [mode](mode.md) | `setMode('edit'\|'browse') / toggleMode` |

## 最小代码

```js
// 打开地图 + 挂 MapCadLayer（官方示例与模板写法）
const map = new vjmap.Map({ container: 'map', ... });
map.attach(svc, prj);          // 必须：CoordMapper 靠 map.getMapProjection() 做 CAD↔经纬度
await map.onLoad();
const mapcadLayer = new vjmapext.MapCadLayer();
map.addControl(mapcadLayer);   // addControl 即触发 onAdd，无需再等 ready

// 顺序说明：onAdd 内部是 `map.loaded() ? doConnect() : map.on('load', doConnect)`，
// 所以 onLoad 前挂、onLoad 后紧接着挂都可以。⚠️ 风险窗口：onLoad 之后又 await 了别的异步、
// 地图正在加载新瓦片时才 addControl，`map.loaded()` 可能为 false 而 'load' 不会再发
// → doConnect 两路落空，mapcad-* 渲染桶永远不建，实体进 store 但**画面静默不渲染**。
// 已错过时可 `map.fire('load')` 重发事件触发补连（或 removeControl+addControl 重挂）。

// 启动一条 LINE 命令（等用户点击两点后自动落实）
mapcadLayer.executeCommand('LINE');

// 键入其他命令
mapcadLayer.executeCommand('CIRCLE');    // 6 种绘制模式（圆心半径 / 两点 / 三点 / TTR / TTT / 圆心直径）
mapcadLayer.executeCommand('MOVE');      // 选集 → 基点 → 目标点
```

## 主题级 Top 5 陷阱

1. **多命令互斥**：一次只有一条命令在跑；启动新命令会取消旧命令。
2. **忘记 `ctx.inputManager.cancelCurrentInput`**：UI 切换时要主动取消，否则留下"半死"命令。
3. **以为自定义命令要手动包 undo 事务**：不必——`commandRegistry.execute` 已在 `main(ctx,opts)` 外层自动 `beginTransaction`/`endTransaction`，命令内所有 `store.add/delete`/`recordModify` 天然合并成一条 undo；手动再包只是无害嵌套。
4. **`getPoint` 的 `message` 不写关键字提示**：用户看不到 `C`/`U` 等可用命令字母（提示字段是 `message` 不是 `prompt`）。
5. **预览完没 `clear()`**：预览实体残留在画面上（方法名 `pm.clear()`，不存在 `clearPreview`）。

## 相关主题

- 事件（command:started/ended / input:started/ended）→ [event/bus-command-mode](../event/bus-command-mode.md)
- 实体写入 → [entity/lifecycle](../entity/lifecycle.md)
- 右键菜单触发命令 → [interaction/context-menu](../../../vjmap/references/interaction/context-menu.md)
