唯杰地图 logo唯杰地图
VJMAP
EN

custom-entity

vjcad/references/entity/custom-entity.md 5.2 KB118 行
原文

本文回答:自定义实体继承 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