如果你做过一段时间的CAD二次开发一定知道画圆弧这件事看着简单实际坑不少。C#/CAD二次开发里常用图元很多前面几篇我把直线、圆、多段线的创建都过了一遍这篇单独把圆弧拉出来写是因为它同时涉及圆心、半径、起始角度、终止角度、法向量五个维度任何一个理解不到位生成的图就会和你预期差十万八千里。这篇是2021年7月初整理的第4篇内容聚焦在创建圆弧这件事本身从API选型到角度系统再到完整实操和排坑思路都会讲到适合正在用C#做CAD平台二次开发、尤其是刚把直线和圆搞定的朋友参考。1. 圆弧这个图元到底特殊在哪1.1 一张图里圆弧的本质圆只需要圆心加半径就能完全定义圆弧不行。圆弧本质上是带起止角度的圆也就是说它在圆的基础上多出了两个关键信息从哪里开始到哪里结束。这个额外信息的代价就是代码里必须显式处理角度而角度恰恰是CAD二次开发新手最容易翻车的地方。我在实际项目中见过不少这样的情况直线代码跑得溜圆的代码也一次通过结果一到圆弧画出来的要么是半圆要么角度明显偏了要么方向反了。原因基本都不是不会写代码而是没有真正理解CAD底层对圆弧的定义方式。这里先明确一个概念在主流CAD平台的托管API里圆弧就是通过圆心、法向量、半径、起始角度、终止角度这五个要素来描述的。你写的代码最终无非是给这几个赋值只是不同的构造方式赋值的来源和计算路径不一样。1.2 三种常见创建方式与选型根据场景我一般把创建圆弧的方式分成三种创建方式核心思路适合场景圆心半径起始角终止角直接构造参数最全已知圆心和角度批量生成、参数化建模起点终点圆弧上一点三点定弧构造函数直接支持用户手动点选三个点创建圆弧圆心起点包角先算起点角度再加包角得到终点角已知圆心、一个端点和需要旋转的角度扫角第一种最常用也是这篇的重点。第二种优点是对用户非常友好因为操作者脑子里想的就是过这三个点画一条弧不需要算任何角度。但要注意三个点不能共线否则圆弧退化API会直接抛出异常或者生成一个难以预料的结果。第三种是我个人非常喜欢用的一种尤其在需要生成扇形、倒角、过渡线这类图形时特别省事因为工程上很多时候给的就是起点在哪、扫过多少度。2. 创建圆弧必懂的CAD角度系统2.1 弧度制是第一个下马威CAD平台.NET API里所有角度参数用的都是弧度制。日常我们习惯说30度90度但代码里必须传0.5236、1.5708这样的弧度值。我见过不少同事直接传30下去结果圆弧只转了一个非常小的角度然后就开始怀疑人生。换算公式没什么神秘的角度转弧度就是度数乘以Math.PI / 180弧度转角度就是弧度乘以180 / Math.PI。我在项目里习惯在公共类里放两个静态方法一个DegToRad一个RadToDeg所有涉及角度的地方都走这两个方法避免每次手写公式导致不一致。2.2 零度在X轴正方向逆时针为正这是圆弧创建里最容易被忽略、也最容易导致角度看起来不对的点。CAD平台的圆弧角度系统遵循数学上的单位圆约定零度在X轴正方向逆时针方向角度增大。也就是说90度在Y轴正方向180度在X轴负方向270度在Y轴负方向。有次我在项目里做一个旋转对称的零件轮廓按照习惯从Y轴正方向算起点角度结果每个圆弧都偏了90度。排查了老半天最后发现就是参考方向理解错了。如果你也发现圆弧角度整体偏移了固定值优先检查是不是把Y轴正方向当成了零度基准。2.3 跨零度边界时的扫角计算圆弧有个特性总是从起始角逆时针走向终止角。如果终止角小于起始角它会跨过零度继续走实际扫角等于终止角减起始角再加一个完整圆周长2π。举例来说如果起始角是350度终止角是10度按终止角大于起始角才是扫角的直觉来判断会以为这是一个从350度逆时针扫过340度的大圆弧。但实际上因为终止角小于起始角CAD会从350度逆时针跨过0度走到10度扫过的是20度的小弧。反过来如果起始角是10度、终止角是350度才会得到那个340度的大弧。这个规则很容易把人绕晕。我的建议是不要依赖直觉去传起始角和终止角而是明确先算出起点角度再根据需要的扫角算出终点角度。也就是上面说的圆心起点包角方案包角本身是明确的不存在跨边界歧义。2.4 法向量的作用与影响法向量决定了圆弧所在平面以及哪一面朝上。默认情况下法向量是(0,0,1)也就是WCS的Z轴方向圆弧落在XY平面上。如果法向量变成(0,0,-1)圆弧虽然还在同一个平面但看起来走向会变成顺时针视觉上完全反转。在三维建模场景里法向量必须垂直于圆弧所在平面且必须是单位向量。如果你创建的是平面上的一般圆弧保持默认法向量就好不要随便改。只有需要把圆弧放到任意空间平面时才需要显式设置法向量并配合右手定则确认旋转方向。3. 实操做一个带圆弧轮廓的零件图3.1 明确需求与整体流程这次实操做一个小零件轮廓一个扇形垫片圆心在指定坐标半径为给定值圆弧从30度到120度再加上两条半径直线形成一个封闭的扇形区域。这个场景很典型既用圆弧又用直线而且能比较好地检验你对角度和坐标计算的理解。整体流程分三步先算圆弧起点和终点的坐标再创建圆弧和两条直线最后把它们批量写入模型空间并提交事务。代码逻辑不复杂但每一步都有需要注意的细节。3.2 完整代码与参数计算过程以某主流CAD平台的托管API为例代码如下// 请根据你实际使用的CAD平台替换命名空间 using CAD.DatabaseServices; using CAD.Geometry; using CAD.Runtime; using CAD.ApplicationServices; public static void CreateFanArc() { Document doc Application.DocumentManager.MdiActiveDocument; Database db doc.Database; Editor ed doc.Editor; using (doc.LockDocument()) using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTable bt (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord btr (BlockTableRecord)tr.GetObject( bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); Point3d center new Point3d(100, 100, 0); double radius 80; double startDeg 30; double endDeg 120; // 角度转弧度 double startAngle startDeg * Math.PI / 180; double endAngle endDeg * Math.PI / 180; // 计算圆弧起点和终点坐标 Point3d startPt new Point3d( center.X radius * Math.Cos(startAngle), center.Y radius * Math.Sin(startAngle), 0); Point3d endPt new Point3d( center.X radius * Math.Cos(endAngle), center.Y radius * Math.Sin(endAngle), 0); // 创建圆弧和两条半径直线 Arc arc new Arc(center, Vector3d.ZAxis, radius, startAngle, endAngle); Line line1 new Line(center, startPt); Line line2 new Line(center, endPt); // 写入模型空间并提交 btr.AppendEntity(arc); tr.AddNewlyCreatedDBObject(arc, true); btr.AppendEntity(line1); tr.AddNewlyCreatedDBObject(line1, true); btr.AppendEntity(line2); tr.AddNewlyCreatedDBObject(line2, true); tr.Commit(); ed.Regen(); } }这里最核心的坐标计算就是center.X radius * Math.Cos(startAngle)和center.Y radius * Math.Sin(startAngle)这其实就是极坐标到直角坐标的转换。很多圆弧角度错乱的问题追到根上就是这里用错了三角函数或者角度没转弧度。3.3 指定图层和颜色如果想把生成的圆弧和直线放到指定图层有一个很简单的方法在把实体加入数据库之后、事务提交之前直接设置实体的Layer属性。但前提是这个图层必须已经存在否则CAD会抛异常。我习惯写一个确保图层存在的辅助方法这样调用时就不用担心图层不存在private static void EnsureLayer(Database db, Transaction tr, string layerName) { LayerTable lt (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); if (!lt.Has(layerName)) { using (LayerTableRecord ltr new LayerTableRecord()) { ltr.Name layerName; lt.UpgradeOpen(); lt.Add(ltr); tr.AddNewlyCreatedDBObject(ltr, true); } } }然后这样使用EnsureLayer(db, tr, 轮廓层); arc.Layer 轮廓层; line1.Layer 轮廓层; line2.Layer 轮廓层; arc.ColorIndex 1;颜色方面ColorIndex 1表示红色2是黄色3是绿色。如果只想让实体跟随图层颜色可以不设置ColorIndex保持默认的ByLayer。3.4 通过事务批量修改已生成的实体有些场景下实体已经加入数据库但你还想统一修改它们的属性比如把一批圆弧和直线全部换成另一个图层。这时不需要重新创建实体可以在同一个事务里通过对象ID打开它们进行修改。ObjectIdCollection ids new ObjectIdCollection(); ids.Add(arc.ObjectId); ids.Add(line1.ObjectId); ids.Add(line2.ObjectId); foreach (ObjectId id in ids) { Entity ent (Entity)tr.GetObject(id, OpenMode.ForWrite); ent.Layer 新图层; ent.ColorIndex 3; }这里务必注意如果这些实体是在同一个事务里创建的打开方式要用OpenMode.ForWrite因为需要写操作。如果只是读取属性用OpenMode.ForRead就行。3.5 三点圆弧与包角圆弧的代码写法三点圆弧在API里有直接对应的构造函数传起点、终点和弧上一点即可Point3d startPoint new Point3d(0, 0, 0); Point3d endPoint new Point3d(100, 0, 0); Point3d pointOnArc new Point3d(50, 50, 0); Arc arc3P new Arc(startPoint, endPoint, pointOnArc);这段代码的效果和你手动执行CAD里三点画弧命令是一样的。需要留意的是如果三个点近似共线或者计算精度导致它们共线API会抛异常所以实际使用前最好做一次三点共线判断。包角圆弧是我推荐的方式。已知圆心、半径、起点坐标以及需要扫过的角度时先用Atan2算出起点相对圆心的角度再累加包角作为终止角double sweep 90 * Math.PI / 180; Point3d center new Point3d(50, 50, 0); Point3d startPoint new Point3d(100, 50, 0); double radius startPoint.DistanceTo(center); double startAngle Math.Atan2( startPoint.Y - center.Y, startPoint.X - center.X); Arc arcBySweep new Arc( center, Vector3d.ZAxis, radius, startAngle, startAngle sweep);Atan2的返回值范围是-π到π可能得到负角度但这个负角度作为起始角传给CAD平台不会有问题CAD会按照从起始角逆时针到终止角的规则自动处理。4. 常见问题排查与调试实录4.1 圆弧创建成功但屏幕上什么都没有这个问题我遇到太多次了排查顺序基本固定先看图层是否关闭或冻结再看实体颜色是否和背景色一致最后看视角是否在实体所在范围外。图层问题最隐蔽尤其是当实体被自动放到了当前图层而当前图层恰好是关闭状态。解决方法是把实体的Layer强制指定到一个可见图层。颜色问题也很常见实体是白色背景也是白色自然看不见。视角问题则可以用一行代码解决doc.Editor.ZoomExtents();如果没有调Regen在某些视图状态下也会出现数据里有、显示上没有的情况。手动调用一次doc.Editor.Regen()就能强制重生成图形。4.2 圆弧角度整体偏移了90度这是零度参考方向搞错了。CAD平台的角度零度在X轴正方向不是你习惯的任何方向。如果你是按Y轴正方向为0度来算的所有角度都会偏90度。排查方法很简单把已知角度代入圆弧的起点坐标计算看算出来的点坐标是否符合预期。如果起点坐标偏了角度参考系统肯定有问题如果起点坐标对但圆弧形状不对那就要看法向量和角度方向。4.3 圆弧方向总是反的默认法向量是(0,0,1)圆弧逆时针从起始角走向终止角。如果你看到圆弧是顺时针走的优先检查是否手动设置了法向量尤其是设成了(0,0,-1)。有些老代码里会把法向量传给构造函数一不留神就导致方向反转。还有一种情况是起始角和终止角传反了比如想把90度到180度的弧画出来却传了180和90结果CAD走了另一个方向形成一段优弧。解决这类问题我建议在代码里显式注释出起点角度、终点角度对应的坐标一眼就能看出传参是否合理。4.4 圆弧看起来不圆像多边形这个不是创建逻辑错了而是显示精度的问题。CAD的圆弧在屏幕上是用一系列短直线段近似显示的视图放大到一定程度就能看到折线感。这种情况不影响数据正确性图纸打印或者导出时也不会有问题。如果想在屏幕上看到更平滑的圆弧可以调整CAD平台的圆弧显示精度变量但不推荐在二次开发代码里随便改这个参数容易影响整个绘图环境的性能。4.5 事务没提交修改全部丢失新手最容易犯的错误是只写了StartTransaction却忘了tr.Commit()。结果代码跑完模型空间里一切照旧也没有报错。这个问题在调试时非常迷惑因为断点显示实体确实创建了但就是不写入数据库。我的经验是一开始就把事务模板背下来开启事务、获取块表、获取模型空间、创建实体、追加实体、提交事务。每一步都不要省。如果你是在一个长循环里创建大量实体也记得最后统一提交一次不要每创建一个实体就提交一次性能会差很多。4.6 常见问题速查表现象最可能原因快速解决办法画完看不到图层关闭或颜色与背景一致检查Layer和ColorIndex再ZoomExtents角度偏90度零度参考方向错误确认按X轴正方向为0度计算方向反了法向量为(0,0,-1)或起止角反了检查法向量和起止角传参顺序圆弧不圆显示精度不足调整显示相关变量或忽略修改丢失事务未Commit补上tr.Commit()三点画弧报错三点共线计算共线判断或换用其他构造方式5. 一些写在后面的经验圆弧这个东西代码量不大但每个参数背后都是一个几何知识点。把这几个点串起来其实能帮助你建立对整个CAD几何系统的理解。我最想说的是一个实践建议所有涉及圆弧的地方不要直接在业务代码里写角度换算和坐标计算抽一个工具类统一处理。这个工具类至少提供三个方法角度转弧度、弧度转角度、根据圆心半径和角度求坐标。我在几个项目里都是这么做的后期维护起来特别省心。另外如果只是做图形预计算、碰撞检测不一定要把圆弧立刻加入数据库。可以先使用几何类在内存里把圆弧对象构建出来做各种判断确认没问题之后再创建数据库实体。这样既避免了事务回滚的麻烦也让计算逻辑更清晰。这个系列后面我打算再写一篇关于多段线里圆弧段的内容。工程图纸里很多圆弧并不是独立的圆弧图元而是作为多段线的一部分存在的那个凸度参数又是另一套逻辑。先把独立圆弧吃透再看多段线弧段会轻松很多。