CAD二次开发之Hatch填充:从对象模型到批量面积统计 做CAD二次开发的人早晚都会跟填充Hatch打交道。哪怕你专职写图框、写标注、写块属性也总有那么一天用户会甩给你一句能不能把这些封闭区域全部填上剖面线能不能把每块填充的面积自动统计出来这时候如果你还只把填充当成“画几根线”的小事后面大概率会被边界、比例、关联性、事务这些坑连环锤。这篇文章就是冲着“填充二次开发”去的。我把自己在实际项目里碰过的坑、写过的套路、还有调API时总结出来的经验整理出来从对象模型讲到代码实现再讲到批量处理和排错。内容以.NET托管API为主适合刚上手CAD插件开发的朋友也适合已经被Hatch API折磨过、想系统搞懂填充机制的开发者。看完你至少能搞清楚填充在数据库里到底存了什么、怎样用代码创建合格的填充、怎样批量提取面积、以及边界报错时从哪里入手查。1. 填充二次开发到底在干什么1.1 填充不是画几根线而是一整套对象模型很多第一次接触填充二次开发的人会下意识以为填充就是把一组斜线画到封闭区域里。这种理解在视觉上没错但在CAD的对象模型里完全不是一回事。你在界面里点一下“填充”按钮软件往数据库里写入的其实是一个独立的填充对象而不是一堆Line实体。这个填充对象至少携带了这几类信息图案定义是斜线还是网格、还是纯色、变换参数比例、角度、是否双向、一个或多个边界环、以及可选的关联标志。边界环本身不是实体它是由闭合曲线的引用关系组成的集合CAD靠这些环去计算填充到底覆盖哪个区域。为什么说搞清楚这个模型很重要因为如果你用批量画线的方式去模拟填充画出来的东西只是“看起来像填充”一裁剪、一打印、一统计面积就露馅了。真正的填充对象可以响应边界变化可以通过Area属性直接读出面积可以和剖面线样式联动这些都不是普通线条能替代的。理解这一点你才算是从“画图思维”切换到“对象思维”。另外填充对象和普通图元一样存在于块表记录里受事务管理可以被选中、被编辑、被删除。这意味着它和块、标注、多段线处在同一个数据库生态里。你后续写的批量修改、统计面积、版本对比工具都要围绕这个对象模型来转。1.2 哪些场景逼着你必须用代码操作填充我自己遇到的填充二次开发需求总结下来大概能分成四类你可以对照看看手头的项目属于哪一类。第一类是批量出图。比如某个设备厂把所有非金属垫片在装配图里填上剖面线还要区分材质用不同的图案。人工操作是一个区域一个区域去点图纸一多就非常痛苦而且容易漏。用代码做就是遍历所有符合条件的区域按材质规则指定图案名和比例几十秒全部搞定。第二类是面积统计。填充算面积是CAD用户的天然需求但单纯界面上看面积只能一次看一个。土地测绘、户型图、零件表面积统计这类场景真正需要的是把每个封闭区域的面积批量导出到表格或者外部文件。这就必须从填充对象的Area属性或者从闭合边界去计算。第三类是遮罩和覆盖。用SOLID实体填充遮挡多余的图形元素是很多制图规范里常见的做法。这种填充要求边界精确、颜色可控、层可控用手点容易出错代码写出来反而非常稳定。第四类是图纸检查。比如检查是否存在应该填充但没有填充的区域或者检查填充图案是否符合公司标准。这种审核类工具也依赖对填充对象的深度读取。除了这四类你还会遇到一些边角需求比如把填充边界提取成多段线、把渐变填充改成实体填充、把关联填充批量改为非关联以便打印。这些其实都围绕同一个核心你能不能在代码层面熟练地创建、读取、修改一个填充对象。顺便说一句如果你还在犹豫用哪种开发方式我建议优先考虑.NET托管API。LISP处理简单逻辑很快但涉及窗体、批量选择集、复杂异常捕获时会很吃力VBA在较新的CAD版本上兼容性是个隐患ObjectARX性能最好但开发周期和门槛都高。.NET托管API在功能覆盖、开发效率、异常处理、内存安全之间取了一个相对好的平衡点是当前做业务型插件的主流选择。2. 动手前必须搞懂的核心概念与API结构2.1 Hatch对象的三类关键信息填充对象在API层面最核心的属性我习惯分成三类来记。第一类是图案来源与名称。图案来源用PatternType表示常见的是预定义模式PreDefined、用户自定义模式UserDefined和自定义文件模式CustomDefined。预定义就是软件自带的那些比如ANSI31、ANSI37、SOLID用户自定义一般是指你需要通过线型和角度现场组合出来的简单案自定义文件则是从外部图案文件加载的。名称就是PatternName创建时你必须保证这个名字在当前环境中真的存在否则后面EvaluateHatch会给你脸色看。第二类是变换参数。图案不是画上去就完事它需要比例和角度来控制疏密和方向。比例由PatternScale控制角度由PatternAngle控制。这两个参数很容易被忽略但恰恰是“界面填充效果好、代码填充效果丑”的最大原因。默认比例往往是1而CAD自带的ANSI31在1:1比例下线条间距非常小填到A4图框里可能糊成一片黑色角度默认是0但实际剖面线经常要转45度或者按构件方向走。第三类是边界与关联信息。填充对象内部维护着一个边界环集合每个环由一组闭合实体的引用构成。关联标志Associative则决定边界变化时填充是否自动更新。这块内容比较多我单独拎出来讲。把这三类信息分开记有一个好处出问题时你能快速定位是哪一类出了问题。图案不对查PatternType和PatternName疏密不对查PatternScale朝向不对查PatternAngle范围不对查边界环更新异常查Associative。这个排查路径我用了很多年非常管用。2.2 边界是怎么塞给填充对象的边界是填充对象里最复杂、也最容易翻车的部分。API里你主要跟AppendLoop方法打交道它把一条闭合曲线实体的引用追加为填充对象的一个边界环。追加时必须指定环的类型也就是HatchLoopTypes枚举。最常用的是External外部边界和Default默认用于内部孤岛或附加外部边界。你需要理解一个基本规则外部边界定义填充的最外围内部环定义不填充的孤岛区域比如一个法兰盘中间那个螺栓孔。填充算法会扫描这些环的关系内部环如果全部落在外部环内部那这块区域就会被挖空。实际操作里有几个点特别重要。第一加入的曲线必须严格闭合多段线要检查闭合标志圆弧和直线首尾要真正相接哪怕有0.001毫米的间隙算法也可能认为边界不成立。第二所有边界曲线必须在同一平面内这是CAD填充计算的基本前提混入一条奇葩的3D样条曲线会导致调用直接失败。第三多个外部边界不能用一个External环乱塞多个不相交区域要么建多个填充对象要么按API约定分别追加并指定正确的标志。我自己的习惯是在写代码前先把CAD自带的填充命令打开手动对同样的边界试一次。如果界面操作也失败那基本可以断定是几何问题不是代码问题。对于界面能成功填充的边界代码里用同样一组实体去AppendLoop大概率也能成。2.3 关联性与事务两个容易被忽略的前提关联性为什么是前提因为它影响你到底该怎么处理边界。创建填充时把Associative设为trueCAD会在填充对象和边界实体之间建立关联。好处是用户往后拉伸多段线填充区域会自动跟着变这在建筑、机械图纸里非常实用。坏处是这个关联是有代价的你不能轻易删除边界实体否则填充对象会处于异常状态批量处理时也要格外小心别把一堆填充做成你根本控制不住的“活物”。如果你是做一次性出图或者批量统计我一般建议把关联性关掉Associative设为false。这样得到的填充是独立的边界删了它也不受影响数据更稳定程序行为更容易预测。关联填充在交互场景里很香在无人值守的批处理场景里反而容易添乱。事务则是另一个容易被新手忽略的东西。在CAD的托管API里任何对数据库的修改都必须放进事务里并在结束时提交。你可以把事务理解成数据库操作里的“一个工作单元”要么全部成功提交要么全部回滚。创建填充对象、添加边界环、写块表记录、设置属性这些操作必须处于同一个事务上下文中。很多人第一次写填充代码创建对象成功却看不到图形十有八九就是事务没提交或者提交时机不对。还有一点容易踩文档锁。CAD是单文档界面你通过外部命令操作当前文档时需要主动获取文档锁。如果在一个没有锁定文档的上下文里去写数据库轻则报错重则让整个CAD进程崩溃。尤其注意异步操作、外部事件回调里拿不到文档锁就盲目写库几乎是事故高发区。3. 从零到一一个可用于生产的填充创建流程3.1 开发框架与前置准备我用.NET托管API讲代码用C#写。开发框架本身不复杂创建一个类库项目引用两个主要程序集一个管数据库操作一个管应用程序服务然后注册一个命令方法。项目属性里要注意目标框架与CAD版本匹配这一步不对后面调试都是折磨。下面是最小的框架代码你新建项目之后可以直接套用using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; public class HatchCommands { [CommandMethod(CREATEHATCH)] public void CreateHatch() { Document doc Application.DocumentManager.MdiActiveDocument; Database db doc.Database; using (doc.LockDocument()) using (Transaction tr db.TransactionManager.StartTransaction()) { // 具体逻辑写在这里 tr.Commit(); } } }不要小看这段模板。LockDocument保证当前文档可安全写入StartTransaction保证所有数据库操作在一个事务里Commit保证修改真正落库。我把它们称为“安全三件套”每次写命令最先想到的就是这三个东西。在开始写填充逻辑前建议先确认当前空间和块表记录。一般你往模型空间写东西需要拿到模型空间的块表记录。很多报错“无法添加实体”其实是你打开块表记录的方式不对或者打开之后类型判断错误。这块虽然基础但值得你多写几遍形成肌肉记忆。3.2 创建填充的核心代码与参数计算接下来看真正的核心代码。我现在要写一个命令根据用户选择的一条闭合多段线在其中创建ANSI31剖面线填充图案比例和角度自动计算。[CommandMethod(HATCHFROMPOLY)] public void HatchFromPoly() { Document doc Application.DocumentManager.MdiActiveDocument; Database db doc.Database; Editor ed doc.Editor; // 1. 让用户选择一条闭合多段线 PromptEntityOptions opts new PromptEntityOptions(\n选择一条闭合多段线: ); opts.SetRejectMessage(请选择闭合多段线。); opts.AddAllowedClass(typeof(Polyline)); PromptEntityResult res ed.GetEntity(opts); if (res.Status ! PromptStatus.OK) return; using (doc.LockDocument()) using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTable bt tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; BlockTableRecord btr tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord; // 2. 读取多段线并检查闭合性 Polyline pline tr.GetObject(res.ObjectId, OpenMode.ForRead) as Polyline; if (pline null || !pline.Closed) { ed.WriteMessage(\n边界必须是闭合多段线。); return; } // 3. 创建Hatch对象并设置图案 Hatch hatch new Hatch(); hatch.SetDatabaseDefaults(); hatch.Associative false; hatch.PatternType HatchPatternType.PreDefined; hatch.SetHatchPattern(hatch.PatternType, ANSI31); // 4. 根据包围盒估算一个合适的比例 Extents3d ext pline.GeometricExtents; double diagonal (ext.MaxPoint - ext.MinPoint).Length; double patternScale diagonal / 20.0; // 平均容纳20条左右线 hatch.PatternScale patternScale; hatch.PatternAngle 0; // 5. 加入边界环 hatch.AppendLoop(HatchLoopTypes.External, new ObjectId[] { pline.ObjectId }); hatch.EvaluateHatch(true); // 6. 添加到模型空间和事务 btr.AppendEntity(hatch); tr.AddNewlyCreatedDBObject(hatch, true); tr.Commit(); ed.WriteMessage(\n填充创建完成面积: {0:F2}, hatch.Area); } }这段代码看起来不长但里面有几处是“隐藏考点”。先说图案比例。比例本身没有绝对正确的值因为不同图幅、不同出图比例下ANSI31看起来的效果差别很大。我这里的做法是拿多段线包围盒的对角线长度除以20意思是在图形尺寸跨度内大致出现20条线。这样写的好处是自适应图形大比例就大图形小比例就小。当然你完全可以把比例做成用户输入参数或者从图纸的出图比例反推。再说AppendLoop。HatchLoopTypes.External表示这是外部边界代码里把一个对象ID数组传进去。注意这里传的是多段线的ObjectId不是实体本身因为数据库操作是基于引用的。EvaluateHatch(true)这一步很多人会漏它的作用是让CAD重新计算填充区域。不调用它填充对象虽然存在但图案区域可能还是空的页面上看起来就像什么都没发生。还有一个细节我关闭了关联性并且是在AppendLoop之前设置。原因前面说过批量处理场景下不需要关联更新关联反而增加风险。另外SetDatabaseDefaults是给填充对象补一些默认上下文数据建议保留它能避免一部分因为默认值缺失导致的奇怪问题。3.3 批量填充与面积统计的思路单个填充会写了批量其实就是在外面套一层循环。最典型的场景批量给所有闭合多段线填充并输出每块面积。这个需求我在培训里讲过很多次最难的不是填充本身而是选择集怎么筛。下面的例子演示如何筛选出所有闭合的多段线然后逐个填充[CommandMethod(BATCHHATCHAREA)] public void BatchHatchArea() { Document doc Application.DocumentManager.MdiActiveDocument; Database db doc.Database; Editor ed doc.Editor; // 过滤LWPOLYLINE 且闭合DXF组码70 的闭合标志为1 TypedValue[] filterList new TypedValue[] { new TypedValue(0, LWPOLYLINE), new TypedValue(70, 1) }; SelectionFilter filter new SelectionFilter(filterList); PromptSelectionResult selRes ed.SelectAll(filter); if (selRes.Status ! PromptStatus.OK) return; using (doc.LockDocument()) using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTable bt tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; BlockTableRecord btr tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord; int okCount 0; foreach (SelectedObject so in selRes.Value) { Polyline pline tr.GetObject(so.ObjectId, OpenMode.ForRead) as Polyline; if (pline null || !pline.Closed) continue; try { Hatch hatch new Hatch(); hatch.SetDatabaseDefaults(); hatch.Associative false; hatch.PatternType HatchPatternType.PreDefined; hatch.SetHatchPattern(hatch.PatternType, ANSI31); Extents3d ext pline.GeometricExtents; double diagonal (ext.MaxPoint - ext.MinPoint).Length; hatch.PatternScale diagonal / 20.0; hatch.PatternAngle 0; hatch.AppendLoop(HatchLoopTypes.External, new ObjectId[] { pline.ObjectId }); hatch.EvaluateHatch(true); btr.AppendEntity(hatch); tr.AddNewlyCreatedDBObject(hatch, true); // 面积必须在对象可读状态下读取 ed.WriteMessage(\n边界 {0} 面积: {1:F2}, pline.Handle, hatch.Area); okCount; } catch (System.Exception ex) { ed.WriteMessage(\n处理边界 {0} 失败: {1}, pline.Handle, ex.Message); } } tr.Commit(); ed.WriteMessage(\n完成成功 {0} 个失败 {1} 个。, okCount, selRes.Value.Count - okCount); } }这个批处理代码里有几个设计决策值得说。第一筛选时用(70, 1)过滤闭合多段线DXF组码70在LWPOLYLINE里表示多段线标志值为1代表闭合。这个过滤条件能把大量不相干的开放线段排除掉大幅减少后续无效计算。第二对每个边界单独做了try-catch这是批处理最重要的习惯。一张图里总有一些烂边界你不能让它拖垮整个批次。第三面积属性在填充对象打开状态下一口气读出来存进变量而不是等所有操作完再回头读避免对象被关闭后读取报错。3.4 让插件更稳的性能处理批量填充做得多了性能问题就会浮出水面。几百个填充对象时没什么感觉但到几千个尤其是图形本身复杂、图案计算量大时用户会明显感到卡顿甚至假死。我在性能这块有几个实在的心得。第一能用一次事务处理就不要开多个事务。事务不是越碎越好它有自己的开销。上面的批处理例子把整个批次放在一个事务里既快又不容易出现“改了一半”的中间状态。第二避免频繁调用EvaluateHatch后再大量重生成。EvaluateHatch是真正的计算热点你无法省掉它但你可以确保它只在边界加入后调用一次而不是在设置属性过程中反复调用。第三批处理时尽量在模型空间块表记录里一次性追加而不是每填充一个就重新打开一次块表记录。另一个容易被忽视的点是打印和视口。如果脚本运行过程中要刷新视图你可能会不自觉地调用Regen这会引发全图重生成在复杂图纸里代价极高。我的建议是批处理期间不要刷新视图只在结束时必要的时候刷一次。你可以在命令开头把一些系统变量关掉比如命令回显、对象捕捉减少不必要的交互计算。这属于经验层面的事但实战里提升非常明显。4. 已有填充的编辑、样式控制与隐藏技巧4.1 批量修改已有填充填充创建只是入门工作上更常见的需求是“把图纸里已有的填充批量改掉”。比如图纸规范调整原来用的ANSI31要统一改成ANSI37或者原比例太密要整体调大。修改已有填充的核心逻辑是打开Hatch对象改PatternType和PatternName重设比例再调用EvaluateHatch。这里有一个容易被坑的点调用SetHatchPattern之后原有的边界环不会丢但图案定义会重建所以如果你在修改之前对边界做了缓存不要以为它永远不变。示例代码大概是这样的思路先过滤(0, HATCH)拿到所有填充对象然后逐个打开为ForWrite调用SetHatchPattern(HatchPatternType.PreDefined, ANSI37)设置PatternScale和PatternAngle最后EvaluateHatch(true)。注意修改时如果填充是关联填充改完图案后最好检查一下它和边界是否还保持一致我遇到过关联关系破裂导致填充显示异常的情况。还有一个小技巧如果你不想改图案名只想把已有填充的透明度或者颜色统一调整可以直接设置ColorIndex和Transparency这些属性修改后不需要重新EvaluateHatch性能上会快很多。4.2 颜色、透明度与渐变填充别以为填充只能画斜线。工程上的遮罩块、示意图上的渐变背景、水体表面效果都依赖填充的变种。这里说几个我实际用过且稳定的方案。实体填充是用的最多的。把PatternName设为SOLID配一个颜色索引它就是一个干净的色块。这种填充经常被拿来做遮挡比如在平面图里用白色实体填充盖住底下的杂物。关键点在于SOLID填充的颜色不受图层颜色控制影响不一定但你可以显式指定ColorIndex 7白色之类避免跟随图层变成奇怪的颜色。渐变填充在托管API里同样不算复杂。设置PatternType HatchPatternType.GradientDefined然后用SetHatchPattern(hatch.PatternType, gradientName)指定渐变名称常用的是LINEAR和CYLINDER一类的预定义渐变再设置渐变色。渐变填充的细节很多尤其是渐变角度和颜色方向这里不展开但你要知道它和普通填充共用同一套对象模型和事务机制。透明度是个容易忽略的好东西。设置hatch.Transparency new Transparency(40)可以让填充半透明显示。这在方案对比、区域叠置分析里非常实用批量修改透明度也比重新填充高效得多。4.3 填充对象与几何边界的转换有时候你需要从已有填充反推出边界几何。比如第三方图纸只有填充对象没有闭合多段线但你的下游工序需要多段线去做数控切割或者二次算量。遍历填充对象的边界环可以用GetLoopAt配合循环。每个环提取出来是一组ObjectId你可以逐个打开这些实体把它们的几何信息抽取出来重新生成一条新的闭合多段线。这里要特别注意孤岛结构外环和内环的实体是分开存储的提取时要区分开不然生成的多段线会把整个区域当成一个外轮廓形状完全错误。还有一个思路是直接用Region转换。把闭合边界生成面域再用面域和填充区域做交集判断很多复杂几何判断会简化很多。面域和填充在算法层面有很多共通之处但API上它们是两个对象转换时要预估好性能开销。这类转换代码做成一两个内部工具函数慢慢沉淀下来后续项目里复用率很高。5. 常见报错与排查思路实录5.1 高频异常速查表我把这些年遇到的填充相关异常整理成一个速查表按“异常信息、大概率原因、排查方向”三列给你。遇到报错先照表查比自己瞎试快得多。异常或现象大概率原因排查方向eInvalidInput边界未闭合、曲线类型不支持、环标志不正确检查每一条边界实体是否闭合、是否共面、类型是否为曲线类eInvalidExtents边界包围盒异常或图案比例导致计算域过大打印边界包围盒数据检查是否有异常远点图元填充创建成功但看不到效果缺少EvaluateHatch调用或者图案比例过小/过大确认调用EvaluateHatch用写日志方式打印比例和边界信息对象已关闭或正在使用在Fill对象关闭后继续读取属性尽量在打开的using块内读取数据不要延迟访问事务冲突或无法提交事务嵌套、多个命令同时写库、文档锁未获取检查是否存在嵌套事务确认LockDocument范围填充面积与视觉不一致边界存在自交、重叠或孤岛处理不正确用CAD自带填充验证边界检查内部环是否漏加关联填充导致图纸异常关联边界被删除或移动后未更新批量场景建议关闭Associative或处理前先解绑这个表格里每一条背后都有真实事故。比如“看不到效果”这个情况我自己新手期就遇到过代码跑完没有报错图上一个填充都没有最后发现是没有调用EvaluateHatch。所以排查的第一个动作永远是确认所有必要方法都调了而不是急着怀疑CAD有问题。5.2 边界问题的实地排查边界相关的错误占填充开发问题的七成以上。如果你在写批量填充工具边界问题几乎是必然遇到的关键是怎么快速定位。我的排查套路是这样的。第一步先用CAD自带命令手动对一个疑似边界做一次填充。如果界面操作也提示“未找到有效的填充边界”那问题就在几何上不在代码上。第二步检查多段线的闭合标志。很多人以为图形“看起来是闭的”就行但DXF组码70的闭合标志可能没有置位或者多段线最后一点和第一点只是视觉重合数值上差了一丁点。你用代码打印一下pline.Closed和首尾点坐标马上能看出问题。第三步排查自相交。自相交边界在CAD里非常难肉眼识别但填充算法对自相交极其敏感。我常用的方法是用面域命令试一次转换如果失败基本就是自相交。第四步检查共面性。偶尔会有边界曲线被无意改成了带Z坐标的3D多段线这时所有点的Z值不一致填充计算必然失败。还有一类顽固问题重合边界。某些图纸是从其他软件转过来的一条边被重复画了两遍肉眼完全看不出来但填充算法会认为边界关系不唯一。遇到这种情况我一般先用清理类命令把重复对象清一遍跑通再回到代码里通过对象选择和去重逻辑规避。5.3 事务和文档锁的坑边界问题之外排第二的坑就在事务和锁上。填充对象涉及数据库写入这个领域的问题通常很隐蔽报错信息也晦涩。最常见的事务坑是嵌套。有些开发者习惯在命令入口开一个事务在某个辅助方法里又开了一个想着“里面那个先提交外面再提交”结果里面那个提交后外面的事务还持有对同一批对象的状态跟踪一提交就冲突。我的建议是全程只用一个事务辅助方法不要开新事务只接收对象引用进行操作。文档锁的问题多发生在事件回调、异步代码、以及多文档批处理场景。一次典型的事故是你从一个外部表格程序触发CAD命令在回调线程里直接访问当前文档的数据库没有获取文档锁结果整个实例崩溃。解决方式很简单任何非命令入口的代码路径都先用Document.LockDocument()把文档锁拿到手再动数据库。锁的作用范围要尽量大宁可提前锁也不要边操作边锁。还有一个经验尽量在对象打开后立刻读取你要的数据然后关闭不要长时间持有打开状态。尤其是在循环批处理里保持短暂、快速的打开-读取-关闭节奏能显著减少“对象已打开”之类的冲突。这也是我在批处理代码里为什么强调面积在打开状态下一口气读出来的原因。5.4 填充分区与异常回滚的兜底策略批量处理还有一个兜底策略值得单独说宁可跳过不可整体翻车。你的工具是给业务人员用的他们不会像你一样对待异常那么耐心。一次批量填充里哪怕一个边界有问题用户可能就认为整个工具不可用。我现在的做法是批处理命令永远带一个try-catch的外壳内部每个填充单独捕获异常。失败时记录实体的Handle和失败原因最后统一打印一个汇总报告。甚至可以考虑把失败项写进一个日志文件让用户直接拿日志给我们排查。这种“兜底策略”不是代码技巧而是工程习惯但在实际交付中价值极大。另外我建议在命令正式跑之前增加一个“预检模式”只检查边界质量不真正创建填充把不合格的边界数量统计出来。用户可以先跑一次预检看看到底有多少边界不闭合、多少自相交再决定要不要执行真实填充。这个小功能能劝退大量“你这个工具是不是有问题”的反馈。实现思路就是在创建填充前对每个候选边界做一遍闭合性、共面性的快速判断把结果汇总输出。我个人做填充相关插件最深的一个体会是先把边界搞对再谈图案好看。几乎所有莫名其妙的填充报错最后追到根上都是边界几何的问题。代码、API、事务这些只是工具真正考验的是你能不能把几何对象之间的关系理顺。最后再分享一个小技巧当你准备用代码还原一套人工填充效果时先花几分钟在界面里手动填一次把图案名、比例、角度、关联状态都记下来然后用代码去逐项还原这些参数。哪怕你之前对API不熟这套“反推参数法”也能让你迅速定位到该调哪个属性。填充二次开发没那么玄乎它就是把一个对象模型里的属性、边界和计算规则搞清楚剩下的就是经验和耐心的事情。