> **本文回答**：基础 API = `Engine.undoManager.{undo, redo, canUndo, canRedo, getUndoCount, getRedoCount}`。命令版 = 命令行 `UNDO` / `REDO`（**没有 `U` 别名**）或快捷键 `Ctrl+Z`（仅撤销；菜单上显示的 `Ctrl+Y` 并未绑定）。`undo()/redo()/undoSteps()` 只改数据、**不刷新画布**，之后要自己 `Engine.regen()`。批量撤销 / 重做 N 步用 `undoSteps(n)` / `redoSteps(n)`，可按名称或步数。

## 一、最小 API

```js
const u = Engine.undoManager;

u.undo(); Engine.regen();   // undo/redo/undoSteps 只改数据不刷新画布（返回值是所需重绘级别）
u.redo(); Engine.regen();

if (u.canUndo()) console.log('可撤销');
if (u.canRedo()) console.log('可重做');

console.log('剩余撤销步数:', u.getUndoCount());
console.log('剩余重做步数:', u.getRedoCount());
```

MCP 参考：`vjcad/doc/undo/index.md`、`vjcad/example/undo/01basic.js`。

## 二、批量撤销 / 重做

```js
// 撤销 3 步
u.undoSteps(3);

// 按名称撤销 — 撤销到最近一次匹配（含），无匹配则不执行
u.undoSteps('绘制直线');

// 静默撤销：silent 只抑制逐步的 UndoStackChanged（undoSteps 结束时仍派发一次）；
// UndoManager 本身从不写命令行
u.undoSteps(3, { silent: true });
Engine.regen();

// 重做
u.redoSteps(2);
u.redoSteps('绘制圆');

// 静默回滚最后一组操作（用于对话框取消等）：全程不派发 UndoStackChanged，UI 撤销按钮不会刷新
u.rollbackLastGroup();
Engine.regen();
```

MCP 参考：`vjcad/doc/undo/undo-redo.md`、`vjcad/example/undo/04description.js`。

## 三、命令行 / 快捷键

| 命令 | 快捷键 | 说明 |
|---|---|---|
| `UNDO` | `Ctrl+Z` | 撤销（UNDO 命令会按返回值自动 redraw/regen 并在命令行提示） |
| `REDO` | —（菜单显示 `Ctrl+Y`，但键盘处理只绑定了 `Ctrl+Z`） | 重做 |

```js
Engine.editor.executerWithOp('UNDO');   // ⚠️ 不是 'U'——CommandAlias 里没有 U→UNDO 的别名
Engine.editor.executerWithOp('REDO');   // 'MREDO' 也不存在
```

## 四、典型 UI 集成

```js
function refreshUndoButton() {
  const btnUndo = document.getElementById('btn-undo');
  const btnRedo = document.getElementById('btn-redo');
  btnUndo.disabled = !Engine.undoManager.canUndo();
  btnRedo.disabled = !Engine.undoManager.canRedo();
}

// 监听撤销栈变化（推荐）
import { CadEventManager, CadEvents } from 'vjcad';
CadEventManager.getInstance().on(CadEvents.UndoStackChanged, (args) => {
  refreshUndoButton();
  console.log('下一步撤销:', args.nextUndoLabel);
});
```

MCP 参考：`vjcad/doc/undo/undo-redo.md`、`vjcad/example/undo/04description.js`。

## 反例

- 以为 `u.undo()` / `u.undoSteps(n)` 会自动刷新画布：它们只改数据、返回所需重绘级别，不会 redraw/regen——画面不变，要自己 `Engine.regen()`（`UNDO` 命令才会自动刷新）。批量撤销用 `u.undoSteps(n)`（还支持按名称）。
- 在没有 `startUndoRecord` 的情况下直接 `move/scale` 实体后 undo：因为没有撤销标记，看上去什么都没回滚。

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/undo/index.md`、`vjcad/doc/undo/undo-redo.md`
- 示例：`vjcad/example/undo/01basic.js`
