(H5前端CAD)在线CAD如何实现图形识别功能
前言
CAD图形识别功能可帮助用户快速识别和提取CAD图纸中的各种图形,从而加速设计过程。可应用在识别与分类阶段,自动识别图纸中的不同元素,通过特定特征进行区分,减少了手动分类的工作量;也可应用在数量统计阶段,统计图纸中各种构件的数量用于预算;还可运用在图纸定位和应用阶段,快速定位图纸上的特定元素,便于快速查找和修改。
mxcad 为用户提供了图形识别功能和API,用户可根据自身需求对该功能进行拓展或二次开发,更多开发文档关注公众号:梦想云图网页CAD。
图形识别步骤
1. 打开mxcad在线示例demo:https://demo.mxdraw3d.com:3000/mxcad/。
2. 点击【工具(A)】菜单栏,找到图形识别功能,选择【图形识别】按钮,如下图:
3. 根据命令行提示,点击鼠标左键点选图元或拉框选择需要识别的图形(图形识别功能目前支持识别直线类、多段线类、弧线类、圆类,以及块这五类图元),如下图:
4. 点击右键结束选择后,在弹出的图形识别框内设置查找图形详情,设置图形名称可方便后续查找已识别图形;设置重新选择图形时,会覆盖当前选中图形;设置区域查找时,需要用户框选查找范围,若未框选范围则会默认为全图纸查找图形,如下图:
5. 在图形识别框内点击【开始识别】按钮,开始在目标范围内查找图形,并将查找结果展示在图形识别列表中。点击图形识别列表下的坐标对应的【查看】按钮会自动定位到对应的图形位置,并圈选出目标图形,如下图:
6. 当识别多个图形后,可点击目标图形统计表格对应的操作列中的【详情】按钮查看识别详情,图形坐标列表将转换为该目标图形的识别结果,如下图:
识别注意事项:
如果需要识别的图形较为复杂,为保证识别速度与精度,我们需尽可能查找图形中的特殊部分而不是选取整个图形(图形对象过多时可能会导致卡顿,影响用户使用效率)。
图形识别支持区域选择,用户可根据自身需求精确定位图形筛选区域。
功能开发
mxcad 图形识别功能中运用的核心思想是通过[McDbEntity]实体中每个实体的特征在图纸中进行查找,查找的实体中包括了[直线类]、[多段线类]、[弧线类]、[圆类],以及[块]。
下列为基于mxcad 封装的图形识别类 SearchSamePattern 用户可根据该类的使用方法利用 mxcad 进行二次开发,代码如下:
import { DxfCode, McCmColor, McDbArc, McDbBlockReference, McDbCircle, McDbLine, McDbPolyline, McDbWipeout, McGePoint3d, McGePoint3dArray, McGeVector3d, MxCADResbuf, MxCADSelectionSet, MxCADUtility, MxCpp } from "mxcad"; import { MxFun } from "mxdraw"; export class SearchSamePattern { /** 识别图元数 */ private objectCount: number = 0; /** 选择图形集合 Map<className,[Ent]>*/ private selectObjects: any; private dTol = 0.000001; /** * pt1:左下角 * pt2:右上角 * pt3:左上角 * pt4:右下角 */ private pt1: McGePoint3d; private pt2: McGePoint3d; private pt3: McGePoint3d; private pt4: McGePoint3d;//包围盒大小 private pl: McDbPolyline; /** 识别图形类名集合 [string] */ private classNames: any /** 识别图形图片地址 */ private patternImgUrl: String /** 识别对象详情 Map<ClassName,[{}]>*/ private objectsDetails: any = {}; // 下列为识别结果数据 /** 识别后的图形位置数组集合 */; private graphBoxsPos: any[] = []; private formPt: McGePoint3d; private toPt: McGePoint3d; /** 点精度值 */ private dTolPt: any; /** 识别后图形列表 */ private patternList: any = {}; /** 选择的图形集合 */ public setSelectset(ss: MxCADSelectionSet): Promise<String> { if (!ss?.count()) return; const mxcad = MxCpp.getCurrentMxCAD(); // 获取当前图纸的空间块表记录包围盒 const { maxPt, minPt } = mxcad.getDatabase().currentSpace.getBoundingBox(); const currentDrawOrder = mxcad.getDatabase().currentSpace.getMinMaxDrawOrder() const n = MxFun.screenCoordLong2Doc(Math.abs(maxPt.x - minPt.x)); minPt.x -= n; minPt.y -= n; maxPt.x += n; maxPt.y += n; let points = new McGePoint3dArray(); points.append(minPt); points.append(new McGePoint3d(minPt.x, maxPt.y, 0)); points.append(new McGePoint3d(maxPt.x, maxPt.y, 0)); points.append(new McGePoint3d(maxPt.x, minPt.y, 0)); this.objectCount = ss.count(); // 获取遮罩层drawOrder let wipeout = new McDbWipeout wipeout.setVertices(points); const wipeoutId = mxcad.drawEntity(wipeout); const e = wipeoutId.getMcDbEntity(); e.drawOrder = currentDrawOrder.maxDrawOrder + 1; this.selectObjects = {}; const oldDrawOder = []; ss.forEach(id => { const ent = id.getMcDbEntity(); /** 记录识别图形的信息 */ if (!this.selectObjects[ent.objectName]) this.selectObjects[ent.objectName] = []; this.selectObjects[ent.objectName].push(ent); if (ent.objectName === 'McDbLine') { if (!this.objectsDetails['McDbLine']) this.objectsDetails['McDbLine'] = []; this.objectsDetails['McDbLine'].push({ entity: ent, length: (ent as McDbLine).getLength().val }); } else if (ent.objectName === 'McDbArc') { if (!this.objectsDetails['McDbArc']) this.objectsDetails['McDbArc'] = []; const length = (ent as McDbArc).getLength().val; const { bugle } = this.getBugle((ent as McDbArc), length) this.objectsDetails['McDbArc'].push({ entity: ent, length, bugle }); } else if (ent.objectName === 'McDbCircle') { if (!this.objectsDetails['McDbCircle']) this.objectsDetails['McDbCircle'] = []; const radius = (ent as McDbCircle).radius; this.objectsDetails['McDbCircle'].push({ entity: ent, radius }); } else if (ent.objectName === 'McDbBlockReference') { if (!this.objectsDetails['McDbBlockReference']) this.objectsDetails['McDbBlockReference'] = []; const blkName = (ent as McDbBlockReference).blockName; this.objectsDetails['McDbBlockReference'].push({ entity: ent, blkName }); } else if (ent.objectName === 'McDbPolyline') { if (!this.objectsDetails['McDbPolyline']) this.objectsDetails['McDbPolyline'] = []; const length = (ent as McDbPolyline).getLength().val; this.objectsDetails['McDbPolyline'].push({ entity: ent, length }); } /** 渲染顺序 */ oldDrawOder.push({ obj: ent, drawOrder: ent.drawOrder }); ent.drawOrder = e.drawOrder + 1; const { maxPt, minPt } = ent.getBoundingBox(); if (!this.pt1 || !this.pt2) { this.pt1 = minPt; this.pt2 = maxPt; } else { if (minPt.x < this.pt1.x) this.pt1.x = minPt.x; if (minPt.y < this.pt1.y) this.pt1.y = minPt.y; if (maxPt.x > this.pt2.x) this.pt2.x = maxPt.x; if (maxPt.y > this.pt2.y) this.pt2.y = maxPt.y; } }); this.classNames = Object.keys(this.selectObjects); /** * 计算包围盒其他两个角点 */ this.pt3 = new McGePoint3d(this.pt1.x, this.pt2.y); this.pt4 = new McGePoint3d(this.pt2.x, this.pt1.y); /** 绘制识别图形包围盒 */ this.pl = new McDbPolyline() this.pl.addVertexAt(this.pt1); this.pl.addVertexAt(this.pt3); this.pl.addVertexAt(this.pt2); this.pl.addVertexAt(this.pt4); this.pl.isClosed = true; const num = MxFun.screenCoordLong2Doc(10); this.pt1.x -= num; this.pt1.y -= num; this.pt2.x += num; this.pt2.y += num; // 生成预览图 let w = Math.abs(this.pt1.x - this.pt2.x); let h = Math.abs(this.pt1.y - this.pt2.y); if (w < 1 || h < 1) return; let jpg_width = 300; let jpg_height = 240; return new Promise<String>((resolve, reject) => { MxFun.getCurrentDraw().createCanvasImageData( (imageData: String) => { this.patternImgUrl = imageData; // 恢复绘制顺序 oldDrawOder.forEach(item => { item.obj.drawOrder = item.drawOrder; }) wipeoutId.erase(); resolve(imageData) }, { width: jpg_width, // 图片宽 height: jpg_height, // 图片高 range_pt1: this.pt1.toVector3(), // 截图范围角点1 range_pt2: this.pt2.toVector3(), // 截图范围角点2 } ); }); }; // 开始识别图形 /** * 1.查找初始包围盒 * 2.筛选符合条件的包围盒 * 3.比较包围盒内的每一个图元与需要识别的图元是否匹配 */ public identifyGraphics(corner1?: McGePoint3d, corner2?: McGePoint3d): any { /** 记录图形缩略图 */ this.patternList.patternImgUrl = this.patternImgUrl; /**初始化图形位置列表 */ this.graphBoxsPos.length = 0; const mxcad = MxCpp.getCurrentMxCAD(); this.dTolPt = mxcad.mxdraw.viewCoordLong2Cad(0.5);// 设置精度值 if (!this.classNames) return; const array = this.classNames.map(name => this.selectObjects[name].length); const minIndex = array.findIndex(element => element === Math.min(...array)); const str = this.classNames[minIndex]; /** 识别块 */ if (str === 'McDbBlockReference') { const refBlk = this.selectObjects['McDbBlockReference'][0].clone() as McDbBlockReference; const blkName = refBlk.blockName; // 图块名 const { position } = this.getBlkMidPt(refBlk); this.formPt = position; const rotate = refBlk.rotation; // 设置图块过滤器 const filter = new MxCADResbuf([DxfCode.kEntityType, "INSERT"]) const ss = new MxCADSelectionSet() if (corner1 && corner2) { // 局部选择 ss.crossingSelect(corner1.x, corner1.y, corner2.x, corner2.y, filter); } else { // 全局选择 ss.allSelect(filter); }; // 筛选出符合条件的块 const boxs: any[] = []; ss.forEach(id => { const _blk = id.getMcDbEntity().clone() as McDbBlockReference; /** 块名相等=》计算旋转角度=》计算包围盒大小 */ if (blkName === _blk.blockName) { const _rotate = _blk.rotation; const angle = rotate >= 0 ? _rotate - rotate : rotate - _rotate; const { position: _position, minPt, maxPt } = this.getBlkMidPt(_blk); if (this.classNames.length === 1) { this.toPt = _position; this.identificationBBox(minPt, maxPt, 0) } else { // 平移旋转初始包围盒。计算出最新包围盒的大小 const _pl = this.pl.clone() as McDbPolyline; _pl.move(this.formPt, _position); _pl.rotate(_position, angle); let x = [], y = []; for (let i = 0; i < _pl.numVerts(); i++) { const pt = _pl.getPointAt(i).val; x.push(pt.x); y.push(pt.y); } const _minPt = new McGePoint3d(Math.min(...x) - 1, Math.min(...y) - 1); const _maxPt = new McGePoint3d(Math.max(...x) + 1, Math.max(...y) + 1); const res = boxs.filter(item => { let ret1 = _minPt.distanceTo(item.pt1) < this.dTolPt && _maxPt.distanceTo(item.pt2) < this.dTolPt let ret2 = _minPt.distanceTo(item.pt2) < this.dTolPt && _maxPt.distanceTo(item.pt1) < this.dTolPt return ret1 || ret2 }) if (!res.length) { boxs.push({ pt1: _minPt, pt2: _maxPt }); this.toPt = _position; // MxCpp.getCurrentMxCAD().drawLine(_maxPt.x, _maxPt.y, _minPt.x, _minPt.y); this.identificationBBox(_minPt, _maxPt, angle); } } } }); } /** 识别弧线 */ else if (str === 'McDbArc') { // 识别直线 const refArc = this.selectObjects['McDbArc'][0].clone() as McDbArc; const length = refArc.getLength().val; // 弧线线长度 // 圆弧凸度 const { bugle, midPt } = this.getBugle(refArc, length); const center = refArc.center; const vec = center.sub(midPt); // 设置弧线过滤器 const filter = new MxCADResbuf([DxfCode.kEntityType, "ARC"]) const ss = new MxCADSelectionSet() if (corner1 && corner2) { // 局部选择 ss.crossingSelect(corner1.x, corner1.y, corner2.x, corner2.y, filter); } else { // 全局选择 ss.allSelect(filter); }; // 筛选出符合条件的弧线 const boxs: any[] = []; ss.forEach(id => { const _arc = id.getMcDbEntity().clone() as McDbArc; const _length = _arc.getLength().val; const { bugle: _bugle, midPt: _midPt } = this.getBugle(_arc, _length); /** 筛选出圆弧凸度与长度相同的弧线*/ if (Math.abs(_length - length) < this.dTol && Math.abs(_bugle - bugle) < this.dTol) { /** 分两种情况 * 1.直接旋转平移得到 * 2.镜像得到=》移动到目标位置+旋转角度+镜像翻转=》比较起始点位置 */ // 平移+旋转 const _center = _arc.center; const _vec = _center.sub(_midPt); const angle = _vec.angleTo2(vec, McGeVector3d.kNegateZAxis); // 旋转平移初始包围盒。计算出最新包围盒的大小 const _pl = this.pl.clone() as McDbPolyline; _pl.move(midPt, _midPt); _pl.rotate(_midPt, angle); let x = [], y = []; for (let i = 0; i < _pl.numVerts(); i++) { const pt = _pl.getPointAt(i).val; x.push(pt.x); y.push(pt.y); } const _minPt = new McGePoint3d(Math.min(...x) - 1, Math.min(...y) - 1); const _maxPt = new McGePoint3d(Math.max(...x) + 1, Math.max(...y) + 1); const res = boxs.filter(item => { let ret1 = _minPt.distanceTo(item.pt1) < this.dTolPt && _maxPt.distanceTo(item.pt2) < this.dTolPt let ret2 = _minPt.distanceTo(item.pt2) < this.dTolPt && _maxPt.distanceTo(item.pt1) < this.dTolPt let ret3 = _midPt.distanceTo(item.midPt) < this.dTolPt return (ret1 || ret2) && ret3 }) if (!res.length) { boxs.push({ pt1: _minPt, pt2: _maxPt, midPt: _midPt }); this.toPt = _midPt; this.formPt = midPt; this.identificationBBox(_minPt, _maxPt, angle); } } }); } /** 识别直线 */ else if (str === 'McDbLine') { // 识别直线 const refLine = this.selectObjects['McDbLine'][0].clone() as McDbLine; const length = refLine.getLength().val; // 直线长度 const vec = refLine.endPoint.sub(refLine.startPoint); // 直线角度 // 设置直线过滤器 const filter = new MxCADResbuf([DxfCode.kEntityType, "LINE"]) const ss = new MxCADSelectionSet() if (corner1 && corner2) { // 局部选择 ss.crossingSelect(corner1.x, corner1.y, corner2.x, corner2.y, filter); } else { // 全局选择 ss.allSelect(filter); }; // 筛选出符合条件的直线 const lineArr: McDbLine[] = []; ss.forEach(id => { const line = id.getMcDbEntity().clone() as McDbLine; const _length = line.getLength().val; /** * 1.未旋转 * 2.旋转过 */ if (Math.abs(_length - length) < this.dTol) { lineArr.push(line); } }); // 筛选出符合条件的直线后,根据包围盒范围再次筛选出符合条件的直线组合 const boxs: any[] = []; if (lineArr.length > 0) { lineArr.forEach((item) => { /** * 将要识别的图形进行平移+旋转到目标位置 */ const ptArr = [item.startPoint, item.endPoint]; ptArr.forEach((point, index) => { const _line = refLine.clone() as McDbLine; _line.move(refLine.startPoint, point); const _vec = ptArr[1 - index].sub(point); const angle = _vec.angleTo2(vec, McGeVector3d.kNegateZAxis); _line.rotate(point, angle) const pt = _line.endPoint; const _pl = this.pl.clone() as McDbPolyline; _pl.move(refLine.startPoint, point); _pl.rotate(point, angle); const _refBoxPt1 = _pl.getPointAt(0).val; const _refBoxPt2 = _pl.getPointAt(1).val; const refVec1 = _refBoxPt1.sub(_line.startPoint); const refVec2 = _refBoxPt2.sub(_line.startPoint); if (pt.distanceTo(ptArr[1 - index]) < this.dTolPt && refVec1.isEqualTo(_refBoxPt1.sub(point)) && refVec2.isEqualTo(_refBoxPt2.sub(point))) { let x = [], y = []; for (let i = 0; i < _pl.numVerts(); i++) { const pt = _pl.getPointAt(i).val; x.push(pt.x); y.push(pt.y); } const _minPt = new McGePoint3d(Math.min(...x) - 1, Math.min(...y) - 1); const _maxPt = new McGePoint3d(Math.max(...x) + 1, Math.max(...y) + 1); const res = boxs.filter(item => { let r1 = _minPt.distanceTo(item.pt1) < this.dTolPt && _maxPt.distanceTo(item.pt2) < this.dTolPt let r2 = _minPt.distanceTo(item.pt2) < this.dTolPt && _maxPt.distanceTo(item.pt1) < this.dTolPt return r1 || r2 }) if (!res.length) { boxs.push({ pt1: _minPt, pt2: _maxPt }); this.toPt = point; this.formPt = refLine.startPoint; // MxCpp.getCurrentMxCAD().drawLine(_maxPt.x, _maxPt.y, _minPt.x, _minPt.y); this.identificationBBox(_minPt, _maxPt, angle); } } }) }) }; } /** 识别圆 */ else if (str === 'McDbCircle') { const refCricle = this.selectObjects['McDbCircle'][0].clone() as McDbCircle; const radius = refCricle.radius; // 圆半径 // 设置圆过滤器 const filter = new MxCADResbuf([DxfCode.kEntityType, "CIRCLE"]) const ss = new MxCADSelectionSet() if (corner1 && corner2) { // 局部选择 ss.crossingSelect(corner1.x, corner1.y, corner2.x, corner2.y, filter); } else { // 全局选择 ss.allSelect(filter); }; // 筛选出符合条件的圆 ss.forEach(id => { const _circle = id.getMcDbEntity().clone() as McDbCircle; const _radius = _circle.radius; /** 筛选出圆半径相同的圆*/ if (Math.abs(_radius - radius) < this.dTol) { const _center = _circle.center; this.toPt = _center; this.formPt = refCricle.center; // 平移初始包围盒。计算出最新包围盒的大小 const _pl = this.pl.clone() as McDbPolyline; _pl.move(this.formPt, this.toPt); let x = [], y = []; for (let i = 0; i < _pl.numVerts(); i++) { const pt = _pl.getPointAt(i).val; x.push(pt.x); y.push(pt.y); } const _minPt = new McGePoint3d(Math.min(...x) - 1, Math.min(...y) - 1); const _maxPt = new McGePoint3d(Math.max(...x) + 1, Math.max(...y) + 1); // MxCpp.getCurrentMxCAD().drawLine(_maxPt.x, _maxPt.y, _minPt.x, _minPt.y); this.identificationBBox(_minPt, _maxPt, 0); } }); } /** 识别多段线 */ else if (str === 'McDbPolyline') { /** 长度+顶点数+顶点对应的凸度相等=》旋转+平移后的顶点相等+有凸度线段的中点相等*/ const refPoly = this.selectObjects['McDbPolyline'][0].clone() as McDbPolyline; const length = refPoly.getLength().val; const { lengthArr, bugleArr, vertNum, vec, point } = this.getPlDetails(refPoly); // 设置多段线过滤器 const filter = new MxCADResbuf([DxfCode.kEntityType, "LWPOLYLINE"]) const ss = new MxCADSelectionSet() if (corner1 && corner2) { // 局部选择 ss.crossingSelect(corner1.x, corner1.y, corner2.x, corner2.y, filter); } else { // 全局选择 ss.allSelect(filter); }; // 筛选出符合条件的多段线 const boxs: any[] = []; ss.forEach(id => { const _poly = id.getMcDbEntity().clone() as McDbPolyline; const _length = _poly.getLength().val; /** 筛选 长度+顶点数+顶点对应的凸度 相同的多段线*/ if (Math.abs(_length - length) < this.dTol && _poly.isClosed === refPoly.isClosed) { const { lengthArr: _lengthArr, bugleArr: _bugleArr, vertNum: _vertNum, vec: _vec, point: _point } = this.getPlDetails(_poly); const revBugleArr = _bugleArr.map(item => item === 0 ? item : -item); // 全等 const r1 = lengthArr.toString() === _lengthArr.toString() && bugleArr.toString() === _bugleArr.toString(); let r2 = false; if (!r1) { // 反向 r2 = lengthArr.toString() === [..._lengthArr].reverse().toString() && bugleArr.toString() === [...revBugleArr].reverse().toString(); } const r3 = vertNum === _vertNum; // 镜像 const r4 = lengthArr.toString() === _lengthArr.toString() && bugleArr.toString() === [...revBugleArr].reverse().toString(); if ((r1 || r2 || r4) && r3) { const poly = refPoly.clone() as McDbPolyline; const angle1 = vec.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); // 如果是反向则方向取反 const angle2 = (r2 ? _vec.negate() : _vec).angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); const angle = angle2 - angle1; poly.move(point, _point); poly.rotate(_point, angle); poly.trueColor = new McCmColor(255, 0, 0); let num = 0; for (let i = 0; i < vertNum; i++) { const pt = poly.getPointAt(i).val; const width = poly.getWidthsAt(i); const _pt = r2 ? _poly.getPointAt(vertNum - 1 - i).val : _poly.getPointAt(i).val; const _width = r2 ? _poly.getWidthsAt(vertNum - 1 - i) : _poly.getWidthsAt(i); if (pt.distanceTo(_pt) < this.dTolPt && width.val1 === _width.val1 && width.val2 === _width.val2) { num += 1 } else { // 两个点且反向的情况 if (vertNum === 2 && i === 0) { const _pt1 = _poly.getPointAt(0).val; const _pt2 = _poly.getPointAt(1).val; const pt2 = poly.getPointAt(1).val; const _width = _poly.getWidthsAt(0); if (pt2.distanceTo(_pt2) < this.dTolPt && pt.distanceTo(_pt1) < this.dTolPt && width.val1 === _width.val2 && width.val2 === _width.val1) { num += 2; } } } }; if (num === vertNum) { // 旋转平移初始包围盒。计算出最新包围盒的大小 const _pl = this.pl.clone() as McDbPolyline; _pl.move(point, _point); _pl.rotate(_point, angle); let x = [], y = []; for (let i = 0; i < _pl.numVerts(); i++) { const pt = _pl.getPointAt(i).val; x.push(pt.x); y.push(pt.y); } const _minPt = new McGePoint3d(Math.min(...x) - 1, Math.min(...y) - 1); const _maxPt = new McGePoint3d(Math.max(...x) + 1, Math.max(...y) + 1); const res = boxs.filter(item => { let ret1 = _minPt.distanceTo(item.pt1) < this.dTolPt && _maxPt.distanceTo(item.pt2) < this.dTolPt let ret2 = _minPt.distanceTo(item.pt2) < this.dTolPt && _maxPt.distanceTo(item.pt1) < this.dTolPt return ret1 || ret2 }) if (!res.length) { boxs.push({ pt1: _minPt, pt2: _maxPt }); this.toPt = _point; this.formPt = point; // MxCpp.getCurrentMxCAD().drawLine(_maxPt.x, _maxPt.y, _minPt.x, _minPt.y); this.identificationBBox(_minPt, _maxPt, angle); } } } } }); } /** 返回识别列表 */ return this.patternList; } // 计算块中心点 private getBlkMidPt = (blkRef: McDbBlockReference) => { const { minPt, maxPt } = blkRef.getBoundingBox(); const pt = minPt.clone().addvec(maxPt.sub(minPt).normalize().mult(maxPt.distanceTo(minPt) / 2)) return { position: pt, minPt, maxPt } } // 计算凸度 private getBugle = (arc: McDbArc, length: number) => { const midPt = arc.getPointAtDist(length / 2).val; const bugle = MxCADUtility.calcBulge(arc.getPointAtDist(0).val, midPt, arc.getPointAtDist(length).val).val; return { bugle, midPt } } // 计算多段线的每一段线的长度和凸度 private getPlDetails = (pl: McDbPolyline) => { const vertNum = pl.numVerts(); const lengthArr: number[] = []; const bugleArr: number[] = []; let point1: McGePoint3d, point2: McGePoint3d; for (let i = 0; i < vertNum; i++) { if (i < vertNum - 1) { const p1 = pl.getPointAt(i).val; const p2 = pl.getPointAt(i + 1).val; const length = pl.getDistAtPoint(p2).val - pl.getDistAtPoint(p1).val; const bugle = pl.getBulgeAt(i); lengthArr.push(Number(length.toFixed(6))); bugleArr.push(Number(bugle.toFixed(6))); if (i === 0) { point1 = p1; } if (i === vertNum - 2) { point2 = p2; } } }; const vec = point2.sub(point1)// 多段线角度 const point = point1.clone().addvec(point2.sub(point1).normalize().mult(point2.distanceTo(point1) / 2)) return { lengthArr, bugleArr, vertNum, vec, point } } // 将找到的实体数组与需要识别的实体数组比对 private comparisonPhysical = (objects: any, maxPt: McGePoint3d, minPt: McGePoint3d, angle: number): any => { const calssNames = Object.keys(this.selectObjects); if (!calssNames) return; let num = 0; if (calssNames.includes('McDbLine')) { let ents = objects['McDbLine']; const lineObjects = this.objectsDetails['McDbLine']; for (let i = 0; i < lineObjects.length; i++) { const element = lineObjects[i]; let _line = (element.entity as McDbLine).clone() as McDbLine; _line.move(this.formPt, this.toPt); _line.rotate(this.toPt, angle); const indexArr: number[] = []; const mLineArr = ents.filter((item: McDbLine, index: number) => { if (Math.abs(element.length - item.getLength().val) < this.dTol) { let r1 = item.startPoint.distanceTo(_line.startPoint) < this.dTolPt && item.endPoint.distanceTo(_line.endPoint) < this.dTolPt; let r2 = item.startPoint.distanceTo(_line.endPoint) < this.dTolPt && item.endPoint.distanceTo(_line.startPoint) < this.dTolPt; if (r1 || r2) indexArr.push(index); return r1 || r2; } }); if (mLineArr.length > 0) { num += 1; ents.splice(indexArr[0], 1); ents = [...ents]; } else { break; } } }; if (calssNames.includes('McDbArc')) { let ents = objects['McDbArc']; const arcObjects = this.objectsDetails['McDbArc']; for (let i = 0; i < arcObjects.length; i++) { const element = arcObjects[i]; let _arc = (element.entity as McDbArc).clone() as McDbArc; _arc.move(this.formPt, this.toPt); _arc.rotate(this.toPt, angle); const startPoint = _arc.getPointAtDist(0).val; const endPoint = _arc.getPointAtDist(element.length).val; const indexArr: number[] = []; const mArcArr = ents.filter((item: McDbArc, index: number) => { const _length = item.getLength().val; const { bugle: _bugle } = this.getBugle(item, _length) if (Math.abs(element.length - item.getLength().val) < this.dTol && Math.abs(_bugle - element.bugle) < this.dTol) { const _startPoint = item.getPointAtDist(0).val; const _endPoint = item.getPointAtDist(_length).val; let r1 = startPoint.distanceTo(_startPoint) < this.dTolPt && endPoint.distanceTo(_endPoint) < this.dTolPt; let r2 = startPoint.distanceTo(_endPoint) < this.dTolPt && endPoint.distanceTo(_startPoint) < this.dTolPt; if (r1 || r2) { indexArr.push(index); } return r1 || r2; } }); if (mArcArr.length > 0) { num += 1; ents.splice(indexArr[0], 1); ents = [...ents]; } else { break; } } }; if (calssNames.includes('McDbCircle')) { let ents = objects['McDbCircle']; const circleObjects = this.objectsDetails['McDbCircle']; for (let i = 0; i < circleObjects.length; i++) { const element = circleObjects[i]; let _circle = (element.entity as McDbCircle).clone() as McDbCircle; _circle.move(this.formPt, this.toPt); _circle.rotate(this.toPt, angle); const center = _circle.center; const radius = _circle.radius; const indexArr: number[] = []; const mCiecleArr = ents.filter((item: McDbCircle, index: number) => { const _center = item.center; const _radius = item.radius; const r = Math.abs(_radius - radius) < this.dTol && center.distanceTo(_center) < this.dTolPt; if (r) { indexArr.push(index) } return r; }); if (mCiecleArr.length > 0) { num += 1; ents.splice(indexArr[0], 1); ents = [...ents]; } else { break; } } }; if (calssNames.includes('McDbBlockReference')) { /** 平移+旋转=>比对包围盒大小+块名字 */ let ents = objects['McDbBlockReference']; const blkObjects = this.objectsDetails['McDbBlockReference']; for (let i = 0; i < blkObjects.length; i++) { const element = blkObjects[i]; let _blk = (element.entity as McDbBlockReference).clone() as McDbBlockReference; _blk.move(this.formPt, this.toPt); _blk.rotate(this.toPt, angle); const blkName = _blk.blockName; const { minPt: blkMinPt, maxPt: blkMaxPt } = this.getBlkMidPt(_blk); const indexArr: number[] = []; const mArcArr = ents.filter((item: McDbBlockReference, index: number) => { const _blkName = item.blockName; if (_blkName === blkName) { if (this.classNames.length === 1) { indexArr.push(index); return true } else { const { minPt: _blkMinPt, maxPt: _blkMaxPt } = this.getBlkMidPt(item); let r1 = blkMinPt.distanceTo(_blkMinPt) < this.dTolPt && blkMaxPt.distanceTo(_blkMaxPt) < this.dTolPt; if (r1) { indexArr.push(index); } return r1 } } }); if (mArcArr.length > 0) { num += 1; ents.splice(indexArr[0], 1); ents = [...ents]; } else { return; } } } if (calssNames.includes('McDbPolyline')) { /** 平移+旋转=>比对凸度+点位置 */ let ents = objects['McDbPolyline']; const plObjects = this.objectsDetails['McDbPolyline']; for (let i = 0; i < plObjects.length; i++) { const element = plObjects[i]; let _poly = (element.entity as McDbPolyline).clone() as McDbPolyline; _poly.move(this.formPt, this.toPt); _poly.rotate(this.toPt, angle); const { lengthArr, bugleArr, vertNum } = this.getPlDetails(_poly); const indexArr: number[] = []; const mPlArr = ents.filter((item: McDbPolyline, index: number) => { const _length = item.getLength().val; if (Math.abs(_length - element.length) < this.dTol && _poly.isClosed === _poly.isClosed) { const { lengthArr: _lengthArr, bugleArr: _bugleArr, vertNum: _vertNum } = this.getPlDetails(item); const revBugleArr = _bugleArr.map(item => item === 0 ? item : -item); const r1 = lengthArr.toString() === _lengthArr.toString() && bugleArr.toString() === _bugleArr.toString(); let r2 = false; if (!r1) { r2 = lengthArr.toString() === [..._lengthArr].reverse().toString() && bugleArr.toString() === [...revBugleArr].reverse().toString(); } const r3 = vertNum === _vertNum; if ((r1 || r2) && r3) { let _num = 0; for (let i = 0; i < vertNum; i++) { const pt = _poly.getPointAt(i).val; const width = _poly.getWidthsAt(i); const _pt = r2 ? item.getPointAt(vertNum - 1 - i).val : item.getPointAt(i).val; const _width = r2 ? item.getWidthsAt(vertNum - 1 - i) : item.getWidthsAt(i); if (pt.distanceTo(_pt) < this.dTolPt && width.val1 === _width.val1 && width.val2 === _width.val2) { _num += 1 } else { // 两个点且反向的情况 if (vertNum === 2 && i === 0) { const _pt1 = item.getPointAt(0).val; const _pt2 = item.getPointAt(1).val; const _width = item.getWidthsAt(0); const pt2 = _poly.getPointAt(1).val; if (pt2.distanceTo(_pt2) < this.dTolPt && pt.distanceTo(_pt1) < this.dTolPt && width.val1 === _width.val2 && width.val2 === _width.val1) { _num += 2; } } } }; if (_num === vertNum) { indexArr.push(index); } return _num === vertNum; } } }); if (mPlArr.length > 0) { num += 1; ents.splice(indexArr[0], 1); ents = [...ents]; } else { break; } } } if (num === this.objectCount) { // 匹配成功 const pt = minPt.clone().addvec(maxPt.clone().sub(minPt).normalize().mult(minPt.distanceTo(maxPt) / 2)); const res = this.graphBoxsPos.filter(point => pt.distanceTo(point) < this.dTolPt); if (!res.length) { this.graphBoxsPos.push(pt) } }; this.patternList.graphBoxsPos = [...this.graphBoxsPos]; } // 识别包围盒是否符合要求 private identificationBBox = (minPt: McGePoint3d, maxPt: McGePoint3d, angle: number): any => { const _ss = new MxCADSelectionSet() _ss.crossingSelect(minPt.x, minPt.y, maxPt.x, maxPt.y); const boxEntity = {}; _ss.forEach(id => { const ent = id.getMcDbEntity(); if (!boxEntity[ent.objectName]) boxEntity[ent.objectName] = []; boxEntity[ent.objectName].push(ent); }); let r = this.classNames.filter(name => boxEntity[name] && this.selectObjects[name].length <= boxEntity[name].length); if (r.length === this.classNames.length) { this.comparisonPhysical(boxEntity, maxPt, minPt, angle); } } };
调用 SearchSamePattern 类,实现图形识别功能,代码如下:
/** 获取需要识别的图形 */ const getIdentificationPattern = async () => { // 获取需要识别的图形 const ss = new MxCADSelectionSet();// 选择集 const filter = new MxCADResbuf([DxfCode.kEntityType, "LINE,ARC,CIRCLE,LWPOLYLINE,INSERT"]) if (!await ss.userSelect("请点击左键或拉框选择需要识别的图形:", filter)) return; if (ss.count() == 0) return; // 搜索图形 const searchPattern = new SearchSamePattern(); // 搜索图形简图地址 const patternImgUrl = await searchPattern.setSelectset(ss); if (!patternImgUrl.value) return; // 获取识别结果 const searchList = searchPattern.identifyGraphics(); console.log(searchList); }