命令设计模式
大约 7 分钟
命令设计模式
不同类型的命令有不同的交互模式。本章介绍 WebCAD 中常见的命令设计模式,帮助你选择合适的实现方式。
模式概览
| 模式 | 特点 | 适用场景 | 示例 |
|---|---|---|---|
| 简单命令 | 一次性执行,无交互 | 批量操作、系统命令 | ERASE, REGEN |
| 步骤式命令 | P1->P2->P3 方法链 | 固定步骤的绘图命令 | LINE, CIRCLE |
| 状态机命令 | while + switch | 复杂分支的命令 | 多选项命令 |
| 循环交互命令 | while(true) 循环 | 连续操作 | TRIM, OFFSET |
| 模式切换命令 | 单次/连续切换 | 可选连续操作 | COPY |
模式1:简单命令
最简单的命令模式,执行后立即完成,无需用户交互。
特点
- 一次性执行
- 无用户交互或仅选择实体
- 适合批量操作
示例:删除命令
import { Engine } from 'vjcad';
import { getSelections, InputStatusEnum, writeMessage } from 'vjcad';
export class EraseCommand {
async main(): Promise<void> {
// 选择实体
const result = await getSelections();
if (result.status === InputStatusEnum.OK && result.value.length > 0) {
const entities = result.value;
// 执行删除
Engine.currentDoc.currentSpace.erase(entities);
Engine.undoManager.erased_undoMark(entities);
// 刷新
Engine.pcanvas.clearGrip();
Engine.pcanvas.regen();
writeMessage(`<br/>删除了 ${entities.length} 个实体。`);
}
}
}示例:全图显示命令
import { Engine } from 'vjcad';
export class ZoomExtentsCommand {
async main(): Promise<void> {
Engine.pcanvas.zoomExtents();
Engine.pcanvas.regen();
}
}示例:重生成命令
import { Engine } from 'vjcad';
export class RegenCommand {
async main(): Promise<void> {
Engine.pcanvas.regen();
}
}模式2:步骤式命令
通过 P1()、P2()、P3() 等方法实现固定步骤的命令流程。
特点
- 清晰的步骤划分
- 使用 stepNumber 控制流程
- 支持撤销到上一步
示例:直线命令
import { Point2D, LineEnt, Engine } from 'vjcad';
import {
getPoint,
PointInputOptions,
InputStatusEnum,
ssSetFirst,
writeMessage
} from 'vjcad';
export class LineCommand {
private points: Point2D[] = [];
private stepNumber: number = 1;
private startPoint: Point2D = new Point2D();
async main(): Promise<void> {
ssSetFirst([]);
Engine.undoManager.start_undoMark();
// 步骤循环
while (this.stepNumber > 0) {
switch (this.stepNumber) {
case 1:
await this.P1();
break;
case 2:
await this.P2();
break;
default:
await this.P3();
break;
}
}
Engine.undoManager.end_undoMark();
}
// 第一步:获取起点
private async P1(): Promise<void> {
const options = new PointInputOptions("指定第一点:");
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.startPoint = result.value;
this.points.push(result.value);
this.stepNumber = 2;
} else {
this.stepNumber = 0; // 退出
}
}
// 第二步:获取第二点
private async P2(): Promise<void> {
const lastPoint = this.points[this.points.length - 1];
const options = new PointInputOptions("指定第二点 [撤销(U)]:");
options.keywords = ["U"];
options.useBasePoint = true;
options.basePoint = lastPoint;
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.createLine(lastPoint, result.value);
this.points.push(result.value);
this.stepNumber = 3;
} else if (result.stringResult === "U") {
this.points.pop();
this.stepNumber = 1;
} else {
this.stepNumber = 0;
}
}
// 第三步及后续:继续绘制
private async P3(): Promise<void> {
const lastPoint = this.points[this.points.length - 1];
const options = new PointInputOptions(
`指定第${this.stepNumber}点 [闭合(C)/撤销(U)]:`
);
options.keywords = ["C", "U"];
options.useBasePoint = true;
options.basePoint = lastPoint;
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.createLine(lastPoint, result.value);
this.points.push(result.value);
this.stepNumber++;
} else if (result.stringResult === "C") {
// 闭合
if (this.points.length >= 2) {
this.createLine(lastPoint, this.startPoint);
}
this.stepNumber = 0;
} else if (result.stringResult === "U") {
// 撤销
if (this.points.length > 1) {
this.points.pop();
Engine.currentDoc.currentSpace.items.pop();
Engine.undoManager.items.pop();
Engine.pcanvas.regen();
this.stepNumber--;
}
} else {
this.stepNumber = 0;
}
}
private createLine(p1: Point2D, p2: Point2D): void {
const line = new LineEnt(p1, p2);
line.setDefaults();
Engine.pcanvas.addEntity(line);
Engine.undoManager.added_undoMark([line]);
}
}示例:圆命令
import { Point2D, CircleEnt, Engine } from 'vjcad';
import {
getPoint,
PointInputOptions,
InputStatusEnum,
ssSetFirst,
writeMessage
} from 'vjcad';
export class CircleCommand {
private static lastRadius: number = 10;
private center: Point2D = new Point2D();
private radius: number = 0;
async main(): Promise<void> {
ssSetFirst([]);
await this.P1();
}
// 第一步:获取圆心
private async P1(): Promise<void> {
const options = new PointInputOptions("指定圆心:");
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.center = result.value;
await this.P2();
}
}
// 第二步:获取半径
private async P2(): Promise<void> {
const options = new PointInputOptions(
`指定半径 <${CircleCommand.lastRadius}>:`
);
options.useBasePoint = true;
options.basePoint = this.center;
// 预览回调
options.callback = (canvasPoint: Point2D) => {
const worldPoint = Engine.trans.CanvasToWcs(canvasPoint);
const radius = this.center.distanceTo(worldPoint);
const preview = new CircleEnt(this.center, radius);
preview.setDefaults();
Engine.pcanvas.drawControl.previewGraphics.clear();
Engine.pcanvas.drawControl.drawPreviewEntity(preview);
};
const result = await getPoint(options);
Engine.pcanvas.drawControl.previewGraphics.clear();
if (result.status === InputStatusEnum.OK) {
this.radius = this.center.distanceTo(result.value);
this.createCircle();
} else if (result.status === InputStatusEnum.EnterOrSpace) {
// 使用默认半径
this.radius = CircleCommand.lastRadius;
this.createCircle();
}
}
private createCircle(): void {
const circle = new CircleEnt(this.center, this.radius);
circle.setDefaults();
Engine.pcanvas.addEntity(circle);
Engine.undoManager.added_undoMark([circle]);
CircleCommand.lastRadius = this.radius;
writeMessage("<br/>圆已创建。");
}
}模式3:状态机命令
使用 while 循环和 switch 语句实现复杂的分支逻辑。
特点
- 灵活的状态转换
- 适合多分支、多选项的命令
- 状态可以跳转到任意步骤
示例:多步骤命令
import { Point2D, PolylineEnt, BulgePoint, Engine } from 'vjcad';
import {
getPoint,
PointInputOptions,
InputStatusEnum,
writeMessage
} from 'vjcad';
enum CommandState {
GetFirstPoint = 1,
GetNextPoint = 2,
GetArcPoint = 3,
Finish = 0
}
export class PlineCommand {
private state: CommandState = CommandState.GetFirstPoint;
private points: Point2D[] = [];
private bulges: number[] = [];
private isArcMode: boolean = false;
async main(): Promise<void> {
Engine.undoManager.start_undoMark();
try {
while (this.state !== CommandState.Finish) {
switch (this.state) {
case CommandState.GetFirstPoint:
await this.stateGetFirstPoint();
break;
case CommandState.GetNextPoint:
await this.stateGetNextPoint();
break;
case CommandState.GetArcPoint:
await this.stateGetArcPoint();
break;
}
}
this.createPolyline();
} finally {
Engine.undoManager.end_undoMark();
Engine.clearPreview();
}
}
private async stateGetFirstPoint(): Promise<void> {
const options = new PointInputOptions("指定起点:");
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.points.push(result.value);
this.bulges.push(0);
this.state = CommandState.GetNextPoint;
} else {
this.state = CommandState.Finish;
}
}
private async stateGetNextPoint(): Promise<void> {
const lastPoint = this.points[this.points.length - 1];
const options = new PointInputOptions(
"指定下一点 [圆弧(A)/闭合(C)/撤销(U)] <完成>:"
);
options.keywords = ["A", "C", "U"];
options.useBasePoint = true;
options.basePoint = lastPoint;
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.points.push(result.value);
this.bulges.push(0);
} else if (result.stringResult === "A") {
this.isArcMode = true;
this.state = CommandState.GetArcPoint;
} else if (result.stringResult === "C") {
// 闭合并完成
if (this.points.length >= 3) {
this.state = CommandState.Finish;
} else {
writeMessage("<br/>需要至少3个点才能闭合。");
}
} else if (result.stringResult === "U") {
if (this.points.length > 1) {
this.points.pop();
this.bulges.pop();
} else {
this.state = CommandState.GetFirstPoint;
}
} else {
this.state = CommandState.Finish;
}
}
private async stateGetArcPoint(): Promise<void> {
const lastPoint = this.points[this.points.length - 1];
const options = new PointInputOptions(
"指定圆弧端点 [直线(L)/撤销(U)]:"
);
options.keywords = ["L", "U"];
options.useBasePoint = true;
options.basePoint = lastPoint;
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.points.push(result.value);
// 计算凸度(简化处理)
this.bulges.push(0.5);
} else if (result.stringResult === "L") {
this.isArcMode = false;
this.state = CommandState.GetNextPoint;
} else if (result.stringResult === "U") {
if (this.points.length > 1) {
this.points.pop();
this.bulges.pop();
}
this.state = CommandState.GetNextPoint;
} else {
this.state = CommandState.Finish;
}
}
private createPolyline(): void {
if (this.points.length < 2) return;
const pline = new PolylineEnt();
for (let i = 0; i < this.points.length; i++) {
pline.bulgePoints.add(new BulgePoint(this.points[i], this.bulges[i] || 0));
}
pline.setDefaults();
Engine.pcanvas.addEntity(pline);
Engine.undoManager.added_undoMark([pline]);
}
}模式4:循环交互命令
使用 while(true) 实现持续的用户交互,直到用户明确退出。
特点
- 持续循环直到用户退出
- 每次循环独立完成一个操作
- 适合批量修改类命令
示例:修剪命令
import { Engine, Point2D } from 'vjcad';
import {
getSelections,
getEntity,
SelectionInputOptions,
SelectionSetInputOptions,
InputStatusEnum,
writeMessage
} from 'vjcad';
export class TrimCommand {
private boundaries: any[] = [];
async main(): Promise<void> {
Engine.undoManager.start_undoMark();
try {
// 选择修剪边界
await this.selectBoundaries();
if (this.boundaries.length === 0) return;
// 循环修剪
await this.trimLoop();
} finally {
Engine.undoManager.end_undoMark();
Engine.pcanvas.clearHighLight();
Engine.pcanvas.regen();
}
}
private async selectBoundaries(): Promise<void> {
writeMessage("<br/>选择修剪边界:");
const result = await getSelections(new SelectionInputOptions());
if (result.status === InputStatusEnum.OK && result.value.length > 0) {
this.boundaries = result.value;
writeMessage(`<br/>已选择 ${this.boundaries.length} 个边界。`);
}
}
private async trimLoop(): Promise<void> {
// 高亮边界
Engine.pcanvas.highLightEntities(this.boundaries);
writeMessage("<br/>选择要修剪的对象(回车结束):");
// 循环选择
while (true) {
const options = new SelectionSetInputOptions("选择要修剪的对象:");
const result = await getEntity(options);
if (result.status === InputStatusEnum.OK && result.pickedEntity) {
// 执行修剪
this.trimEntity(result.pickedEntity, result.pickedPoint);
Engine.pcanvas.regen();
} else if (result.status === InputStatusEnum.EnterOrSpace) {
break; // 用户按回车结束
} else if (result.status === InputStatusEnum.Cancel) {
break; // 用户按 ESC 取消
}
}
}
private trimEntity(entity: any, pickPoint: Point2D): void {
// 修剪逻辑...
writeMessage(`<br/>修剪 ${entity.type}`);
}
}示例:偏移命令
import { Engine, Point2D } from 'vjcad';
import {
getPoint,
getEntity,
PointInputOptions,
SelectionSetInputOptions,
InputStatusEnum,
ssSetFirst,
writeMessage
} from 'vjcad';
export class OffsetCommand {
private offsetDistance: number = 0;
async main(): Promise<void> {
ssSetFirst([]);
Engine.undoManager.start_undoMark();
try {
// 获取偏移距离
if (!await this.getDistance()) return;
// 循环偏移
await this.offsetLoop();
} finally {
Engine.undoManager.end_undoMark();
Engine.pcanvas.clearHighLight();
Engine.pcanvas.regen();
}
}
private async getDistance(): Promise<boolean> {
const options = new PointInputOptions(
`输入偏移距离 <${Engine.OFFSETDIST}>:`
);
options.allowNumberResult = true;
const result = await getPoint(options);
if (result.status === InputStatusEnum.IsNumber) {
this.offsetDistance = result.numberValue;
Engine.OFFSETDIST = this.offsetDistance;
return true;
} else if (result.status === InputStatusEnum.EnterOrSpace) {
this.offsetDistance = Engine.OFFSETDIST;
return true;
}
return false;
}
private async offsetLoop(): Promise<void> {
while (true) {
// 选择要偏移的实体
const selectOptions = new SelectionSetInputOptions("选择要偏移的对象:");
const selectResult = await getEntity(selectOptions);
if (selectResult.status === InputStatusEnum.OK && selectResult.pickedEntity) {
// 高亮
Engine.pcanvas.drawControl.drawHighLightEntity(selectResult.pickedEntity);
Engine.pcanvas.redraw();
// 指定偏移方向
const sideOptions = new PointInputOptions("指定偏移方向:");
sideOptions.useOsnap = false;
const sideResult = await getPoint(sideOptions);
Engine.pcanvas.clearHighLight();
if (sideResult.status === InputStatusEnum.OK) {
this.executeOffset(selectResult.pickedEntity, sideResult.value);
Engine.pcanvas.regen();
}
} else if (
selectResult.status === InputStatusEnum.EnterOrSpace ||
selectResult.status === InputStatusEnum.Cancel
) {
break;
}
}
}
private executeOffset(entity: any, sidePoint: Point2D): void {
// 偏移逻辑...
writeMessage(`<br/>偏移 ${entity.type},距离 ${this.offsetDistance}`);
}
}模式5:模式切换命令
支持单次执行和连续执行两种模式切换。
特点
- 可在单次和连续模式间切换
- 用户可选择操作方式
- 适合复制、阵列等命令
示例:复制命令
import { Engine, Point2D } from 'vjcad';
import {
getSelections,
getPoint,
SelectionInputOptions,
PointInputOptions,
InputStatusEnum,
writeMessage
} from 'vjcad';
type CopyMode = 'Single' | 'Multi';
export class CopyCommand {
private selectedEntities: any[] = [];
private basePoint: Point2D = new Point2D();
private copyMode: CopyMode = 'Multi'; // 默认连续复制
async main(): Promise<void> {
// 选择实体
const selResult = await getSelections();
if (selResult.status !== InputStatusEnum.OK || selResult.value.length === 0) {
return;
}
this.selectedEntities = selResult.value;
// 高亮
Engine.pcanvas.highLightEntities(this.selectedEntities);
// 获取基点
await this.getBasePoint();
// 执行复制
await this.copyLoop();
// 清理
Engine.pcanvas.clearHighLight();
Engine.pcanvas.regen();
}
private async getBasePoint(): Promise<boolean> {
const message = this.copyMode === 'Single'
? "指定基点 [连续(M)]:"
: "指定基点:";
const options = new PointInputOptions(message);
if (this.copyMode === 'Single') {
options.keywords = ["M"];
}
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.basePoint = result.value;
return true;
} else if (result.stringResult === "M") {
this.copyMode = 'Multi';
writeMessage("<br/>连续复制模式");
return await this.getBasePoint();
}
return false;
}
private async copyLoop(): Promise<void> {
while (true) {
const message = this.copyMode === 'Single'
? "指定复制目标 [连续(M)]:"
: "指定复制目标 <完成>:";
const options = new PointInputOptions(message);
options.useBasePoint = true;
options.basePoint = this.basePoint;
if (this.copyMode === 'Single') {
options.keywords = ["M"];
}
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
this.executeCopy(result.value);
// 单次模式下复制一次后退出
if (this.copyMode === 'Single') {
break;
}
} else if (result.stringResult === "M") {
this.copyMode = 'Multi';
writeMessage("<br/>连续复制模式");
} else {
break; // 完成或取消
}
}
}
private executeCopy(targetPoint: Point2D): void {
Engine.undoManager.start_undoMark();
const copiedEntities: any[] = [];
for (const entity of this.selectedEntities) {
const cloned = entity.clone();
cloned.move(this.basePoint, targetPoint);
Engine.pcanvas.addEntity(cloned);
copiedEntities.push(cloned);
}
Engine.undoManager.added_undoMark(copiedEntities);
Engine.undoManager.end_undoMark();
Engine.pcanvas.redraw();
}
}