
1. 从CAD图纸到设备轨迹SMT行业的这个“缝隙”到底有多大做SMT设备上位机这几年来我遇到最多的需求不是界面美化也不是通讯协议调试而是怎么把客户发来的CAD图纸变成设备能跑的轨迹程序。点胶机、涂覆机、焊膏印刷检测这类设备控制器大多只认文本格式的G代码而工艺设计图基本都在CAD软件里画好中间隔着一道天然的鸿沟。最开始我们采用的是最笨的办法操作员拿尺子在屏幕上量坐标一条路径几十个点量完再手工敲成程序慢且容易出错尤其是圆弧多的胶路抄错一个点就是整板报废。后来我花了大概两周时间用C#写了一个专门做DXF轨迹导入并生成G代码的小工具流程一句话概括就是读DXF文件、提取图元坐标、按加工逻辑排序、套用点胶模板输出G代码。程序本身不复杂核心代码量也就两千行左右但它把过去一个小时的抄坐标工作缩短到了几十秒而且出错率几乎降为零。这个工具后来一直在用支撑过不少新机型的调试任务所以我想把这次开发过程中踩过的坑和设计思路整理出来给同样在SMT行业做软件的朋友一个参考。凡是接触过设备控制的人都知道G代码本身的语法很简单难的是“轨迹从哪来”。CAD图纸里画的是工程师眼里的加工路径设备控制器要的是一个个有序坐标点中间缺少一整套转译机制。这个工具解决的就是三个核心问题第一正确读取DXF中的几何信息第二把图纸上零散的图元整理成连续、有序、可加工的轨迹第三把轨迹结合工艺参数生成不同控制器能识别的G代码文本。如果你是刚入门的上位机开发或者正在为现场的点胶路径转换发愁这篇文章里的大部分经验和代码思路都能直接拿过去用。1.1 为什么这类转换不能靠“CtrlC坐标”很多人会问CAD软件不是能直接导出坐标吗甚至有些高版本还支持导出点坐标表。但实际现场拿到的主流图纸画的是带图层的完整加工图里面有轮廓线、尺寸标注、中心线、文字说明这些元素混在同一个文件里。设备需要的则是一段首尾相接、带方向、带开关胶动作的轨迹两者根本不是同一种东西。直接复制坐标还会遇到另一个问题控制器需要的轨迹是“先走到安全高度再下降开胶沿路径移动关胶抬刀再跳跃到下一条路径”这个过程需要程序去判断哪些线是连在一起的、哪里该抬刀、哪里该保持连续运行。这些判断如果在CAD软件里靠人工完成一条轨迹可能就要操作十几次。而代码自动处理时一次导入可以做完全部判断还能在界面上实时预览选中的路径是否合理。1.2 技术栈选择为什么是C#而不是Python或C选C#没有什么高深理由核心原因是我们当时的上位机软件已经在使用C#底层通讯库、界面框架都是现成的再单独引入Python环境反而增加维护成本。如果你从头选型我更推荐从团队现有技术栈出发而不是看某个语言本身好不好。从开发角度看C#在处理DXF文本解析时有几个优势StreamReader逐行读取很方便List和Dictionary够用且性能尚可WinForms做预览界面非常快速最终发布成单个exe现场操作员双击就能打开不需要装额外的运行时或Python解释器。对3000以内的图元数量级C#解析DXF也就几十毫秒完全没有性能压力。这个体量下用不着C除非你要处理几万级别的复杂大图但SMT点胶轨迹通常不会有那么多图元。1.3 程序整体流程从文件到可下发指令的四个阶段程序的实际处理流程我分成四段。第一段是DXF解析把文件读进来变成内存里的一组几何对象比如直线、圆弧、圆、多段线第二段是轨迹整理把几何对象按加工顺序连接起来形成一条条“路径”第三段是参数生成根据用户填写的工艺参数给每条路径配置移动速度、开关胶指令、抬刀高度第四段是输出与校验预览、统计总长度和预计加工时间最后导出成文本或直接下发控制器。这四个阶段里最容易出问题的不是最后输出而是第二段。因为图纸上图形元素的物理顺序和加工顺序经常不一致需要专门设计排序算法来处理。这也是我在下文中花最多篇幅讲的部分。2. DXF不是二进制解析前先看懂它的组码结构很多不熟悉DXF格式的人容易误以为它是某种二进制格式必须借助专业库才能解析。实际上DXF的本质是一个ASCII文本文件只要了解它的组织规则自己动手写解析器并没有多难而且比引入重型第三方库更可控。2.1 组码group code结构每一行都成对出现DXF文件的最小单位是“组码值”这样的配对。组码是一个整数独占一行表示后面这个值的类型和含义值紧接着下一行。比如组码10通常代表X坐标20代表Y坐标30代表Z坐标40代表半径0代表实体类型。整个文件从HEADER段到ENTITIES段一层一层全是这种配对。当我第一次打开DXF文件看到一堆成对出现的数字时第一反应是“这有什么难的”但真正写解析器时才发现它的结构不是按固定字段顺序来的而是“需要什么字段就出现什么组码”。一个实体可能有组码70也可能没有必须用循环读取的方式处理不能假设字段一定存在。我的解析核心就一个循环while ((groupCode sr.ReadLine()) ! null) { string value sr.ReadLine(); ProcessPair(groupCode.Trim(), value.Trim()); }读入之后按当前所在的实体上下文去解释每个组码。比如遇到0这个组码时如果值是LINE就新建一个直线对象如果值是LWPOLYLINE就新建一个多段线对象。后面跟着的10、20就属于当前对象。实现上用一个switch语句或字典映射当前实体类型把坐标填充到对应字段里。2.2 需要重点处理的几种实体类型DXF文档里可以出现的实体类型很多但对SMT轨迹导入来说真正有用的就几种LINE直线、LWPOLYLINE轻量多段线、CIRCLE圆、ARC圆弧、INSERT块引用。另外我会顺手读取TEXT和MTEXT不是为了生成轨迹而是为了在界面上显示“这段文字是哪个程序的注释”方便操作员识别。实体类型关键组码解析要点LINE10/20起点11/21终点两个点组成一条线段LWPOLYLINE90顶点数量70闭合标志10/20顶点坐标42凸度顶点成对出现42可有可无CIRCLE10/20圆心40半径闭合圆环需决定走整圆路径ARC10/20圆心40半径50起始角51终止角角度单位为度默认逆时针方向INSERT2块名10/20插入点41/42/43比例50旋转角必须先解析块定义再展开TEXT/MTEXT10/20插入点1文字内容只做注释不参与轨迹生成这里有个容易被忽略的点LWPOLYLINE中的凸度组码42只在某些顶点后面出现如果没有说明这个顶点到下一个顶点之间是直线段。而闭合多段线的标注方式是组码70的最低位置1不能只看首尾坐标是否重合否则可能丢掉一条回程线。这些细节看文档时很容易漏掉但实际处理时少了任何一项轨迹都会偏差。2.3 单位问题为什么同一张图坐标大了一千倍这个坑几乎每个做过DXF导入的人都会遇到。DXF本身只是一个绘图格式没有严格要求单位必须是多少。很多CAD模板用英制画一条10毫米的线坐标值会是10但如果工程图是从英寸环境转出来的坐标值会变成0.3937甚至有些模板把模型空间做成千米级坐标就成千上万。我的处理办法是在解析后弹出一个单位选择框让操作员确认“当前图纸单位是毫米还是英寸”再根据比例换算。程序会读取标记单位但实际图纸经常不可靠所以最终以操作员的选择为准。还有一种更省事的做法读取图纸图框的大小自动估算比例。比如图框尺寸大概接近420×297就按A3毫米图处理如果接近16.5×11.7则可能是A3英寸图。这个启发式方法在日常现场应用中准确率很高。3. 从散乱图元到连续轨迹排序、连线、圆弧拟合的处理顺序DXF解析完成得到的只是一个个独立的几何对象它们并没有顺序。CAD图元在文件里的存储顺序往往就是绘制顺序与加工顺序基本无关。如果按文件顺序直接生成G代码设备会到处乱跳加工效率很低甚至可能撞到工件。3.1 先把所有图元拆成“带端点的路径段”我处理的第一步是把所有实体统一抽象成“路径段”。直线自然是一段圆弧拆成一段或多段多段线按顶点和凸度拆成多个子段。每条路径段都有起点和终点加工顺序算法的目标就是把这些路径段按首尾相连的方式重新排列。当图元之间确实存在间隙时路径段无法首尾相连处理策略是如果间隙很小比如0.1毫米以内直接用一条直线补上不让设备抬刀否则每过一个极小的间隙就开关一次胶不仅影响效率还会在胶路上留下节点如果间隙很大就认为这是两条独立路径中间抬刀跳跃并且确保在跳跃前已经执行关胶动作。3.2 排序算法最近邻原则加上一点限制排序的核心算法并不复杂我用的是一种贪心策略。首先找到整个图纸中距离加工原点最近的路径段作为起始段然后反复执行从当前路径段末端出发在尚未访问的路径段里找到两端点距离最近的那一条将它的起点或终点翻转后接上。重复这个过程直到所有路径段都被访问。这个算法属于典型的“最近邻”思路不是全局最优但对SMT轨迹来说足够。现场有位工程师一开始坚持用全局最优解跑出来的顺序确实更短但在有几百段轨迹的图纸里计算耗时从几十毫秒涨到几百毫秒而且路线更短带来的时间节省远小于排序本身的等待时间。后来大家默认都用贪心法。3.3 圆弧、圆和多段线凸度的处理方案圆的处理比较特殊。一个闭合圆可以看作一个完整路径设备绕圆跑一圈就是一条加工路径。但加工方向上有讲究根据工艺要求可能需要逆时针或顺时针。我提供的默认规则是用户可以在界面上选择“外轮廓统一逆时针”“内腔统一顺时针”或者“保持CAD原方向”因为点胶路径的方向会影响胶路在转角处的堆积状态现场不同产品的要求并不一致。圆弧段我默认不生成G02/G03圆弧插补指令而是按弦高误差细分成多个直线段。为什么这样做因为不同控制器的圆弧指令支持程度参差不齐有的厂家把G02/G03没实现完整尤其是跨象限圆弧插补经常出问题。细分成直线段后所有控制器都用G01直线插补兼容性最好还能保证胶量按路程均匀控制。细分步距由允许误差决定误差一般设0.02毫米弦长计算按头圆公式最终G代码文件会大一些但SMT轨迹点数不算多完全可接受。多段线中的凸度解析则是一个容易遗漏的技术点。凸度的含义可以通俗理解为“这段多段线弯成圆弧的程度”。0表示直线绝对值越接近1表示弯得越厉害正负号表示弯曲方向。解析凸度需要用到三角函数核心代码如下double bulge GetBulge(args); if (Math.Abs(bulge) 1e-6) { AddLine(pt1, pt2); } else { double theta 4.0 * Math.Atan(Math.Abs(bulge)); double chord pt1.DistanceTo(pt2); double radius chord / (2.0 * Math.Sin(theta / 2.0)); // 用求出的圆心和周向范围把圆弧细分成若干直线段 ListPointD points ApproximateArc(pt1, pt2, bulge, radius); AddPolyline(points); }这里最需要注意的是符号。bulge大于0和小于0对应的圆心位置在弦的不同侧如果方向搞反了生成出来的轨迹会在工件外侧而不是内侧。我的做法是在预览界面同时显示原始图元和生成轨迹方向错了肉眼就能看出差在偏移的一侧这样调试一次就能发现。3.4 路径排序时如何避免死循环排序过程看似简单但有一个非常隐蔽的坑如果存在零长度线段或者端点误差超过连接阈值贪心算法会陷入死循环。零长度线段指起点和终点几乎重合的线图纸里经常会残留多半是制图时误操作留下的。如果不处理排序时可能出现“同一段线反复被选为最近段”的情况。我在排序前增加了两个步骤第一过滤掉长度小于0.005毫米的线段第二使用容差连接而非严格匹配。所谓容差连接就是在判断“当前端点与另一条线段端点重合”时不是做浮点数相等比较而是看两者距离是否小于0.05毫米。小于阈值就视为同一个点取两个坐标的中点作为连接点。这个阈值可以在界面上调整但我不建议设得太大否则两条本该独立的邻近路径会被错误地连成一条。4. G代码模板化输出工艺参数怎么“翻译”成控制器指令轨迹排好序之后下一步就是把它输出成G代码。这里我一直强调一个原则输出模块必须是模板化的不能把某款控制器的指令格式写死在程序里。因为SMT产线上的设备品牌五花八门今天这台点胶机认M3开胶明天那台涂覆机可能只认OUT1或者使用M71/M72来切换胶阀。4.1 用配置文件描述控制器指令差异我的做法是定义一组语义化的占位符比如开胶指令、关胶指令、退回安全高度、移动到某点、直线插补等。用户可以在程序目录下放一个template.gcode配置文件在里面写清楚当前控制器对应的实际指令。程序从DXF解析并排序好的轨迹数据会经过一个替换渲染步骤把每个语义动作替换成配置文件里的真实文本。示意模板文件的格式大致如下; 模板变量示例 ; {X} {Y} {Z} 为坐标值{F} 为速度 SAFE_HEIGHT 5.000 WORK_HEIGHT 1.500 MOVE_FAST G00 X{X} Y{Y} MOVE_SLOW G01 X{X} Y{Y} F{F} DOSE_ON M3 DOSE_OFF M4 UP_TO_SAFE G00 Z{SAFE_HEIGHT} DOWN_TO_WORK G01 Z{WORK_HEIGHT} F{Z_SPEED}通过这种方式换一台控制器只需要改模板文件不用改主程序代码。实际项目里我遇到过一台老设备它的开胶指令不是M代码而是S1和S0花两分钟改模板就解决了。如果当初把指令写死在代码里又得升级整个程序。4.2 点胶轨迹的标准输出节奏点胶工艺本身有一个非常标准的动作节奏先快速移到起点上方下降到工作高度开胶沿路径移动一段或多段关胶抬升到安全高度然后跳到下一条路径。这个节奏用伪代码描述是G00 上升到安全高度 G00 移动到当前路径起点上方 G01 下降到工作高度 开胶 G01 沿路径走 关胶 G00 上升到安全高度在这个基础上真正影响加工质量的是三个细节。第一提前开胶量路径起点往往有胶量不足的问题所以有些工艺要求开胶后停顿一两个脉冲再移动我把它做成可配置的{ON_DELAY}参数。第二提前关胶量在路径终点前提前关胶利用余胶走完最后一段否则终点会堆胶。第三连续段判断如果两条相邻路径段在容差范围内首尾相连就继续走直线插补不做抬刀关胶动作保证轨迹连续。4.3 实际输出示例与加工时间估算下面是一段实际工具输出的G代码。坐标值保留三位小数速度参数从模板读取G90 G21 G00 Z5.000 G00 X10.000 Y15.000 G01 Z1.500 F300 M3 G01 X20.000 Y15.000 F800 G01 X30.000 Y25.000 G01 X30.000 Y35.000 M4 G00 Z5.000 M3 P200 G00 X40.000 Y35.000 G01 Z1.500 F300 G01 X50.000 Y35.000 F800 M4 G00 Z5.000 G00 Z50.000 M30在生成输出文件的同时程序还会计算总加工长度和预计时间。总长度简单累加所有线段路程即可预计时间则根据每条线的速度和平均运动时间估算同时加上开关胶延时和抬刀跳跃时间。虽然这个估算达不到精确仿真的程度但足够让操作员在试产前大致判断这台机一小时能打多少片板。5. 调试期被图纸坑过的四个典型场景工具开发过程中最花时间的往往不是算法本身而是处理各种“不按常理出牌”的图纸。下面这四个场景我都实际遇到过每一个都足以让程序输出完全错误的轨迹或者干脆崩溃。5.1 INSERT块引用不展开坐标全部错位DXF中的INSERT实体代表的是一个块引用也就是“把某个预先定义好的图块放到当前图纸位置同时支持缩放和旋转”。如果只解析ENTITIES段遇到INSERT只会得到插入点和块名块内的实际图元根本不会出现。第一次遇到这种图纸时我输出的轨迹只剩一个孤立点当时还以为是坐标解析错了。正确的处理方式是在解析ENTITIES之前先完整解析文件里的BLOCKS段把块名对应到块内的图元列表。遇到INSERT时读出插入点、X/Y比例和旋转角度然后把块内图元逐个做变换后写入当前轨迹。块内还可能嵌套另一个INSERT需要递归展开直到当前图元不是块引用为止。在实际场景中比例和旋转角度不一定都是0或1我遇到过一块旋转45度、X比例1.2、Y比例0.8的标牌图块如果不做变换轨迹就完全偏移。5.2 老版本DXF里的POLYLINE和VERTEX现在主流CAD软件导出的DXF大多使用LWPOLYLINE表示多段线但老版本如R12格式的DXF文件里多段线是用POLYLINE加一长串VERTEX再加SEQEND表示的。这两种实体结构完全不一样解析器如果只处理LWPOLYLINE遇到老图会直接丢失整个多段线。兼容方案是在读取实体时把POLYLINE也归到“多段线”逻辑里。当遇到POLYLINE时程序进入“收集顶点模式”后续所有VERTEX实体的10/20坐标都追加到当前多段线的顶点列表直到遇到SEQEND结束。这样在后续轨迹整理阶段两种多段线就完全统一了。这段代码不长但如果没有它很多客户传来的老图纸就无法导入。5.3 样条曲线、椭圆和拟合多段线这是所有类型里最让人头疼的。客户用CAD里的样条曲线画了一段胶路导出DXF后对应的实体类型是SPLINE其中的控制点、权重、阶数信息非常复杂自己完全实现NURBS近似至少在工程量上不划算。我的策略是程序检测到SPLINE或ELLIPSE时不做静默忽略而是在界面上明确提示“检测到样条/椭圆请让工艺人员用多段线拟合后重新导出”。现场效果证明这个“拒绝”比盲目解析更可靠。因为SMT图纸数量毕竟有限与其花很大精力去精确解析样条不如推动上游规范化。退一步说即使我把样条转换成若干直线段和原图之间的误差多大、需要多少个段才能满足精度都必须做详尽的测试否则带来的风险远远大于便利。5.4 隐藏图层、零长度线、重复图元一份完整的DXF图纸里通常除了轨迹层还有尺寸层、中心线层、注释层。如果不做图层过滤程序会把尺寸线也当成点胶轨迹输出的路径会乱成一片。我的解决办法是解析时记录每个实体所属的图层名在界面上以列表形式显示所有图层以及各自的图元数量操作员用勾选框决定哪些图层参与轨迹生成。这个功能再配上颜色显示能极大降低误用概率。另外还有两类垃圾图元需要预防。零长度线段我们已经讨论过必须在排序前过滤。重复图元则是指同一段轨迹在图纸里画了两遍虽然不影响排序结果但会让加工时间翻倍。我通过把坐标哈希后存入集合的方式检测重复段发现重复时自动去重并给出日志避免现场操作员对着多出来的实际加工时间疑惑。6. 工具跑起来之后还可以做的几个实用性增强程序的第一个版本只要能完成“DXF转G代码”就够用了但随着现场使用反馈增多我又陆续加了一些功能。这些功能单拎出来都不算复杂但它们真正让工具从“开发人员自己用的脚本”变成了“现场操作员愿意日常使用的生产工具”。6.1 轨迹预览与回读校验生成G代码前界面必须能显示解析出来的原始图纸和整理后的轨迹顺序否则操作员无法判断程序有没有理解错。预览我用自绘窗口实现原理很简单OnPaint里遍历所有轨迹段按顺序画线起点用一个小圆圈标记终点用一个方块标记不同路径用不同颜色区分。绘制代码大概一百行但带来的价值极大操作员不需要读G代码就能确认路径是否正确。回读校验是另一个重要功能。有些生成后的G代码操作员会在设备厂商的软件里进一步修改改完再导回工具进行二次分析。因此我实现了G代码文件的解析回读能把G01 X... Y...这一行行指令重新转换成坐标序列和原始DXF轨迹叠加比对计算偏差。这样就能快速发现手工修改是否造成了轨迹越界或偏移。6.2 参数记忆多组配置现场生产工艺经常会按不同机型保存多套参数。A型点胶头的开胶延时、上升高度B型涂覆头的XY速度、工作间隙数值差别很大。如果每次导入都要重新填一遍参数效率会打折扣。因此我做了参数配置文件记忆功能把常用的参数组合保存为一个个命名配置下次导入时直接选择程序自动加载对应参数。这个功能实现很简单用JSON序列化保存到本地目录即可。但做了之后现场反馈非常好因为操作员不用再翻工艺文件查参数也避免了填错数值导致的首件报废风险。类似的我还把“最近打开的文件”和“上次使用的单位设置”也记住了看似不起眼日常使用体验差别很大。6.3 关于图层规范化的一点长期建议如果你正在做类似工具最值得先花时间去推动的其实是上游图纸规范化。程序的解析能力再强也挡不住一张轨迹层里混着尺寸标注、注释文字和隐藏边框的图纸。我后来在工具里加了一条规则导入后检查是否存在名为TRAJ的图层如果存在且轨迹实体数量大于0就默认只用这个图层如果没有才退回手动勾选图层。这个简单规则把现场图转到正确结果的概率一下子提升了很多。总结成一句话这个工具的成功不在于代码写得多花哨而在于把“CAD图纸”和“设备动作”两边的语义对齐了。哪怕你从零开始照着我这个思路写也建议先花半天时间把图层规范化规则想清楚再动手写解析器一定比我当时边写边补来得快。