Skip to content
On this page

Continuous measurement

Below we will introduce how to use mxcad plug-in to realize the function of continuous measurement in CAD drawings. In this function, the user selects the capture point between the continuous line segments to automatically calculate the combined length of the selected line segments, and the user can customize the location of the marked text. The continuous measurement function can help users quickly grasp the data information of the drawing object and facilitate the statistics of the engineering quantity.

Function implementation

  1. Implement custom continuous measurement classes

In order to facilitate later management and annotation modification, We can through inheritance McDbCustomEntity Custom entity classes to extend the implementation of custom continuous measurement classes. Since continuous measurement is to calculate the combined length of an arc and a straight line, We can be a continuous line and arc integrated into McDbPolyline section of the line. Among them, we can draw the arc by setting the bulge bulge of the multi-segment line.

Then, we can use McDbText measurement information text object, the object combination the length of the label information on the page.

ts
// Custom continuous measurement classes
class McDbTestConMeasurement extends McDbCustomEntity {
    // Defines a point object inside McDbTestConMeasurement 
    // An array of measuring points
    private points: McGePoint3d[] = [];
    private dbulges: number[] = [];
    // Text point position
    private position: McGePoint3d = new McGePoint3d();
    // Text height
    private height: number = 50;
    /** Label color */
    private dimColor: McCmColor = new McCmColor(255, 0, 0);
    /** Mark line width */
    private width: number = 0.1;
    /** Whether to display segment length */
    private showSegLength: boolean = false;

    // constructor
    constructor(imp?: any) {
        super(imp);
    }
    // Create function
    public create(imp: any) {
        return new McDbTestConMeasurement(imp)
    }
    // Get class name
    public getTypeName(): string {
        return "McDbTestConMeasurement";
    }
    // 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 the annotation color
     * val: array of color rgb values
     */
    public set color(val: McCmColor) {
        this.dimColor = val.clone();
    }
    public get color(): McCmColor {
        return this.dimColor;
    }
    /** Set the segment width */
    public set conWidth(val: number) {
        this.width = MxFun.screenCoordLong2Doc(val);
    }
    public get conWidth(): number {
        return this.width;
    }
    /** Display segment length */
    public set isShowSegLength(val: boolean) {
        this.showSegLength = val;
    }
    public get isShowSegLength(): boolean {
        return this.showSegLength;
    }
    // Read from defined entity data
    public dwgInFields(filter: IMcDbDwgFiler): boolean {
        this.points = filter.readPoints("points").val;
        this.position = filter.readPoint("position").val;
        const _dbulges = filter.readString("dbulges").val;
        this.dbulges = _dbulges.split(',').map(Number);
        const _dimColor = filter.readString("dimColor").val;
        this.dimColor = new McCmColor(..._dimColor.split(',').map(Number));
        this.width = filter.readDouble("conWidth").val;
        this.showSegLength = filter.readLong('showSegLength').val === 1;
        return true;
    }
    // Writes custom entity data
    public dwgOutFields(filter: IMcDbDwgFiler): boolean {
        const _dbulges = this.dbulges.toString();
        const _color = `${this.dimColor.red},${this.dimColor.green},${this.dimColor.blue}`;
        const _showSegLength = this.showSegLength ? 1 : 0;
        filter.writePoints("points", this.points);
        filter.writePoint("position", this.position);
        filter.writeString("dbulges", _dbulges);
        filter.writeString("dimColor", _color);
        filter.writeDouble("conWidth", this.width);
        filter.writeLong('showSegLength', _showSegLength)
        return true;
    }

    // Moves the pinch point of a custom object
    public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
        this.assertWrite();
        if (iIndex < this.points.length) {
            this.points[iIndex].x += dXOffset;
            this.points[iIndex].y += dYOffset;
            this.points[iIndex].z += dZOffset;
        } else {
            this.position.x += dXOffset;
            this.position.y += dYOffset;
            this.position.z += dZOffset;
        }
    };
    // 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.position);
        return ret;
    };
    // Draw entity
    public worldDraw(draw: MxCADWorldDraw): void {
        const pl = new McDbPolyline();
        const lengthArr = [];
        this.points.forEach((pt, index) => {
            let width = this.width;
            if (index === this.points.length - 1) width = 0;
            pl.addVertexAt(pt, this.dbulges[index], width, width);
            if (index > 0) {
                const pt1 = this.points[index - 1];
                const l = new McDbPolyline();
                l.addVertexAt(pt1, this.dbulges[index - 1]);
                l.addVertexAt(pt, this.dbulges[index]);
                const length = l.getLength().val;
                const position = l.getPointAtDist(length / 2).val;
                const text = new McDbText();
                text.textString = `${length.toFixed(2)}`;
                text.height = MxFun.screenCoordLong2Doc(this.height * 0.6);
                text.horizontalMode = McDb.TextHorzMode.kTextCenter;
                // Rotation Angle
                let vec;
                if(pt1.x < pt.x){
                    vec = pt.sub(pt1); 
                }else{
                    vec = pt1.sub(pt)
                }
                // Take a vertical vector
                const perpVec = vec.clone().perpVector().normalize();
                const textNum = MxFun.screenCoordLong2Doc(this.height * 0.15);
                const line = new McDbLine(pt1, pt);
                const midPt = line.getPointAtDist(line.getLength().val / 2).val;
                if (midPt.y >= position.y) {
                    position.addvec(perpVec.mult(textNum));
                } else {
                    position.subvec(perpVec.mult(textNum));
                };
                text.position = text.alignmentPoint = position;
                const angle = vec.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);
                text.rotation = angle === Math.PI ? 0 : angle;
                lengthArr.push(text)
            }
        });
        const mxcad = MxCpp.getCurrentMxCAD();
        const plId = mxcad.drawEntity(pl);
        const length = (plId.getMcDbEntity() as McDbPolyline).getLength().val;
        const endPt = this.points[this.points.length - 1];
        const vec = pl.getFirstDeriv(endPt).val;
        const _vec = vec.clone();
        _vec.rotateBy(Math.PI / 4).normalize().mult(this.width * 5);
        const pt1 = endPt.clone().addvec(_vec);
        const pt2 = endPt.clone().subvec(_vec)
        const line = new McDbPolyline();
        line.addVertexAt(pt1, 0, this.width, this.width);
        line.addVertexAt(pt2);
        plId.erase();
        const text = new McDbText();
        text.textString = `${length.toFixed(2)}`;
        text.height = MxFun.screenCoordLong2Doc(this.height);
        const num = MxFun.screenCoordLong2Doc(4)
        text.position = text.alignmentPoint = new McGePoint3d(this.position.x + num, this.position.y + num);
        text.horizontalMode = McDb.TextHorzMode.kTextLeft;
        const textId = mxcad.drawEntity(text);
        const { minPt, maxPt } = textId.getMcDbEntity().getBoundingBox();
        textId.erase();
        const lastPoint = new McGePoint3d(this.position.x + Math.abs(minPt.x - maxPt.x) + num * 6, this.position.y);
        pl.addVertexAt(this.position);
        pl.addVertexAt(lastPoint);
        if (this.showSegLength) {
            lengthArr.forEach(ent => {
                draw.drawEntity(ent);
            })
        }
        draw.trueColor = this.dimColor;
        draw.drawEntity(pl);
        draw.drawEntity(text);
        draw.drawEntity(line);
    }
    // Set vertex
    public addVertex(pt: McGePoint3d, dbulge: number) {
        this.assertWrite();
        this.dbulges.push(dbulge);
        this.points.push(pt);
    }
    // Get points group
    public getPoints() {
        return this.points;
    }
    // Set position
    public setPosition(pt: McGePoint3d) {
        this.assertWrite();
        this.position = pt.clone();
    }
    // Get position
    public getPosition() {
        return this.position;
    }
}
  1. Register custom class information
ts
new McDbTestConMeasurement().rxInit();
  1. Encapsulate the fetch point function
  • In the process of taking points, we need the system to be able to intelligently identify special points in the drawing, such as straight end points, curve intersection points, vertical feet, center points, etc. But due to the initial system variable of each drawing Settings are different, so we need to first record the initial system variable values, then turn on the object trace in the system variable, turn off the grid and orthogonal in the system variable during the point fetching process, and finally restore the original system variable Settings after the point fetching.
ts
/**
 * Set system variables
 * Close orthogonal, grille
 * Turn on all object tracking
 * @param param:0 restores 1
 */
let orginSet = [];
function setSystemVariables(param: number) {
    const mxcad = MxCpp.getCurrentMxCAD();
    if (param === 1) {
        orginSet.push(
            {
                name: "ORTHOMODE",
                value: mxcad.getSysVarLong("ORTHOMODE"),
            },
            {
                name: "GRIDMODE",
                value: mxcad.getSysVarLong("GRIDMODE"),
            },
            {
                name: "OSMODE",
                value: mxcad.getSysVarLong("OSMODE"),
            }
        )
        // Turn off orthogonality, grille, turn on all object tracking
        mxcad.setSysVarLong("ORTHOMODE", 0);
        mxcad.setSysVarLong("GRIDMODE", 0);
        mxcad.setSysVarLong("OSMODE", 16383);
    } else if (param === 0) {
        orginSet.forEach(item => {
            mxcad.setSysVarLong(item.name, item.value);
        })
    }
}
  • we call MxCADUiPrPoint Continuous take take objects, and can be set by MxCADUiPrPoint.setKeyWords() key word list to control operation process backs up a capture point or end point object capture. Among them, In the process of capturing the arc we need to call MxCADUtility.findEntAtPoint() method to determine when we take the choice of entity object and call the MxCADResbuf to filter out the target curve, Then call MxCADUtility.calcBulge() Method The convexity of the arc was obtained. It is worth noting that when capturing an arc, we need to capture a certain end point of the arc before capturing the entire arc, so as to ensure that the line segment we capture is a continuous line segment.

In the process of arc capture, we will encounter the situation that we need to break the arc, so we can set the intelligent operation of breaking the arc during the capture process.

At the end of the point-fetching process, the point-fetching function returns the endpoint and convexity of the line segment in the form of an array.

ts
// Continuous point taking
async function ContinuousSampling() {
    // Set system variable
    setSystemVariables(1);
    let ptArr = []; // An array of measuring points
    let flag = false; // Whether the first click is an arc mark
    const filter = new MxCADResbuf([DxfCode.kEntityType, "ARC"]); // Screening arc
    const mxcad = MxCpp.getCurrentMxCAD();
    let dTol = mxcad.mxdraw.viewCoordLong2Cad(0.5);// Set precision value

    // Records the last selected arc id
    let arcId = null;
    while (true) {
        // Cycle the measuring points
        const getPt = new MxCADUiPrPoint();
        getPt.setMessage("Please select the end or arc of the line");
        getPt.setKeyWords("[Rollback(B)/End(O)]");
        if (ptArr.length > 0) getPt.setBasePt(ptArr[ptArr.length - 1].pt);
        getPt.setUserDraw((pt, pw) => {
            const pl = new McDbPolyline();
            ptArr.forEach(obj => {
                pl.addVertexAt(obj.pt, obj.dbulge, 0.1, 0.1);
            });
            pw.drawMcDbEntity(pl)
        })
        const pt = await getPt.go();
        const key = getPt.keyWordPicked();
        if (key === "B") {
            // backspace
            if (ptArr.length > 1) ptArr.pop();
            arcId = null;
        } else if (key === "O") {
            // finish
            break;
        } else {
            // Add measuring points
            if (!pt) break;
            const entId = MxCADUtility.findEntAtPoint(pt.x, pt.y, pt.z, -1, filter);
            if (!entId.id) {
                arcId = null;
                if (flag) {
                    // The first time was an arc
                    const res = ptArr.filter(item => item.pt.distanceTo(pt) < dTol);
                    if (!res.length) {
                        alert('Please select a continuous line segment!')
                    }
                } else {
                    ptArr.push({ pt, dbulge: 0 })
                }
            } else {
                const arc = entId.getMcDbEntity() as McDbArc;
                const length = arc.getLength().val;
                const startPt = arc.getPointAtDist(0).val;
                const endPt = arc.getPointAtDist(length).val;

                if (startPt.distanceTo(pt) < dTol || endPt.distanceTo(pt) < dTol) {
                    // pt is the end point of the arc
                    if (flag) {
                        // The first click is an arc
                        const dbulge = -ptArr[0].dbulge;
                        const pt1 = ptArr[ptArr.length - 2].pt;
                        const pt2 = ptArr[ptArr.length - 1].pt;
                        if (pt1.distanceTo(pt) < dTol) {
                            ptArr.slice(0, ptArr.length - 2);
                            ptArr.push({ pt: pt2, dbulge });
                            ptArr.push({ pt: pt1, dbulge: 0 });
                        };
                        flag = false;
                        arcId = entId;
                    } else {
                        ptArr.push({ pt, dbulge: 0 })
                    }
                } else {
                    // pt is the point on the arc
                    if (ptArr.length > 0) {
                        const lastPt = ptArr[ptArr.length - 1].pt;
                        if (startPt.distanceTo(lastPt) < dTol || endPt.distanceTo(lastPt) < dTol) {
                            if (arcId?.id === entId.id) {
                                ptArr.pop();
                                const preItem = ptArr[ptArr.length - 1]
                                // Three points determine the convexity lastPt pt and the middle point of two points
                                const l1 = arc.getDistAtPoint(preItem.pt).val;
                                const l2 = arc.getDistAtPoint(pt).val;
                                let midPt = arc.getPointAtDist(l1 + (l2 - l1) / 2).val; // Halfway between two points
                                let arcTest = new McDbArc();
                                arcTest.computeArc(preItem.pt.x, preItem.pt.y, midPt.x, midPt.y, pt.x, pt.y);//Three-point arc
                                if (arcTest.getLength().val > arc.getLength().val) {
                                    const _clone = arcTest.clone() as McDbArc;
                                    arcTest.startAngle = _clone.endAngle;
                                    arcTest.endAngle = _clone.startAngle;
                                    midPt = arcTest.getPointAtDist(arcTest.getLength().val / 2).val;
                                }
                                preItem.dbulge = MxCADUtility.calcBulge(preItem.pt, midPt, pt).val;
                                ptArr.push({ pt, dbulge: 0 });
                                arcId = null;
                            } else {
                                const point = startPt.distanceTo(lastPt) < dTol ? endPt : startPt; // End point
                                ptArr[ptArr.length - 1].dbulge = MxCADUtility.calcBulge(lastPt, pt, point).val;
                                ptArr.push({ pt: point, dbulge: 0 });
                                arcId = entId;
                            }
                        } else {
                            alert("Please select a continuous line segment!")
                        }
                    } else {
                        // The first click is arc
                        const dbulge = MxCADUtility.calcBulge(startPt, pt, endPt).val;
                        ptArr.push({ pt: startPt, dbulge });
                        ptArr.push({ pt: endPt, dbulge: 0 });
                        flag = true;
                        arcId = entId;
                    }
                }
            }
        }
    };
    // Restore the initial system variables
    setSystemVariables(0)
    return ptArr
}
  1. Write a method, call McDbTestConMeasurement custom continuous measurement class to achieve continuous measurement function
ts
// Continuous measurement
async function Mx_ContinueMeasurement() {

    const mxcad = MxCpp.getCurrentMxCAD();
    const ptArr = await ContinuousSampling();
    if(ptArr.length < 2) return;
    // Draw measurement labels
    const plDim = new McDbTestConMeasurement();
    ptArr.forEach(item => {
        plDim.addVertex(item.pt, item.dbulge)
    });
    const getPos = new MxCADUiPrPoint();
    getPos.setMessage("Please specify the location of the text");
    getPos.setUserDraw((pt, pw) => {
        plDim.setPosition(pt);
        pw.drawMcDbEntity(plDim)
    });
    const position = await getPos.go();
    if (!position) return;
    plDim.setPosition(position);
    mxcad.drawEntity(plDim);
}

Function extension

  1. View the segment measurement length

In the McDbTestConMeasurement custom continuous measurement class above, we reserve the property showSegLength to set the view of the segmented measurement length, so we can set whether to display the segmented measurement length according to our needs for the project.

ts
// View segment length
async function Mx_CountList() {
    const getEnt = new MxCADUiPrEntity();
    getEnt.setMessage('Please select a label for continuous measurement');
    const entId = await getEnt.go();
    if (!entId.id) return;
    const ent = entId.getMcDbEntity();
    if (!(ent instanceof McDbCustomEntity)) return;
    if (ent.getTypeName() === 'McDbTestConMeasurement') {
        const _clone = ent.clone() as McDbTestConMeasurement;
        _clone.isShowSegLength = !_clone.isShowSegLength;
        MxCpp.getCurrentMxCAD().drawEntity(_clone);
        entId.erase();
    }
}
  1. Measuring area (including arc)

Since the underlying logic of McDbTestAreaComment Custom area annotation class we encapsulated before is also to redraw the target graph through multiple segment lines and calculate the area, we can take points (including arcs) according to the above encapsulated point taking function to calculate the shaped area containing arcs.

ts
// The area contains arcs
async function Mx_AreaArc() {
    const ptArr = await ContinuousSampling()
    const mxcad = MxCpp.getCurrentMxCAD();
    const area = new McDbTestAreaComment();
    ptArr.forEach(item => {
        area.addVertex(item.pt, item.dbulge);
    });
    const position = new MxCADUiPrPoint();
    position.setMessage(' Please select the location marked by area ');
    position.setUserDraw((pt, pw) => {
        area.setPoint(pt);
        pw.drawMcDbEntity(area);
    })
    const positionPt = await position.go();
    if (!positionPt) return;
    area.setPoint(positionPt);
    mxcad.drawEntity(area);
}

Functional practice

Practical effects are as follows:

  1. Continuous measurement
  • Click the Continuous measurement button to perform the continuous measurement method
  • Click the left mouse button to select the capture point between the lines
  • Enter the keyword and perform the operation corresponding to the keyword
  • Click the right mouse button to take the point and set the position of the point
  • Successfully draw continuous measurement annotations
  1. View the segment measurement length
  • Click the View segment measurement Length button to perform the View segment measurement length method
  • Click the left mouse button to select the continuous measurement label
  • Segment measurement length successfully displayed
  1. Arc smart interruptions
  • Select the target arc
  • Click on the target arc again to select the break endpoint
  • Reselect the arc to the one after the break