连续测量
下面我们将介绍如何利用 mxcad 插件实现在CAD图纸中连续测量的功能,该功能中用户选中连续的线段间的捕捉点自动计算出选取线段的组合长度,同时用户可以自定义选择标注文字的位置。连续测量功能能够帮助用户快速掌握图纸对象的数据信息,方便统计工程量。
功能实现
- 实现自定义连续测量类
为了方便后期管理与修改标注,我们可以通过继承 McDbCustomEntity 自定义实体类来扩展实现自定义连续测量类。由于连续测量是计算圆弧与直线的组合长度,因此,我们可以将连续的直线与圆弧整合为 McDbPolyline 多段线。其中,我们可以通过设置多段线的bulge凸度来绘制圆弧。
然后,我们可以利用 McDbText 构造测量信息文本对象,将对象组合长度的标注信息绘制在页面中。
ts
// 自定义连续测量类
class McDbTestConMeasurement extends McDbCustomEntity {
// 定义McDbTestConMeasurement内部的点对象
// 测量点数组
private points: McGePoint3d[] = [];
private dbulges: number[] = [];
// 文字点位置
private position: McGePoint3d = new McGePoint3d();
// 文字高度
private height: number = 50;
/** 标注颜色 */
private dimColor: McCmColor = new McCmColor(255, 0, 0);
/** 标注线段宽度 */
private width: number = 0.1;
/** 是否显示分段长度 */
private showSegLength: boolean = false;
// 构造函数
constructor(imp?: any) {
super(imp);
}
// 创建函数
public create(imp: any) {
return new McDbTestConMeasurement(imp)
}
// 获取类名
public getTypeName(): string {
return "McDbTestConMeasurement";
}
//设置或获取文本字高
public set textHeight(val: number) {
this.height = val;
}
public get textHeight(): number {
return this.height;
}
/** 设置或获取标注颜色
* val:颜色rgb值数组
*/
public set color(val: McCmColor) {
this.dimColor = val.clone();
}
public get color(): McCmColor {
return this.dimColor;
}
/** 设置线段宽度 */
public set conWidth(val: number) {
this.width = MxFun.screenCoordLong2Doc(val);
}
public get conWidth(): number {
return this.width;
}
/** 显示分段长度 */
public set isShowSegLength(val: boolean) {
this.showSegLength = val;
}
public get isShowSegLength(): boolean {
return this.showSegLength;
}
// 读取自定义实体数据
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;
}
// 写入自定义实体数据
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;
}
// 移动自定义对象的夹点
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;
}
};
// 获取自定义对象的夹点
public getGripPoints(): McGePoint3dArray {
let ret = new McGePoint3dArray()
this.points.forEach(pt => {
ret.append(pt)
});
ret.append(this.position);
return ret;
};
// 绘制实体
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;
// 旋转角度
let vec;
if(pt1.x < pt.x){
vec = pt.sub(pt1);
}else{
vec = pt1.sub(pt)
}
// 取垂直向量
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);
}
// 设置顶点
public addVertex(pt: McGePoint3d, dbulge: number) {
this.assertWrite();
this.dbulges.push(dbulge);
this.points.push(pt);
}
// 获取点数组
public getPoints() {
return this.points;
}
// 获取position
public setPosition(pt: McGePoint3d) {
this.assertWrite();
this.position = pt.clone();
}
// 获取position
public getPosition() {
return this.position;
}
}
- 注册自定义类信息
ts
new McDbTestConMeasurement().rxInit();
- 封装取点函数
- 取点过程中我们需要系统能够智能识别图纸中的特殊点,如直线端点、曲线交点、垂足、中心点等。但由于每张图纸的初始系统变量设置不同,因此,我们需要先记录下初始系统变量值,再将在取点过程中将系统变量中的对象追踪开启、格栅与正交关闭,最后在取点结束后恢复原始系统变量设置。
ts
/**
* 设置系统变量
* 关闭正交,格栅
* 打开所有对象追踪
* @param param:0 恢复 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"),
}
)
// 关闭正交、格栅、打开所有对象追踪
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);
})
}
}
- 我们调用 MxCADUiPrPoint 取点对象进行连续取点,并可以通过设置 MxCADUiPrPoint.setKeyWords() 关键字列表来控制操作过程中的回退到上一个捕捉点或结束点对象捕捉。其中,在捕捉弧线的过程中我们需要调用 MxCADUtility.findEntAtPoint() 方法来确定我们取点时选取的实体对象并调用 MxCADResbuf 来过滤出目标弧线,然后调用 MxCADUtility.calcBulge() 方法获取到圆弧的凸度。值得注意的是捕捉弧线时,我们需要先捕捉到弧线的某一个端点后再去捕捉整条弧线,以此来保证我们捕捉的线段时一条连续的线段。
在弧线捕捉过程中我们会遇到需要打断弧线的情况,因此,我们可以在捕捉过程中设置智能打断弧线的操作。
在取点过程结束后,取点函数将以数组的形式返回线段的端点和凸度。
ts
// 连续取点(含弧线凸度)
async function ContinuousSampling() {
// 设置系统变量
setSystemVariables(1);
let ptArr = []; // 测量点数组
let flag = false; // 第一次点击是否为圆弧标注
const filter = new MxCADResbuf([DxfCode.kEntityType, "ARC"]); // 筛选弧线
const mxcad = MxCpp.getCurrentMxCAD();
let dTol = mxcad.mxdraw.viewCoordLong2Cad(0.5);// 设置精度值
// 记录最后选择的圆弧id
let arcId = null;
while (true) {
// 循环取测量点
const getPt = new MxCADUiPrPoint();
getPt.setMessage("请选择直线的端点或弧线");
getPt.setKeyWords("[回退(B)/结束(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") {
// 回退
if (ptArr.length > 1) ptArr.pop();
arcId = null;
} else if (key === "O") {
// 结束
break;
} else {
// 添加测量点
if (!pt) break;
const entId = MxCADUtility.findEntAtPoint(pt.x, pt.y, pt.z, -1, filter);
if (!entId.id) {
arcId = null;
if (flag) {
// 第一次是弧线
const res = ptArr.filter(item => item.pt.distanceTo(pt) < dTol);
if (!res.length) {
alert('请选择连续的线段!')
}
} 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为圆弧端点
if (flag) {
// 第一次点击为圆弧
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为圆弧上的点
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]
// 三点确定凸度 lastPt pt 以及两点的中间点
const l1 = arc.getDistAtPoint(preItem.pt).val;
const l2 = arc.getDistAtPoint(pt).val;
let midPt = arc.getPointAtDist(l1 + (l2 - l1) / 2).val; // 两点的中间点
let arcTest = new McDbArc();
arcTest.computeArc(preItem.pt.x, preItem.pt.y, midPt.x, midPt.y, pt.x, pt.y);//三点画圆弧
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; // 结束点
ptArr[ptArr.length - 1].dbulge = MxCADUtility.calcBulge(lastPt, pt, point).val;
ptArr.push({ pt: point, dbulge: 0 });
arcId = entId;
}
} else {
alert("请选择连续的线段!")
}
} else {
// 第一次点击就是arc
const dbulge = MxCADUtility.calcBulge(startPt, pt, endPt).val;
ptArr.push({ pt: startPt, dbulge });
ptArr.push({ pt: endPt, dbulge: 0 });
flag = true;
arcId = entId;
}
}
}
}
};
// 恢复初始系统变量
setSystemVariables(0)
return ptArr
}
- 编写方法,调用 McDbTestConMeasurement 自定义连续测量类实现连续测量功能
ts
// 连续测量
async function Mx_ContinueMeasurement() {
const mxcad = MxCpp.getCurrentMxCAD();
const ptArr = await ContinuousSampling();
if(ptArr.length < 2) return;
// 绘制测量标注
const plDim = new McDbTestConMeasurement();
ptArr.forEach(item => {
plDim.addVertex(item.pt, item.dbulge)
});
const getPos = new MxCADUiPrPoint();
getPos.setMessage("请指定文字的位置");
getPos.setUserDraw((pt, pw) => {
plDim.setPosition(pt);
pw.drawMcDbEntity(plDim)
});
const position = await getPos.go();
if (!position) return;
plDim.setPosition(position);
mxcad.drawEntity(plDim);
}
功能扩展
- 查看分段测量长度
在上述 McDbTestConMeasurement 自定义连续测量类中,我们预留了设置查看分段测量长度的属性 showSegLength ,因此,我们可以根据自身对项目的需求设置是否显示分段测量长度。
ts
// 查看分段长度
async function Mx_CountList() {
const getEnt = new MxCADUiPrEntity();
getEnt.setMessage('请选择一个连续测量的标注');
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();
}
}
- 测量面积(含弧线)
由于之前我们封装的 McDbTestAreaComment 自定义面积标注类 底层逻辑也是将目标图形通过多段线重绘并计算面积,因此,我们可以根据上述封装的取点函数进行取点(包含弧线)来计算包含弧线的异形面积。
ts
// 面积含弧线
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('请选择面积标注的位置');
position.setUserDraw((pt, pw) => {
area.setPoint(pt);
pw.drawMcDbEntity(area);
})
const positionPt = await position.go();
if (!positionPt) return;
area.setPoint(positionPt);
mxcad.drawEntity(area);
}
功能实践
实践效果如下:
- 连续测量
- 点击连续测量按钮,执行连续测量方法
- 点击鼠标左键选择线段间的捕捉点
- 输入关键字,执行关键字对应操作
- 点击鼠标右键结束取点,设置标注点位置
- 成功绘制连续测量标注内容
- 查看分段测量长度
- 点击查看分段测量长度按钮,执行查看分段测量长度方法
- 点击鼠标左键选中连续测量标注
- 成功显示分段测量长度
- 弧线智能打断
- 选中目标弧线
- 再次点击目标弧线选择打断端点
- 重新选中弧线为打断后的弧线