一、概述

圆孔标记是一种常用的工程图纸标注方式,用于标识圆形孔的位置和特征。本文档将详细介绍如何通过自定义实体McDbTestRoundHoleMark来实现圆孔标记功能。具体功能源码可下载MxDraw云图开发包。

二、自定义实体类定义

2.1 类结构
export class McDbTestRoundHoleMark extends McDbCustomEntity {
    // 圆孔圆心
    private center: McGePoint3d;
    // 圆孔半径
    private radius: number;
    // 标记圆上的点集合
    private circlePoints: McGePoint3d[];
    // 标记中心点集合
    private midPoints: McGePoint3d[];
    // 标记圆弧中心点
    private circleMidPts: McGePoint3d[];
    // 标记基点
    private originPt: McGePoint3d;
    // 包围盒点
    private minPt: McGePoint3d;
    private maxPt: McGePoint3d;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
2.2 核心属性说明

- `center`: 圆孔的圆心坐标

- `radius`: 圆孔的半径

- `circlePoints`: 标记圆弧的端点集合

- `midPoints`: 标记射线上的中点集合

- `circleMidPts`: 标记圆弧的中心点集合

- `originPt`: 标记的基准点

- `minPt`/`maxPt`: 包围盒的最小/最大点

三、核心功能实现

3.1 数据序列化

将上述定义的圆孔标记内的属性通过dwgInFields、dwgOutFields两方法分别设置,使得在圆孔标记自定义实体内部能够写入或读取相关的实体数据。

// 读取自定义实体数据
public dwgInFields(filter: IMcDbDwgFiler): boolean {
    this.center = filter.readPoint("center").val;
    this.originPt = filter.readPoint("originPt").val;
    this.circlePoints = filter.readPoints("circlePoints").val;
    this.circleMidPts = filter.readPoints("circleMidPts").val;
    this.midPoints = filter.readPoints("midPoints").val;
    this.radius = filter.readDouble("radius").val;
    this.minPt = filter.readPoint("minPt").val;
    this.maxPt = filter.readPoint("maxPt").val;
    return true;
}
// 写入自定义实体数据
public dwgOutFields(filter: IMcDbDwgFiler): boolean {
    filter.writePoint("center", this.center);
    filter.writePoint("originPt", this.originPt);
    filter.writePoints("circlePoints", this.circlePoints);
    filter.writePoints("circleMidPts", this.circleMidPts);
    filter.writePoints("midPoints", this.midPoints);
    filter.writeDouble("radius", this.radius);
    filter.writePoint("minPt", this.minPt);
    filter.writePoint("maxPt", this.maxPt);
    return true;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
3.2 标记数据设置

根据标记在不同象限下的向量方向记录标记内部具体的圆弧曲线首尾两点以及圆弧中点,为后续动态绘制标记实体提供点位。

public setRoundData(center: McGePoint3d, radius: number, quadrant: number = 1, count: number = 1, angle: number = Math.PI / 2) {
    this.center = this.originPt = center.clone();
    this.radius = radius;
    // 根据象限确定向量方向
    let vec: McGeVector3d;
    switch (quadrant) {
        case 1: vec = McGeVector3d.kXAxis.clone(); break;
        case 2: vec = McGeVector3d.kYAxis.clone(); break;
        case 3: vec = McGeVector3d.kXAxis.clone().negate(); break;
        case 4: vec = McGeVector3d.kYAxis.clone().negate(); break;
        default: vec = McGeVector3d.kXAxis.clone(); break;
    }
    // 计算标记点位
    const arcPt1 = this.center.clone().addvec(vec.clone().mult(this.radius));
    const arcPt2 = this.center.clone().addvec(vec.clone().rotateBy(angle / 2).mult(this.radius));
    const arcPt3 = this.center.clone().addvec(vec.clone().rotateBy(angle).mult(this.radius));
    // 设置标记点集合
    this.circlePoints = [arcPt1, arcPt3];
    this.circleMidPts = [arcPt2];
    // 计算射线中点
    const lMidPt = this.center.clone().addvec(arcPt1.sub(this.center).mult(0.5));
    const rMidPt = this.center.clone().addvec(arcPt3.sub(this.center).mult(0.5));
    this.midPoints = [lMidPt, rMidPt];
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
3.3 标记夹点设置

在 getGripPoints() 方法内放入作为实体夹点的点位,在moveGripPointsAt()方法内设置每个夹点被移动编辑后需要执行的操作,如移动标注基点后,标注实体内部的所有夹点都跟着移动;移动标注实体圆弧中心点后重新计算新点位下圆弧的大小;移动标记实体半径上的点后会修改标注基点和相关联圆弧的大小等。

// 移动自定义对象的夹点
    public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
        this.assertWrite();
        // 移动所有点
        const moveAllPoints = () => {
            // 基点移动,所有点都跟着移动
            this.originPt.x += dXOffset;
            this.originPt.y += dYOffset;
            this.originPt.z += dZOffset;
 
            this.center.x += dXOffset;
            this.center.y += dYOffset;
            this.center.z += dZOffset;
 
            const pointArr = [...this.circleMidPts, ...this.circlePoints, ...this.midPoints]
            pointArr.forEach(pt => {
                pt.x += dXOffset;
                pt.y += dYOffset;
                pt.z += dZOffset;
            });
        }
 
        // 重新计算所有半径上的中点
        const resetRMidPts = () => {
            this.circlePoints.forEach((pt, i) => {
                const rMidPt = this.center.clone().addvec(pt.sub(this.center).mult(0.5));
                this.midPoints[i] = rMidPt
            })
        }
        // 重新计算圆弧上的中心点
        const resetArcMidPts = () => {
            let num = 0;
            for (let i = 0; i < this.circlePoints.length; i += 2) {
                const arc = new McDbArc();
                arc.computeArc(this.circlePoints[i].x, this.circlePoints[i].y, this.circleMidPts[num].x, this.circleMidPts[num].y, this.circlePoints[i + 1].x, this.circlePoints[i + 1].y);
                const midPt = arc.getPointAtDist(arc.getLength().val / 2).val;
                this.circleMidPts[num] = midPt.clone();
                num += 1;
            }
        }
        if (iIndex === 0) {
            moveAllPoints()
        } else if (iIndex === 1) {
            const pt = this.center.clone();
            if (pt.distanceTo(this.originPt) < 0.0001) {
                moveAllPoints()
            } else {
                this.center.x += dXOffset;
                this.center.y += dYOffset;
                this.center.z += dZOffset;
 
                resetRMidPts();
            }
        } else if (iIndex < this.circlePoints.length + 2) {
            // 圆弧两端点移动=》半径中点+圆弧中点跟着移动
            const i = iIndex - 2;
            const pt = this.circlePoints[i].clone();
            pt.x += dXOffset;
            pt.y += dYOffset;
            pt.z += dZOffset;
            this.circlePoints[i] = pt;
 
            // 重新计算圆弧中心点
            resetArcMidPts();
 
            // 计算半径上的中心点
            const rMidPt = this.center.clone().addvec(pt.sub(this.center).mult(0.5));
            this.midPoints[i] = rMidPt
 
        } else if (iIndex < this.circlePoints.length + 2 + this.circleMidPts.length) {
            // 圆弧中心点移动=>重新计算圆弧中心点
            const i = iIndex - 2 - this.circlePoints.length;
            const pt = this.circleMidPts[i].clone();
            pt.x += dXOffset;
            pt.y += dYOffset;
            pt.z += dZOffset;
            this.circleMidPts[i] = pt;
 
            // 重新计算圆弧中心点
            resetArcMidPts();
 
        } else {
            const i = iIndex - 2 - this.circlePoints.length - this.circleMidPts.length;
            this.center.x += dXOffset;
            this.center.y += dYOffset;
            this.center.z += dZOffset;
 
            this.midPoints[i].x += dXOffset;
            this.midPoints[i].y += dYOffset;
            this.midPoints[i].z += dZOffset;
 
            this.circlePoints[i].x += dXOffset;
            this.circlePoints[i].y += dYOffset;
            this.circlePoints[i].z += dZOffset;
 
            // 重新计算圆弧中心点
            resetArcMidPts();
 
            // 计算半径上的中心点
            resetRMidPts()
        }
    };
    // 获取自定义对象的夹点
    public getGripPoints(): McGePoint3dArray {
        let ret = new McGePoint3dArray();
        ret.append(this.originPt);
        ret.append(this.center);
        this.circlePoints.forEach(pt => {
            ret.append(pt);
        })
        this.circleMidPts.forEach(pt => {
            ret.append(pt);
        })
        this.midPoints.forEach(pt => {
            ret.append(pt);
        })
        return ret;
    };
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
3.4 实体绘制

通过上述步骤中的操作我们可以得到标注内部的圆弧点位,根据这些点位我们就能够计算出圆弧所在的位置并通过创建 McDbHatch 填充类来绘制最终的标注实体。

public worldDraw(draw: MxCADWorldDraw): void {
    const allEntityArr: McDbEntity[] = [];
    // 绘制标记圆弧和射线
    for (let i = 0; i < this.circlePoints.length; i += 2) {
        const num = (i + 2) / 2;
        const bulges = MxCADUtility.calcBulge(
            this.circlePoints[i],
            this.circleMidPts[num - 1],
            this.circlePoints[i + 1]
        ).val;
       
        // 创建填充实体
        const hatch = new McDbHatch();
        hatch.appendLoop(
            new McGePoint3dArray([this.center, this.circlePoints[i], this.circlePoints[i + 1]]),
            [0, bulges, 0]
        );
        draw.drawEntity(hatch);
        allEntityArr.push(hatch);
    }
 
    // 计算包围盒
    this.getBox(allEntityArr);
}
   // 计算标注实体包围盒大小
    private getBox(entityArr: McDbEntity[]) {
        const mxcad = MxCpp.getCurrentMxCAD();
        let _minPt, _maxPt = null;
        entityArr.forEach(entity => {
            const { minPt, maxPt, ret } = entity.getBoundingBox();
            if (!_minPt) _minPt = minPt.clone();
            if (!_maxPt) _maxPt = maxPt.clone();
            if (minPt.x < _minPt.x) _minPt.x = minPt.x;
            if (minPt.y < _minPt.y) _minPt.y = minPt.y;
            if (maxPt.x > _maxPt.x) _maxPt.x = maxPt.x;
            if (maxPt.y > _maxPt.y) _maxPt.y = maxPt.y;
        });
        if (_minPt && _maxPt) {
            this.maxPt = _maxPt;
            this.minPt = _minPt
        }
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.

四、使用方法

4.1 初始化注册
// 在插件初始化时注册自定义实体
new McDbTestRoundHoleMark().rxInit();
MxFun.addCommand("Mx_RoundHoleMark", Mx_RoundHoleMark);
  • 1.
  • 2.
  • 3.
4.2 创建圆孔标记
async function Mx_RoundHoleMark() {
    // 选择圆形实体
    const filter = new MxCADResbuf([DxfCode.kEntityType, "CIRCLE"]);
    const ss = new MxCADSelectionSet();
    if (!await ss.userSelect(t("请选择需要标记的圆:"), filter)) return;
    if (ss.count() == 0) return;
    // 为每个选中的圆创建标记
    ss.forEach(entityId => {
        const circle = entityId.getMcDbEntity() as McDbCircle;
        const roundHoleMark = new McDbTestRoundHoleMark();
        // 设置标记数据
        roundHoleMark.setRoundData(circle.center, circle.radius, [1, 1, Math.PI / 2]);
        roundHoleMark.trueColor = new McCmColor(0, 255, 0);
        // 绘制标记
        mxcad.drawEntity(roundHoleMark);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

五、最佳实践

圆孔标注实体具有以下特性:

1. 支持多象限标记:可在四个象限中选择标记方向

2. 可调节标记角度:通过angle参数控制标记的张开角度

3. 支持多重标记:可同时创建多个对称的标记

4. 动态编辑:支持通过夹点编辑修改标记形状和位置

5. 自动计算包围盒:用于空间定位和选择操作

根据 McDbTestRoundHoleMark 圆孔标注自定实体,我们可以结合MxCAD项目实现一个完整的圆孔标注功能,效果如下:

(JS实现预览DWG文件)WEB CAD的圆孔标记功能_3d

(JS实现预览DWG文件)WEB CAD的圆孔标记功能_3d_02