自定义实体 (CustomEntityBase)
大约 4 分钟
自定义实体 (CustomEntityBase)
CustomEntityBase 是创建自定义实体类型的基类,允许开发者扩展 WebCAD 的实体系统。
概述
当标准实体无法满足需求时,可以通过继承 CustomEntityBase 创建自定义实体,实现特定的几何形状、交互行为和显示效果。
创建自定义实体
基本结构
import {
CustomEntityBase,
Point2D,
BoundingBox,
Engine
} from 'vjcad';
class CrossMarkEntity extends CustomEntityBase {
private _center: Point2D;
private _size: number;
constructor(center: Point2D = new Point2D(0, 0), size: number = 10) {
super();
this.type = 'CROSS_MARK'; // 自定义类型标识
this._center = center;
this._size = size;
}
// 中心点
get center(): Point2D {
return this._center;
}
set center(val: Point2D) {
this._center = val;
this.setModified();
}
// 尺寸
get size(): number {
return this._size;
}
set size(val: number) {
this._size = val;
this.setModified();
}
// 计算包围盒
boundingBox(): BoundingBox {
const half = this._size / 2;
return new BoundingBox(
this._center.x - half,
this._center.y - half,
this._center.x + half,
this._center.y + half
);
}
// 克隆
clone(): CrossMarkEntity {
const cloned = new CrossMarkEntity(
new Point2D(this._center.x, this._center.y),
this._size
);
this.clonePropertiesTo(cloned);
return cloned;
}
// 移动
move(from: Point2D, to: Point2D): void {
const dx = to.x - from.x;
const dy = to.y - from.y;
this._center = new Point2D(
this._center.x + dx,
this._center.y + dy
);
this.setModified();
}
// 旋转(十字标记旋转无意义,保持不变)
rotate(base: Point2D, angle: number): void {
// 只移动中心点
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const dx = this._center.x - base.x;
const dy = this._center.y - base.y;
this._center = new Point2D(
base.x + dx * cos - dy * sin,
base.y + dx * sin + dy * cos
);
this.setModified();
}
// 缩放
scale(base: Point2D, factor: number): void {
this._center = new Point2D(
base.x + (this._center.x - base.x) * factor,
base.y + (this._center.y - base.y) * factor
);
this._size *= Math.abs(factor);
this.setModified();
}
// 序列化(使用 Point2D.toDb() 自动处理 2D/3D 格式)
toDb(): object {
return {
...super.toDb(),
center: this._center.toDb(), // 输出 [x, y] 或 [x, y, z]
size: this._size
};
}
// 反序列化
fromDb(data: any): void {
super.fromDb(data);
this._center = new Point2D().fromDb(data.center); // 支持数组和对象格式
this._size = data.size;
}
}注册自定义实体
import { EntityRegistry } from 'vjcad';
// 注册自定义实体类型
EntityRegistry.register('CROSS_MARK', CrossMarkEntity);
// 使用
const mark = new CrossMarkEntity(new Point2D(100, 100), 20);
mark.setDefaults();
Engine.addEntities(mark);实现渲染
自定义实体需要提供渲染逻辑:
import { CustomEntityBase, Point2D, LineEnt, CircleEnt } from 'vjcad';
class CrossMarkEntity extends CustomEntityBase {
// ... 属性定义 ...
// 获取用于渲染的子实体
getDisplayEntities(): EntityBase[] {
const half = this._size / 2;
const center = this._center;
// 水平线
const hLine = new LineEnt(
new Point2D(center.x - half, center.y),
new Point2D(center.x + half, center.y)
);
hLine.color = this.color;
// 垂直线
const vLine = new LineEnt(
new Point2D(center.x, center.y - half),
new Point2D(center.x, center.y + half)
);
vLine.color = this.color;
// 中心圆
const circle = new CircleEnt(center, half * 0.3);
circle.color = this.color;
return [hLine, vLine, circle];
}
}实现夹点编辑
class CrossMarkEntity extends CustomEntityBase {
// ... 其他代码 ...
// 获取夹点位置
getGripPoints(): Point2D[] {
const half = this._size / 2;
return [
this._center, // 中心点
new Point2D(this._center.x + half, this._center.y), // 右
new Point2D(this._center.x, this._center.y + half), // 上
];
}
// 夹点编辑
gripEdit(gripIndex: number, from: Point2D, to: Point2D): void {
const dx = to.x - from.x;
const dy = to.y - from.y;
if (gripIndex === 0) {
// 移动中心点
this._center = new Point2D(
this._center.x + dx,
this._center.y + dy
);
} else if (gripIndex === 1) {
// 调整尺寸(右侧夹点)
this._size = Math.abs(to.x - this._center.x) * 2;
} else if (gripIndex === 2) {
// 调整尺寸(上方夹点)
this._size = Math.abs(to.y - this._center.y) * 2;
}
this.setModified();
}
}完整示例:带标签的标记点
import {
CustomEntityBase,
Point2D,
BoundingBox,
LineEnt,
CircleEnt,
TextEnt,
Engine
} from 'vjcad';
class LabeledMarker extends CustomEntityBase {
private _position: Point2D;
private _label: string;
private _markerSize: number;
private _textHeight: number;
constructor(
position: Point2D = new Point2D(0, 0),
label: string = '',
markerSize: number = 10,
textHeight: number = 5
) {
super();
this.type = 'LABELED_MARKER';
this._position = position;
this._label = label;
this._markerSize = markerSize;
this._textHeight = textHeight;
}
get position(): Point2D { return this._position; }
set position(val: Point2D) { this._position = val; this.setModified(); }
get label(): string { return this._label; }
set label(val: string) { this._label = val; this.setModified(); }
get markerSize(): number { return this._markerSize; }
set markerSize(val: number) { this._markerSize = val; this.setModified(); }
get textHeight(): number { return this._textHeight; }
set textHeight(val: number) { this._textHeight = val; this.setModified(); }
boundingBox(): BoundingBox {
const half = this._markerSize / 2;
const textWidth = this._label.length * this._textHeight * 0.7;
return new BoundingBox(
this._position.x - half,
this._position.y - half,
this._position.x + Math.max(half, textWidth),
this._position.y + this._markerSize + this._textHeight
);
}
getDisplayEntities(): EntityBase[] {
const entities: EntityBase[] = [];
const pos = this._position;
const half = this._markerSize / 2;
// 十字线
const hLine = new LineEnt(
new Point2D(pos.x - half, pos.y),
new Point2D(pos.x + half, pos.y)
);
hLine.color = this.color;
entities.push(hLine);
const vLine = new LineEnt(
new Point2D(pos.x, pos.y - half),
new Point2D(pos.x, pos.y + half)
);
vLine.color = this.color;
entities.push(vLine);
// 圆圈
const circle = new CircleEnt(pos, half);
circle.color = this.color;
entities.push(circle);
// 标签文字
if (this._label) {
const text = new TextEnt();
text.position = new Point2D(pos.x, pos.y + this._markerSize);
text.text = this._label;
text.height = this._textHeight;
text.color = this.color;
entities.push(text);
}
return entities;
}
clone(): LabeledMarker {
const cloned = new LabeledMarker(
new Point2D(this._position.x, this._position.y),
this._label,
this._markerSize,
this._textHeight
);
this.clonePropertiesTo(cloned);
return cloned;
}
move(from: Point2D, to: Point2D): void {
const dx = to.x - from.x;
const dy = to.y - from.y;
this._position = new Point2D(
this._position.x + dx,
this._position.y + dy
);
this.setModified();
}
toDb(): object {
return {
...super.toDb(),
position: this._position.toDb(), // 输出 [x, y] 或 [x, y, z]
label: this._label,
markerSize: this._markerSize,
textHeight: this._textHeight
};
}
fromDb(data: any): void {
super.fromDb(data);
this._position = new Point2D().fromDb(data.position); // 支持数组和对象格式
this._label = data.label;
this._markerSize = data.markerSize;
this._textHeight = data.textHeight;
}
}
// 使用自定义实体
const marker = new LabeledMarker(
new Point2D(100, 100),
'P1',
15,
6
);
marker.color = 1; // 红色
marker.setDefaults();
Engine.addEntities(marker);最佳实践
- 类型标识:使用唯一的
type字符串标识自定义实体 - 序列化:正确实现
toDb()和fromDb()以支持保存和加载 - 克隆:实现
clone()方法以支持复制操作 - 包围盒:准确计算包围盒以支持选择和缩放视图
- 夹点:提供直观的夹点位置以便编辑