1. 填充看着只是一块阴影,落地却是一整套数据链
绝大多数人第一次意识到"填充"是个技术活,都是在改别人的图纸时:图层里明明有填充,屏幕上要么一片空白,要么密得像块黑板,选中之后改比例,图案位置又整体跑偏,想把它对齐到某个墙角,只能靠肉眼一点点挪。我在做CAD 填充增强插件的过程中,反复被同一类问题绊住——大家默认填充是"装饰性"的,随手一点就完事,结果到了出图、审图、交付环节,填充的图案定义、比例、角度、原点、边界关联性任何一项没管住,都会演变成返工。
这篇内容讲的就是围绕"高效读取、编辑与批量创建填充图案"这套能力,插件该怎么做、坑在哪里、参数怎么定。适合三类人看:一是天天画图、被填充折腾过的设计岗;二是准备给自家团队做二次开发工具的技术岗;三是对图纸数据自动化感兴趣、想搞清楚填充对象内部结构的读者。哪怕你只写脚本、不做完整插件,中间关于 PAT 解析、边界闭合判定、事务分块的部分也能直接拿去用。
先给结论:填充不是一个"图形对象",而是一组图案定义 + 边界环 + 变换参数的复合体。你改任何一项,另外两项的语义都可能随之变化。理解这句话,后面 80% 的异常现象都有解释。
1.1 原生填充命令到底卡在哪三处
第一处卡在图案来源。原生填充对话框里能选的图案来自两类:一类是平台内置的,一类是外部图案定义文件。前者不可改,后者要手动放到指定目录、手动刷新,团队里换台机器就得重新配一遍。实际项目里经常遇到"甲方指定的某类剖面线",图案文件在别人电脑上,你这边根本没有,只能用近似图案凑,交付时被挑出来。
第二处卡在批量能力缺失。原生交互是"拾取一个内部点→生成一个填充",十个区域就点十次。一个楼梯详图、一个吊顶布置图,动辄几十上百个封闭区域,人工点选既慢又不一致——有人按一个方向选,有人按另一个方向选,最后图案原点各不相同,图面看起来乱糟糟。这不是画图水平问题,是工具没给出批量投放入口。
第三处卡在编辑不闭环。改图案名称、改比例、改角度这些单独操作原生都支持,但组合起来就散:比例改完角度没跟着调,原点没重设,边界因为上游多段线被改而失效,填充变成一堆孤立线。你想批量修一遍,原生没有"筛选出所有比例异常的填充"这种入口。
运营者真正需要的是三件事,而不是更花哨的界面:把外部图案稳定加载进来、把选中的填充按规则统一改掉、把一片闭合区域一次性填上。插件所有设计都该围绕这三点压缩操作路径。
1.2 填充对象落库之后长什么样
要写插件,先得接受一个事实:填充对象在数据库里存的不是"画出来的那些线",而是种子点 + 边界环 + 图案定义 + 变换参数。显示的时候由图形引擎按图案定义逐条推演线条位置,所以它很省存储,但也很脆——任何一项参数异常,推演结果就离谱。
我习惯把填充拆成下面这几块来看,排查问题时也按这个顺序过一遍:
| 组成 | 关键字段 | 出问题时的典型表现 |
|---|---|---|
| 图案定义 | 图案名、图案类型(预定义/自定义)、线条角度与偏移 | 图案名失效,填充变实心或消失 |
| 变换参数 | 比例、角度、原点(基准点) | 图案密度突变、方向错、位置偏 |
| 边界环 | 外环 + 若干内环(孤岛) | 填充溢出边界、孤岛被填满 |
| 关联性 | 是否关联到边界对象 | 边界改了填充不跟随,或跟随错位 |
| 显示/打印 | 图层、颜色、透明度、填充显示开关 | 屏幕可见、打印不见,或反之 |
这里最容易忽视的是原点。很多人改完比例就以为对齐了,其实图案的排布基准是"原点 + 比例 + 角度"三者共同决定的。原点偏移 1 个单位,在比例 100 的图案上就放大成 100 个单位的错位。我在插件里把原点做成显式可编辑项,就是因为吃过这个亏:批量处理时如果不统一原点,同一张图上的剖面线完全无法衔接。
1.3 三类真实场景决定了插件的功能边界
场景一:标准图库复用。某设计团队有一套内部图案库,不同专业用不同图案表示材料。插件要做的就是扫描指定目录下的图案定义文件,做语法校验,把可用图案列表缓存下来,供快速选择。这里的关键不是"能读",而是"读错了要能报出来"——图案文件一个字符写错,整行定义就作废,原生往往只给一句模糊提示。
场景二:存量图纸规范化。接手别人画了一半的图,填充比例五花八门。插件需要能按图层、按图案名筛选,批量把比例归一到某个值,同时保持原点相对关系不乱。
场景三:批量生成。屋面、地面、墙面材料分格,一次几十个区域。插件需要从一堆线里识别闭合环,再按面积、图层做过滤,最后分块创建。这块性能压力最大,也是最容易把平台卡死的地方。
2. 读懂图案定义文件:语法、容错与解析器实现
插件要"高效读取",绕不开图案定义文件(通用叫法是 PAT 格式)。这个格式极简,几行文本定义一个图案族,但极简也意味着容错空间小——没有校验、没有版本号,错一个字就整行失效。
2.1 一行定义里每个数字都有明确语义
先看一个典型定义(示意,非真实示例):
*MY_PATTERN, 示例图案 用于示意线型 0, 0,0, 0,5, 10,-5 90, 0,0, 0,5, 10,-5 45, 0,0, 2.5,2.5, 8,-4, 2,-2逐字段拆:
- 以
*开头的行是图案名 + 描述,名字在前、逗号后是说明文字。名字里不能有逗号,不能用保留字符。 - 以
;开头是注释行,空行通常被忽略。 - 定义行是逗号分隔的数字序列,前四个是固定语义:角度(度)、原点 X、原点 Y、ΔX(沿线条方向的位移)、ΔY(垂直线方向的偏移),之后是一组"短线长度"序列。
- 短线长度序列里:正数 = 画线段,负数 = 留空,0 = 打一个点。正负交替,形成虚线节奏。
关键在 ΔX 和 ΔY 的理解。ΔY 决定相邻平行线的间距,ΔX 决定同一条线上下一个循环起点的偏移。很多人以为 ΔX 恒为 0,其实它在做"错缝"效果时是核心参数——比如砖砌图案的错缝,就靠 ΔX 与 ΔY 的配合。
还有一个容易被忽视的点:角度是逆时针为正,而且它是线条方向角,不是旋转整个图案的角。如果你想让某个图案整体转 30 度,正确做法不是改定义里的角度,而是在填充对象上改旋转角。这两者混用,图案会彻底走形。
2.2 图案加载失败的五种典型原因
我在实际使用中统计过,图案"选了没效果"基本落在这几类:
- 文件末尾缺空行或者最后一行被截断。有些编辑器保存时不留结尾换行,解析器按行读到最后一行废数据,整段丢弃。
- 逗号用了中文全角,或者数字里混了空格。肉眼几乎看不出来,解析直接失败。
- 角度写了负值或超过 360。部分实现不归化,导致线条方向算错,图案看起来"消失"——其实是画到了很远的地方。
- ΔY 写成 0。垂直线偏移为零意味着所有平行线重合,屏幕上就是一条线,用户以为加载失败。
- 图案名重复。同名定义在后加载的会覆盖前者,容易造成"我明明改了文件怎么没变化"的错觉。
注意:解析图案定义时,一定要把"行号 + 原始行内容"一起记进错误日志。只报"某某图案解析失败"等于没报,排查时你还是得回去逐行看。
2.3 写一个能吃脏数据的解析器
解析器不需要多聪明,但必须永不抛异常、永不吞错误。我的做法是两段式:先整文件按行切分,再逐行做宽松解析,失败的行走收集器,最后一次性汇报。
// 结构化解析,接口名按各平台实际替换 public sealed class PatternDef { public string Name; public string Description; public List<PatternLine> Lines = new List<PatternLine>(); } public sealed class PatternLine { public double Angle, OriginX, OriginY, DeltaX, DeltaY; public List<double> Dashes = new List<double>(); } public static List<PatternDef> Parse(string[] rawLines, List<string> errors) { var result = new List<PatternDef>(); PatternDef current = null; for (int i = 0; i < rawLines.Length; i++) { var line = rawLines[i].Trim(); if (line.Length == 0 || line.StartsWith(";")) continue; if (line.StartsWith("*")) { var body = line.Substring(1); var idx = body.IndexOf(','); current = new PatternDef { Name = idx < 0 ? body.Trim() : body.Substring(0, idx).Trim(), Description = idx < 0 ? "" : body.Substring(idx + 1).Trim() }; result.Add(current); continue; } if (current == null) { errors.Add($"第 {i + 1} 行:定义行出现在图案名之前 -> {line}"); continue; } var parts = line.Split(','); if (parts.Length < 5) { errors.Add($"第 {i + 1} 行:字段不足 5 个 -> {line}"); continue; } var nums = new List<double>(); bool ok = true; foreach (var p in parts) { if (!double.TryParse(p.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var v)) { ok = false; break; } nums.Add(v); } if (!ok) { errors.Add($"第 {i + 1} 行:存在非数字字段 -> {line}"); continue; } var pl = new PatternLine { Angle = nums[0], OriginX = nums[1], OriginY = nums[2], DeltaX = nums[3], DeltaY = nums[4] }; for (int k = 5; k < nums.Count; k++) pl.Dashes.Add(nums[k]); if (Math.Abs(pl.DeltaY) < 1e-9) errors.Add($"第 {i + 1} 行:ΔY 为 0,图案会退化成单线 -> {line}"); current.Lines.Add(pl); } return result; }这里有几个刻意的设计取舍,值得说明:
- 用 InvariantCulture 解析数字。如果跟随系统区域设置,某些环境下小数点会被当成千分位,
0.5解析成5,这种 bug 极难复现,因为只在你同事的机器上出现。 - 不因为一行错就放弃整块图案。丢掉错误行、保留有效行,比整块丢弃更实用——很多图案只有一根辅助线写错了,丢掉一根完全能接受。
- ΔY 为 0 只警告不报错。有些特殊图案确实会刻意用零间距,硬拦下来会误伤。
解析完还要做一次缓存。图案列表的读取频率远高于变更频率,我一般按"文件路径 + 最后修改时间 + 文件大小"做缓存键,文件一改就失效。这样启动时扫一遍,之后都是内存命中,切图案面板基本无感。
3. 编辑已有填充:改什么、按什么顺序改、什么时候不该改
编辑这块的难点不在 API 调用,而在顺序和副作用。填充的图案、比例、角度、原点、边界是相互耦合的,随手一改,图面就变形。
3.1 先筛对象,再谈修改
批量编辑的第一步永远是把目标集合收窄。我的筛选维度一般是四层:
- 类型层:只取填充对象,别混进多段线、面域。
- 图层层:按图层名或图层过滤器圈定范围,比如只处理"剖面线"图层。
- 图案层:按图案名(含通配)筛选,比如所有
*CONC*。 - 参数层:按比例、旋转角、是否关联做范围判断,比如"比例小于 1 或大于 500 的异常值"。
第四层是最有价值的。我在实际项目里经常看到同一张图上比例从 0.5 到 2000 都有,来源是不同人从不同底图复制粘贴。这种图不改,打印出来一定出问题。
// 收集阶段只做读操作,快速、无副作用 var targets = new List<ObjectId>(); using (var tr = db.TransactionManager.StartTransaction()) { foreach (var id in selection.GetObjectIds()) { if (!id.ObjectClass.IsDerivedFrom(hatchClass)) continue; var h = (Hatch)tr.GetObject(id, OpenMode.ForRead); if (h.Layer != layerName) continue; if (!PatternMatch(h.PatternName, "CONC*")) continue; if (h.PatternScale < 1.0 || h.PatternScale > 500.0) targets.Add(id); } tr.Commit(); }先收集、再修改,是我坚持的两段式。原因很直接:一边遍历一边改,容易在同一事务里反复触发重算,遇到大图纸直接卡死;而且一旦中途异常,你不知道改到哪了。收集完打印一份清单让用户确认,也避免了"一键全改"带来的不可逆破坏。
3.2 改图案、改比例、改角度、改原点的正确顺序
顺序错了会出现"改完看起来更乱"的假象。我验证下来比较稳的顺序是:
- 先设图案定义(图案名或自定义定义);
- 再设比例;
- 再设旋转角;
- 最后设原点;
- 必要时重算/刷新边界与显示。
为什么图案必须最先设?因为不同图案的基准偏移量不同,图案换了但比例和原点沿用旧值,密度和排布就是错配的。为什么原点要放最后?因为原点是"在当前比例和角度下的基准点",先设原点后改比例,等于用一个旧坐标系去套新尺度,图案会整体漂移。
foreach (var id in targets) { var h = (Hatch)tr.GetObject(id, OpenMode.ForWrite); h.SetHatchPattern(HatchPatternType.CustomDefined, "MY_PATTERN"); // 1 图案 h.PatternScale = 50.0; // 2 比例 h.PatternAngle = Math.PI / 6.0; // 3 角度(弧度) h.Origin = new Point3d(baseX, baseY, 0.0); // 4 原点 h.Associative = true; // 5 关联 }角度单位是弧度,这是最常见的换算事故。面板上填 30(度)直接赋进去,实际得到 30 弧度,相当于 1719 度,图案转得莫名其妙。我在面板层统一做deg * Math.PI / 180.0转换,并把这个约定写进配置文件的注释里。
3.3 关联性丢失:这是设计缺陷还是必然代价
填充的"关联"意味着边界对象被修改时,填充跟着变。听起来是好事,但实际项目里经常变成麻烦:上游多段线被夹点拖了一下,填充没跟着重算,却仍标记为"关联",看着像关联的、其实已经错位。更糟的是批量重建边界时,关联标志没清干净,同一个填充被重复刷新,性能直接崩。
我的处理策略是按场景分两套逻辑:
- 出图阶段:一律关闭关联。出图要的是确定的图形状态,不需要跟随能力,关了关联反而稳定,复制、镜像、块插入都不出幺蛾子。
- 方案阶段:保留关联,但每次批量编辑后强制做一次边界有效性检查——判断每个环的顶点数量、闭合误差是否在容差内,不合格的标记出来让用户手动处理。
边界重建的成本比想象中高。重建本质上是"拿边界几何重新求一次环",对直线和多段线很快,对样条、带弧的连接、自相交轮廓就很容易踩到退化情况。我的经验值是:单个填充的边界顶点数超过 2000、或者环里含超过 4 个方向反转,重建就要谨慎,宁可提示用户简化几何。
3.4 撤销栈与文档锁:不做就会出奇怪的错
批量改几百个对象时,有两个细节不做会出问题:
一是把整批操作包在单个事务里。用户按一次 Ctrl+Z 应该整体回退,而不是退 300 次。事务粒度按"一次用户意图"划分,这是最基本的体验要求。
二是多文档场景加文档锁。如果插件跑在后台或面板线程里操作非当前文档,不加锁会直接抛异常或者静默失败。哪怕当前是单文档,加锁的开销也可以忽略,我习惯性都加上。
using (var docLock = doc.LockDocument()) using (var tr = db.TransactionManager.StartTransaction()) { // 批量修改 tr.Commit(); }提示:事务包太大也有代价——极端情况下内存会堆高。我的做法是"单批上限 500 个对象",超过就分成多个事务,但每个事务外再包一层"操作组",让撤销仍然按组生效。这个折中在实测中比极端的大事务稳。
4. 批量创建:从一堆乱线到能填的闭合环
批量创建是这个插件里最"显本事"的部分,也是最容易翻车的部分。核心链路是:候选几何 → 闭合环 → 孤岛关系 → 过滤条件 → 分块创建。每一步都要有明确的失败出口。
4.1 闭合环判定的容差怎么定
图里的线很少是完美接上的,端点差个 0.001 到 0.01 个单位非常常见。判定闭合必须带容差,容差定多少直接决定识别率。
我的经验做法是双阈值:
- 端点吸附容差:图纸单位下取
1e-3到1e-2,取决于图纸精度习惯。公制建筑图取 0.5mm 就够,机械图往往要更小。 - 最小环面积:过滤掉因为容差误判形成的"细缝环"。面积低于图纸图框面积的万分之一,直接丢弃。
判定算法本身,如果平台 API 提供了"由曲线集合生成闭合区域"的能力,优先用平台实现,省事且兼容性好。没有的话就自己写:把端点按容差量化成网格坐标(比如乘以 1000 取整),用哈希表把端点相同的曲线串起来,再从每个未访问的曲线出发做深度优先串联,回到起点即为闭环。
// 端点量化后建邻接,简化示意 long Key(Point3d p, double tol) => (long)(Math.Round(p.X / tol) * 73856093L) ^ (long)(Math.Round(p.Y / tol) * 19349663L); var buckets = new Dictionary<long, List<int>>(); // 端点键 -> 曲线索引用异或拼哈希会有碰撞,我在正式实现里换成了(long)x * 1000003 + (long)y这种可逆组合,实际更稳。碰撞的后果是两条毫不相干的线被当成相连,生成一个跨越整张图的巨大环——这种 bug 排查起来极其费劲,因为生成结果"看起来是闭合的",只是范围离谱。
串环时还要处理自相交和重复边。前者如果平台 API 能处理就直接交给它,后者一定要去重,否则同一个环会被生成两次。
4.2 孤岛检测与填充样式选择
环识别出来之后要判嵌套关系:谁在外、谁在内。判定方法是"点在多边形内"测试,用射线法或者绕数法实现,对每个环取一个代表点,测试它在其他环内的次数,奇数为内环、偶数为外环。
这一步决定了填充会不会把小洞也涂满。填充对象支持三种孤岛样式:
| 样式 | 效果 | 适用场景 |
|---|---|---|
| 普通(嵌套交替) | 每深一层填充状态翻转 | 大多数建筑、机械剖面 |
| 外部 | 只填最外层,内部全部挖空 | 有大量开孔的板件 |
| 忽略 | 内环全部忽略,整块填满 | 快速示意,不看细节 |
我默认选普通,因为它最符合直觉:外环填、内环空、内环里的内环再填。但要注意,孤岛判定依赖环的"包含关系"正确,如果前一步的环识别把两个不相交的环误判成嵌套,填充结果会是一块莫名其妙的镂空。所以我在创建前会输出"识别到 N 个外环、M 个内环"的统计,让用户先看一眼数字对不对,再点确定。
注意:种子点(填充内部点)的位置别随手取几何中心。带内环的形状,几何中心很可能落在空洞里,导致填充生成失败。稳妥做法是先用外环做一次"内缩点"计算,再用点在多边形内测试验证它不在任何内环里。
4.3 性能:为什么一次创建 2000 个填充会卡死
填充创建的开销主要在两项:边界环的重算和图形数据库的提交。前者是 CPU 密集,后者是 IO 和内存密集。两者叠加,一次性全上必然卡。
我在实测里试过三档策略,效果差别很明显:
| 策略 | 2000 个填充耗时 | 稳定性 |
|---|---|---|
| 全部一个大事务 | 明显卡顿,内存峰值高 | 易超时、失败后全部丢弃 |
| 每个填充一个事务 | 慢,提交次数过多 | 稳定但慢 |
| 分块事务(每 100 个) | 折中,耗时可接受 | 稳定,失败只丢一块 |
分块大小我最终定在 80 到 120 之间,按机器性能微调。分块时还要考虑关联性——同块内的填充如果互相依赖同一条边界,最好放同一块,避免边界对象在两次重算之间处于中间态。
另外有两个容易忽略的性能点:
- 填充显示模式影响极大。处理过程中把填充显示临时切到简化模式(只显示轮廓),处理完再切回来,图形刷新量能降一个数量级。
- 图层直通关闭。如果给填充设置"非直通"的图层状态,图形引擎的计算量会上升。批量创建时统一用直通,创建完再按需调整。
4.4 过滤条件决定结果可用度
批量创建最大的问题不是"填不上",而是"填太多"。一张图里闭合区域成百上千,全填上等于把图涂黑。所以过滤条件的设计比创建逻辑本身更重要。我一般提供这几个维度:
- 面积区间:低于下限的忽略(缝隙、误判),高于上限的单独提示(可能是图框本身)。
- 图层归属:只处理指定图层上的边界线,这是最有效的降噪手段。
- 几何类型:只接受多段线、圆、椭圆、闭合样条,排除散乱的单线组合。
- 空间范围:只对当前视图或指定矩形范围内的区域生效,便于分区分批处理。
真正落地的做法是"预览 + 确认"。先在内存里算出所有候选环,用一个临时图层画出轮廓并标注序号,让用户看清再执行。这一步多花的时间,能省掉后面成倍的返工。
5. 实测里最容易翻车的六个现象
下面这些现象我在不同图纸、不同版本、不同机器上都撞过,排查链路比结论更有参考价值。
5.1 填充"看得见但选不中"
先别怀疑插件。按这个顺序过一遍:
- 打开选择过滤器,确认填充是否被"选择预览"排除;
- 检查填充所在图层是否被锁定——锁定图层上的对象默认不可选,这是最高频原因;
- 检查填充是不是块内对象,块内对象要在块编辑状态或通过嵌套选择才能选中;
- 检查图形是否是外部参照内容,外部参照本身不可直接编辑。
我在实际操作中吃过一次教训:写了半天的批量编辑脚本,跑完一个对象都没处理,最后发现目标图层是锁定的,而脚本里没做锁定检测。后来我在收集阶段加了一条"跳过锁定图层并计入统计"的逻辑,日志里明确写"跳过 N 个锁定图层对象",问题就一目了然了。
5.2 屏幕上很淡,打印出来一片黑
这一类几乎全是比例与打印样式的问题。排查顺序:
- 图案比例是否过小。比例 1 的密集图案在 A1 图上打印,视觉上就是实心块。判断方法:把比例放大 10 倍看图案是否清晰可辨,能辨认为止。
- 填充分辨率/线条密度设置是否过高。
- 打印样式表是否把填充颜色映射成了高覆盖率颜色。
- 是否用了实体填充而非图案填充。
比例该怎么定?我的经验公式是:先确定图纸单位与出图比例,再按目标线间距反推。假设你要在打印图上看到大约 1.5mm 的线间距,出图比例 1:100,那么图纸单位(mm)下的间距应该是 150,若图案定义里的 ΔY 是 3,则比例 = 150 / 3 = 50。这个算法比瞎试快得多,而且能保证同一套图纸里所有填充视觉密度一致。
5.3 打开别人的图,填充变成空白或纯实心
跨版本、跨机器打开时,图案定义文件不在本机,填充会降级显示。排查路径:
- 检查填充的图案名是否还在;
- 检查本机的图案搜索路径是否包含该定义文件;
- 检查图案名是否与内置图案重名冲突;
- 检查填充是否被"图案不可用"降级为实体填充。
这一步我做插件时专门加了"图案依赖检查"功能:扫描全图填充,提取所有图案名,与本机可用图案列表做差集,差集里的名字列出来提示用户。这个功能救过好几次交付前的图。
5.4 批量处理跑到一半卡住
大概率是事务过大 + 图形刷新过频。处理方式:
- 处理前挂起图形刷新,处理完统一刷新;
- 分块提交,每块之间不主动刷新;
- 关闭不必要的提示和对话框;
- 处理过程中禁用"自动保存",避免和大事务抢 IO。
有个隐蔽点:某些场景下填充创建会触发空间索引重建,如果图纸里对象极多,每次重建都很慢。判断方法是用分段计时,看时间是不是花在提交阶段而不是循环体里。如果是,说明瓶颈在索引,只能靠减少提交次数和分块来缓解。
5.5 填充边界"跟着动了但图案没动"
这是关联标志与实际几何不同步的典型表现。原因通常是边界对象被程序化修改过,但没触发填充的重新计算。修复方法是在修改边界后,显式对相关填充做一次边界重算,或者干脆清掉关联标志、按新边界重建。
我的建议是:程序化改几何时,别指望自动同步。要么在改完统一做一次全图填充校验,要么在修改时就把对应填充的关联解除,改完重建。指望引擎自动帮你搞定,十次有三四次会漏。
5.6 图案明明定义对了,看起来还是错
检查三件事:
- 角度单位:度 vs 弧度,前面提过,最常见。
- 旋转方向:屏幕坐标系与几何坐标系的 Y 轴方向差异,导致镜像效果。
- 原点基准:原点是在世界坐标系下还是用户坐标系下。如果用户坐标系被旋转过,你按世界坐标设的原点会跑偏。
我现在的做法比较笨但有效:面板上放一个"预览小方块",实时画出当前参数下的图案样式,用户调参数时一眼就能看出对不对,不用反复点确定再看图。
6. 让插件能长期用下去:配置、日志与灰度投放
工具能不能活过三个月,看的不是功能多少,而是出错时能不能快速定位、改动时敢不敢动。
6.1 哪些参数该暴露,哪些必须锁死
我的原则是:影响图纸结果的暴露,影响内部实现的锁死。
该暴露的:图案搜索路径、默认比例、默认角度、默认原点策略、分块大小、容差阈值、过滤条件预设。这些是用户在不同项目里必须调整的。
必须锁死的:数字解析的区域设置(固定不变的区域无关解析)、角度单位换算方式、事务边界策略、缓存失效规则。这些一旦暴露,用户配错了就是灾难,而且错误现象极具迷惑性。
配置格式我倾向用纯文本键值对,不用数据库、不用二进制。理由是版本升级时好做兼容,出问题时用户能直接把配置文件发过来看。
6.2 日志该记什么
日志的价值在于"事后能还原现场"。我的最小集合是:操作类型、目标数量、成功/失败计数、失败原因分类、耗时、配置文件版本、图案库版本。每条失败记录必须带对象句柄 + 具体原因,否则等于没记。
[2024-xx-xx 10:21:03] BatchEdit Hatch filter: layer=剖面线, pattern=CONC*, scale<1 or scale>500 matched=412 changed=407 skipped=5 skip reasons: { lockedLayer: 3, invalidBoundary: 2 } elapsed=1842ms chunkSize=100这个格式我在实际排查里用过很多次,一眼就能看出是"过滤条件写错了"还是"对象本身有问题"。
6.3 灰度投放:先小范围跑,再全量推
插件上线别指望一次到位。我的做法是分三步:
第一步,只读模式先放出去。用户能扫描、能预览、能出报告,但不能改图。这一步收集的是"识别准确率"和"性能数据"。
第二步,单对象编辑。允许改单个填充,收集参数换算和边界重建的失败案例。
第三步,批量能力。这时候容差、分块、过滤条件都已经被真实数据验证过了,出问题的概率大幅下降。
我自己踩过最疼的一次,是跳过前两步直接上批量,结果容差设得偏大,把一个本该独立的环和旁边一条线串在了一起,生成了一个横跨半张图的填充。用户撤销之后不敢再用,那个版本的信任度基本归零。从那以后我再没跳过只读阶段。
6.4 一个容易被低估的功能:图案库自检
最后补一个实用功能。插件启动时做一次图案库自检,输出四类信息:可用图案数量、解析失败的行数与位置、重名图案、以及"图纸里用到但本地缺失"的图案名。这四个数字放在面板角落,平时没人看,一旦出问题就是最快的第一手线索。
自检的执行成本很低——只读文本、只解析行,一千个图案文件也就几百毫秒。我通常把它做成异步,启动后后台跑,完成后在状态栏闪一下提示。用户不会被打扰,但需要的时候信息已经在那儿了。
实际用下来,填充这块的复杂度和它的"存在感"完全不成比例——大家觉得它就是画阴影,实际上它牵扯到图案定义语法、几何闭合判定、坐标变换、事务性能、跨环境依赖一整套东西。我这套插件做到现在,功能上其实没多少花样,真正让它好用起来的是那些笨功夫:解析器不吞错、批量前先预览、日志带句柄、容差写死在配置里、上线分三步。如果你也在做类似的工具,建议先从"只读检查"这一个功能做起,它不改变任何图形,但能让你在最短时间里看清一张图纸里的填充到底有多乱。