惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The GitHub Blog
The GitHub Blog
J
Java Code Geeks
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
A
About on SuperTechFans
博客园 - 三生石上(FineUI控件)
罗磊的独立博客
MongoDB | Blog
MongoDB | Blog
B
Blog RSS Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Vercel News
Vercel News
腾讯CDC
博客园 - 聂微东
The Cloudflare Blog
F
Fortinet All Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
Last Week in AI
Last Week in AI
B
Blog

博客园 - 梦想CAD控件

网页CAD如何在web端实现AutoCAD的绘圆弧功能 网页CAD控件中对象捕捉的运用和二次开发方法(网页端编辑DWG) 网页端CAD的图形选择、编辑与夹点操作教程(CAD in WEB) 云原生CAD后台处理DWG(MxServiceCode) 网页CAD图纸保存与数据持久化(BS架构CAD插件) 网页CAD中命令行驱动与参数化绘图的使用教程 在线CAD视图控制与图层管理二开实战 Web CAD图形引擎实现DWG图纸网页批注和查看 开源网页实时协同设计平台-在线编辑CAD图纸 在线CAD SDK开发包图纸转换功能使用教程(在线CAD API) 统信麒麟鸿蒙系统软件集成CAD功能(国产系统CAD控件) 网页CAD开发包结构与功能说明(网页CAD实时协同编辑) 网页CAD结合AI实现自然语言设计CAD图纸 (JS实现预览)在线CAD中的“组”功能开发 (在线CAD)html在线预览cad图纸SDK集成方法 (JS编辑DWG文件)在线CAD二开实现图纸表格提取到excel 网页版CAD与gis结合实现在线地图和CAD编辑 (html在线预览cad图纸插件)在线CAD实现形位公差标注 (WEB CAD API )网页CAD插件如何开发焊接符号 (HTML VUE在线编辑DWG)网页CAD利用AI实现多行文本功能的开发 (web cad sdk)在线CAD中配置属性的使用教程 (WEB CAD二次开发)在线CAD SDK调用内部弹框的方法 在线CAD插件通过上下文对象MxPluginContext修改UI界面的方法 国产操作系统中好用的CAD软件(统信,麒麟,信创环境安装CAD) (pdm集成CAD SDK)在线CAD绘制条形码、二维码的教程 在线CAD绘制墙体(WEB端开发室内设计软件) (在线三维参数化建模)网页三维CAD中加载和保存STEP模型 (H5在线CAD SDK)在线CAD中线型表的使用方法 (web查看三维CAD图纸)在三维网页CAD中绘制一个窗户模型
在线CAD绘制门和窗(WEB CAD二次开发家装设计软件)
梦想CAD控件 · 2025-01-03 · via 博客园 - 梦想CAD控件

一、前言

Mxcad是使用TypeScriptC++语言开发的一个网页CAD底层平台,它为用户提供了丰富的开发接口,此框架功能丰富、使用简易高效,可帮助大家在网页二开与自己专业相关网页CAD应用。我们以家装行业为例,介绍mxcad如何快速实现墙体、单开门、标准窗等实体,并实现这些实体之间的联动。

mxcad 相关开发文档地址:https://www.mxdraw3d.com/mxcad_docs/zh/

在线mxcad DEMO地址:https://demo.mxdraw3d.com:3000/mxcad/

在线查看效果:(位置:工具=>家装设置=>示例户型图,命令:Mx_PlanView

二、基础实体实现

在本介绍的所有实体,都将通过mxcad的自定义实体实现如果不清楚mxcad的自定义实体实现原理和使用方法,请参考[mxcad 自定义实体介绍][McDbCustomEntity]下面我们将以实现标准窗为例,详细介绍如何通过自定义实体实现标准窗实体。

[在线DEMO]绘制示例:(位置:工具=》家装设计=》标准窗;命令:BZC)

三、实体分析

3.1实体形状

标准窗形状为一个矩形框与两条平分矩形内部的直线组成,因此,我们可以使用 mxcad 中的[多段线实体][直线实体]绘制标准窗。

3.2实体交互方式

绘制标准窗实体时,应先确定窗体宽度,若用户未输入窗体宽度值则设置一个默认值;再确定窗体长度,用户可再图纸中绘制线段指定长度,又可直接输入窗体长度;最后再在图纸中选择放置实体的位置。因此,我们可以使用[取点对象MxCADUiPrPoint()][获取距离对象MxCADUiPrDist()]实现交互。

3.3实体夹点

标准窗有三个夹点,分别位于标准窗实体开始端、中心、结束端。移动首尾两端的夹点,可以修改标准窗的长度和方向,中心夹点也应该在首尾夹点移动后重新计算;移动中心夹点,整个标准窗保持方向不变,位置随中心夹点移动,因此首尾两夹点位置跟随中心夹点移动。

3.4实体关联(句柄关联)

标准窗在实现后应与图纸中的墙体联动,当标准窗靠近目标墙体时应该自动旋转角度与墙体适配,并自动识别墙体宽度,调整标准窗宽度与墙体宽度一致。

若关联位置的墙体有交叉、拐点、墙体长度不够等情况则只绘制实体,不与墙体关联。若与墙体关联后又离开墙体,则需要取消与墙体的关联。因此,我们可以监听图纸中的夹点编辑事件,当标准窗夹点移动后进行相关操作。

四、实体实现

4.1标准窗

实现 McDbTestStandardWindow 自定义实体窗

 // 自定义标准窗实体类
  class McDbTestStandardWindow extends McDbCustomEntity {
      /** 标准窗开始点 */
      private startPt: McGePoint3d = new McGePoint3d();
      /** 标准窗结束点 */
      private endPt: McGePoint3d = new McGePoint3d();
      /** 标准窗中心点 */
      private windowPosition: McGePoint3d = new McGePoint3d();
      /** 窗宽 */
      private _windowWidth: number = 30;
      /** 窗旋转角度 */
      private _windowAngle: number = 0;
      /** 窗长 */
      private _windowLength: number = 0;
      /** 关联墙体句柄 */
      private _wallHandle: string = "";
      /**自身句柄 */
      private _entityHandle: string = "";
      /** 之前关联的墙体句柄 */
      private _oldWallHandle: string = "";
      constructor(imp?: any) {
          super(imp);
      }
      public create(imp: any) {
          return new McDbTestStandardWindow(imp)
      }
      /** 获取类名 */
      public getTypeName(): string {
          return "McDbTestStandardWindow";
      }
      //设置或获取窗宽
      public set windowWidth(val: number) {
          this._windowWidth = val;
      }
      public get windowWidth(): number {
          return this._windowWidth;
      }
      /** 获取或设置窗旋转角度 */
      public set windowAngle(val: number) {
          this._windowAngle = val;
          this.resetPoint();
      }
      public get windowrAngle(): number {
          return this._windowAngle;
      }
      /** 获取或设置窗长 */
      public set width(val: number) {
          this._windowLength = val;
      }
      public get width(): number {
          return this._windowLength;
      }
  
      /** 获取关联墙体句柄 */
      public get wallHandle(): string {
          return this._wallHandle;
      }
      /** 设置关联墙体句柄 */
      public set wallHandle(val: string) {
          this._wallHandle = val;
      }
      /** 获取自身句柄 */
      public get entityHandle(): string {
          return this._entityHandle;
      }
      /** 设置自身句柄 */
      public set entityHandle(val: string) {
          this._entityHandle = val;
      }
      /** 获取之前关联的墙体句柄 */
      public get oldWallHandle(): string {
          return this._oldWallHandle;
      }
      /** 设置之前关联的墙体句柄 */
      public set oldWallHandle(val: string) {
          this._oldWallHandle = val;
      }
      /** 读取数据 */
      public dwgInFields(filter: IMcDbDwgFiler): boolean {
          this.startPt = filter.readPoint('satrtPt').val;
          this.endPt = filter.readPoint('endPt').val;
          this.windowPosition = filter.readPoint('windowPosition').val;
          this._windowWidth = filter.readDouble('windowWidth').val;
          this._windowAngle = filter.readDouble('windowAngle').val;
          this._windowLength = filter.readDouble('windowLength').val;
          this._wallHandle = filter.readString('wallHandle').val;
          this._entityHandle = filter.readString('entityHandle').val;
          this._oldWallHandle = filter.readString('oldWallHandle').val;
          return true;
      }
      /** 写入数据 */
      public dwgOutFields(filter: IMcDbDwgFiler): boolean {
          filter.writePoint("satrtPt", this.startPt);
          filter.writePoint("endPt", this.endPt);
          filter.writePoint("windowPosition", this.windowPosition);
          filter.writeDouble("windowWidth", this._windowWidth);
          filter.writeDouble("windowAngle", this._windowAngle);
          filter.writeDouble("windowLength", this._windowLength);
          filter.writeString("wallHandle", this._wallHandle);
          filter.writeString("entityHandle", this._entityHandle);
          filter.writeString("oldWallHandle", this._oldWallHandle);
          return true;
      }
      /** 移动夹点 */
      public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
          this.assertWrite();
          if (iIndex === 0) {
              this.startPt.x += dXOffset;
              this.startPt.y += dYOffset;
              this.startPt.z += dZOffset;
          } else if (iIndex === 1) {
              this.endPt.x += dXOffset;
              this.endPt.y += dYOffset;
              this.endPt.z += dZOffset;
          } else if (iIndex === 2) {
              this.startPt.x += dXOffset;
              this.startPt.y += dYOffset;
              this.startPt.z += dZOffset;
              this.endPt.x += dXOffset;
              this.endPt.y += dYOffset;
              this.endPt.z += dZOffset;
          }
          this._windowLength = this.startPt.distanceTo(this.endPt);
          this.windowPosition = this.startPt.clone().addvec(this.endPt.sub(this.startPt).mult(1 / 2));
          // 移动标准窗
          const { wallArr, angle, position } = getWallPts(this.windowPosition, this._windowLength);
          this._oldWallHandle = this._wallHandle;
          if (wallArr.length > 0) {
              // 有墙体与门窗相关联
              this._windowAngle = angle;
              this.windowPosition = position;
              this.resetPoint();
              const id = wallArr[0];
              const newWall = id.getMcDbEntity() as McDbTestWall;
              this._windowWidth = newWall.wallWidth;
              this._wallHandle = newWall.getHandle();
          } else {
              // 没有墙体与门窗关联
              this._wallHandle = "";
          }
      };
      private resetPoint() {
          const v = McGeVector3d.kXAxis.clone().rotateBy(this._windowAngle);
          this.startPt = this.windowPosition.clone().addvec(v.clone().mult(this._windowLength / 2));
          this.endPt = this.windowPosition.clone().addvec(v.clone().negate().mult(this._windowLength / 2));
      }
      /** 获取夹点 */
      public getGripPoints(): McGePoint3dArray {
          let ret = new McGePoint3dArray();
          ret.append(this.startPt);
          ret.append(this.endPt);
          ret.append(this.windowPosition)
          return ret;
      };
      /** 动态绘制 */
      public worldDraw(draw: MxCADWorldDraw): void {
          const v = this.endPt.sub(this.startPt).normalize().perpVector();
          const pt1 = this.startPt.clone().addvec(v.clone().mult(this._windowWidth / 2));
          const pt2 = this.endPt.clone().addvec(v.clone().mult(this._windowWidth / 2));
          const pt3 = this.endPt.clone().addvec(v.clone().negate().mult(this._windowWidth / 2));
          const pt4 = this.startPt.clone().addvec(v.clone().negate().mult(this._windowWidth / 2));
          const pl = new McDbPolyline();
          pl.isClosed = true;
          pl.addVertexAt(pt1);
          pl.addVertexAt(pt2);
          pl.addVertexAt(pt3);
          pl.addVertexAt(pt4);
          draw.drawEntity(pl);
          const line = new McDbLine(this.startPt, this.endPt);
          const line1 = line.clone() as McDbLine;
          line1.move(this.windowPosition, this.windowPosition.clone().addvec(v.clone().mult(this._windowWidth / 6)));
          const line2 = line.clone() as McDbLine;
          line2.move(this.windowPosition, this.windowPosition.clone().addvec(v.clone().negate().mult(this._windowWidth / 6)));
          draw.drawEntity(line1);
          draw.drawEntity(line2);
      }
      /** 设置窗所在位置 */
      public setPosition(pt: McGePoint3d) {
          this.windowPosition = pt.clone();
          this.resetPoint();
      }
      /** 获取窗所在位置 */
      public getPosition(): McGePoint3d {
          return this.windowPosition;
      }
      public transformBy(_mat: McGeMatrix3d): boolean {
          this.startPt.transformBy(_mat);
          this.endPt.transformBy(_mat);
          this.windowPosition.transformBy(_mat);
          return true;
      }
  }

关联墙体信息,代码如下:

/**
   * 获取门窗关联墙体信息
   * @param pt 门窗的位置
   * @param width 门窗宽
   * @param insertType 插入方式
   * @returns { wallArr: 门窗所在墙体Id数组, angle: 门窗的旋转角度, position: 门的位置, isRed: 放置位置是否合理 }
   */
  function getWallPts(pt: McGePoint3d, width: number, insertType?: string): { wallArr: McObjectId[], angle: number, position: McGePoint3d, isRed: boolean } {
      let wallArr: McObjectId[] = [];
      let angle: number = 0;
      let isRed: boolean = false;
      const dol = 0.00001;
      const startPt = pt.clone().addvec(McGeVector3d.kXAxis.clone().mult(width));
      const endPt = pt.clone().addvec(McGeVector3d.kXAxis.clone().negate().mult(width));
      const line = new McDbLine(startPt, endPt);
      line.rotate(pt, Math.PI / 4);
      // 查找符合与单开门关联的墙体
      const filter = new MxCADResbuf();
      filter.AddString("McDbCustomEntity", 5020);
      const ss = new MxCADSelectionSet();
      ss.crossingSelect(line.startPoint.x, line.startPoint.y, line.endPoint.x, line.endPoint.y, filter);
      if (ss.count() > 0) {
          ss.forEach(id => {
              const entity = id.getMcDbEntity() as McDbCustomEntity;
              if (entity.getTypeName() === "McDbTestWall") {
                  const wall = entity.clone() as McDbTestWall;
                  const pt1 = wall.getStartPoint();
                  const pt2 = wall.getEndPoint();
                  const _line = new McDbLine(pt1, pt2);
                  const closePos = _line.getClosestPointTo(pt, false).val;
                  const v = pt1.sub(pt2).normalize().mult(width / 2);
                  const doorStart = closePos.clone().addvec(v);
                  const doorEnd = closePos.clone().addvec(v.negate());
                  const point1 = _line.getClosestPointTo(doorStart, false).val;
                  const point2 = _line.getClosestPointTo(doorEnd, false).val;
                  if (point1.distanceTo(doorStart) < dol && point2.distanceTo(doorEnd) < dol && closePos.distanceTo(pt) < wall.wallWidth / 2) {
                      // 判断门所在墙体位置是否还和其他墙体相交
                      let res = wall.insterPoints.filter(pt => {
                          const doorLine = new McDbLine(doorStart, doorEnd);
                          const point = doorLine.getClosestPointTo(pt, false).val;
                          return point.distanceTo(pt) < dol;
                      })
                      // 有符合条件的相交墙体
                      if (res.length === 0) {
                          const _v = startPt.sub(endPt);
                          const angle1 = v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);// 墙体的角度
                          const angle2 = _v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);// 门的初始角度
                          angle = angle1 - angle2;
                          wallArr.push(id);
                          if (insertType === 'E' || !insertType) {
                              pt = closePos.clone();
                          } else if (insertType === 'M') {
                              pt = _line.getPointAtDist(_line.getLength().val / 2).val;
                          }
                      } else {
                          isRed = true;
                      }
                  }
              }
          })
      };
      return { wallArr, angle, position: pt, isRed };
  }

设置监听事件,参考代码:

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 ent = id.getMcDbEntity() as McDbEntity;
          if (!ent) return;
          if (ent.objectName === "McDbCustomEntity") {
              const typeName = (ent as McDbCustomEntity).getTypeName();
             if (typeName === "McDbTestStandardWindow") {
                  // 单开门夹点变换,更新相关墙体
                  const door = ent as McDbTestSingleDoor | McDbTestStandardWindow;
                  let newWall: McDbTestWall | null = null;
                  let oldWall: McDbTestWall | null = null;
                  const dataBase: McDbDatabase = MxCpp.getCurrentMxCAD().getDatabase();
                  if (door.wallHandle) {
                      newWall = dataBase.handleToIdIndex(door.wallHandle).getMcDbEntity() as McDbTestWall;
                  }
                  if (door.oldWallHandle) {
                      oldWall = dataBase.handleToIdIndex(door.oldWallHandle).getMcDbEntity() as McDbTestWall;
                  }
                  if (door.wallHandle === door.oldWallHandle) {
                      // 新墙体句柄与老墙体句柄相等,则更新墙体
                      if (newWall && oldWall) {
                          // 重新计算门窗在墙体中的相关信息
                          newWall.countDoorData();
                          // 更新墙体
                          newWall.assertObjectModification(true);
                      }
                  } else {
                      // 新老关联墙体不相等,新墙体添加句柄,旧墙体删除句柄并更新
                      if (newWall) {
                          newWall.doorHandles = [...newWall.doorHandles, door.entityHandle];
                          newWall.assertObjectModification(true);
                      }
                      if (oldWall) {
                          oldWall.doorHandles = oldWall.doorHandles.filter(handle => handle !== door.entityHandle);
                          oldWall.assertObjectModification(true);
                      }
                  }
              };
              mxcad.updateDisplay();
          };
      })
  })
  // 监听mxcad选择,若门窗被删除则触发更新
  const oldDoorSelectIds: McObjectId[] = [];
  mxcad.on("selectChange", (ids: McObjectId[]) => {
      if (ids.length > 0) {
          oldDoorSelectIds.length = 0;
          ids.forEach(id => {
              const entity = id.getMcDbEntity();
              if (entity && entity.objectName === "McDbCustomEntity") {
                 if ((entity as McDbCustomEntity).getTypeName() === "McDbTestStandardWindow") {
                      oldDoorSelectIds.push(id)
                  }
              }
          })
      } else {
          setTimeout(() => {
              if (oldDoorSelectIds.length > 0) {
                  oldDoorSelectIds.forEach(id => {
                      if (id.isErase()) {
                          const door = id.getMcDbEntity() as McDbTestSingleDoor | McDbTestStandardWindow;
                          let newWall: McDbTestWall | null = null;
                          const dataBase: McDbDatabase = MxCpp.getCurrentMxCAD().getDatabase();
                          if (door.wallHandle) {
                              newWall = dataBase.handleToIdIndex(door.wallHandle).getMcDbEntity() as McDbTestWall;
                              newWall.doorHandles = newWall.doorHandles.filter(handle => handle !== door.entityHandle);
                              newWall.assertObjectModification(true);
                          }
                      }
                  });
                  mxcad.updateDisplay();
              };
          }, 0)
      }
  })

实现交互绘制方法

// 绘制标准窗
  async function drawStandardWindow() {
      // 设置窗的长度
      const getLength = new MxCADUiPrDist();
      getLength.setMessage(`${t('请设置标准窗的长度')}`)
      let windowLength = await getLength.go();
      if (!windowLength) windowLength = 30;
      const window = new McDbTestStandardWindow();
      window.width = windowLength;
      // 设置窗的插入方式
      const getInsertType = new MxCADUiPrKeyWord();
      getInsertType.setMessage(`${t('请选择插入方式<E>')}`);
      getInsertType.setKeyWords(`[${t("任意位置")}(E)/${t("中心位置")}(M)]`)
      let insertType = await getInsertType.go();
      if (!insertType) insertType = 'E'
      // 设置窗的位置
      const getDoorPos = new MxCADUiPrPoint();
      getDoorPos.setMessage(`${t('请选择窗的位置')}`);
      let _wallArr: McObjectId[] = [];
      getDoorPos.setUserDraw((pt, pw) => {
          const { isRed, angle, position, wallArr } = getWallPts(pt, windowLength, insertType);
          window.setPosition(position);
          if (wallArr.length > 0) {
              _wallArr = wallArr;
              const wall = wallArr[0].getMcDbEntity() as McDbTestWall;
              window.windowWidth = wall.wallWidth;
          }
          if (!isRed) {
              pw.setColor(0x00FFFF);
              window.windowAngle = angle;
              pw.drawMcDbEntity(window)
          } else {
              pw.setColor(0xFF0000);
              pw.drawMcDbEntity(window)
          }
      })
      const windowPos = await getDoorPos.go();
      if (!windowPos) return;
      if (_wallArr.length > 0) {
          // 关联门窗和墙
          const id = _wallArr[0];
          const wall = id.getMcDbEntity() as McDbTestWall;
          const windowId = MxCpp.getCurrentMxCAD().drawEntity(window);
          const _window = windowId.getMcDbEntity() as McDbTestStandardWindow;
          const handle: string = _window.getHandle();
          wall.doorHandles = [...wall.doorHandles, handle];
          wall.assertObjectModification(true)
          _window.wallHandle = wall.getHandle();
          _window.entityHandle = handle;
      } else {
          // 绘制单开门
          const id = MxCpp.getCurrentMxCAD().drawEntity(window);
          const _window = id.getMcDbEntity() as McDbTestStandardWindow;
          const handle: string = _window.getHandle();
          _window.entityHandle = handle;
      }
  }

4.2实现效果

可在[在线DEMO]中查看完整绘制效果:

五、实体扩展

根据上述实现家装设计实体的思路,我们可以扩展实现更多的家装设计实体,下面我们将参照上文中的基础实体实现思路去实现家装设计中的单开门实体,单开门实体样式示例:

5.1单开门实体

A.实体形状

单卡开门实体形状为一个非闭合的矩形框与一段圆弧组成,因此我们可以使用mxcad 中的[多段线实体][圆弧实体]绘制单开门。

B.实体夹点

单开门有四个夹点,分别位于单开门实体开始端、中心、结束端和单开门内部。移动首尾两端的夹点,可以修改单开门实体的长度和方向,中心夹点和单开门内部的夹点也应该在首尾夹点移动后重新计算;移动中心夹点,整个单开门实体保持方向不变,位置随中心夹点移动,因此首尾两夹点和单开门内部的夹点也跟随中心夹点移动;移动单开门内部夹点,改变单开门的圆弧方向,其他三个夹点位置保持不变。

C.预留与墙体关联的接口

根据上述对实体的分析,我们就可以通过 [McDbCustomEntity](https://www.mxdraw3d.com/mxcad_docs/api/classes/2d.McDbCustomEntity.html#class-mcdbcustomentity) 实现一个单开门的自定义实体 McDbTestSingleDoor ,示例代码如下:

   class McDbTestSingleDoor extends McDbCustomEntity {
       /** 门开始点 */
       private startPt: McGePoint3d = new McGePoint3d();
       /** 门结束点 */
       private endPt: McGePoint3d = new McGePoint3d();
       /** 门中心点 */
       private doorPosition: McGePoint3d = new McGePoint3d();
       /** 门宽 */
       private _doorWidth: number;
       /** 门方向点 */
       private directPt: McGePoint3d = new McGePoint3d();
       /** 门所在象限 */
       private quadrant: number = 3;
       /** 门旋转角度 */
       private _doorAngle: number = 0;
       /** 门所在墙体的句柄 */
       private _wallHandle: string = '';
       /** 门句柄 */
       private _entityHandle: string = '';
       /** 移动前关联墙体句柄 */
       private _oldWallHandle: string = '';
       constructor(imp?: any) {
           super(imp);
       }
       public create(imp: any) {
           return new McDbTestSingleDoor(imp)
       }
       /** 获取类名 */
       public getTypeName(): string {
           return "McDbTestSingleDoor";
       }
       //设置或获取门宽
       public set width(val: number) {
           this._doorWidth = val;
       }
       public get width(): number {
           return this._doorWidth;
       }
       /** 获取或设置门旋转角度 */
       public set doorAngle(val: number) {
           this._doorAngle = val;
       }
       public get doorAngle(): number {
           return this._doorAngle;
       }
       /** 获取或设置门所在墙体的句柄 */
       public set wallHandle(val: string) {
           this._wallHandle = val;
       }
       public get wallHandle(): string {
           return this._wallHandle;
       }
       /** 获取或设置门句柄 */
       public set entityHandle(val: string) {
           this._entityHandle = val;
       }
       public get entityHandle(): string {
           return this._entityHandle;
       }
       /** 获取移动前关联墙体句柄 */
       public set oldWallHandle(val: string) {
           this._oldWallHandle = val;
       }
       public get oldWallHandle(): string {
           return this._oldWallHandle;
       }
       /** 读取数据 */
       public dwgInFields(filter: IMcDbDwgFiler): boolean {
           this.startPt = filter.readPoint('satrtPt').val;
           this.endPt = filter.readPoint('endPt').val;
           this.doorPosition = filter.readPoint('doorPosition').val;
           this.directPt = filter.readPoint('directPt').val;
           this._doorWidth = filter.readDouble('doorWidth').val;
           this.quadrant = filter.readLong('quadrant').val;
           this._doorAngle = filter.readDouble('doorAngle').val;
           this._wallHandle = filter.readString('wallHandle').val;
           this._entityHandle = filter.readString('entityHandle').val;
           this._oldWallHandle = filter.readString('oldWallHandle').val;
           return true;
       }
       /** 写入数据 */
       public dwgOutFields(filter: IMcDbDwgFiler): boolean {
           filter.writePoint("satrtPt", this.startPt);
           filter.writePoint("endPt", this.endPt);
           filter.writePoint("doorPosition", this.doorPosition);
           filter.writePoint("directPt", this.directPt);
           filter.writeDouble("doorWidth", this._doorWidth);
           filter.writeLong("quadrant", this.quadrant);
           filter.writeDouble("doorAngle", this._doorAngle);
           filter.writeString("wallHandle", this._wallHandle);
           filter.writeString("entityHandle", this._entityHandle);
           filter.writeString("oldWallHandle", this._oldWallHandle);
           return true;
       }
       /** 移动夹点 */
       public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
           this.assertWrite();
           if (iIndex === 0) {
               this.startPt.x += dXOffset;
               this.startPt.y += dYOffset;
               this.startPt.z += dZOffset;
               this.doorPosition = this.startPt.clone().addvec(this.endPt.sub(this.startPt).mult(0.5));
               this.getQuadrant();
           } else if (iIndex === 1) {
               this.endPt.x += dXOffset;
               this.endPt.y += dYOffset;
               this.endPt.z += dZOffset;
               this.doorPosition = this.startPt.clone().addvec(this.endPt.sub(this.startPt).mult(0.5));
               this.getQuadrant();
           } else if (iIndex === 2) {
               this.doorPosition.x += dXOffset;
               this.doorPosition.y += dYOffset;
               this.doorPosition.z += dZOffset;
               this.endPt.x += dXOffset;
               this.endPt.y += dYOffset;
               this.endPt.z += dZOffset;
               this.startPt.x += dXOffset;
               this.startPt.y += dYOffset;
               this.startPt.z += dZOffset;
               this.directPt.x += dXOffset;
               this.directPt.y += dYOffset;
               this.directPt.z += dZOffset;
           } else if (iIndex === 3) {
               const v = this.endPt.sub(this.startPt);
               const pt = this.directPt.clone();
               pt.x += dXOffset;
               pt.y += dYOffset;
               pt.z += dZOffset;
               const _v = pt.sub(this.doorPosition);
               const angle = _v.angleTo2(v, McGeVector3d.kNegateZAxis);
               if (0 < angle && angle < Math.PI / 2) {
                   this.quadrant = 1;
               } else if (Math.PI / 2 <= angle && angle < Math.PI) {
                   this.quadrant = 2;
               } else if (Math.PI <= angle && angle < Math.PI * 3 / 2) {
                   this.quadrant = 3;
               } else {
                   this.quadrant = 4;
               }
               this.getQuadrant();
           };
           this._doorWidth = this.startPt.distanceTo(this.endPt);
           this._oldWallHandle = this._wallHandle;
   
           if (iIndex !== 3) {
               // 移动单开门
               const { wallArr, angle, position } = getWallPts(this.doorPosition, this._doorWidth);
               if (wallArr.length > 0) {
                   // 有墙体与单开门相关联
                   this._doorAngle = angle;
                   this.doorPosition = position;
                   const v = McGeVector3d.kXAxis.clone().rotateBy(this._doorAngle);
                   this.resetPoint(v, this._doorWidth);
                   const id = wallArr[0];
                   const newWall = id.getMcDbEntity() as McDbTestWall;
                   this._wallHandle = newWall.getHandle();
               } else {
                   // 没有墙体与单开门关联
                   this._wallHandle = "";
               }
           }
       };
       private resetPoint(v: McGeVector3d, width: number) {
           this.startPt = this.doorPosition.clone().subvec(v.clone().mult(width / 2));
           this.endPt = this.doorPosition.clone().addvec(v.clone().mult(width / 2));
           this.directPt = this.startPt.clone().addvec(v.clone().mult(width / 5)).subvec(v.clone().perpVector().mult(width / 3));
           this.quadrant = 3;
       }
       private getQuadrant() {
           const v = this.endPt.sub(this.startPt);
           const angleOrigin = v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);
           const width = this.startPt.distanceTo(this.endPt);
           let v1, v2, point;
           if (this.quadrant === 1) {
               point = this.endPt.clone();
               v1 = McGeVector3d.kXAxis.clone().negate().mult(width / 5);
               v2 = McGeVector3d.kYAxis.clone().mult(width / 3);
           } else if (this.quadrant === 2) {
               point = this.startPt.clone();
               v1 = McGeVector3d.kXAxis.clone().mult(width / 5);
               v2 = McGeVector3d.kYAxis.clone().mult(width / 3);
           } else if (this.quadrant === 3) {
               point = this.startPt.clone();
               v1 = McGeVector3d.kXAxis.clone().mult(width / 5);
               v2 = McGeVector3d.kYAxis.clone().negate().mult(width / 3);
           } else if (this.quadrant === 4) {
               point = this.endPt.clone();
               v1 = McGeVector3d.kXAxis.clone().negate().mult(width / 5);
               v2 = McGeVector3d.kYAxis.clone().negate().mult(width / 3);
           }
           this.directPt = point.clone().addvec(v1.rotateBy(angleOrigin)).addvec(v2.rotateBy(angleOrigin));
       }
       /** 获取夹点 */
       public getGripPoints(): McGePoint3dArray {
           let ret = new McGePoint3dArray();
           ret.append(this.startPt);
           ret.append(this.endPt);
           ret.append(this.doorPosition);
           ret.append(this.directPt);
           return ret;
       };
       /** 动态绘制 */
       public worldDraw(draw: MxCADWorldDraw): void {
           const dist1 = this.directPt.distanceTo(this.startPt);
           const dist2 = this.directPt.distanceTo(this.endPt);
           const width = this.startPt.distanceTo(this.endPt);
           let startPoint, endPoint;
           if (dist1 < dist2) {
               startPoint = this.startPt.clone();
               endPoint = this.endPt.clone();
           } else {
               startPoint = this.endPt.clone();
               endPoint = this.startPt.clone();
           }
           // 绘制圆弧
           const directV = endPoint.sub(startPoint);
           const arc = new McDbArc();
           arc.setCenter(startPoint.x, startPoint.y, startPoint.z);
           arc.radius = width;
           let startAngle, endAngle, v1, v2, angle;
           if (this.quadrant === 1) {
               startAngle = Math.PI / 2;
               endAngle = Math.PI;
               v1 = McGeVector3d.kYAxis.clone().mult(width);
               v2 = McGeVector3d.kXAxis.clone().negate().mult(width / 10);
               angle = directV.angleTo2(McGeVector3d.kXAxis.clone().negate(), McGeVector3d.kNegateZAxis)
           } else if (this.quadrant === 2) {
               endAngle = Math.PI / 2;
               startAngle = 0;
               v1 = McGeVector3d.kYAxis.clone().mult(width);
               v2 = McGeVector3d.kXAxis.clone().mult(width / 10);
               angle = directV.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis)
           } else if (this.quadrant === 3) {
               startAngle = Math.PI * (3 / 2);
               endAngle = 0;
               v1 = McGeVector3d.kYAxis.clone().negate().mult(width);
               v2 = McGeVector3d.kXAxis.clone().mult(width / 10);
               angle = directV.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis)
           } else if (this.quadrant === 4) {
               endAngle = Math.PI * (3 / 2);
               startAngle = Math.PI;
               v1 = McGeVector3d.kYAxis.clone().negate().mult(width);
               v2 = McGeVector3d.kXAxis.clone().negate().mult(width / 10);
               angle = directV.angleTo2(McGeVector3d.kXAxis.clone().negate(), McGeVector3d.kNegateZAxis)
           }
           arc.startAngle = startAngle;
           arc.endAngle = endAngle;
           arc.rotate(startPoint, angle);
           draw.drawEntity(arc);
           // 绘制直线
           const pl = new McDbPolyline();
           pl.addVertexAt(startPoint.clone().addvec(v1))
           pl.addVertexAt(startPoint)
           pl.addVertexAt(startPoint.clone().addvec(v2));
           pl.addVertexAt(startPoint.clone().addvec(v1).addvec(v2));
           pl.rotate(startPoint, angle);
           draw.drawEntity(pl);
       }
       /** 设置门所在位置 */
       public setPosition(pt: McGePoint3d) {
           this.doorPosition = pt.clone();
           let v: McGeVector3d;
           if (this._doorAngle === 0) {
               v = McGeVector3d.kXAxis.clone();
           } else {
               v = McGeVector3d.kXAxis.clone().rotateBy(this._doorAngle);
           }
           this.resetPoint(v, this._doorWidth);
       }
       /** 获取门所在位置 */
       public getPosition(): McGePoint3d {
           return this.doorPosition;
       }
       /**
        * 获取自定义对象矩阵坐标变换
        */
       public transformBy(_mat: McGeMatrix3d): boolean {
           this.startPt.transformBy(_mat);
           this.endPt.transformBy(_mat);
           this.doorPosition.transformBy(_mat);
           this.directPt.transformBy(_mat);
           return true;
       };
   }

5.2实体关联(句柄关联)

单开门实体在实现后应与图纸中的墙体联动,当标准窗靠近目标墙体时应该自动旋转角度与墙体适配,并自动识别墙体的中心线,单开门放置位置应与墙体中心线对齐。

若关联位置的墙体有交叉、拐点、墙体长度不够等情况则只绘制实体,不与墙体关联。若与墙体关联后又离开墙体,则需要取消与墙体的关联。因此,我们可以监听图纸中的夹点编辑事件,当单开门夹点(首尾两夹点和中心点)移动后进行相关操作。(同上述标准窗一样的操作,识别自定义实体类别中添加上McDbTestSingleDoor

5.3实体交互方式

绘制单开门实体时,应先确定单开门宽度,若用户未输入单开门宽度值则设置一个默认值,然后在在图纸中指定单开门的位置。因此我们可以使用[取点对象MxCADUiPrPoint()] [获取距离对象MxCADUiPrDist()]实现交互。

根据其交互方式就可以设置出相应的绘制方法,下面为其示例代码:

   // 绘制单开门
   async function drawSingleDoor() {
       // 设置门的宽度
       const getWidth = new MxCADUiPrDist();
       getWidth.setMessage(`${t('请输入门的宽度')}`)
       let doorWidth = await getWidth.go();
       if (!doorWidth) doorWidth = 30;
       const door = new McDbTestSingleDoor();
       door.width = doorWidth;
       // 设置门的插入方式
       const getInsertType = new MxCADUiPrKeyWord();
       getInsertType.setMessage(`${t('请选择插入方式<E>')}`);
       getInsertType.setKeyWords(`[${t("任意位置")}(E)/${t("中心位置")}(M)]`)
       let insertType = await getInsertType.go();
       if (!insertType) insertType = 'E'
       // 设置门的位置
       const getDoorPos = new MxCADUiPrPoint();
       getDoorPos.setMessage(`${t('请选择门的位置')}`);
       let _wallArr: McObjectId[] = [];
       getDoorPos.setUserDraw((pt, pw) => {
           const { isRed, angle, position, wallArr } = getWallPts(pt, doorWidth, insertType);
           door.setPosition(position);
           _wallArr = wallArr;
           if (!isRed) {
               pw.setColor(0x00FFFF);
               door.doorAngle = angle;
               pw.drawMcDbEntity(door)
           } else {
               pw.setColor(0xFF0000);
               pw.drawMcDbEntity(door)
           }
       })
       const doorPos = await getDoorPos.go();
       if (!doorPos) return;
       if (_wallArr.length > 0) {
           // 关联门窗和墙
           const id = _wallArr[0];
           const wall = id.getMcDbEntity() as McDbTestWall;
           const doorId = MxCpp.getCurrentMxCAD().drawEntity(door);
           const _door = doorId.getMcDbEntity() as McDbTestSingleDoor;
           const handle: string = _door.getHandle();
           wall.doorHandles = [...wall.doorHandles, handle];
           wall.assertObjectModification(true)
           _door.wallHandle = wall.getHandle();
           _door.entityHandle = handle;
       } else {
           // 绘制单开门
           const id = MxCpp.getCurrentMxCAD().drawEntity(door);
           const _door = id.getMcDbEntity() as McDbTestSingleDoor;
           const handle: string = _door.getHandle();
           _door.entityHandle = handle;
       }
   }

5.4 运行效果

可在[在线DEMO]中查看完整绘制效果: