> **本文回答**：枚举里有 4 个命令事件，但**实际派发的只有 3 个**：`CommandStarting`（前，可取消）/ `CommandStarted`（后）/ `CommandEnded`（结束，args `{commandName, success, duration}`）。`CommandCancelled` 是未派发的保留事件——按 ESC 退出命令同样触发 `CommandEnded`（`success=false` 仅表示 `main()` 抛了异常），"是否被取消"需由命令自身判断。常用于操作日志、自动化、命令钩子。

## 一、事件清单

| 事件 | 说明 | 可取消 |
|---|---|---|
| `CommandStarting` | 命令开始前 | 是 |
| `CommandStarted` | 命令开始后 | 否 |
| `CommandEnded` | 命令结束（正常完成、ESC 退出、`main()` 抛异常都触发；`success=false` 仅表示抛了异常） | 否 |
| `CommandCancelled` | ✗ **未派发**（枚举保留成员，订阅永不触发） | — |

MCP 参考：`vjcad/doc/event/event-types.md`；派发点在 `invokeCommandWithOp`（只 emit Starting/Started/Ended）。

## 二、最小用法

```js
import { Engine, CadEvents } from 'vjcad';

Engine.eventManager.on(CadEvents.CommandStarted, (args) => {
  console.log('命令开始:', args.commandName);
});

Engine.eventManager.on(CadEvents.CommandEnded, (args) => {
  // ESC 取消也走这里；success 只区分 main() 是否抛异常
  console.log('命令结束:', args.commandName, args.success, args.duration + 'ms');
});
```

MCP 参考：`vjcad/example/event/04commandevents.js`、`vjcad/doc/event/event-usage.md`。

## 三、操作日志

```js
Engine.eventManager.on(CadEvents.CommandEnded, (args) => {
  log(`用户执行了命令: ${args.commandName}`, new Date());
});
```

## 四、阻止特定命令

```js
const blocked = new Set(['EXPORTDWG', 'PURGE']);

Engine.eventManager.on(CadEvents.CommandStarting, (args) => {
  if (blocked.has(args.commandName)) {
    args.cancel = true;
    alert(`命令 ${args.commandName} 已被禁用`);
  }
});
```

MCP 参考：`vjcad/doc/event/event-usage.md`。

## 五、配合 PLINE / SPLINE 等交互式命令

```js
let activePline = null;

Engine.eventManager.on(CadEvents.CommandStarted, (args) => {
  if (args.commandName === 'PLINE') activePline = null;
});

Engine.eventManager.on(CadEvents.EntityAdded, (args) => {
  if (args.entity.type === 'PLINE') activePline = args.entity;
});

// 正常结束与 ESC 退出都只触发 CommandEnded（CommandCancelled 不派发）
Engine.eventManager.on(CadEvents.CommandEnded, (args) => {
  if (args.commandName === 'PLINE' && activePline) {
    console.log('最终点数:', activePline.bulgePoints.items.length);
  }
  activePline = null;
});
```

MCP 参考：`vjcad/example/event/06plinepoints.js`，详见 [pline-timing](pline-timing.md)。

## 六、用 `CommandEnded` 做"绘图后处理"

如果在 `EntityAdded` 里改实体属性，会被后续的命令交互再覆盖（PLINE 加点会重置某些字段）。**最稳的做法是在 `CommandEnded` 里改**：

```js
Engine.eventManager.on(CadEvents.CommandEnded, (args) => {
  if (args.commandName === 'CIRCLE') {
    const last = Engine.getEntities(e => e.type === 'CIRCLE').slice(-1)[0];
    if (last) {
      last.color = 1;
      last.layer = '装饰层';
      last.setModified();
    }
  }
});
```

## 反例

- 在 `CommandStarting` 里弹出 confirm()：阻塞主线程，所有命令变慢。
- 不分 `Started` / `Ended`，把统计逻辑放在 `Started`：命令还没执行就被计数。注意 `Ended` 同样无法区分"完成"和"ESC 取消"，要统计完成数需自行判断（如命令前后实体数 / `UndoStackChanged`）。
- 订阅 `CommandCancelled` 处理取消分支：该事件不派发，分支永远不执行。
- `args.commandName` 大小写不一致：vjcad 命令名一般是大写（`'PLINE'`、`'CIRCLE'`），写 `'pline'` 比较不上。

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/event/event-types.md`、`vjcad/doc/event/event-usage.md`
- 示例：`vjcad/example/event/04commandevents.js`、`06plinepoints.js`
