Skip to content

MXCAD McDbCustomEntity 自定义实体

在 MXCAD 中,开发者可以通过继承 McDbCustomEntity 类来创建满足特定业务需求的自定义实体。自定义实体允许您定义独特的数据结构、渲染逻辑和交互行为。下文将详细介绍自定义实体中核心方法的用途、调用时机及通用实现规范。

点击 McDbCustomEntity 查看详细属性和方法说明。

数据持久化方法

自定义实体需要在文件保存、读取或复制时正确序列化和反序列化其内部数据。

1. dwgInFields(filter: IMcDbDwgFiler): boolean

  • 用途:从文件或流中读取自定义实体的数据,用于恢复实体状态。
  • 调用时机:打开图纸、插入块、复制实体等需要从存储介质加载数据的场景。
  • 实现要点
    • 使用 filter 对象按字段名读取数据(如 readPoint, readString, readInt 等)。
    • 将读取的数据赋值给实体的私有属性。
    • 若涉及复杂结构(如矩阵),需手动解析并重建(例如将 JSON 字符串解析为二维数组并填入矩阵)。
    • 成功返回 true,失败返回 false
ts
public dwgInFields(filter: IMcDbDwgFiler): boolean {
  this.pt1 = filter.readPoint("pt1").val;
  this.pt2 = filter.readPoint("pt2").val;
  const matrixStr = filter.readString("matrix").val;
  const matrixData = JSON.parse(matrixStr) as number[][];

  // 重建矩阵数据
  for (let i = 0; i < matrixData.length; i++) {
    for (let j = 0; j < matrixData[i].length; j++) {
      this.matrix.setData(i, j, matrixData[i][j]);
    }
  }
  return true;
}

2. dwgOutFields(filter: IMcDbDwgFiler): boolean

  • 用途:将自定义实体的当前状态写入文件或流,用于持久化存储。
  • 调用时机:保存图纸、复制实体、导出块等需要将内存数据写入存储的场景。
  • 实现要点
    • 使用 filter 对象按字段名写入数据(如 writePoint, writeString, writeInt 等)。
    • 复杂数据类型(如矩阵)需转换为可序列化格式(如 JSON 字符串)。
    • 成功返回 true,失败返回 false
ts
public dwgOutFields(filter: IMcDbDwgFiler): boolean {
  filter.writePoint("pt1", this.pt1);
  filter.writePoint("pt2", this.pt2);

  const matrix: number[][] = [];
  for (let i = 0; i < 4; i++) {
    matrix[i] = [];
    for (let j = 0; j < 4; j++) {
      matrix[i][j] = this.matrix.getData(i, j);
    }
  }
  filter.writeString("matrix", JSON.stringify(matrix));
  return true;
}

交互与编辑方法

自定义实体需支持用户通过夹点(Grip Points)进行直观编辑。

1. getGripPoints(): McGePoint3dArray

  • 用途:返回实体在视图中显示的编辑夹点位置集合。
  • 调用时机:实体被选中时,系统调用此方法获取可操作的夹点。
  • 实现要点
    • 返回一个 McGePoint3dArray 对象,包含所有关键控制点。
    • 夹点顺序对应 moveGripPointsAt 中的索引 iIndex(例如:索引 0 对应起点,索引 1 对应终点)。
    • 通常返回起点、终点、中心点等关键几何位置。
ts
// 以自定义直线实体为例
public getGripPoints(): McGePoint3dArray {
  const points = new McGePoint3dArray();
  points.append(this.pt1); // 索引 0
  points.append(this.pt2); // 索引 1
  return points;
}

2. moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number): void

  • 用途:处理用户拖动某个夹点后的坐标偏移,更新实体几何数据。
  • 调用时机:用户拖动夹点并释放鼠标时触发。
  • 实现要点
    • 根据 iIndex 判断是哪个夹点被移动。
    • 对对应属性应用偏移量 (dXOffset, dYOffset, dZOffset)。
    • 必须调用 this.assertWrite() 确保实体处于可写状态。
    • 无需返回值,直接修改内部状态。
ts
// 以自定义直线实体为例
public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
  this.assertWrite(); // 标记为可写
  if (iIndex === 0) {
    this.pt1.x += dXOffset;
    this.pt1.y += dYOffset;
    this.pt1.z += dZOffset;
  } else if (iIndex === 1) {
    this.pt2.x += dXOffset;
    this.pt2.y += dYOffset;
    this.pt2.z += dZOffset;
  }
}

渲染与显示方法

自定义实体需定义其在视图中的视觉表现形式。

1. worldDraw(draw: MxCADWorldDraw): void

  • 用途:绘制实体在视图中的图形表示。
  • 调用时机:视图刷新、实体创建、变换操作后自动触发。
  • 实现要点
    • 利用 draw 对象绘制基础图元(如线、圆、多段线等)组合成自定义图形。
    • 可应用变换矩阵、颜色、线型等属性。
    • 不应在此方法中修改实体数据,仅负责可视化。
ts
// 以自定义直线实体为例
public worldDraw(draw: MxCADWorldDraw): void {
  // 创建临时基础实体用于绘制
  const line = new McDbLine(this.pt1, this.pt2);
  // 应用自定义变换
  line.transformBy(this.matrix);
  // 委托给系统绘制
  draw.drawEntity(line);
}

TIP

注意:由于每次重绘都会重建实例,请确保所有状态都保存在实体属性中,而非局部变量或外部引用。

几何计算与变换方法

自定义实体需支持空间变换和边界计算,以兼容 CAD 系统的通用操作。

1. getBoundingBox(): { minPt: McGePoint3d; maxPt: McGePoint3d; ret: boolean }

  • 用途:计算实体的最小包围盒。
  • 调用时机:视图缩放至实体、选择集判断、碰撞检测等场景。
  • 实现要点
    • 返回包含 minPt(最小角点)、maxPt(最大角点)和 ret(是否成功)的对象。
    • 需考虑实体所有几何部分的空间范围(包括变换后的坐标)。
    • 若实体无有效几何,ret 应设为 false
ts
public getBoundingBox(): { minPt: McGePoint3d; maxPt: McGePoint3d; ret: boolean } {
  const minX = Math.min(this.pt1.x, this.pt2.x);
  const minY = Math.min(this.pt1.y, this.pt2.y);
  const maxX = Math.max(this.pt1.x, this.pt2.x);
  const maxY = Math.max(this.pt1.y, this.pt2.y);

  return {
    minPt: new McGePoint3d(minX, minY, 0),
    maxPt: new McGePoint3d(maxX, maxY, 0),
    ret: true
  };
}

2. transformBy(mat: McGeMatrix3d): boolean

  • 用途:对实体应用仿射变换(平移、旋转、缩放、镜像等)。
  • 调用时机:执行 MOVE、ROTATE、SCALE、MIRROR 等编辑命令时。
  • 实现要点
    • 将传入矩阵 mat 与实体当前变换矩阵相乘(通常使用 preMultBy 前乘或 postMultBy 后乘,依坐标系需求而定)。
    • 调用 this.assertWrite() 标记实体为可写。
    • 成功返回 true,失败返回 false。
ts
// 以自定义直线实体为例
public transformBy(mat: McGeMatrix3d): boolean {
  // 将外部变换矩阵应用到内部存储的矩阵上
  this.matrix = this.matrix.preMultBy(mat);
  this.assertWrite();
  return true;
}

辅助方法与最佳实践

1. 构造函数与 create 方法

  • 构造函数用于初始化实例。
  • create(imp: any) 是工厂方法,用于在反序列化或克隆时创建新实例,必须重写。
ts
// 以自定义直线实体为例

constructor(imp?: any) {
  super(imp);
}

public create(imp: any) {
  return new McDbTestLineCustomEntity(imp);
}

2. 类型标识

重写 getTypeName(): string 返回唯一类型名称,用于系统识别和调试。

ts
// 自定义实体类型名称
public getTypeName(): string {
  return "McDbTestLineCustomEntity";
}

3. 数据访问器

提供 getXXX()setXXX() 方法供外部安全访问私有属性,并在 setter 中调用 assertWrite()

ts
// 以自定义直线实体为例

public setPoint1(pt1: McGePoint3d) {
  this.assertWrite();
  this.pt1 = pt1.clone();
}

public getPoint1() {
  return this.pt1;
}

TIP

也可以设置其他方法访问名,只要保证访问的是公共方法即可。

自定义实体注册

根据上述步骤创建完成自定义实体后,需要在 mxcad 对象中注册该自定义实体,一个自定义实体只需要在 mxcad 对象中注册一次且自定义实体的类名是唯一的,若重复多次注册同一个自定义实体类名,控制台将会抛出 ”MxCADError:already has this class name McDbTestLineCustomEntity“ 的错误。因此,我们可以在 mxcad 初始化的时候进行注册。

ts
import { MxCpp } from "mxcad";

//  McDbTestLineCustomEntity 为你创建的自定义实体类名
const mxcad = MxCpp.getCurrentMxCAD();
mxcad.on("init", () => {
  new McDbTestLineCustomEntity().rxInit();
});

功能实践

下面以创建一条自定义直线实体为例,演示完整的自定义实体创建。

  • 继承 McDbCustomEntity 定义实体
ts
import {
  IMcDbDwgFiler,
  McDbCustomEntity,
  McDbLine,
  McGePoint3d,
  McGePoint3dArray,
  MxCADUiPrPoint,
  MxCADWorldDraw,
  MxCpp,
  McGeMatrix3d,
} from "mxcad";

class McDbTestLineCustomEntity extends McDbCustomEntity {
  private pt1: McGePoint3d = new McGePoint3d();
  private pt2: McGePoint3d = new McGePoint3d();
  private matrix: McGeMatrix3d = new McGeMatrix3d();

  constructor(imp?: any) {
    super(imp);
  }

  public create(imp: any) {
    return new McDbTestLineCustomEntity(imp);
  }

  public getTypeName(): string {
    return "McDbTestLineCustomEntity";
  }

  public dwgInFields(filter: IMcDbDwgFiler): boolean {
    this.pt1 = filter.readPoint("pt1").val;
    this.pt2 = filter.readPoint("pt2").val;
    const matrix = filter.readString("matrix").val;
    const matrixData = JSON.parse(matrix) as number[][];
    for (let index = 0; index < matrixData.length; index++) {
      const element = matrixData[index];
      for (let j = 0; j < element.length; j++) {
        const data = element[j];
        this.matrix.setData(index, j, data);
      }
    }
    return true;
  }

  public dwgOutFields(filter: IMcDbDwgFiler): boolean {
    filter.writePoint("pt1", this.pt1);
    filter.writePoint("pt2", this.pt2);
    const matrix: number[][] = [];
    for (let i = 0; i < 4; i++) {
      matrix[i] = [];
      for (let j = 0; j < 4; j++) {
        const data = this.matrix.getData(i, j);
        matrix[i][j] = data;
      }
    }
    filter.writeString("matrix", JSON.stringify(matrix));
    return true;
  }

  public moveGripPointsAt(
    iIndex: number,
    dXOffset: number,
    dYOffset: number,
    dZOffset: number,
  ) {
    this.assertWrite();
    if (iIndex == 0) {
      this.pt1.x += dXOffset;
      this.pt1.y += dYOffset;
      this.pt1.z += dZOffset;
    } else if (iIndex == 1) {
      this.pt2.x += dXOffset;
      this.pt2.y += dYOffset;
      this.pt2.z += dZOffset;
    }
  }

  public getGripPoints(): McGePoint3dArray {
    let ret = new McGePoint3dArray();
    ret.append(this.pt1);
    ret.append(this.pt2);
    return ret;
  }

  public worldDraw(draw: MxCADWorldDraw): void {
    let tmpline = new McDbLine(this.pt1, this.pt2);
    tmpline.transformBy(this.matrix);
    draw.drawEntity(tmpline);
  }

  public setPoint1(pt1: McGePoint3d) {
    this.assertWrite();
    this.pt1 = pt1.clone();
  }

  public setPoint2(pt2: McGePoint3d) {
    this.assertWrite();
    this.pt2 = pt2.clone();
  }

  public getPoint1() {
    return this.pt1;
  }

  public getPoint2() {
    return this.pt2;
  }

  // 编辑变换
  public transformBy(_mat: McGeMatrix3d): boolean {
    this.matrix = this.matrix.preMultBy(_mat);
    this.assertWrite();
    return true;
  }

  public getBoundingBox(): {
    minPt: McGePoint3d;
    maxPt: McGePoint3d;
    ret: boolean;
  } {
    return {
      minPt: new McGePoint3d(
        Math.min(this.pt1.x, this.pt2.x),
        Math.min(this.pt1.y, this.pt2.y),
      ),
      maxPt: new McGePoint3d(
        Math.max(this.pt1.x, this.pt2.x),
        Math.max(this.pt1.y, this.pt2.y),
      ),
      ret: true,
    };
  }
}
  • 注册实体
ts
import { MxCpp } from "mxcad";

const mxcad = MxCpp.getCurrentMxCAD();
mxcad.on("init", () => {
  new McDbTestLineCustomEntity().rxInit();
});
  • 设置方法绘制目标实体
ts
import { MxCADUiPrPoint, MxCpp } from "mxcad";

async function MxTest_DrawCustomEntity() {
  const getPoint = new MxCADUiPrPoint();
  getPoint.setMessage("\n指定一点:");
  let pt1 = await getPoint.go();
  if (!pt1) return;

  getPoint.setBasePt(pt1);
  getPoint.setUseBasePt(true);

  getPoint.setMessage("\n指定二点:");
  let pt2 = await getPoint.go();
  if (!pt2) return;

  let myline = new McDbTestLineCustomEntity();
  myline.setPoint1(pt1);
  myline.setPoint2(pt2);
  const mxcad = MxCpp.getCurrentMxCAD();
  mxcad.drawEntity(myline);
}