custom-entity
本文回答:自定义实体继承
CustomEntityBase,必须实现customType / customDisplayName / getSnapPoints / getGripPoints / buildNestEnts / clone / fromDb / getEntityData / setEntityData;构造函数必须可无参调用,并用CustomEntityRegistry.getInstance().register(type, Class)注册,否则保存后重开无法还原。完整示例见vjcad/example/entity/network/01create.js。
一、CustomEntityBase(来自架构图)
引自
vjcad/doc/guide/architecture.md:
abstract class CustomEntityBase extends EntityBase {
abstract readonly customType: string; // 子类必须声明(readonly)
abstract readonly customDisplayName: string; // 属性栏显示名(readonly)
abstract getSnapPoints(): SnapPointResult[]; // [{point, type:'endpoint'|...}],不是 Point2D[]
abstract getGripPoints(): GripPointResult[]; // [{point, gripId, type:'move'|'stretch'|...}],不是 Point2D[]
protected abstract buildNestEnts(): EntityBase[]; // 子类实现,返回值被基类缓存(getNestEnts)
abstract clone(): CustomEntityBase; // 复制 / 粘贴 / 撤销快照用
abstract fromDb(dbData: CustomEntityDbData): void; // 打开图纸时反序列化(EntityBase 上没有默认实现)
abstract getEntityData(): any;
abstract setEntityData(data): void;
}
注:clone/fromDb 同为 abstract(官方架构图里漏列)。
继承图:EntityBase <|-- CustomEntityBase <|-- UserCustomEntity。
二、最小自定义实体(伪代码骨架)
class MyArrowEntity extends CustomEntityBase {
get customType() { return 'MyArrow'; } // 官方示例用 getter 声明
get customDisplayName() { return '箭头'; }
// 必须可无参构造:打开图纸时注册表先无参实例化子类,再调 fromDb()
constructor() {
super();
this.start = new Point2D(0, 0);
this.end = new Point2D(0, 0);
}
// 内嵌真实实体(vjcad 用这些去渲染、抛事件)
buildNestEnts() {
const line = new LineEnt(this.start, this.end);
line.setDefaults();
// 箭头三角形(用 SolidEnt 或 PolylineEnt 都可)
const head = new SolidEnt(/* p1, p2, p3 箭头三点,计算略 */);
return [line, head];
}
// 返回 {point, type} / {point, gripId, type} 对象,不是裸 Point2D
getSnapPoints() {
return [
{ point: this.start, type: 'endpoint' },
{ point: this.end, type: 'endpoint' },
];
}
getGripPoints() {
return [
{ point: this.start, gripId: 'start', type: 'stretch' },
{ point: this.end, gripId: 'end', type: 'stretch' },
];
}
clone() {
const c = new MyArrowEntity();
c.fromDefaultProps(this); // 复制图层 / 颜色等通用属性
c.start = this.start.clone();
c.end = this.end.clone();
return c;
}
// 存纯 {x,y},读回时还原成 Point2D
getEntityData() { return { start: { x: this.start.x, y: this.start.y }, end: { x: this.end.x, y: this.end.y } }; }
setEntityData(d) {
if (d.start) this.start = new Point2D(d.start.x, d.start.y);
if (d.end) this.end = new Point2D(d.end.x, d.end.y);
this.setModified();
}
fromDb(db) { this.fromDbDefaultProps(db); if (db.data) this.setEntityData(db.data); }
}
// 注册(页面加载时、打开图纸前执行);入口是 CustomEntityRegistry.getInstance(),Engine.customEntities 不存在
CustomEntityRegistry.getInstance().register('MyArrow', MyArrowEntity); // 与 customType 一致
// 使用:无参构造后设字段
const arrow = new MyArrowEntity();
arrow.start = new Point2D(0, 0);
arrow.end = new Point2D(100, 0);
arrow.setDefaults();
Engine.addEntities(arrow);
三、典型扩展点
| 想做的 | 重写 |
|---|---|
| 改变实体怎么画 | buildNestEnts()(返回内嵌真实实体集合) |
| 改变捕捉点 / 夹点 | getSnapPoints / getGripPoints |
| 自定义保存格式 | getEntityData / setEntityData |
| 自定义碰撞 / 包围盒 | boundingBox()(vjcad@1.0.16 d.ts 已在实体类上声明,返回 BoundingBox) |
四、注册
① CustomEntityRegistry.getInstance().register(customType, Class);② Engine.addEntities(myEnt);③ 打开图纸时 CadDocument 调 CustomEntityRegistry.getInstance().createFromDb(dbData):按 customType 找到类 → 无参实例化 → entity.fromDb(dbData)。未注册的类型会打印警告并跳过,所以注册必须在打开图纸之前完成。参考 vjcad/example/entity/network/01create.js。
反例
- 不重写
buildNestEnts:自定义实体在画布上看不见。 - 构造函数要求必传参数(如
constructor(start, end)且直接用):反序列化时注册表无参实例化,字段为 undefined,重开图纸就坏。 - 漏实现
clone/fromDb或忘了register:复制、撤销或保存后重开时无法还原。 - 把"业务字段"放在普通属性:保存 / 加载后会丢,应放在
getEntityData / setEntityData。 customType与已有内置类型重名:触发未定义行为。
参考(官方文档 MCP)
- 文档:
vjcad/doc/entity/custom-entities.md、vjcad/doc/guide/architecture.md