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

推荐订阅源

有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
N
Netflix TechBlog - Medium
WordPress大学
WordPress大学
罗磊的独立博客
H
Help Net Security
MongoDB | Blog
MongoDB | Blog
A
About on SuperTechFans
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
Docker
云风的 BLOG
云风的 BLOG
Microsoft Security Blog
Microsoft Security Blog
Blog — PlanetScale
Blog — PlanetScale
P
Proofpoint News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
I
InfoQ
J
Java Code Geeks
博客园 - 聂微东
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
美团技术团队
小众软件
小众软件
Stack Overflow Blog
Stack Overflow Blog
C
Check Point Blog

博客园 - 梦想CAD控件

网页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二次开发家装设计软件) 在线CAD绘制墙体(WEB端开发室内设计软件) (在线三维参数化建模)网页三维CAD中加载和保存STEP模型 (H5在线CAD SDK)在线CAD中线型表的使用方法 (web查看三维CAD图纸)在三维网页CAD中绘制一个窗户模型
网页CAD如何在web端实现AutoCAD的绘圆弧功能
梦想CAD控件 · 2026-09-08 · via 博客园 - 梦想CAD控件

前言

在 Web 端实现 CAD 绘图,核心难点不仅在于图形的渲染,更在于如何复刻桌面端严谨的几何逻辑与交互体验。MxCAD 作为专业的 Web CAD 解决方案,提供了完整的绘图能力体系。

对于开发者而言,MxCAD 的能力分为两个层次:

  1. 开箱即用的内置命令: 系统预置了符合 CAD 标准的一系列绘图指令,可直接调用,满足基础需求。

  2. 精细化的 API 二次开发: 针对复杂的业务场景,MxCAD 提供了底层的几何类库与交互接口,允许开发者像搭建积木一样,实现如"多模式圆弧绘制"等高度定制化的专业功能。

本文将首先介绍如何快速调用内置命令,随后以工程制图中最为复杂的"圆弧绘制"为例,深入解析如何利用 MxCAD API 实现精细化的图形开发。

image-20260817161806523

一、内置绘图命令的快速调用

MxCAD 为开发者预置了一套完善的基础图形绘制命令体系。这些命令经过高度封装,用户无需关注底层的几何计算与渲染逻辑,即可直接调用以执行标准的绘图操作。

MxCAD 内置的命令覆盖了 CAD 绘图中最常用的几大类图元:

  • 线性与点类: 包含 Mx_Line(直线)、Mx_Point(点)、Mx_Xline(构造线)以及 Mx_Ray(射线),满足基础定位与连线需求。

  • 多边形与矩形类: 提供 Mx_Polygon(正多边形)和 Mx_Rectang(矩形),支持快速生成规则几何闭合区域。

  • 基础曲线类: 核心包含 Mx_Arc(圆弧)和 Mx_Circle(圆),这是工程制图中不可或缺的元素。

  • 复合曲线类: 集成了 Mx_Pline(多段线)和 Mx_Spline(样条曲线),用于处理更复杂的路径与拟合线条。

调用方式说明:

内置命令采用了标准化的命名规范,支持直接调用。例如需要绘制一段基础圆弧时,只需在代码中或命令行中直接调用 Mx_Arc 命令,系统便会自动激活圆弧绘制交互流程,极大地简化了基础功能的开发流程。

image-20260817161826570

二、"圆弧"绘制的精细化讲解

在 CAD 绘图中,圆弧(Arc)的绘制逻辑远比直线复杂。AutoCAD 等桌面软件之所以强大,是因为它提供了"三点法"、"起点-圆心-端点"、"起点-角度-弦长"等多种几何约束模式。

MxCAD 的 API 开发核心,就在于如何通过代码精准复刻这些几何逻辑。本节将拆解完整圆弧绘制命令的实现步骤,展示如何利用 MxCAD 提供的几何类库与交互接口,实现精细化的多模式圆弧绘制。

1. 圆弧绘制的核心数据结构

在深入具体绘制方式之前,需要了解圆弧在 MxCAD 中的核心参数。无论采用哪种绘制模式(三点绘圆弧除外),圆弧最终都由以下四个参数唯一确定:

  • 圆心(center): 圆弧所在圆的圆心坐标

  • 半径(radius): 圆弧的半径大小

  • 起始角(startAngle): 圆弧的起始角度(弧度制)

  • 终止角(endAngle): 圆弧的终止角度(弧度制)

MxCAD 中圆弧实体通过 McDbArc类表示,所有绘制模式的最终目标都是计算出这四个参数,然后赋值给圆弧对象并添加到数据库。

import { McDbArc, MxCpp } from "mxcad";
const arc = new McDbArc();
arc.startAngle = startAngle;
arc.endAngle = endAngle;
arc.radius = radius;
arc.center = center;
MxCpp.mxcad.drawEntity(arc);

2. 圆弧的多种绘制方式

在实际 CAD 绘图中,用户可能掌握的信息各不相同——有时知道圆弧经过的三个点,有时知道圆心和起止角度,有时则需要相切连接。MxCAD 的 API 为每种场景都提供了对应的构造方法。下面逐一解析源码中实现的七种核心绘制模式。

2.1 三点画弧(3P)

知道圆弧起点、弧上任意一点(第二点)和圆弧端点,系统自动计算过这三点的唯一圆,生成圆弧。

import { McDbArc, MxCpp } from "mxcad";
/**
 * 三点计算圆弧
 * startPoint 圆弧起始点
 * anyPoint 圆弧任意点
 * endPoint 圆弧结束点
 */
const arc = new McDbArc();
arc.computeArc(startPoint.x, startPoint.y, anyPoint.x, anyPoint.y, endPoint.x, endPoint.y);
MxCpp.mxcad.drawEntity(arc);

2.2 起点、圆心、端点(SCE)

知道圆弧起点,圆心,圆弧端点,计算并生成圆弧。

适用于已知圆心和半径范围的场景,如机械零件的倒角绘制。

/** 辅助函数:计算两个坐标的角度 */
function angleTo(x1: number, y1: number, x2: number, y2: number): number {
  const x = x1 - x2;
  const y = y1 - y2;
  let angle_temp = 0;
  if (x == 0) {
    angle_temp = Math.PI / 2;
  } else {
    angle_temp = Math.atan(Math.abs(y / x));
  }
  if (x < 0 && y >= 0) {
    angle_temp = Math.PI - angle_temp;
  } else if (x < 0 && y < 0) {
    angle_temp = Math.PI + angle_temp;
  } else if (x >= 0 && y < 0) {
    angle_temp = Math.PI * 2.0 - angle_temp;
  }
  return angle_temp;
}
import { McDbArc, MxCpp } from "mxcad";
/**
 * startPoint:圆弧起始点
 * pt:圆弧圆心
 * endPoint:圆弧结束点
 */
// 用户指定圆心
const center = pt;
// 半径由圆心到起点的距离自动计算
const radius = center.distanceTo(startPoint);
// 起始角:起点相对于圆心的极角(减去PI,统一坐标系)
const startAngle = angleTo(center.x, center.y, startPoint.x, startPoint.y) - Math.PI;
// 终止角:当前鼠标位置相对于圆心的极角
const endAngle = angleTo(center.x, center.y, endPoint.x, endPoint.y) - Math.PI;
// 赋值给圆弧对象并绘制
// center、radius、startAngle、endAngle

2.3 起点、圆心、角度(CSA)

知道圆弧起点、圆弧圆心和圆弧夹角(弧度值),系统自动生成指定角度的圆弧。常用于机械制图中已知旋转角度的场景,如螺栓孔的均布排列。

// 辅助函数:由圆心、起始角和圆弧夹角反推终点的直角坐标
function findEndPoint(C: McGePoint3d, startAngle: number, centralAngle: number, radius: number): McGePoint3d {
    const endAngle = startAngle + centralAngle;
    return new McGePoint3d(
        C.x + radius * Math.cos(endAngle),
        C.y + radius * Math.sin(endAngle)
    );
}
// 辅助函数:由圆心、开始点、结束点和圆弧起始角度反推圆弧结束角度
function findEndAngle(C: McGePoint3d, P1: McGePoint3d, P2: McGePoint3d, startAngle: number) {
  // 计算起始向量和结束向量
  const V1 = { x: P1.x - C.x, y: P1.y - C.y };
  const V2 = { x: P2.x - C.x, y: P2.y - C.y }
  // 计算起始角和结束角
  const theta1 = Math.atan2(V1.y, V1.x);
  let theta2 = Math.atan2(V2.y, V2.x);
  // 计算结束角相对于起始角的夹角差
  let deltaTheta = theta2 - theta1;
  // 如果夹角差为负值,则加上 2*pi
  if (deltaTheta < 0) {
    deltaTheta += 2 * Math.PI;
  }
  // 计算结束角
  const endAngle = startAngle + deltaTheta;
  return endAngle;
}
/**
 * centerAngle:圆弧夹角
 * center:圆弧圆心
 * startPoint:圆弧开始点
 */
// 半径由圆心到起点的距离自动计算
const radius = center.distanceTo(startPoint);
// 起始角固定为起点相对于圆心的极角(圆弧开始角)
const startAngle = Math.PI + angleTo(center.x, center.y, startPoint.x, startPoint.y);
// 根据起始角和圆弧夹角计算终点
const endPoint = findEndPoint(center, startPoint, centerAngle, radius);
// 计算结束角度
const endAngle = findEndAngle(center, startPoint, endPoint, startAngle);
// 赋值给圆弧对象并绘制
// center、radius、startAngle、endAngle

2.4 起点、圆心、弦长(CSL)

知道圆弧起点、圆弧圆心和圆弧弦长(弧两端点之间的直线距离),系统自动计算对应的圆弧。适用于已知弧跨度的工程场景。

// 辅助函数:由圆弧圆心、圆弧开始点、弦长、圆弧半径反推圆弧结束点
function calculateEndPoint(centerX, centerY, startX, startY, chordLength, radius): McGePoint3d {
    let vectorX = startX - centerX;
    let vectorY = startY - centerY;
    // 弦长对应的圆心角
    let theta = chordLength / radius;
    // 旋转向量得到终点坐标
    let endX = vectorX * Math.cos(theta) - vectorY * Math.sin(theta);
    let endY = vectorX * Math.sin(theta) + vectorY * Math.cos(theta);
    endX += centerX;
    endY += centerY;
    return new McGePoint3d(endX, endY);
}
/**
 * length:弦长
 * center:圆弧圆心
 * startPoint:圆弧开始点
 */
// 半径由圆心到起点的距离自动计算
const radius = center.distanceTo(startPoint);
// 起始角固定为起点相对于圆心的极角(圆弧开始角)
const startAngle = Math.PI + angleTo(center.x, center.y, startPoint.x, startPoint.y);
// 用户输入弦长后,调用辅助函数计算终点
const endPoint = calculateEndPoint(center.x, center.y, startPoint.x, startPoint.y, length, arc.radius);
// 计算终点相对于圆心的极角作为终止角
const endAngle = angleTo(center.x, center.y, endPoint.x, endPoint.y) - Math.PI;
// 赋值给圆弧对象并绘制
// center、radius、startAngle、endAngle

2.5 起点、端点、角度(SEA)

知道圆弧起点、圆弧端点和圆弧夹角,系统自动反算出圆心位置并生成圆弧。这是一种"先定跨度、再定弧度"的绘制方式。

/** 根据开始点和节点和圆心夹角 计算除圆心位置 */
function findArcCenter(P1: McGePoint3d, P2: McGePoint3d, angle: number): McGePoint3d {
  // 计算圆弧的中点
  const M = {
    x: (P1.x + P2.x) / 2,
    y: (P1.y + P2.y) / 2
  };
  // 计算圆弧的半径
  const r = Math.sqrt((P1.x - M.x) ** 2 + (P1.y - M.y) ** 2) / Math.sin(angle / 2);

  // 计算圆心位置
  const V = { x: P2.x - P1.x, y: P2.y - P1.y };
  const N = { x: V.x / Math.sqrt(V.x ** 2 + V.y ** 2), y: V.y / Math.sqrt(V.x ** 2 + V.y ** 2) };
  const R = { x: -N.y, y: N.x };
  const x = M.x + r * R.x
  const y = M.y + r * R.y
  return new McGePoint3d(x, y)
}
/**
 * centerAngle:圆弧夹角
 * endPoint:圆弧结束点
 * startPoint:圆弧开始点
 */
// 由起点、端点和圆弧夹角反算圆心
const center = findArcCenter(startPoint, endPoint, centerAngle);
// 半径即圆心到起点的距离
const radius = center.distanceTo(startPoint);
// 开始角度
const startAngle = angleTo(center.x, center.y, startPoint.x, startPoint.y) - Math.PI
// 结束角度
const endAngle = angleTo(center.x, center.y, endPoint.x, endPoint.y) - Math.PI
// 赋值给圆弧对象并绘制
// center、radius、startAngle、endAngle

图片3

2.6 起点、端点、切向(SED)

知道圆弧起点、圆弧端点和圆弧起点的切线方向,系统自动生成与该方向相切的圆弧。这是高级绘图功能,常用于机械制图中光滑过渡的连接弧。

// 辅助函数:由圆弧开始点、圆弧结束点和切向量反推圆弧凸度
function CMxDrawPolylineDragArcDraw_CalcArcBulge(firstPoint: McGePoint3d, nextPoint: McGePoint3d, vecArcTangent: McGeVector3d): number {
  if (firstPoint.isEqualTo(nextPoint))
    return 0.0;
  let midPt = firstPoint.c().addvec(nextPoint.c().sub(firstPoint).mult(0.5));

  let vecMid = nextPoint.c().sub(firstPoint);
  vecMid.rotateBy(Math.PI / 2.0, McGeVector3d.kZAxis);

  let tmpMidLine = new McDbLine(midPt, midPt.c().addvec(vecMid));

  let vecVertical: McGeVector3d = vecArcTangent.c();
  vecVertical.rotateBy(Math.PI / 2.0, McGeVector3d.kZAxis);

  let tmpVerticalLine = new McDbLine(firstPoint, firstPoint.c().addvec(vecVertical));

  let aryPoint: McGePoint3dArray = tmpMidLine.IntersectWith(tmpVerticalLine, McDb.Intersect.kExtendBoth);
  if (aryPoint.isEmpty())
    return 0.0;

  let arcCenPoint = aryPoint.at(0);

  let dR = arcCenPoint.distanceTo(firstPoint);
  vecMid.normalize();
  vecMid.mult(dR);

  let arcMidPt1 = arcCenPoint.c().addvec(vecMid);
  let arcMidPt2 = arcCenPoint.c().subvec(vecMid);
  let vecArcDir1 = arcMidPt1.c().sub(firstPoint);
  let vecArcDir2 = arcMidPt2.c().sub(firstPoint);
  let arcMidPt = arcMidPt1;
  if (vecArcDir1.angleTo1(vecArcTangent) > vecArcDir2.angleTo1(vecArcTangent)) {
    arcMidPt = arcMidPt2;
  }
  return MxCADUtility.calcBulge(firstPoint, arcMidPt, nextPoint).val;
}
import { McDbArc, MxCpp, McDbPolyline } from "mxcad";
/**
 * vecArcTangent:圆弧起点切线向量
 * endPoint:圆弧结束点
 * startPoint:圆弧开始点
 */
// 利用 MxCAD 几何库计算圆弧的 bulge(拱高比)
const bulge = CMxDrawPolylineDragArcDraw_CalcArcBulge(startPoint,endPoint,vecArcTangent);
// 通过多段线(Polyline)的 bulge 属性绘制圆弧
pl = new McDbPolyline();
pl.addVertexAt(startPoint, bulge);
pl.addVertexAt(endPoint);
// 根据多段线得到圆弧上的任意点位,再根据三点画圆弧绘制
const length = pl.getLength().val;
const midPt = pl.getPointAtDist(length / 2).val;
const arc = new McDbArc();
arc.computeArc(startPoint.x, startPoint.y, midPt.x, midPt.y, endPoint.x, endPoint.y);
MxCpp.mxcad.drawEntity(arc);

2.7 起点、端点、半径(SER)

知道圆弧起点、圆弧端点和圆弧半径,系统自动求解满足条件的圆心位置(通常有两个解),并生成圆弧。适用于已知弧跨度和拱高的场景。

// 辅助函数:由圆弧开始点、圆弧结束点、圆弧半径反推圆弧圆心
function findArcCenterWithRadius(P1, P2, radius):McGePoint3d {
    // 圆心到弦中点的距离
    const d = Math.sqrt(
        radius ** 2 - ((P1.x - P2.x) ** 2 + (P1.y - P2.y) ** 2) / 4
    );
    // 弦的中点
    const M = {
        x: (P1.x + P2.x) / 2,
        y: (P1.y + P2.y) / 2
    };
    // 从中点沿垂直方向偏移 d 得到圆心
    return new McGePoint3d(
        M.x - d * (P2.y - P1.y) / Math.sqrt((P1.x - P2.x) ** 2 + (P1.y - P2.y) ** 2),
        M.y - d * (P1.x - P2.x) / Math.sqrt((P1.x - P2.x) ** 2 + (P1.y - P2.y) ** 2)
    );
}
/**
 * radius:圆弧半径
 * endPoint:圆弧结束点
 * startPoint:圆弧开始点
 */
// 由起点、端点和半径反算圆心
const center = findArcCenterWithRadius(startPoint, endPoint, radius);
// 圆弧开始角度
const startAngle = angleTo(center.x, center.y, startPoint.x, startPoint.y) - Math.PI
// 圆弧结束角度
const endAngle = angleTo(center.x, center.y, endPoint.x, endPoint.y) - Math.PI
// 赋值给圆弧对象并绘制
// center、radius、startAngle、endAngle

3. 整合多种绘制方式

                                   开始
                                    |
                                    v
                            指定圆弧起点(startPoint)
                                    |
                                    v
                             是否输入关键词 [C/E]?
                             /                \
                           C(是)              E(是)
                             |                  |
                             v                  v
                        指定圆心(center)    指定端点(endPoint)
                             |                  |
                             v                  v
                        指定端点/角度/弦长   指定圆心/角度/切向/半径
                         [A/L/R] 子选项      [A/D/R] 子选项
                             |                  |
                             +--------+---------+
                                      |
                                      v
                                 绘制圆弧并返回

在实际开发中,我们不需要为每种绘制模式编写独立的函数,而是可以将所有模式整合到一个统一的交互流程中。下面展示实现的完整 drawArc 函数,它能有效管理用户的交互输入,支持在绘制过程中动态切换绘制模式。
该绘制函数采用“起点锚定 + 关键字分流”的交互逻辑,通过动态分支适配不同的绘图习惯:
  1. 确立基准:首先强制获取圆弧起点,作为所有后续计算的绝对坐标原点。

  2. 模式决策:在第二步交互中,根据用户输入的关键字将逻辑一分为二:

    • 输入 C (圆心模式):优先锁定圆心位置,随后通过角度或弦长确定终点。适用于已知中心点的场景。

    • 输入 E (端点模式):优先锁定终点位置,随后通过半径或切向反推圆弧曲率。适用于已知连接目标的场景。

  3. 闭环绘制:无论走哪条分支,最终都会补全几何约束(如半径、角度),从而生成精确的圆弧实体。

/**
  - `startPoint`:已输入的起点(`undefined` 表示尚未输入)
  - `anyPoint`:三点法中的第二点(弧上点)
  - `endPoint`:已输入的端点
  - `center`:已输入的圆心
  - `isCenterArc`:是否处于"圆心优先"模式(SCE/CSA/CSL)
  - `currentBranch`:当前分支标识(`"E"` 端点优先、`"C"` 圆心优先、`"L"` 弦长模式)
  */
async function drawArc() {
    let mxcad = MxCpp.getCurrentMxCAD();
    const getPoint = new MxCADUiPrPoint();
    const arc = new McDbArc();
    let startPoint: McGePoint3d;
    let anyPoint: McGePoint3d;
    let endPoint: McGePoint3d;
    let center: McGePoint3d;
    let radius: number;

    // 动态预览回调
    let draw: (currentPoint: McGePoint3d, pWorldDraw: McEdGetPointWorldDrawObject) => void = () => { };
    let msg = "\n指定圆弧的起点:";
    let key = "[圆心(C)]";
    let isCenterArc = false;
    let currentBranch: "E" | "C" | "L";
    // 主交互循环
    while (true) {
        getPoint.setUserDraw(draw);
        getPoint.setMessage(msg);
        getPoint.setKeyWords(key);
        const point = await getPoint.go() as McGePoint3d;
        // --- 处理命令行关键词 ---
        if (getPoint.isKeyWordPicked("C")) {
            // 切换到"圆心优先"模式:起点 → 圆心 → ...
            currentBranch = "C";
            isCenterArc = true;
            if (startPoint) {
                getPoint.setMessage("\n指定圆弧的圆心");
                const pt = await getPoint.go();
                if (!pt) return;
                center = pt;
                msg = "\n指定圆弧的端点(按住 ctrl 键取相反方向)";
                key = "[角度(A)/弦长(L)/修改半径(R)]";
                getPoint.setLastInputPoint(center);
                guideLine.startPoint = center;
                radius = center.distanceTo(startPoint);
                // 设置动态预览回调
                draw = (currentPoint, pWorldDraw) => {
                    angle = angleTo(center.x, center.y, startPoint.x, startPoint.y) - Math.PI;
                    angle1 = angleTo(center.x, center.y, currentPoint.x, currentPoint.y) - Math.PI;
                    guideLine.endPoint = currentPoint;
                    drawCenterArc(currentPoint, pWorldDraw);
                };
            }
            continue;
        }
        if (isCenterArc) {
            // --- 圆心优先模式下的子分支 ---
            
            // 关键词 R:修改半径
            if (getPoint.isKeyWordPicked("R")) {
                getPoint.setMessage("\n指定圆弧的半径");
                const pt = await getPoint.go();
                if (!pt) return;
                radius = pt.distanceTo(center);
                msg = "\n指定圆弧的端点(按住 ctrl 键取相反方向)";
                key = "[角度(A)/弦长(L)/修改半径(R)]";
                continue;
            }
            // 关键词 A:指定角度
            if (getPoint.isKeyWordPicked("A")) {
                msg = "指定圆弧夹角(按住 ctrl 键取相反方向)";
                angle = Math.PI + angleTo(center.x, center.y, startPoint.x, startPoint.y);
                if (!radius) radius = center.distanceTo(startPoint);
                draw = (currentPoint, pWorldDraw) => {
                    const centerAngle = angleTo(center.x, center.y, currentPoint.x, currentPoint.y) - Math.PI;
                    endPoint = findEndPoint(center, angle, centerAngle, radius);
                    angle1 = findEndAngle(center, startPoint, endPoint, angle);
                    drawCenterArc(currentPoint, pWorldDraw);
                };
                continue;
            }
            // 关键词 L:指定弦长
            if (getPoint.isKeyWordPicked("L")) {
                currentBranch = "L";
                msg = "\n指定弦长(按住 ctrl 键取相反方向)";
                getPoint.setLastInputPoint(startPoint);
                draw = (currentPoint, pWorldDraw) => {
                    const length = currentPoint.distanceTo(startPoint);
                    if (length > radius * 2) {
                        guideLine.endPoint = currentPoint;
                        pWorldDraw.drawMcDbEntity(guideLine);
                        return;
                    }
                    endPoint = calculateEndPoint(center.x, center.y, startPoint.x, startPoint.y, length, arc.radius);
                    angle1 = angleTo(center.x, center.y, endPoint.x, endPoint.y) - Math.PI;
                    drawCenterArc(currentPoint, pWorldDraw);
                };
                continue;
            }
            // 关键词 D:切向模式
            if (getPoint.isKeyWordPicked("D")) {
                getPoint.setMessage("\n指定圆弧起点的切向");
                getPoint.setLastInputPoint(startPoint);
                let pl = new McDbPolyline();
                getPoint.setUserDraw((currentPoint, pWorldDraw) => {
                    const vecArcTangent = currentPoint.sub(startPoint);
                    const bulge = CMxDrawPolylineDragArcDraw_CalcArcBulge(
                        isOpposite ? startPoint : endPoint,
                        isOpposite ? endPoint : startPoint,
                        vecArcTangent
                    );
                    pl = new McDbPolyline();
                    pl.addVertexAt(startPoint, bulge);
                    pl.addVertexAt(endPoint);
                    guideLine.endPoint = currentPoint;
                    pWorldDraw.drawMcDbEntity(pl, true);
                    pWorldDraw.drawMcDbEntity(guideLine);
                });
                const pt = await getPoint.go();
                if (!pt) return;
                mxcad.drawEntity(pl);
                return;
            }
            // 直接点击端点
            if (!center) {
                center = point;
                continue;
            }
            if (!startPoint) {
                startPoint = point;
                radius = startPoint.distanceTo(center);
                msg = "\n指定圆弧的端点";
                key = "[角度(A)/弦长(L)/修改半径(R)]";
                continue;
            }
            // 绘制完成
            mxcad.drawEntity(arc);
            return;
        }
        // --- 非圆心优先模式(三点法 / 端点优先) ---
        if (getPoint.isKeyWordPicked("E")) {
            // 切换到"端点优先"模式:起点 → 端点 → ...
            currentBranch = "E";
            getPoint.setMessage("\n指定圆弧的端点");
            getPoint.setKeyWords("");
            startPoint = startPoint;
            const pt = await getPoint.go();
            if (!pt) return;
            endPoint = pt;
            isCenterArc = true;
            msg = "\n指定圆弧的圆心(按住 ctrl 键取相反方向)";
            key = "[角度(A)/方向(D)/半径(R)]";
            guideLine.startPoint = endPoint;
            draw = (currentPoint, pWorldDraw) => {
                center = currentPoint;
                radius = center.distanceTo(startPoint);
                angle = angleTo(center.x, center.y, startPoint.x, startPoint.y) - Math.PI;
                angle1 = angleTo(center.x, center.y, endPoint.x, endPoint.y) - Math.PI;
                drawCenterArc(currentPoint, pWorldDraw);
            };
            continue;
        }
        // 三点法主流程
        if (!startPoint) {
            startPoint = point;
            msg = "\n指定圆弧第二个点";
            key = "[圆心(C)/端点(E)]";
            draw = (currentPoint, pWorldDraw) => {
                pWorldDraw.drawLine(startPoint.toVector3(), currentPoint.toVector3());
            };
            continue;
        }
        if (!anyPoint) {
            anyPoint = point;
            msg = "\n指定圆弧的端点";
            draw = (currentPoint, pWorldDraw) => {
                endPoint = currentPoint;
                arc.computeArc(startPoint.x, startPoint.y, anyPoint.x, anyPoint.y, endPoint.x, endPoint.y);
                pWorldDraw.drawMcDbEntity(arc, true);
            };
            continue;
        }

        // 绘制完成
        mxcad.drawEntity(arc);
        return;
    }
}
 MxFun.addCommand("Mx_Arc", drawArc);

三、基础图形开发的通用范式与总结

MxCAD 的基础图形开发并非孤立的知识堆砌,而是一套逻辑严密、高度复用的通用范式。无论是直线、圆、多段线还是文字标注,其底层逻辑都遵循着“交互定义参数 -> 几何计算约束 -> API 实例化实体”的标准化流程。

1. 通用开发思路:三步走策略

  • 第一步:交互参数收集(UI 层) 这是所有图形绘制的起点。利用 MxOcxUiPrPoint(点选)、MxOcxUiPrDist(距离)、MxOcxUiPrAngle(角度)等交互类,精准捕获用户的输入意图。核心在于利用“关键字(Keywords)”机制,在单一的交互流中通过 addKeyword 动态切换子命令(如圆绘制的“三点/两点/切点”模式),实现复杂功能的轻量化交互。

  • 第二步:几何逻辑解算(Math 层) 这是连接用户意图与图形实体的桥梁。在获取基础点位后,需通过几何算法反推图形的核心属性。例如,由三点反求圆心与半径,或由起点、端点及切向推算控制点。这一层要求开发者具备扎实的解析几何基础,确保在极端数据下(如三点共线)程序的健壮性。

  • 第三步:实体构建与上屏(API 层) 这是最终的执行阶段。将解算出的几何参数(如圆心坐标、半径值、起始角)注入到对应的数据库实体类(如 McDbCircleMcDbLine)中,并通过事务处理将其添加至模型空间。此阶段需严格遵循 CAD 的数据库规范,确保图层、线型等属性的正确挂载。

2. 总结

MxCAD 的 API 开发本质上是对 CAD 绘图行为的代码化重构。从简单的直线绘制到复杂的圆弧状态机,其核心都在于对“参数约束”的精准控制。掌握这一套从 UI 交互到几何解算,再到实体生成的完整链路,能让我们轻松驾驭基础图形的开发,更为后续处理块参照、属性文字乃至复杂参数化设计奠定了逻辑基石。以“交互与计算分离、状态与逻辑解耦”的设计原则,构建出符合工程规范的 CAD 应用。