Skip to content
On this page

Eliciting annotation

Below we will introduce how to use the mxcad plug-in to achieve the function of leading out the annotation in the CAD drawing, the function of the user clicks the canvas to determine the starting point of the content, click the canvas again to determine the lead position and text annotation bit, one of the annotations can have multiple leading arrows. In the extraction annotation function, users can customize the arrow shape, as well as the content of superscript text and subscript text, and modify the text position according to the drawing needs. The extraction annotation function can help users quickly annotate the contents of drawings, and increase the completeness and readability of the contents of drawings.

Function implementation

  1. Implement custom eliciting annotation classes

In order to facilitate later management and annotation modification, We can through inheritance McDbCustomEntity Custom entity classes to extend the implementation of custom extraction annotation classes. Then, We can use McDbMText or McDbText measurement information text object, will mark information on the page.

In the following example, we provide arrow, point, cross, half arrow and other arrow styles in the annotation class, which can fix the lead Angle, and align the beginning, middle and end of the upper and lower script text. Users can refer to the following example code for secondary development according to their own project needs.

ts
// Elicited tag class
class McDbTestExportAnnotation extends McDbCustomEntity {
    // Define point objects inside McDbTestExportAnnotation 
    // Mark point set
    private points: McGePoint3d[] = [];
    // Lead position
    private exportLinePos: McGePoint3d = new McGePoint3d();
    // Text base position
    private textBasePos: McGePoint3d = new McGePoint3d();
    // Text point location set
    private positionArr: McGePoint3d[] = [];
    // Text height
    private height: number = 0;
    // Superscript text content
    private _textUp: string = "";
    // Subscript text content
    private _textDown: string = "";
    // Arrow style
    private _arrowType: string = "";
    // 对齐方式
    private _alginType: string = "";
    // Initial record length
    private arrowLength: number = 0;
    // Fixed Angle
    private _alignAngle: number = 0;

    // constructor
    constructor(imp?: any) {
        super(imp);
    }
    // Create function
    public create(imp: any) {
        return new McDbTestExportAnnotation(imp)
    }
    // Get class name
    public getTypeName(): string {
        return "McDbTestExportAnnotation";
    }
    //Sets or gets the text word height
    public set textHeight(val: number) {
        this.height = val;
    }
    public get textHeight(): number {
        return this.height;
    }
    //Sets or gets superscript text
    public set textUp(val: string) {
        this._textUp = val;
    }
    public get textUp(): string {
        return this._textUp;
    }
    //Sets or gets the subscript text
    public set textDown(val: string) {
        this._textDown = val;
    }
    public get textDown(): string {
        return this._textDown;
    }
    //Sets or gets the arrow style
    public set arrowType(val: string) {
        this._arrowType = val;
    }
    public get arrowType(): string {
        return this._arrowType;
    }
    //Sets or gets the alignment style
    public set alginType(val: string) {
        this._alginType = val;
    }
    public get alginType(): string {
        return this._alginType;
    }
    //Set or get a fixed Angle
    public set alignAngle(val: number) {
        this._alignAngle = val;
    }
    public get alignAngle(): number {
        return this._alignAngle;
    }
    // Read from defined entity data
    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;
    }
    // Writes custom entity data
    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;
    }

    // Moves the pinch point of a custom object
    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;
        }
    };
    // Gets the pinch point of a custom object
    public getGripPoints(): McGePoint3dArray {
        let ret = new McGePoint3dArray()
        this.points.forEach(pt => {
            ret.append(pt)
        });
        ret.append(this.exportLinePos);
        ret.append(this.textBasePos);
        return ret;
    };
    // Draw an arrow
    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]
        }
    }
    // Draw words
    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') {
            // Beginning end
            this.positionArr[1] = pt1.clone().addvec(vec).subvec(_vec);
            this.positionArr[0] = pt1.clone().addvec(vec).addvec(_vec);
        } else if (this._alginType === 'M') {
            // The upper and lower scripts are centered
            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') {
            // The upper and lower scripts are at the end
            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
    }

    // Take point after calculating fixed Angle
    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;
    }

    // Draw entity 
    public worldDraw(draw: MxCADWorldDraw): void {
        if (!this.arrowLength) {
            this.arrowLength = MxFun.viewCoordLong2Cad(20);
        }
        // Calculate exportLinePos
        if (this._alignAngle && this.points.length === 1) {
            this.exportLinePos = this.getFixedAnglePt(this.points[0], this.exportLinePos);
        }
        // Draw an arrow
        this.points.forEach(pt => {
            if (this._arrowType !== 'N') {
                const arrowArr = this.drawArrow(pt);
                arrowArr.forEach(arrow => {
                    draw.drawEntity(arrow)
                })
            };
            // Draw arrow path
            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);
        // Draw words
        this.drawText().forEach(text => {
            draw.drawEntity(text);
        })
    }
    // Add vertex
    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;
        }
    }
    // Get vertex array
    public getPoints() {
        return this.points;
    }
    // Set the lead position
    public setExportLinePos(pt: McGePoint3d) {
        this.exportLinePos = pt.clone()
    }
    // Get the lead position
    public getExportLinePos() {
        return this.exportLinePos;
    }
    // Set the text baseline position
    public setTextBasePos(pt: McGePoint3d) {
        this.textBasePos = new McGePoint3d(pt.x, this.exportLinePos.y);
    }
    // Gets the text baseline position
    public getTextBasePos() {
        return this.textBasePos;
    }
}
  1. Register custom class information
ts
new McDbTestExportAnnotation().rxInit();
  1. Write methods and call McDbTestExportAnnotation to customize the eliciting annotation class to achieve the eliciting annotation function
  • Set arrow style, subscript text content and alignment

We can use MxCADUiPrString() according to the according to user input by the subscript text content, Or assign values directly by some other means. When you select an arrow style or alignment, We can use MxCADUiPrKeyWord() set up corresponding operation according to user's choice of key words.

ts
// Set arrow style
const getArrowStyle = new MxCADUiPrKeyWord()
getArrowStyle.setMessage("Please select the arrow style:")
getArrowStyle.setKeyWords("[Arrow(A)/Half arrow(HA)/Dot(P)/Cross(C)/None(N)]")
let arrowStyle = await getArrowStyle.go();
// Set alignment
const getAlignType = new MxCADUiPrKeyWord()
getAlignType.setMessage("Please select the text alignment above and below:")
getAlignType.setKeyWords("[Beginning(S)/Middle(M)/End(E)]")
let alignType = await getAlignType.go();
/**
 * Set the subscript text
 */
const getStrUp = new MxCADUiPrString();
getStrUp.setMessage('Please set the superscript text content:');
let strUp = await getStrUp.go();
if (!strUp) strUp = "";
const getStrDown = new MxCADUiPrString();
getStrDown.setMessage('Please set the subscript text content:');
let strDown = await getStrDown.go();
if (!strDown) strDown = "";
// Set a fixed Angle
const getAlignAngle = new MxCADUiPrKeyWord()
getAlignAngle.setMessage("Please select a fixed Angle:")
getAlignAngle.setKeyWords("[0°(0)/30°(30)/45°(45)/60°(60)/90°(90)/]")
let alignAngle = await getAlignAngle.go();
  • Gets lead position, tag position, and text position

We can use take object MxCADUiPrPoint Take points consecutively to get the lead endpoint, the label position, and the text position. Combined with the information obtained in the above steps, a new extraction annotation object is constructed and dynamically drawn for user observation.

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('Please specify the first point of the annotation:');
const point = await getPoint.go();
if (!point) return;
exportAnn.addVertex(point);
const getEtpoint = new MxCADUiPrPoint();
getEtpoint.setMessage('Please specify the lead position:');
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('Please specify the location of the text baseline:');
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('Specify other points, right-click to complete:');
    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);

Functional practice

Practical effects are as follows:

  • Click the Lead annotation button to execute the lead annotation method
  • Follow the command line prompts to set arrow style, upper and lower text content, and alignment
  • enter an option or content in the input box and click Enter to confirm
  • Click the left mouse button to determine the end point of the first lead
  • Move the mouse and click the left mouse button to determine the lead position
  • Move the mouse and click the left mouse button to determine the location of the text annotation
  • Click the left mouse button to continuously draw the label lead
  • Click the right mouse button to end the fetching point and successfully draw out the annotation