撤销重做
大约 6 分钟
撤销重做
WebCAD 提供完整的撤销重做机制,支持各类操作的回退和重做。撤销系统是保证用户操作安全的重要功能。
概述
UndoManager
UndoManager 是撤销重做系统的核心类,负责管理撤销和重做操作。
import { Engine, UndoManager } from 'vjcad';
// 获取撤销管理器
const undoMgr: UndoManager = Engine.undoManager;
// 执行撤销
undoMgr.undo();
// 执行重做
undoMgr.redo();
// 检查状态
const canUndo = undoMgr.canUndo();
const canRedo = undoMgr.canRedo();撤销记录类型
添加实体
import { Engine, LineEnt } from 'vjcad';
// 创建并添加实体(推荐:使用 addEntities 自动记录撤销)
const line = new LineEnt([0, 0], [100, 100]);
line.setDefaults();
Engine.addEntities(line);
// 或者手动记录撤销
const line2 = new LineEnt([0, 0], [50, 50]);
line2.setDefaults();
Engine.pcanvas.addEntity(line2);
Engine.undoManager.added_undoMark([line2]);删除实体
import { Engine, EntityBase } from 'vjcad';
// 删除实体
const entity: EntityBase = /* ... */;
Engine.currentDoc.currentSpace.erase([entity]);
// 记录删除操作(用于撤销时恢复)
Engine.undoManager.erased_undoMark([entity]);
// 或使用 Engine 封装方法
Engine.erasedUndoMark([entity]);
// 最简单的方式:使用 eraseEntities 自动记录
Engine.eraseEntities(entity); // 自动调用 erased_undoMark修改实体
import { Engine, EntityBase } from 'vjcad';
const entity: EntityBase = /* ... */;
// 方式一:使用 modEntity_undoMark
Engine.undoManager.modEntity_undoMark(entity); // 记录修改前状态
entity.color = 1; // 修改属性
entity.setModified();
// 方式二:使用 Engine.markModified
Engine.markModified(entity); // 记录修改前状态
entity.layer = '新图层';
entity.setModified();
// 批量修改
Engine.markModifiedBatch([entity1, entity2, entity3]);移动实体
import { Engine, EntityBase, Point2D } from 'vjcad';
const entities: EntityBase[] = /* ... */;
const fromPoint = new Point2D(0, 0);
const toPoint = new Point2D(100, 100);
// 执行移动
for (const entity of entities) {
entity.move(fromPoint, toPoint);
}
// 记录移动操作
Engine.undoManager.moved_undoMark(entities, fromPoint, toPoint);
// 或使用 Engine 封装方法
Engine.movedUndoMark(entities, fromPoint, toPoint);旋转实体
import { Engine, EntityBase, Point2D } from 'vjcad';
const entities: EntityBase[] = /* ... */;
const basePoint = new Point2D(50, 50);
const angle = Math.PI / 4; // 45度
// 执行旋转
for (const entity of entities) {
entity.rotate(basePoint, angle);
}
// 记录旋转操作
Engine.undoManager.rotate_undoMark(entities, basePoint, angle);
// 或使用 Engine 封装方法
Engine.rotateUndoMark(entities, basePoint, angle);缩放实体
import { Engine, EntityBase, Point2D } from 'vjcad';
const entities: EntityBase[] = /* ... */;
const basePoint = new Point2D(50, 50);
const scale = 2.0;
// 执行缩放
for (const entity of entities) {
entity.scale(basePoint, scale);
}
// 记录缩放操作
Engine.undoManager.scaled_undoMark(entities, basePoint, scale);
// 或使用 Engine 封装方法
Engine.scaledUndoMark(entities, basePoint, scale);镜像实体
import { Engine, EntityBase, Point2D } from 'vjcad';
const entities: EntityBase[] = /* ... */;
const point1 = new Point2D(0, 0);
const point2 = new Point2D(100, 0); // 水平镜像线
// 执行镜像
for (const entity of entities) {
entity.mirror(point1, point2);
}
// 记录镜像操作
Engine.undoManager.mirrored_undoMark(entities, point1, point2);
// 或使用 Engine 封装方法
Engine.mirroredUndoMark(entities, point1, point2);核心方法
| 方法 | 说明 |
|---|---|
canUndo() | 检查是否可以撤销 |
canRedo() | 检查是否可以重做 |
undo() | 执行撤销操作 |
redo() | 执行重做操作 |
start_undoMark() | 开始撤销标记组 |
end_undoMark() | 结束撤销标记组 |
撤销记录类型汇总
| 方法 | 说明 | 撤销时行为 |
|---|---|---|
added_undoMark(entities) | 记录添加实体 | 删除这些实体 |
erased_undoMark(entities) | 记录删除实体 | 恢复这些实体 |
modEntity_undoMark(entity) | 记录实体修改 | 恢复原始状态 |
moved_undoMark(entities, from, to) | 记录移动 | 反向移动 |
rotate_undoMark(entities, base, angle) | 记录旋转 | 反向旋转 |
scaled_undoMark(entities, base, scale) | 记录缩放 | 反向缩放 |
mirrored_undoMark(entities, p1, p2) | 记录镜像 | 再次镜像 |
entLayer_undoMark(entities) | 记录图层修改 | 恢复原图层 |
entColorIndex_undoMark(entities) | 记录颜色修改 | 恢复原颜色 |
entLineType_undoMark(entities) | 记录线型修改 | 恢复原线型 |
entLTScale_undoMark(entities) | 记录线型比例 | 恢复原比例 |
entTransp_undoMark(entities) | 记录透明度 | 恢复原透明度 |
clayer_undoMark(oldLayer) | 记录当前图层切换 | 恢复原图层 |
layerModified_undoMark(layer) | 记录图层属性修改 | 恢复原属性 |
撤销标记组
对于复杂操作(如一个命令中执行多个步骤),使用撤销标记组将多个操作合并为一个撤销单元。
基本用法
import { Engine, LineEnt, CircleEnt } from 'vjcad';
const undoMgr = Engine.undoManager;
// 开始撤销组
undoMgr.start_undoMark();
try {
// 执行多个操作(使用简化写法)
const line = new LineEnt([0, 0], [100, 0]);
line.setDefaults();
line.color = 1;
Engine.addEntities(line);
const circle = new CircleEnt([50, 50], 30);
circle.setDefaults();
circle.color = 3;
Engine.addEntities(circle);
} finally {
// 结束撤销组(确保始终被调用)
undoMgr.end_undoMark();
}
// 撤销时,line 和 circle 会一起被删除使用 Engine 封装方法
import { Engine } from 'vjcad';
// 开始撤销记录
Engine.startUndoRecord();
try {
// 执行操作
Engine.addEntities([entity1, entity2]);
Engine.setEntityColor([entity1, entity2], 1);
} finally {
// 结束撤销记录
Engine.endUndoRecord();
}嵌套撤销组
import { Engine } from 'vjcad';
// 支持嵌套调用
Engine.startUndoRecord();
// 外层操作
Engine.addEntities(entity1);
Engine.startUndoRecord(); // 嵌套
// 内层操作
Engine.addEntities(entity2);
Engine.endUndoRecord(); // 内层结束
// 更多外层操作
Engine.addEntities(entity3);
Engine.endUndoRecord(); // 外层结束
// 只有最外层的 endUndoRecord 会真正创建撤销组
// 所有操作(entity1, entity2, entity3)合并为一个撤销步骤检查嵌套状态
import { Engine } from 'vjcad';
// 检查当前是否在撤销记录组内
if (Engine.isInUndoRecord()) {
console.log('当前在撤销组内');
}撤销流程图
完整命令示例
import {
Engine,
LineEnt,
Point2D,
PointInputOptions,
InputStatusEnum
} from 'vjcad';
export class DrawLineCommand {
async main(): Promise<void> {
const undoMgr = Engine.undoManager;
const lines: LineEnt[] = [];
// 开始撤销组
undoMgr.start_undoMark();
try {
// 获取第一个点
const opt1 = new PointInputOptions("指定起点:");
const result1 = await Engine.getPoint(opt1);
if (result1.status !== InputStatusEnum.OK) return;
let startPoint = result1.value as Point2D;
// 循环获取后续点
while (true) {
const opt2 = new PointInputOptions("指定下一点或 [放弃(U)]:");
opt2.basePoint = startPoint;
opt2.useBasePoint = true;
opt2.keywords = ["U"];
const result2 = await Engine.getPoint(opt2);
if (result2.status === InputStatusEnum.CANCEL) {
break; // 用户取消
}
if (result2.status === InputStatusEnum.KEYWORD) {
if (result2.stringResult === "U" && lines.length > 0) {
// 放弃最后一条线
const lastLine = lines.pop()!;
Engine.currentDoc.currentSpace.erase([lastLine]);
undoMgr.erased_undoMark([lastLine]);
// 恢复起点
if (lines.length > 0) {
startPoint = lines[lines.length - 1].endPoint;
}
}
continue;
}
if (result2.status === InputStatusEnum.OK) {
const endPoint = result2.value as Point2D;
// 创建直线
const line = new LineEnt(startPoint.clone(), endPoint.clone());
line.setDefaults();
// 添加到画布
Engine.pcanvas.addEntity(line);
undoMgr.added_undoMark([line]);
lines.push(line);
startPoint = endPoint;
}
}
} finally {
// 结束撤销组
undoMgr.end_undoMark();
}
// 此时,所有绘制的线条可以一次撤销全部删除
}
}最佳实践
1. 始终配对使用
// 好的做法:使用 try-finally
undoMgr.start_undoMark();
try {
// 操作...
} finally {
undoMgr.end_undoMark(); // 确保被调用
}
// 或使用 Engine 封装
Engine.startUndoRecord();
try {
// 操作...
} finally {
Engine.endUndoRecord();
}2. 批量操作放在同一撤销组
// 好的做法:批量操作合并
Engine.startUndoRecord();
try {
for (const entity of entities) {
Engine.addEntities(entity);
}
} finally {
Engine.endUndoRecord();
}
// 不好的做法:每个操作单独撤销组
for (const entity of entities) {
Engine.startUndoRecord();
Engine.addEntities(entity);
Engine.endUndoRecord(); // 撤销时需要多次 Ctrl+Z
}3. 修改前记录状态
// 好的做法:修改前记录
Engine.markModified(entity); // 先记录
entity.color = 1; // 再修改
entity.setModified();
// 不好的做法:修改后记录(无法恢复原始值)
entity.color = 1;
Engine.markModified(entity); // 此时记录的是新值4. 使用封装方法简化代码
// 推荐:使用 Engine 封装方法
Engine.setEntityColor(entities, 1); // 自动记录撤销
Engine.setEntityLayer(entities, '图层1');
// 而不是手动操作
for (const entity of entities) {
Engine.markModified(entity);
entity.color = 1;
}5. 条件执行时注意撤销组
Engine.startUndoRecord();
try {
if (someCondition) {
Engine.addEntities(entity);
}
// 即使没有执行任何操作,也要正确关闭撤销组
} finally {
Engine.endUndoRecord();
}