在线CAD绘制墙体(WEB端开发室内设计软件)
前言
室内平面图中墙体是最重要的图形之一,其中砖墙、混凝土墙、钢架墙、隔墙、隔热墙等类型的墙在设计图中均有不同的表现方式,墙体的用途一般可以分为一般墙、虚墙、卫生隔断、阳台挡板、矮墙等,根据不同的需求绘制对应的墙体能够增强建筑设计的专业性和准确性。下面我们将介绍如何使用mxcad实现基础墙体功能,并展示其实践运用效果。
下述的墙体功能为一个基于mxcad开发的demo示例,因此存在无法百分百适配用户实际使用需求的情况,用户可在下述源码的基础上基于mxcad实现二次开发一次来适配实际需求。
功能开发
mxcad墙体功能的核心思想是通过继承mxcad中的自定义实体[McDbCustomEntity] ,自己实现一个独立的墙体对象,以及通过监测墙体相交的变化实现自主计算墙体绘制的一系列逻辑。如果你对 mxcad 中的自定义实体还不熟悉,点击[自定义实体开发文档链接]了解自定义实体是什么,内部存在的方法以及如何通过该实体实现自定义的功能实体。
1.封装计算墙体对应的多段线方法
在计算墙体多段线的方法中,我们需要传入墙体开始点、墙体结束点和墙体宽度,如果目标墙体存在与其他墙体相交产生拐点的情况还需要传入目标墙体生成的拐点。为了方便后续与其他墙体之间的比对和计算,我们将返回墙体的四个断点(pt1,pt2,pt3,pt4)、整体多段线(pl)、以及所在的中心直线(line),参考代码:
// 计算多段线相关数据 const getPolyline = (startPoint: McGePoint3d, endPoint: McGePoint3d, width: number, turnPoints?: McGePoint3d[]): any => { const _startPoint = startPoint.clone(); const _endPoint = endPoint.clone(); if (startPoint.x > endPoint.x) { startPoint = _endPoint; endPoint = _startPoint; } const pl = new McDbPolyline(); // 计算初始墙体四个端点 const v = endPoint.sub(startPoint).normalize().perpVector(); let pt1 = startPoint.clone().addvec(v.clone().mult(width / 2)); let pt2 = startPoint.clone().addvec(v.clone().negate().mult(width / 2)); let pt3 = endPoint.clone().addvec(v.clone().negate().mult(width / 2)); let pt4 = endPoint.clone().addvec(v.clone().mult(width / 2)); const pointArr = { pt1Arr: [], pt2Arr: [], pt3Arr: [], pt4Arr: [] } /** *有拐点则计算出墙体变换后的四个端点 */ if (turnPoints && turnPoints.length > 0) { turnPoints.forEach(pt => { const dist1 = pt.distanceTo(startPoint); const dist2 = pt.distanceTo(endPoint); if (dist1 < dist2) { // 开始端 const _dist1 = pt.distanceTo(pt1); const _dist2 = pt.distanceTo(pt2); if (_dist1 < _dist2) { // pt1 = pt; pointArr.pt1Arr.push(pt) } else { // pt2 = pt; pointArr.pt2Arr.push(pt) } } else { // 结束端 const _dist1 = pt.distanceTo(pt3); const _dist2 = pt.distanceTo(pt4); if (_dist1 < _dist2) { // pt3 = pt; pointArr.pt3Arr.push(pt) } else { // pt4 = pt; pointArr.pt4Arr.push(pt); } }; }); // 拐点变换后新的墙体端点 for (let i = 0; i < 4; i++) { if (pointArr[`pt${i + 1}Arr`].length !== 0) { let dist = null; let point = new McGePoint3d(); switch (i + 1) { case 1: point = pt4; break; case 2: point = pt3; break; case 3: point = pt2; break; case 4: point = pt1; break; } pointArr[`pt${i + 1}Arr`].forEach(pt => { const _dist = pt.distanceTo(point); if (!dist || _dist < dist) { dist = _dist; switch (i + 1) { case 1: pt1 = pt; break; case 2: pt2 = pt; break; case 3: pt3 = pt; break; case 4: pt4 = pt; break; } } }) } } } else { pl.addVertexAt(pt1); pl.addVertexAt(pt2); pl.addVertexAt(pt3); pl.addVertexAt(pt4); } pl.isClosed = true; const line = new McDbLine(startPoint, endPoint); return { pl, line, pt1, pt2, pt3, pt4 }; }
2.实现自定义墙体类:McDbTestWall
// 墙体类 class McDbTestWall extends McDbCustomEntity { // 定义McDbTestWall内部的点对象 // 墙体开始点 private startPoint: McGePoint3d = new McGePoint3d(); // 墙体夹点移动前结束点位置 private _oldEndPoint: McGePoint3d = new McGePoint3d(); // 墙体夹点移动前开始点位置 private _oldStartPoint: McGePoint3d = new McGePoint3d(); // 墙体结束点 private endPoint: McGePoint3d = new McGePoint3d(); // 墙体宽 private _wallWidth: number = 30; // 墙体断点 private _breakPoints: McGePoint3d[] = []; // 墙体拐点 private _turnPoints: McGePoint3d[] = []; // 墙体相交点 private _insterPoints: McGePoint3d[] = []; // 每一个断点组的长度记录 private breakPtsCount: number[] = []; // 构造函数 constructor(imp?: any) { super(imp); } // 创建函数 public create(imp: any) { return new McDbTestWall(imp) } // 获取类名 public getTypeName(): string { return "McDbTestWall"; } //设置或获取墙体宽 public set wallWidth(val: number) { this._wallWidth = val; } public get wallWidth(): number { return this._wallWidth; } //设置或获取墙体断点 public set breakPoints(val: McGePoint3d[]) { this._breakPoints = val; } public get breakPoints(): McGePoint3d[] { return this._breakPoints; } //设置或获取墙体拐点 public set turnPoints(val: McGePoint3d[]) { this._turnPoints = val; } public get turnPoints(): McGePoint3d[] { return this._turnPoints; } //设置或获取墙体相交点 public set insterPoints(val: McGePoint3d[]) { this._insterPoints = val; } public get insterPoints(): McGePoint3d[] { return this._insterPoints; } //设置或获取墙体单次相交断点数 public set breakPtsCounts(val: number[]) { this.breakPtsCount = val; } public get breakPtsCounts(): number[] { return this.breakPtsCount; } // 获取墙体移动前开始点 public get oldStartPoint(): McGePoint3d { return this._oldStartPoint; } // 获取墙体移动前结束点 public get oldEndPoint(): McGePoint3d { return this._oldEndPoint; } // 读取自定义实体数据 public dwgInFields(filter: IMcDbDwgFiler): boolean { this.startPoint = filter.readPoint("startPoint").val; this.endPoint = filter.readPoint("endPoint").val; this._oldEndPoint = filter.readPoint("oldEndPoint").val; this._oldStartPoint = filter.readPoint("oldStartPoint").val; this._breakPoints = filter.readPoints("breakPoint").val; this._insterPoints = filter.readPoints("insterPoints").val; this._turnPoints = filter.readPoints("turnPoint").val; this._wallWidth = filter.readDouble("wallWidth").val; const _breakPtsCount = filter.readString("breakPtsCount").val; if (_breakPtsCount) { this.breakPtsCount = _breakPtsCount.split(',').map(Number); } return true; } // 写入自定义实体数据 public dwgOutFields(filter: IMcDbDwgFiler): boolean { filter.writePoint("endPoint", this.endPoint); filter.writePoint("startPoint", this.startPoint); filter.writePoint("oldStartPoint", this._oldStartPoint); filter.writePoint("oldEndPoint", this._oldEndPoint); filter.writePoints("breakPoint", this._breakPoints); filter.writePoints("insterPoints", this._insterPoints); filter.writePoints("turnPoint", this._turnPoints); filter.writeDouble("wallWidth", this._wallWidth); if (this.breakPtsCount.length > 0) { filter.writeString("breakPtsCount", this.breakPtsCount.join(',')); } return true; } // 移动自定义对象的夹点 public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) { this.assertWrite(); if (iIndex === 0) { this._oldStartPoint = this.startPoint.clone(); this.startPoint.x += dXOffset; this.startPoint.y += dYOffset; this.startPoint.z += dZOffset; } else if (iIndex === 1) { this._oldEndPoint = this.endPoint.clone(); this.endPoint.x += dXOffset; this.endPoint.y += dYOffset; this.endPoint.z += dZOffset; }; }; // 获取自定义对象的夹点 public getGripPoints(): McGePoint3dArray { let ret = new McGePoint3dArray() ret.append(this.startPoint); ret.append(this.endPoint); return ret; }; // 绘制实体 public worldDraw(draw: MxCADWorldDraw): void { const { pl, pt1, pt2, pt3, pt4 } = getPolyline(this.startPoint.clone(), this.endPoint.clone(), this._wallWidth, this._turnPoints); const dol = 0.00001; if (this.turnPoints.length > 0 || this.breakPoints.length > 0) { // 有拐点,有断点 const lineArr: McDbLine[] = []; const line1 = new McDbLine(pt1, pt4); const line2 = new McDbLine(pt2, pt3); lineArr.push(line2, line1) const dist1 = pt1.distanceTo(pt2); const dist2 = pt4.distanceTo(pt3); if (dist1 - this._wallWidth < 0.00001) { const line3 = new McDbLine(pt2, pt1); if (this._turnPoints.length > 0) { let r = this._turnPoints.filter(pt => pt2.distanceTo(pt) < dol || pt1.distanceTo(pt) < dol); if (r.length === 0) lineArr.push(line3) } else { lineArr.push(line3); } } if (dist2 - this._wallWidth < 0.00001) { const line4 = new McDbLine(pt3, pt4); if (this._turnPoints.length > 0) { let r = this._turnPoints.filter(pt => pt3.distanceTo(pt) < dol || pt4.distanceTo(pt) < dol); if (r.length === 0) lineArr.push(line4) } else { lineArr.push(line4) } }; if (this.breakPoints.length > 0) { /** * 有断点 * 找出每一个断点所在的直线 * 以每一组断点去切割直线,再绘制切割好的直线 */ let count = 0; const breakPlArr: McDbPolyline[] = []; // 存储每一组断点多段线 const breakPts = []; this.breakPtsCount.forEach((item) => { count += item; const pl = new McDbPolyline() if (count > 0) { // 一组断点 const breakPoint = this.breakPoints.filter((pt, i) => i < count && i >= count - item); breakPoint.forEach(pt => { pl.addVertexAt(pt); }) breakPts.push(breakPoint) }; breakPlArr.push(pl); }); lineArr.forEach((l) => { // 直线上的断点集合 const plArr: McDbPolyline[] = []; // 直线上的断点连线 const r: McGePoint3d[] = []; // 直线上的断点集合 const _r: McGePoint3d[] = []; // 直线上的单断点集合 breakPts.forEach((item) => { const points = item.filter(pt => { const point = l.getClosestPointTo(pt, false).val; const dist = point.distanceTo(pt); return dist < dol }); if (points.length > 0) { r.push(...points) if (points.length % 2 === 0) { points.forEach((pt, index) => { if (index % 2 == 0) { const pl = new McDbPolyline(); pl.addVertexAt(pt); pl.addVertexAt(points[index + 1]); plArr.push(pl); } }); } else { _r.push(...points) } } }) if (r.length > 0) { // 直线上含有断点 l.splitCurves(r).forEach((obj) => { const _line = obj.clone() as McDbLine; const midPt = _line.startPoint.clone().addvec(_line.endPoint.sub(_line.startPoint).mult(1 / 2)) const res1 = r.filter(pt => _line.startPoint.distanceTo(pt) < dol); const res2 = r.filter(pt => _line.endPoint.distanceTo(pt) < dol); // 起始点只有一个是断点 if (res1.length === 0 || res2.length === 0) { if (_r.length === 0) { // 无单断点直接绘制 draw.drawEntity(_line) } else { // 筛选出在切断后直线的单断点 const singlePts = _r.filter(pt => _line.getDistAtPoint(pt).ret);//切割后线段上的单断点 if (singlePts.length === 0) { // 若单断点不在这条直线上,则直接绘制 draw.drawEntity(_line) } else { // 若单断点在这条直线上,则判断是否与对应的交点同向 singlePts.forEach(pt => { if (breakPlArr.length > 0) { const _plNum = breakPlArr.filter((pl, index) => { if (pl.numVerts() === 2) { return this.countLineToPl(pl, index, pt, midPt); } else { const num = pl.numVerts(); let flag = false; for (let i = 0; i < num; i++) { if (i % 2 == 0) { const _pl = new McDbPolyline(); _pl.addVertexAt(pl.getPointAt(i).val); _pl.addVertexAt(pl.getPointAt(i + 1).val); _pl.trueColor = new McCmColor(255, 0, 0); const r = this.countLineToPl(_pl, index, pt, midPt) if (r) flag = true; } }; return flag } }); if (_plNum.length === singlePts.length) { draw.drawEntity(_line) } } }) } } } // 起始点都是断点 if (res1.length !== 0 && res2.length !== 0) { const _r1 = plArr.filter((pl) => { const pt = pl.getClosestPointTo(midPt, false).val; return pt.distanceTo(midPt) < dol }); if (_r1.length === 0) { draw.drawEntity(_line) } } }) } else { // 线段上无断点 const length = l.getLength().val; if (Math.abs(length - this._wallWidth) < dol) { const midPt = l.getPointAtDist(l.getLength().val / 2).val; const distArr = this.insterPoints.map(pt => pt.distanceTo(midPt)); const index = distArr.findIndex(element => element === Math.min(...distArr)); const pl = breakPlArr[index]; const insterPt = this._insterPoints[index]; const p = l.getClosestPointTo(insterPt, true).val; if (p.distanceTo(insterPt) > dol) { const closePt = pl.getClosestPointTo(insterPt, true).val; const insterWallWidth = closePt.distanceTo(insterPt); const insterPtToLine = l.getClosestPointTo(insterPt, true).val.distanceTo(insterPt); if (insterPtToLine > insterWallWidth) { draw.drawEntity(l) } else { const midVec = midPt.sub(insterPt); const num = pl.numVerts(); if (num === 2) { const vec = pl.getPointAt(0).val.sub(pl.getPointAt(1).val); const angle = Number((midVec.angleTo1(vec) * (180 / Math.PI)).toFixed(4)); if (angle === 0 || angle === 180) { draw.drawEntity(l) } } else { let count = 0; for (let i = 0; i < num; i++) { if (i % 2 == 0) { const _pl = new McDbPolyline(); _pl.addVertexAt(pl.getPointAt(i).val); _pl.addVertexAt(pl.getPointAt(i + 1).val); _pl.trueColor = new McCmColor(255, 0, 0); const vec = _pl.getPointAt(0).val.sub(_pl.getPointAt(1).val); const angle = Number((midVec.angleTo1(vec) * (180 / Math.PI)).toFixed(4)); if (angle === 0 || angle === 180) { count += 1; } } } if (count === num / 2) draw.drawEntity(l); } } } } else { draw.drawEntity(l) } } }); } else { // 无断点,全是拐点 lineArr.forEach((l, index) => { if (index < 2) { draw.drawEntity(l) } else { const length = l.getLength().val; const r = this._turnPoints.filter(pt => pt.distanceTo(l.startPoint) < dol || pt.distanceTo(l.endPoint) < dol) if (Math.abs(length - this._wallWidth) < dol && r.length === 0) { draw.drawEntity(l) } } }) } } if (!this.breakPoints.length && !this.turnPoints.length) { draw.drawEntity(pl); } const line = new McDbLine(this.startPoint, this.endPoint) draw.drawOsnapEntity(line); } private countLineToPl(pl: McDbPolyline, index: number, pt: McGePoint3d, midPt: McGePoint3d): Boolean { const dol = 0.00001; const dist1 = pl.getPointAt(0).val.distanceTo(pt); const dist2 = pl.getPointAt(1).val.distanceTo(pt); const insterPt = this._insterPoints[index]; if (dist1 < dol || dist2 < dol) { const _closePt = pl.getClosestPointTo(insterPt, true).val; const v = insterPt.sub(_closePt); const _v = midPt.sub(_closePt); let angle1 = v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); let angle2 = _v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); const angle = Math.abs(angle1 - angle2); if (Number((angle * (180 / Math.PI)).toFixed(4)) >= 90 && Number((angle * (180 / Math.PI)).toFixed(4)) <= 270) { return true } else { return false } } else { return false } } // 设置墙体起点 public setStartPoint(pt: McGePoint3d) { this.startPoint = pt.clone() this._oldStartPoint = pt.clone() } // 获取墙体起点 public getStartPoint(): McGePoint3d { return this.startPoint; } // 设置墙体结束点 public setEndPoint(pt: McGePoint3d) { this.endPoint = pt.clone() this._oldEndPoint = pt.clone() } // 获取墙体结束点 public getEndPoint(): McGePoint3d { return this.endPoint.clone(); } // 增加墙体断点 public addBreakPoints(pts: McGePoint3d[]) { this.breakPoints.push(...pts); } // 增加墙体交点 public addInsterPoints(pts: McGePoint3d[]) { this._insterPoints.push(...pts); } // 增加墙体单次相交的断点数 public addBreakPtsCount(val: number[]) { if (val.length > 0) { this.breakPtsCount.push(...val); } } };
3.计算墙体相交后的断点和拐点。
计算与目标墙体相交的墙体,参考代码:
//相交墙体集合 const intersectingWalls: McObjectId[] = []; /** * startPoint:墙体开始点 * endPoint:墙体结束点 * wallWidth:墙体宽度 */ const { pt1, pt3, line } = getPolyline(startPoint, endPoint, wallWidth); const length = line.getLength().val; // 设置过滤器,过滤出自定义实体 let filter = new MxCADResbuf(); filter.AddString("McDbCustomEntity", 5020); const ss = new MxCADSelectionSet(); ss.crossingSelect(pt1.x - length, pt1.y - length, pt3.x + length, pt3.y + length, filter); // 与其他墙体相交 if (ss.count() !== 0) { ss.forEach(id => { const ent = id.getMcDbEntity(); if ((ent as McDbCustomEntity).getTypeName() === "McDbTestWall") { intersectingWalls.push(id); } }); }
根据相交墙体信息获取墙体断点与拐点,参考代码:
// 处理相交墙体得到断点与拐点 /** * this.startPoint:墙体开始点 * this.endPoint:墙体结束点 * this.wallWidth:墙体宽度 * this.pointArr:断点集合 * this.breakPtsCount:单次断点数集合 * this.turnPointArr:拐点集合 * this.insterPointArr:交点集合 * this.dol:精度 */ function dealingWithWalls(wallObjIds: McObjectId[]) { const { pl, pt1, pt2, pt3, pt4, line } = getPolyline(this.startPoint, this.endPoint, this.wallWidth); if (wallObjIds.length != 0) { // 与新墙体有交点的墙体集合 const wallArr = []; // 与其他墙体相交 wallObjIds.forEach(id => { const entity = id.getMcDbEntity(); if ((entity as McDbCustomEntity).getTypeName() === "McDbTestWall") { const wall = entity.clone() as McDbTestWall const { pl: _pl, pt1: _pt1, pt2: _pt2, pt3: _pt3, pt4: _pt4, line: _line } = getPolyline(wall.getStartPoint(), wall.getEndPoint(), wall.wallWidth); const _pointArr: McGePoint3d[] = []; _pl.IntersectWith(pl, McDb.Intersect.kOnBothOperands).forEach(item => _pointArr.push(item)); if (_pointArr.length > 0) { wallArr.push({ id, wall, pl: _pl, pt1: _pt1, pt2: _pt2, pt3: _pt3, pt4: _pt4, line: _line, pointArr: _pointArr }) } } }); if (wallArr.length != 0) { wallArr.forEach(item => { const { id, wall, pl: _pl, pt1: _pt1, pt2: _pt2, pt3: _pt3, pt4: _pt4, line: _line, pointArr: _pointArr } = item; /** * 根据交点位置判断是否是拐点 * 中心相交的交点与比对中心线端点距离小于1/2墙宽的都是拐点 */ const insterPointArr = (line as McDbLine).IntersectWith(_line as McDbLine, McDb.Intersect.kExtendBoth); if (insterPointArr.length() > 0) { const insterPt = insterPointArr.at(0); this.insterPointArr.push(insterPt); const dist1 = insterPt.distanceTo(_line.startPoint); const dist2 = insterPt.distanceTo(_line.endPoint); const dist3 = insterPt.distanceTo(line.startPoint); const dist4 = insterPt.distanceTo(line.endPoint); let vec, _vec; dist3 < dist4 ? vec = (line as McDbLine).endPoint.sub(insterPt).normalize() : vec = (line as McDbLine).startPoint.sub(insterPt).normalize(); dist1 < dist2 ? _vec = (_line as McDbLine).endPoint.sub(insterPt).normalize() : _vec = (_line as McDbLine).startPoint.sub(insterPt).normalize(); const angle1 = vec.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); const angle2 = _vec.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); if ((dist1 <= wall.wallWidth / 2 || dist2 <= wall.wallWidth / 2) && (dist3 <= this.wallWidth / 2 || dist4 <= this.wallWidth / 2) ) { // 绘制拐角 const turnArr = [] const line1 = new McDbLine(pt1, pt4); const line2 = new McDbLine(pt2, pt3); const _line1 = new McDbLine(_pt1, _pt4); const _line2 = new McDbLine(_pt2, _pt3); let line1ClosePt, line2ClosePt; pt1.distanceTo(insterPt) < pt4.distanceTo(insterPt) ? line1ClosePt = pt1.clone() : line1ClosePt = pt4.clone(); pt2.distanceTo(insterPt) < pt3.distanceTo(insterPt) ? line2ClosePt = pt2.clone() : line2ClosePt = pt3.clone(); const lineInsterPts1 = line1.IntersectWith(_line1, McDb.Intersect.kExtendBoth) const lineInsterPts2 = line1.IntersectWith(_line2, McDb.Intersect.kExtendBoth) const lineInsterPts3 = line2.IntersectWith(_line1, McDb.Intersect.kExtendBoth) const lineInsterPts4 = line2.IntersectWith(_line2, McDb.Intersect.kExtendBoth) const _point1 = lineInsterPts1.length() > 0 ? lineInsterPts1.at(0) : line1ClosePt; const _point2 = lineInsterPts2.length() > 0 ? lineInsterPts2.at(0) : line1ClosePt; const _point3 = lineInsterPts3.length() > 0 ? lineInsterPts3.at(0) : line2ClosePt; const _point4 = lineInsterPts4.length() > 0 ? lineInsterPts4.at(0) : line2ClosePt; const inflectioPoints = [_point1, _point2, _point3, _point4]; const r = inflectioPoints.filter((pt) => { const v = pt.sub(insterPt); const angle3 = v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); return this.isAngleBetween(angle3, angle1, angle2) }) if (r.length) { turnArr.push(r[0]) const point = r[0]; const _r = inflectioPoints.filter((pt) => { const v = pt.sub(insterPt); const _v = point.sub(insterPt) const angle = v.angleTo2(_v, McGeVector3d.kNegateZAxis); return Math.abs(angle - Math.PI) < this.dol }) if (_r.length) turnArr.push(_r[0]) } this.turnPointArr.push(...turnArr); this.breakPtsCount.push(0); const _wall = wall.clone() as McDbTestWall; _wall.turnPoints = [..._wall.turnPoints, ...turnArr]; _wall.addInsterPoints([insterPt]); _wall.addBreakPtsCount([0]); MxCpp.getCurrentMxCAD().drawEntity(_wall); id.erase(); } else { // 没有拐角直接根据断点绘制 this.pointArr.push(..._pointArr); this.breakPtsCount.push(_pointArr.length); const _wall = wall.clone() as McDbTestWall; _wall.addBreakPoints(_pointArr); _wall.addInsterPoints([insterPt]); _wall.addBreakPtsCount([_pointArr.length]); MxCpp.getCurrentMxCAD().drawEntity(_wall); id.erase(); } } }); } } } // 计算角度方向是否在指定角度范围内 function isAngleBetween(angleRad: number, startRad: number, endRad: number) { let minRad, maxRad if (startRad - endRad > 0) { minRad = endRad; maxRad = startRad; } else { minRad = startRad; maxRad = endRad; } if (maxRad - minRad > Math.PI) { return angleRad <= minRad || angleRad >= maxRad; } else { return angleRad >= minRad && angleRad <= maxRad; } }
4.整合绘制墙体方法:MxdrawWalls
// 绘制墙体类 class MxdrawWalls { // 墙体开始点 private startPoint: McGePoint3d = new McGePoint3d(); // 墙体结束点 private endPoint: McGePoint3d = new McGePoint3d(); // 墙体宽度 private wallWidth: number = 10; // 断点集合 private pointArr: McGePoint3d[] = []; // 拐点集合 private turnPointArr: McGePoint3d[] = []; // 交点集合 private insterPointArr: McGePoint3d[] = []; // 单次断点数集合 private breakPtsCount: number[] = []; // 精度 private dol = 0.00001; // 获取相交墙体 public getIntersectingWalls(startPoint: McGePoint3d, endPoint: McGePoint3d, wallWidth: number): McObjectId[] { this.startPoint = startPoint.clone(); this.endPoint = endPoint.clone(); this.wallWidth = wallWidth; const intersectingWalls: McObjectId[] = []; const { pt1, pt3, line } = getPolyline(startPoint, endPoint, wallWidth); const length = line.getLength().val; // 设置过滤器,过滤出自定义实体 let filter = new MxCADResbuf(); filter.AddString("McDbCustomEntity", 5020); const ss = new MxCADSelectionSet(); ss.crossingSelect(pt1.x - length, pt1.y - length, pt3.x + length, pt3.y + length, filter); // 与其他墙体相交 if (ss.count() !== 0) { ss.forEach(id => { const ent = id.getMcDbEntity(); if ((ent as McDbCustomEntity).getTypeName() === "McDbTestWall") { intersectingWalls.push(id); } }); } return intersectingWalls; } // 初始绘制墙体 public drawWall(startPoint: McGePoint3d, endPoint: McGePoint3d, wallWidth: number): McObjectId { const wallObjIds = this.getIntersectingWalls(startPoint, endPoint, wallWidth); this.dealingWithWalls(wallObjIds); const wallId = this.draw(); MxCpp.getCurrentMxCAD().updateDisplay(); return wallId } private isAngleBetween(angleRad: number, startRad: number, endRad: number) { let minRad, maxRad if (startRad - endRad > 0) { minRad = endRad; maxRad = startRad; } else { minRad = startRad; maxRad = endRad; } if (maxRad - minRad > Math.PI) { return angleRad <= minRad || angleRad >= maxRad; } else { return angleRad >= minRad && angleRad <= maxRad; } } // 处理相交墙体得到断点与拐点 private dealingWithWalls(wallObjIds: McObjectId[]) { const { pl, pt1, pt2, pt3, pt4, line } = getPolyline(this.startPoint, this.endPoint, this.wallWidth); if (wallObjIds.length != 0) { // 与新墙体有交点的墙体集合 const wallArr = []; // 与其他墙体相交 wallObjIds.forEach(id => { const entity = id.getMcDbEntity(); if ((entity as McDbCustomEntity).getTypeName() === "McDbTestWall") { const wall = entity.clone() as McDbTestWall const { pl: _pl, pt1: _pt1, pt2: _pt2, pt3: _pt3, pt4: _pt4, line: _line } = getPolyline(wall.getStartPoint(), wall.getEndPoint(), wall.wallWidth); const _pointArr: McGePoint3d[] = []; _pl.IntersectWith(pl, McDb.Intersect.kOnBothOperands).forEach(item => _pointArr.push(item)); if (_pointArr.length > 0) { wallArr.push({ id, wall, pl: _pl, pt1: _pt1, pt2: _pt2, pt3: _pt3, pt4: _pt4, line: _line, pointArr: _pointArr }) } } }); if (wallArr.length != 0) { wallArr.forEach(item => { const { id, wall, pl: _pl, pt1: _pt1, pt2: _pt2, pt3: _pt3, pt4: _pt4, line: _line, pointArr: _pointArr } = item; /** * 根据交点位置判断是否是拐点 * 中心相交的交点与比对中心线端点距离小于1/2墙宽的都是拐点 */ const insterPointArr = (line as McDbLine).IntersectWith(_line as McDbLine, McDb.Intersect.kExtendBoth); if (insterPointArr.length() > 0) { const insterPt = insterPointArr.at(0); this.insterPointArr.push(insterPt); const dist1 = insterPt.distanceTo(_line.startPoint); const dist2 = insterPt.distanceTo(_line.endPoint); const dist3 = insterPt.distanceTo(line.startPoint); const dist4 = insterPt.distanceTo(line.endPoint); let vec, _vec; dist3 < dist4 ? vec = (line as McDbLine).endPoint.sub(insterPt).normalize() : vec = (line as McDbLine).startPoint.sub(insterPt).normalize(); dist1 < dist2 ? _vec = (_line as McDbLine).endPoint.sub(insterPt).normalize() : _vec = (_line as McDbLine).startPoint.sub(insterPt).normalize(); const angle1 = vec.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); const angle2 = _vec.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); if ((dist1 <= wall.wallWidth / 2 || dist2 <= wall.wallWidth / 2) && (dist3 <= this.wallWidth / 2 || dist4 <= this.wallWidth / 2) ) { // 绘制拐角 const turnArr = [] const line1 = new McDbLine(pt1, pt4); const line2 = new McDbLine(pt2, pt3); const _line1 = new McDbLine(_pt1, _pt4); const _line2 = new McDbLine(_pt2, _pt3); let line1ClosePt, line2ClosePt; pt1.distanceTo(insterPt) < pt4.distanceTo(insterPt) ? line1ClosePt = pt1.clone() : line1ClosePt = pt4.clone(); pt2.distanceTo(insterPt) < pt3.distanceTo(insterPt) ? line2ClosePt = pt2.clone() : line2ClosePt = pt3.clone(); const lineInsterPts1 = line1.IntersectWith(_line1, McDb.Intersect.kExtendBoth) const lineInsterPts2 = line1.IntersectWith(_line2, McDb.Intersect.kExtendBoth) const lineInsterPts3 = line2.IntersectWith(_line1, McDb.Intersect.kExtendBoth) const lineInsterPts4 = line2.IntersectWith(_line2, McDb.Intersect.kExtendBoth) const _point1 = lineInsterPts1.length() > 0 ? lineInsterPts1.at(0) : line1ClosePt; const _point2 = lineInsterPts2.length() > 0 ? lineInsterPts2.at(0) : line1ClosePt; const _point3 = lineInsterPts3.length() > 0 ? lineInsterPts3.at(0) : line2ClosePt; const _point4 = lineInsterPts4.length() > 0 ? lineInsterPts4.at(0) : line2ClosePt; const inflectioPoints = [_point1, _point2, _point3, _point4]; const r = inflectioPoints.filter((pt) => { const v = pt.sub(insterPt); const angle3 = v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis); return this.isAngleBetween(angle3, angle1, angle2) }) if (r.length) { turnArr.push(r[0]) const point = r[0]; const _r = inflectioPoints.filter((pt) => { const v = pt.sub(insterPt); const _v = point.sub(insterPt) const angle = v.angleTo2(_v, McGeVector3d.kNegateZAxis); return Math.abs(angle - Math.PI) < this.dol }) if (_r.length) turnArr.push(_r[0]) } this.turnPointArr.push(...turnArr); this.breakPtsCount.push(0); const _wall = wall.clone() as McDbTestWall; _wall.turnPoints = [..._wall.turnPoints, ...turnArr]; _wall.addInsterPoints([insterPt]); _wall.addBreakPtsCount([0]); MxCpp.getCurrentMxCAD().drawEntity(_wall); id.erase(); } else { // 没有拐角直接根据断点绘制 this.pointArr.push(..._pointArr); this.breakPtsCount.push(_pointArr.length); const _wall = wall.clone() as McDbTestWall; _wall.addBreakPoints(_pointArr); _wall.addInsterPoints([insterPt]); _wall.addBreakPtsCount([_pointArr.length]); MxCpp.getCurrentMxCAD().drawEntity(_wall); id.erase(); } } }); } } } // 绘制墙体 private draw(): McObjectId { const mxcad = MxCpp.getCurrentMxCAD(); const wall = new McDbTestWall(); wall.setEndPoint(this.endPoint); wall.setStartPoint(this.startPoint); wall.wallWidth = this.wallWidth; if (this.pointArr && this.pointArr.length) { wall.addBreakPoints(this.pointArr) } wall.turnPoints = this.turnPointArr ? this.turnPointArr : []; if (this.insterPointArr && this.insterPointArr.length) { wall.addInsterPoints(this.insterPointArr) } if (this.breakPtsCount && this.breakPtsCount.length) { wall.addBreakPtsCount(this.breakPtsCount) } return mxcad.drawEntity(wall); } // 更新墙体 public updateWall(wallId: McObjectId, isRedraw: Boolean) { const wall = wallId.getMcDbEntity() as McDbTestWall; if (wall.objectName === "McDbCustomEntity" && (wall as McDbCustomEntity).getTypeName() === "McDbTestWall") { const wallClone = wall.clone() as McDbTestWall; // 先修改移动前相交墙体里的断点和拐点 const oldWallObjIds = this.getIntersectingWalls(wall.oldStartPoint, wall.oldEndPoint, wall.wallWidth); // 更新与墙体相交的墙 oldWallObjIds.forEach(id => { const _wall = (id.getMcDbEntity()) as McDbTestWall; _wall.breakPoints = _wall.breakPoints.filter(pt => { const r = wallClone.breakPoints.filter(point => point.distanceTo(pt) < this.dol); return r.length === 0; }); _wall.turnPoints = _wall.turnPoints.filter(pt => { const r = wallClone.turnPoints.filter(point => point.distanceTo(pt) < this.dol); return r.length === 0; }); _wall.insterPoints = _wall.insterPoints.filter((pt, index) => { const r = wallClone.insterPoints.filter(point => point.distanceTo(pt) < this.dol); if (r.length === 0) { return true } else { _wall.breakPtsCounts.splice(index, 1); } }); _wall.assertObjectModification(true); }); // 是否重绘墙体,重新计算墙体点位 if (isRedraw) { wallId.erase(); setTimeout(() => { this.drawWall(wall.getStartPoint(), wall.getEndPoint(), wall.wallWidth); }, 0) }; } else { return } }
5.调用 MxdrawWalls 绘制墙体
// 绘制墙体 async function drawWall() { const getWallWidth = new MxCADUiPrDist(); getWallWidth.setMessage(`\n${t('请设置墙体宽度')}:`); let wallWidth = await getWallWidth.go(); if (!wallWidth) wallWidth = 30; while (true) { const getPoint1 = new MxCADUiPrPoint(); getPoint1.setMessage(`\n${t('请设置墙体起点')}:`); const pt1 = await getPoint1.go(); if (!pt1) break; const getDist = new MxCADUiPrDist(); getDist.setBasePt(pt1); getDist.setMessage(`\n${t('请设置墙体长度')}:`); let pt2 = new McGePoint3d(); getDist.setUserDraw((pt, pw) => { const v = pt1.sub(pt).normalize().perpVector().mult(wallWidth / 2); const line1 = new McDbLine(pt1.clone().addvec(v), pt.clone().addvec(v)); pw.setColor(0x0000FF); pw.drawMcDbEntity(line1); const line2 = new McDbLine(pt1.clone().addvec(v.clone().negate()), pt.clone().addvec(v.clone().negate())); pw.setColor(0xFF0000); pw.drawMcDbEntity(line2); pt2 = pt.clone(); }); const dist = await getDist.go(); if (!dist) break; const v = pt2.sub(pt1).normalize().mult(dist); const endPt = pt1.clone().addvec(v) const wall = new MxdrawWalls() wall.drawWall(pt1, endPt, wallWidth); } }
6.夹点编辑、实体删除情况处理
调用夹点编辑监听事件,监听墙体变化,当墙体位置发生改变则触发墙体更新方法,参考代码:
const mxcad: McObject = MxCpp.getCurrentMxCAD(); // 监听wall夹点变化 mxcad.mxdraw.on("objectGripEdit", (grips) => { grips.forEach((grip) => { const id = new McObjectId(grip.id, grip.type === "mxcad" ? McObjectIdType.kMxCAD : McObjectIdType.kMxDraw); if (id.isErase()) return; const wall = id.getMcDbEntity() as McDbTestWall; if(!wall) return; if (wall.objectName === "McDbCustomEntity" && (wall as McDbCustomEntity).getTypeName() === "McDbTestWall") { // 更新绘制wall const newWall = new MxdrawWalls(); newWall.updateWall(id, true); mxcad.clearMxCurrentSelect(); }; }) })
调用实体选择监听事件,监听实体选择,若监听到选择的墙体被删除则触法墙体更新方法,参考代码:
// 监听mxcad选择,若wall被删除则触发更新 const oldSelectIds: McObjectId[] = []; mxcad.on("selectChange", (ids: McObjectId[]) => { if (ids.length > 0) { ids.forEach(id => { const entity = id.getMcDbEntity(); if (entity && entity.objectName === "McDbCustomEntity" && (entity as McDbCustomEntity).getTypeName() === "McDbTestWall") { oldSelectIds.push(id) } }) } else { setTimeout(()=>{ oldSelectIds.forEach(id => { if (id.isErase()) { const newWall = new MxdrawWalls(); newWall.updateWall(id, false); } }); oldSelectIds.length = 0; },0) } })
功能实践
在线示例demo地址:https://demo.mxdraw3d.com:3000/mxcad/。
墙体绘制效果展示:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步