Skip to content
On this page

引出标注

下面我们将介绍如何利用 mxcad 插件实现在CAD图纸中引出标注的功能,该功能中用户点击画布确定引出内容起点,再次点击画布确定引线位置和文字标注位,其中一个标注可有多个引出箭头。在引出标注功能中用户可自定义选择箭头形状,以及上标文字和下标文字内容,还可以根据绘制需求修改文字位置等。引出标注功能能够帮助用户快速标注图纸内容,增加图纸内容的完整性和可读性。

功能实现

  1. 实现自定义引出标注类

为了方便后期管理与修改标注,我们可以通过继承 McDbCustomEntity 自定义实体类来扩展实现自定义引出标注类。然后,我们可以利用 McDbMTextMcDbText 构造测量信息多文本对象,将标注信息绘制在页面中。

下面示例的引出标注类中我们提供了箭头、点、十字、半箭头等箭头样式,可固定引线角度,以及上下标文字始端、中间、末端等对齐方式,用户可参考下面的示例代码根据自身项目需求进行二次开发。

ts
// 引出标注类
class McDbTestExportAnnotation extends McDbCustomEntity {
    // 定义McDbTestExportAnnotation内部的点对象 
    // 标注点集合
    private points: McGePoint3d[] = [];
    // 引线位置
    private exportLinePos: McGePoint3d = new McGePoint3d();
    // 文字基线位置
    private textBasePos: McGePoint3d = new McGePoint3d();
    // 文字点位置集合
    private positionArr: McGePoint3d[] = [];
    // 文字高度
    private height: number = 0;
    // 上标文字内容
    private _textUp: string = "";
    // 下标文字内容
    private _textDown: string = "";
    // 箭头样式
    private _arrowType: string = "";
    // 对齐方式
    private _alginType: string = "";
    // 记录初始长度
    private arrowLength: number = 0;
    // 固定角度
    private _alignAngle: number = 0;

    // 构造函数
    constructor(imp?: any) {
        super(imp);
    }
    // 创建函数
    public create(imp: any) {
        return new McDbTestExportAnnotation(imp)
    }
    // 获取类名
    public getTypeName(): string {
        return "McDbTestExportAnnotation";
    }
    //设置或获取文本字高
    public set textHeight(val: number) {
        this.height = val;
    }
    public get textHeight(): number {
        return this.height;
    }
    //设置或获取上标文本
    public set textUp(val: string) {
        this._textUp = val;
    }
    public get textUp(): string {
        return this._textUp;
    }
    //设置或获取下标文本
    public set textDown(val: string) {
        this._textDown = val;
    }
    public get textDown(): string {
        return this._textDown;
    }
    //设置或获取箭头样式
    public set arrowType(val: string) {
        this._arrowType = val;
    }
    public get arrowType(): string {
        return this._arrowType;
    }
    //设置或获取对齐样式
    public set alginType(val: string) {
        this._alginType = val;
    }
    public get alginType(): string {
        return this._alginType;
    }
    //设置或获取固定角度
    public set alignAngle(val: number) {
        this._alignAngle = val;
    }
    public get alignAngle(): number {
        return this._alignAngle;
    }
    // 读取自定义实体数据
    public dwgInFields(filter: IMcDbDwgFiler): boolean {
        this.points = filter.readPoints("points").val;
        this.positionArr = filter.readPoints("positionArr").val;
        this.exportLinePos = filter.readPoint("exportLinePos").val;
        this.textBasePos = filter.readPoint("textBasePos").val;
        this._textDown = filter.readString("textDown").val;
        this._textUp = filter.readString("textUp").val;
        this._arrowType = filter.readString("arrowType").val;
        this._alginType = filter.readString("alginType").val;
        this.arrowLength = filter.readLong("arrowLength").val;
        this._alignAngle = filter.readLong("alignAngle").val;
        this.height = filter.readDouble("height").val;
        return true;
    }
    // 写入自定义实体数据
    public dwgOutFields(filter: IMcDbDwgFiler): boolean {
        filter.writePoints("points", this.points);
        filter.writePoints("positionArr", this.positionArr);
        filter.writePoint("textBasePos", this.textBasePos);
        filter.writePoint("exportLinePos", this.exportLinePos);
        filter.writeString("textDown", this._textDown);
        filter.writeString("textUp", this._textUp);
        filter.writeString("arrowType", this._arrowType);
        filter.writeString("alginType", this._alginType);
        filter.writeLong("arrowLength", this.arrowLength);
        filter.writeLong("alignAngle", this._alignAngle);
        filter.writeDouble("height", this.height);
        return true;
    }

    // 移动自定义对象的夹点
    public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
        this.assertWrite();
        const length = this.points.length
        if (iIndex < length) {
            this.points[iIndex].x += dXOffset;
            this.points[iIndex].y += dYOffset;
            this.points[iIndex].z += dZOffset;
        }
        if (iIndex === length) {
            this.exportLinePos.x += dXOffset;
            this.exportLinePos.y += dYOffset;
            this.exportLinePos.z += dZOffset;
            this.textBasePos.x += dXOffset;
            this.textBasePos.y += dYOffset;
            this.textBasePos.z += dZOffset;
        };
        if (iIndex > length) {
            this.textBasePos.x += dXOffset;
        }
    };
    // 获取自定义对象的夹点
    public getGripPoints(): McGePoint3dArray {
        let ret = new McGePoint3dArray()
        this.points.forEach(pt => {
            ret.append(pt)
        });
        ret.append(this.exportLinePos);
        ret.append(this.textBasePos);
        return ret;
    };
    // 画箭头
    private drawArrow(point: McGePoint3d): McDbEntity[] {
        const pt1 = point;
        const pt2 = this.exportLinePos;
        if (this._arrowType === 'A' || this._arrowType === 'HA') {
            const vec = pt2.sub(pt1).normalize().mult(this.arrowLength);
            const pt = pt1.clone().addvec(vec);
            const _vec = vec.clone().rotateBy(Math.PI / 2).normalize().mult(this.arrowLength / 8);
            const pt3 = pt.clone().addvec(_vec);
            const pt4 = pt.clone().subvec(_vec);
            const solid = new McDbHatch();
            this._arrowType === 'A' ? solid.appendLoop(new McGePoint3dArray([pt1, pt3, pt4])) : solid.appendLoop(new McGePoint3dArray([pt1, pt3, pt]));
            return [solid]
        } else if (this._arrowType === 'P') {
            const solid = new McDbHatch();
            solid.appendCircleLoop(pt1.x, pt1.y, this.arrowLength / 3);
            return [solid]
        } else if (this._arrowType === 'C') {
            const point1 = pt1.clone().addvec(McGeVector3d.kXAxis.normalize().mult(this.arrowLength / 2));
            const point2 = pt1.clone().subvec(McGeVector3d.kXAxis.normalize().mult(this.arrowLength / 2));
            const point3 = pt1.clone().addvec(McGeVector3d.kYAxis.normalize().mult(this.arrowLength / 2));
            const point4 = pt1.clone().subvec(McGeVector3d.kYAxis.normalize().mult(this.arrowLength / 2));
            const line1 = new McDbLine(point1, point2);
            const line2 = new McDbLine(point3, point4);
            return [line1, line2]
        }
    }
    // 画文字
    private drawText(): McDbEntity[] {
        if (!this.height) {
            this.height = this.arrowLength * (2 / 3);
        };

        const textArr = [];
        const textUp = new McDbText();
        textUp.height = this.height;
        textUp.textString = this._textUp;
        textUp.horizontalMode = McDb.TextHorzMode.kTextLeft;

        const textDown = new McDbMText()
        textDown.contents = this._textDown;
        textDown.textHeight = this.height;
        textDown.attachment = McDb.AttachmentPoint.kTopLeft;

        let pt1, pt2;
        if (this.exportLinePos.x < this.textBasePos.x) {
            pt1 = this.exportLinePos.clone();
            pt2 = this.textBasePos.clone();
        } else {
            pt2 = this.exportLinePos.clone();
            pt1 = this.textBasePos.clone();
        }
        const vec = pt2.sub(pt1).normalize().mult(this.height / 2);
        const _vec = vec.clone().rotateBy(Math.PI / 2).normalize().mult(this.height / 2);
        if (this._alginType === 'S') {
            // 开始端
            this.positionArr[1] = pt1.clone().addvec(vec).subvec(_vec);
            this.positionArr[0] = pt1.clone().addvec(vec).addvec(_vec);
        } else if (this._alginType === 'M') {
            // 上下标文字居中
            const distance = pt1.distanceTo(pt2);
            const midPt = pt1.clone().addvec(vec.normalize().mult(distance / 2))
            this.positionArr[1] = midPt.clone().subvec(_vec);
            this.positionArr[0] = midPt.clone().addvec(_vec);
        } else if (this._alginType === 'E') {
            // 上下标文字在末尾
            this.positionArr[1] = pt2.clone().subvec(_vec);
            this.positionArr[0] = pt2.clone().addvec(_vec);
            textUp.horizontalMode = McDb.TextHorzMode.kTextRight;
            textDown.attachment = McDb.AttachmentPoint.kTopRight;
        }
        textUp.position = textUp.alignmentPoint = this.positionArr[0];
        textDown.location = this.positionArr[1];
        textArr.push(textUp);
        textArr.push(textDown);
        return textArr
    }

    // 计算固定角度后的取点
    private getFixedAnglePt(fixedPt: McGePoint3d, pt: McGePoint3d): McGePoint3d {
        const vec = pt.sub(fixedPt);
        const vecAngle = vec.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);
        const num = 360 / this._alignAngle;
        let angleSub = null;
        let angle = 0;
        for (let i = 0; i < num; i++) {
            const a = Math.abs(this._alignAngle * i - vecAngle * 180 / Math.PI);
            if (!angleSub) {
                angleSub = a;
                angle = this._alignAngle * i;
            } else if (angleSub > a) {
                angleSub = a;
                angle = this._alignAngle * i;
            }
        }
        const _pt = fixedPt.clone().addvec(McGeVector3d.kXAxis.normalize().mult(100).rotateBy(angle * Math.PI / 180))
        const _line = new McDbLine(fixedPt, _pt);
        const newPoint = _line.getClosestPointTo(pt, true).val;
        return newPoint;
    }

    // 绘制实体 
    public worldDraw(draw: MxCADWorldDraw): void {
        if (!this.arrowLength) {
            this.arrowLength = MxFun.viewCoordLong2Cad(20);
        }
        // 计算 exportLinePos
        if (this._alignAngle && this.points.length === 1) {
            this.exportLinePos = this.getFixedAnglePt(this.points[0], this.exportLinePos);
        }
        // 画箭头
        this.points.forEach(pt => {
            if (this._arrowType !== 'N') {
                const arrowArr = this.drawArrow(pt);
                arrowArr.forEach(arrow => {
                    draw.drawEntity(arrow)
                })
            };
            // 画箭头路径
            const line = new McDbLine(this.exportLinePos, pt);
            draw.drawEntity(line);
        });
        this.textBasePos = new McGePoint3d(this.textBasePos.x, this.exportLinePos.y);
        const line = new McDbLine(this.exportLinePos, this.textBasePos);
        draw.drawEntity(line);
        // 画文字
        this.drawText().forEach(text => {
            draw.drawEntity(text);
        })
    }
    // 添加顶点
    public addVertex(pt: McGePoint3d) {
        this.assertWrite();
        this.points.push(pt);
        if (this.points.length > 1 && this.exportLinePos && this.alignAngle) {
            const point = this.getFixedAnglePt(this.exportLinePos, pt);
            this.points[this.points.length - 1] = point;
        }
    }
    // 获取顶点数组
    public getPoints() {
        return this.points;
    }
    // 设置引线位置
    public setExportLinePos(pt: McGePoint3d) {
        this.exportLinePos = pt.clone()
    }
    // 获取引线位置
    public getExportLinePos() {
        return this.exportLinePos;
    }
    // 设置文字基线位置
    public setTextBasePos(pt: McGePoint3d) {
        this.textBasePos = new McGePoint3d(pt.x, this.exportLinePos.y);
    }
    // 获取文字基线位置
    public getTextBasePos() {
        return this.textBasePos;
    }
}
  1. 注册自定义类信息
ts
new McDbTestExportAnnotation().rxInit();
  1. 编写方法,调用 McDbTestExportAnnotation 自定义引出标注类实现引出标注功能
  • 设置箭头样式,上下标文字内容及对齐方式

我们可以利用 MxCADUiPrString() 根据根据用户输入得到上下标文字内容,或者通过其他方式直接赋值。选择箭头样式或对齐方式时,我们可以通过 MxCADUiPrKeyWord() 根据用户选择的关键词来设置相应操作。

ts
// 设置箭头样式
const getArrowStyle = new MxCADUiPrKeyWord()
getArrowStyle.setMessage("请选择箭头样式:")
getArrowStyle.setKeyWords("[箭头(A)/半箭头(HA)/点(P)/十字(C)/无(N)]")
let arrowStyle = await getArrowStyle.go();
// 设置对齐方式
const getAlignType = new MxCADUiPrKeyWord()
getAlignType.setMessage("请选择上下标文字对齐方式:")
getAlignType.setKeyWords("[始端(S)/中间(M)/末端(E)]")
let alignType = await getAlignType.go();
/**
 * 设置上下标文字
 */
const getStrUp = new MxCADUiPrString();
getStrUp.setMessage('请设置上标文字内容:');
let strUp = await getStrUp.go();
if (!strUp) strUp = "";
const getStrDown = new MxCADUiPrString();
getStrDown.setMessage('请设置下标文字内容:');
let strDown = await getStrDown.go();
if (!strDown) strDown = "";
// 设置固定角度
const getAlignAngle = new MxCADUiPrKeyWord()
getAlignAngle.setMessage("请选择固定角度:")
getAlignAngle.setKeyWords("[0°(0)/30°(30)/45°(45)/60°(60)/90°(90)/]")
let alignAngle = await getAlignAngle.go();
  • 获取引线位置、标注位置、以及文字位置

我们可以利用取点对象 MxCADUiPrPoint 连续取点来获取引线端点、标注位置以及文字所在位置。结合上述步骤中获取的引注信息,构造新的引出标注对象,并动态绘制方便用户观察。

ts
const exportAnn = new McDbTestExportAnnotation();
exportAnn.textUp = strUp;
exportAnn.textDown = strDown;
exportAnn.arrowType = arrowStyle;
exportAnn.alginType = alignType;
exportAnn.alignAngle = Number(alignAngle);
const getPoint = new MxCADUiPrPoint();
getPoint.setMessage('请指定标注的第一个点:');
const point = await getPoint.go();
if (!point) return;
exportAnn.addVertex(point);
const getEtpoint = new MxCADUiPrPoint();
getEtpoint.setMessage('请指定引线的位置:');
getEtpoint.setUserDraw((pt, pw) => {
    const _clone = exportAnn.clone() as McDbTestExportAnnotation;
    _clone.setExportLinePos(pt);
    _clone.setTextBasePos(new McGePoint3d(pt.x + MxFun.viewCoordLong2Cad(50), pt.y))
    pw.drawMcDbEntity(_clone)
})
const etPoint = await getEtpoint.go();
if (!etPoint) return;
exportAnn.setExportLinePos(etPoint);
const getTpoint = new MxCADUiPrPoint();
getTpoint.setMessage('请指定文字基线的位置:');
getTpoint.setUserDraw((pt, pw) => {
    const _clone = exportAnn.clone() as McDbTestExportAnnotation;
    _clone.setTextBasePos(pt);
    pw.drawMcDbEntity(_clone)
});
const tPoint = await getTpoint.go();
if (!tPoint) return;
exportAnn.setTextBasePos(tPoint);

while (true) {
    const getPt = new MxCADUiPrPoint();
    getPt.setMessage('指定其他标注点,右键完成:');
    getPt.setUserDraw((pt, pw) => {
        const _clone = exportAnn.clone() as McDbTestExportAnnotation;
        _clone.addVertex(pt);
        pw.drawMcDbEntity(_clone)
    })
    const pt = await getPt.go();
    if (!pt) break;
    exportAnn.addVertex(pt);
}
const mxcad = MxCpp.getCurrentMxCAD();
mxcad.drawEntity(exportAnn);

功能实践

实践效果如下:

  • 点击引出标注按钮,执行引出标注方法
  • 根据命令行提示设置箭头样式、上下标文字内容及对齐方式
  • 在输入框中输入选项或内容,点击enter键确定
  • 点击鼠标左键确定第一个引线的端点
  • 移动鼠标,并点击鼠标左键确定引线位置
  • 移动鼠标,并点击鼠标左键确定文字标注位置
  • 点击鼠标左键可连续绘制标注引线
  • 点击鼠标右键结束取点,成功绘制引出标注