预览绘制
大约 6 分钟
预览绘制
预览绘制为用户提供实时的视觉反馈,是良好 CAD 命令体验的关键。本章详细介绍如何在命令中实现预览功能。
预览机制概述
WebCAD 提供两种预览机制:
- 单实体预览:绘制单个预览实体(如画圆时显示圆的预览)
- 多实体预览:绘制多个实体的变换预览(如移动/复制时显示选中实体的预览位置)
单实体预览
使用 callback 回调
最常用的预览方式是通过 PointInputOptions.callback 回调函数:
import { Point2D, CircleEnt, Engine } from 'vjcad';
import { getPoint, PointInputOptions, InputStatusEnum } from 'vjcad';
async function drawCircleWithPreview(): Promise<void> {
// 获取圆心
const centerOptions = new PointInputOptions("指定圆心:");
const centerResult = await getPoint(centerOptions);
if (centerResult.status !== InputStatusEnum.OK) return;
const center = centerResult.value;
// 获取半径点(带预览)
const radiusOptions = new PointInputOptions("指定半径:");
radiusOptions.useBasePoint = true;
radiusOptions.basePoint = center;
// 设置预览回调
radiusOptions.callback = (canvasPoint: Point2D) => {
// 将画布坐标转为世界坐标
const worldPoint = Engine.trans.CanvasToWcs(canvasPoint);
// 计算半径
const radius = center.distanceTo(worldPoint);
// 创建预览圆
const previewCircle = new CircleEnt(center, radius);
previewCircle.setDefaults();
// 清除旧预览
Engine.pcanvas.drawControl.previewGraphics.clear();
// 绘制新预览
Engine.pcanvas.drawControl.drawPreviewEntity(previewCircle);
};
const radiusResult = await getPoint(radiusOptions);
// 清除预览
Engine.pcanvas.drawControl.previewGraphics.clear();
if (radiusResult.status === InputStatusEnum.OK) {
// 创建正式圆
const radiusPoint = radiusResult.value;
const radius = center.distanceTo(radiusPoint);
const circle = new CircleEnt(center, radius);
circle.setDefaults();
Engine.pcanvas.addEntity(circle);
Engine.undoManager.added_undoMark([circle]);
}
}预览绘制 API
| 方法 | 说明 |
|---|---|
Engine.pcanvas.drawControl.drawPreviewEntity(entity) | 绘制单个预览实体 |
Engine.pcanvas.drawControl.previewGraphics.clear() | 清除所有预览 |
Engine.clearPreview() | 清除预览(Engine 级别) |
完整示例:矩形预览
import { Point2D, PolylineEnt, BulgePoint, Engine } from 'vjcad';
import {
getPoint,
PointInputOptions,
InputStatusEnum,
ssSetFirst
} from 'vjcad';
export class RectangleCommand {
private corner1: Point2D = new Point2D();
async main(): Promise<void> {
ssSetFirst([]);
Engine.undoManager.start_undoMark();
try {
// 获取第一个角点
const c1Options = new PointInputOptions("指定第一个角点:");
const c1Result = await getPoint(c1Options);
if (c1Result.status !== InputStatusEnum.OK) return;
this.corner1 = c1Result.value;
// 获取对角点(带预览)
const c2Options = new PointInputOptions("指定对角点:");
c2Options.useBasePoint = true;
c2Options.basePoint = this.corner1;
// 设置矩形预览回调
c2Options.callback = (canvasPoint: Point2D) => {
const worldPoint = Engine.trans.CanvasToWcs(canvasPoint);
this.drawRectanglePreview(worldPoint);
};
const c2Result = await getPoint(c2Options);
// 清除预览
Engine.pcanvas.drawControl.previewGraphics.clear();
if (c2Result.status === InputStatusEnum.OK) {
this.createRectangle(c2Result.value);
}
} finally {
Engine.undoManager.end_undoMark();
}
}
private drawRectanglePreview(corner2: Point2D): void {
const rect = this.buildRectangle(this.corner1, corner2);
Engine.pcanvas.drawControl.previewGraphics.clear();
Engine.pcanvas.drawControl.drawPreviewEntity(rect);
}
private buildRectangle(p1: Point2D, p2: Point2D): PolylineEnt {
const pline = new PolylineEnt();
// 四个角点
pline.bulgePoints.add(new BulgePoint(new Point2D(p1.x, p1.y), 0));
pline.bulgePoints.add(new BulgePoint(new Point2D(p2.x, p1.y), 0));
pline.bulgePoints.add(new BulgePoint(new Point2D(p2.x, p2.y), 0));
pline.bulgePoints.add(new BulgePoint(new Point2D(p1.x, p2.y), 0));
pline.isClosed = true;
pline.setDefaults();
return pline;
}
private createRectangle(corner2: Point2D): void {
const rect = this.buildRectangle(this.corner1, corner2);
Engine.pcanvas.addEntity(rect);
Engine.undoManager.added_undoMark([rect]);
}
}多实体预览
移动、复制等命令需要预览多个实体的变换效果。
多实体预览 API
| 方法 | 说明 |
|---|---|
drawPreviewEntities(entities) | 绘制多个预览实体 |
setPreviewPosition(point) | 设置预览位置 |
setPreviewRotation(angle) | 设置预览旋转角度 |
setPreviewScale(sx, sy) | 设置预览缩放 |
resetPreview() | 重置预览 |
移动命令预览示例
import { Point2D, Engine } from 'vjcad';
import {
getSelections,
getPoint,
SelectionInputOptions,
PointInputOptions,
InputStatusEnum
} from 'vjcad';
export class MoveCommand {
private selectedEntities: any[] = [];
private basePoint: Point2D = new Point2D();
private previewRegistered: boolean = false;
async main(): Promise<void> {
// 选择实体
const selResult = await getSelections(new SelectionInputOptions());
if (selResult.status !== InputStatusEnum.OK || selResult.value.length === 0) {
return;
}
this.selectedEntities = selResult.value;
// 高亮显示
Engine.pcanvas.highLightEntities(this.selectedEntities);
// 获取基点
const baseOptions = new PointInputOptions("指定基点:");
const baseResult = await getPoint(baseOptions);
if (baseResult.status !== InputStatusEnum.OK) {
Engine.pcanvas.clearHighLight();
return;
}
this.basePoint = baseResult.value;
// 获取目标点(带多实体预览)
const targetOptions = new PointInputOptions("指定目标点:");
targetOptions.useBasePoint = true;
targetOptions.basePoint = this.basePoint;
// 设置移动预览回调
targetOptions.callback = (canvasPoint: Point2D) => {
this.updateMovePreview(canvasPoint);
};
// 注册预览实体(只需一次)
if (!this.previewRegistered) {
Engine.pcanvas.drawControl.drawPreviewEntities(this.selectedEntities);
this.previewRegistered = true;
}
const targetResult = await getPoint(targetOptions);
// 清理
Engine.pcanvas.drawControl.resetPreview();
Engine.pcanvas.clearHighLight();
if (targetResult.status === InputStatusEnum.OK) {
this.executeMove(targetResult.value);
}
}
private updateMovePreview(canvasPoint: Point2D): void {
const worldPoint = Engine.trans.CanvasToWcs(canvasPoint);
// 计算预览位置
const previewPosition = new Point2D();
previewPosition.move(this.basePoint.clone(), new Point2D());
previewPosition.move(new Point2D(), worldPoint);
// 更新预览位置
Engine.pcanvas.drawControl.setPreviewPosition(previewPosition);
}
private executeMove(targetPoint: Point2D): void {
Engine.undoManager.start_undoMark();
for (const entity of this.selectedEntities) {
entity.move(this.basePoint, targetPoint);
}
Engine.undoManager.moved_undoMark(
this.selectedEntities,
this.basePoint,
targetPoint
);
Engine.undoManager.end_undoMark();
Engine.pcanvas.regen();
}
}带变换的预览(旋转、镜像)
import { Point2D, Engine } from 'vjcad';
import { ANGLE_90 } from 'vjcad';
class TransformPreviewCommand {
private selectedEntities: any[] = [];
private basePoint: Point2D = new Point2D();
private rotationAngle: number = 0;
private flipX: boolean = false;
private flipY: boolean = false;
private updatePreview(canvasPoint: Point2D): void {
const worldPoint = Engine.trans.CanvasToWcs(canvasPoint);
// 计算缩放系数(用于镜像)
const scaleX = this.flipX ? -1 : 1;
const scaleY = this.flipY ? -1 : 1;
// 计算变换后的位置
const scaledBase = new Point2D(
this.basePoint.x * scaleX,
this.basePoint.y * scaleY
);
const transformedPoint = new Point2D();
transformedPoint.move(scaledBase, new Point2D());
transformedPoint.rotate(this.rotationAngle, new Point2D());
transformedPoint.move(new Point2D(), worldPoint);
// 应用预览变换
Engine.pcanvas.drawControl.setPreviewPosition(transformedPoint);
Engine.pcanvas.drawControl.setPreviewRotation(-this.rotationAngle);
Engine.pcanvas.drawControl.setPreviewScale(scaleX, scaleY);
}
// 左旋转90度
rotateLeft(): void {
this.rotationAngle += ANGLE_90;
}
// 右旋转90度
rotateRight(): void {
this.rotationAngle -= ANGLE_90;
}
// 左右翻转
toggleFlipX(): void {
this.flipX = !this.flipX;
}
// 上下翻转
toggleFlipY(): void {
this.flipY = !this.flipY;
}
}复制命令完整示例
import { Point2D, Engine, ANGLE_90 } from 'vjcad';
import {
getSelections,
getPoint,
SelectionInputOptions,
PointInputOptions,
InputStatusEnum,
writeMessage
} from 'vjcad';
import { normalizeAngleAlt } from 'vjcad';
export class CopyCommand {
private selectedEntities: any[] = [];
private previewEntities: any[] = [];
private basePoint: Point2D = new Point2D();
private rotationAngle: number = 0;
private flipX: boolean = false;
private flipY: boolean = false;
private previewRegistered: boolean = false;
async main(): Promise<void> {
// 选择实体
const selResult = await getSelections();
if (selResult.status !== InputStatusEnum.OK || selResult.value.length === 0) {
return;
}
this.selectedEntities = selResult.value;
// 克隆用于预览
this.previewEntities = this.selectedEntities.map(e => e.clone());
// 高亮原实体
Engine.pcanvas.highLightEntities(this.selectedEntities);
// 获取基点
const baseOptions = new PointInputOptions("指定基点:");
const baseResult = await getPoint(baseOptions);
if (baseResult.status !== InputStatusEnum.OK) {
this.cleanup();
return;
}
this.basePoint = baseResult.value;
// 连续复制循环
await this.copyLoop();
this.cleanup();
}
private async copyLoop(): Promise<void> {
while (true) {
const options = new PointInputOptions(
"指定复制目标 [左旋转(L)/右旋转(R)/左右翻转(X)/上下翻转(Y)] <完成>:"
);
options.keywords = ["L", "R", "X", "Y"];
options.useBasePoint = true;
options.basePoint = this.basePoint;
// 设置预览回调
options.callback = (canvasPoint: Point2D) => {
this.updatePreview(canvasPoint);
};
// 注册预览实体
if (!this.previewRegistered) {
Engine.pcanvas.drawControl.drawPreviewEntities(this.previewEntities);
this.previewRegistered = true;
}
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
// 执行复制
Engine.pcanvas.drawControl.resetPreview();
this.previewRegistered = false;
this.executeCopy(result.value);
} else if (result.status === InputStatusEnum.Keyword) {
this.handleKeyword(result.stringResult);
} else {
// 完成或取消
break;
}
}
}
private updatePreview(canvasPoint: Point2D): void {
const worldPoint = Engine.trans.CanvasToWcs(canvasPoint);
const scaleX = this.flipX ? -1 : 1;
const scaleY = this.flipY ? -1 : 1;
const scaledBase = new Point2D(
this.basePoint.x * scaleX,
this.basePoint.y * scaleY
);
const transformedPoint = new Point2D();
transformedPoint.move(scaledBase, new Point2D());
transformedPoint.rotate(this.rotationAngle, new Point2D());
transformedPoint.move(new Point2D(), worldPoint);
Engine.pcanvas.drawControl.setPreviewPosition(transformedPoint);
Engine.pcanvas.drawControl.setPreviewRotation(-this.rotationAngle);
Engine.pcanvas.drawControl.setPreviewScale(scaleX, scaleY);
}
private handleKeyword(keyword: string): void {
switch (keyword.toUpperCase()) {
case "L":
this.rotationAngle = normalizeAngleAlt(this.rotationAngle + ANGLE_90);
writeMessage("<br/>左旋转90度");
break;
case "R":
this.rotationAngle = normalizeAngleAlt(this.rotationAngle - ANGLE_90);
writeMessage("<br/>右旋转90度");
break;
case "X":
this.flipX = !this.flipX;
writeMessage(`<br/>左右翻转 = ${this.flipX ? "ON" : "OFF"}`);
break;
case "Y":
this.flipY = !this.flipY;
writeMessage(`<br/>上下翻转 = ${this.flipY ? "ON" : "OFF"}`);
break;
}
}
private executeCopy(targetPoint: Point2D): void {
Engine.undoManager.start_undoMark();
const copiedEntities: any[] = [];
const verticalLine = new Point2D(this.basePoint.x, this.basePoint.y + 1);
const horizontalLine = new Point2D(this.basePoint.x + 1, this.basePoint.y);
for (const entity of this.selectedEntities) {
const cloned = entity.clone();
// 应用变换
if (this.flipX) {
cloned.mirror(this.basePoint, verticalLine);
}
if (this.flipY) {
cloned.mirror(this.basePoint, horizontalLine);
}
if (this.rotationAngle !== 0) {
cloned.rotate(this.basePoint, this.rotationAngle);
}
cloned.move(this.basePoint, targetPoint);
Engine.pcanvas.addEntity(cloned);
copiedEntities.push(cloned);
}
Engine.undoManager.added_undoMark(copiedEntities);
Engine.undoManager.end_undoMark();
Engine.pcanvas.redraw();
}
private cleanup(): void {
Engine.pcanvas.drawControl.resetPreview();
Engine.pcanvas.clearHighLight();
Engine.pcanvas.regen();
}
}高亮显示
高亮显示用于标识选中的实体,与预览不同,高亮是对现有实体的视觉强调。
高亮 API
| 方法 | 说明 |
|---|---|
Engine.pcanvas.highLightEntities(entities) | 高亮多个实体 |
Engine.pcanvas.drawControl.drawHighLightEntity(entity) | 高亮单个实体 |
Engine.pcanvas.clearHighLight() | 清除所有高亮 |
高亮使用示例
// 选择后高亮
const result = await getSelections();
if (result.status === InputStatusEnum.OK && result.value.length > 0) {
Engine.pcanvas.highLightEntities(result.value);
Engine.pcanvas.redraw();
}
// 操作完成后清除
Engine.pcanvas.clearHighLight();
Engine.pcanvas.regen();单实体高亮(循环选择时)
while (true) {
const result = await getEntity(options);
if (result.status === InputStatusEnum.OK && result.pickedEntity) {
// 高亮当前选中的实体
Engine.pcanvas.drawControl.drawHighLightEntity(result.pickedEntity);
Engine.pcanvas.redraw();
// 处理实体...
// 清除高亮
Engine.pcanvas.clearHighLight();
} else {
break;
}
}最佳实践
1. 始终清理预览
try {
options.callback = (pt) => {
Engine.pcanvas.drawControl.previewGraphics.clear();
Engine.pcanvas.drawControl.drawPreviewEntity(preview);
};
const result = await getPoint(options);
} finally {
// 确保清理预览
Engine.pcanvas.drawControl.previewGraphics.clear();
// 或
Engine.clearPreview();
}2. 在回调中清除旧预览
options.callback = (canvasPoint: Point2D) => {
// 先清除旧预览
Engine.pcanvas.drawControl.previewGraphics.clear();
// 再绘制新预览
const preview = createPreviewEntity(canvasPoint);
Engine.pcanvas.drawControl.drawPreviewEntity(preview);
};3. 坐标转换
回调函数接收的是画布坐标,需要转换为世界坐标:
options.callback = (canvasPoint: Point2D) => {
// 画布坐标转世界坐标
const worldPoint = Engine.trans.CanvasToWcs(canvasPoint);
// 使用世界坐标创建预览
const preview = new CircleEnt(center, center.distanceTo(worldPoint));
// ...
};4. 多实体预览只注册一次
// 好的写法
if (!this.previewRegistered) {
Engine.pcanvas.drawControl.drawPreviewEntities(entities);
this.previewRegistered = true;
}
// 不好的写法(每次回调都注册)
options.callback = (pt) => {
Engine.pcanvas.drawControl.drawPreviewEntities(entities); // 重复注册!
Engine.pcanvas.drawControl.setPreviewPosition(pt);
};5. 预览实体使用 setDefaults()
const preview = new CircleEnt(center, radius);
preview.setDefaults(); // 使用当前图层、颜色等默认值
Engine.pcanvas.drawControl.drawPreviewEntity(preview);