资讯动态

火焰切割上位机开发:G代码解析与WinForms轨迹预览实践

发布时间:2026/9/13 1:08:29 来源:尧图企业网站定制
简介一份基于C#语言实现的火焰切割机G代码界面与解析源码工程面向数控切割设备开发者、C#上位机学习者和工业自动化爱好者。工程完整覆盖从图形参数输入、G代码生成到切割路径预览的流程涵盖直线/圆弧等常见指令处理适合用于二次开发或教学研究。压缩包共474个文件约12.51MB包含67个.cs源码、31个.resx界面资源、31个.resources资源文件以及大量txt说明与jpg界面截图另有可执行exe、项目工程文件sln/csproj/配置文件等便于直接查看运行效果和阅读代码结构。目前已有526人学习浏览。通过源码与界面文件可掌握C# WinForm下G代码解析、图形参数转换及切割流程控制等关键实现丰富的截图和txt文档有助于快速理解各窗体模块为开发类似火焰切割或数控雕刻系统提供可复用参考。1. 火焰切割上位机G代码不是字符串拼接是坐标空间映射拿到 CuttingMachine 这套 C# 源码时多数人第一反应是去翻 FormB01.cs 里哪一行在发串口结果发现真正吃功夫的是 G 代码解析和轨迹预览。火焰切割和 3D 打印不一样切割头不仅要走路径还要管预热、穿孔、割缝补偿所以 G 代码不是一次性拼接就能下发的文本而是带坐标和工艺参数的指令序列。FormA07、FormB10 这些文件背后藏着从切割图形参数到数控指令的完整换算链。这套 WinForms 上位机适合按界面层、解析层、渲染层、通信层四层来拆对要维护老设备上位机、或者自己动手写切割控制界面的工程师来说每一层都值得单独梳理。2. WinForms 界面分层FormB01 到 FormC06 的参数模块怎么拆2.1 表单文件的职责边界与拆分布局源码里的表单按前缀分了组FormB01、FormB03、FormB04、FormB05、FormB10 属于 B 组FormA07、FormA08、FormA10 属于 A 组FormC06 单独一个 C 组。结合火焰切割流程我一般这么拆B 系列放切什么也就是板材参数和切割工艺A 系列放怎么走也就是坐标轴速度、空移、自动调高C 系列放用什么切也就是割炬、割嘴和气体配置。拿到手先别急着删文件按这个分组读阅读成本会低很多。文件我习惯划分的职责涉及的参数FormB01/B03-B05板材尺寸、排样与基本切割参数板长、板宽、割嘴内外径、切割速度FormB10引弧/收弧与圆孔切割参数穿孔延时、预热时间、收弧速度FormA07/A08/A10运动轴参数与自动调高最大空移速度、加速度、调高参考电压FormC06割炬/气体配置与通信端口预热压力、切割压力、串口波特率这样拆的好处是换料时只动 B 组改运动性能只动 A 组C 组基本是设备装好后就不再碰的配置项。三组表单之间用一个公共的 AppContext 静态类共享状态不互相引用控件实例这是 WinForms 上位机里值得保留的结构。如果哪天上位机要做成多语言界面这种按职责分组的表单也最容易批量替换资源文件。2.2 用实体类收敛参数别在控件上直接读值很多初版上位机的参数散落在各 TextBox.Text 里按下生成按钮才开始解析十几个 TryParse 排在一起出错时连是哪个参数非法都分不清。我一般会先建一个 CuttingParams 实体类输入全部收进来public class CuttingParams { public double PlateThickness { get; set; } // 板厚mm public double CutSpeed { get; set; } // 切割速度mm/min public double KerfWidth { get; set; } // 割缝宽度mm public int PreheatDelayMs { get; set; } // 预热时间ms public double CutOxygenPressure { get; set; } // 切割氧压力MPa public bool TryLoadFromB04(FormB04 form, out string error) { error ; if (!double.TryParse(form.txtThickness.Text, out var t) || t 0) { error 板厚必须是正数; return false; } PlateThickness t; // 其余字段的解析逻辑相同不再重复 return true; } }逻辑说明TryLoadFromB04 接收表单实例控件读取全部集中在方法内失败时直接返回用户可读的错误串。界面层拿到 false 就停在对应输入框上业务层完全不知道控件存在。后面 G 代码生成、轨迹预览、串口下发三处都复用同一个实体避免三套不同参数来源互相不一致。2.3 手输 G 代码和图形参数两种模式的切换界面里通常放两个 RadioButton 切换输入源手动模式直接读多行文本框里的 G 代码图形模式走参数→生成指令→预览链路。两种模式共用同一个 RichTextBox 显示指令内容正好对应 WinForms 里文本显示在指定矩形框的常用写法——把 RichTextBox DockFill 固定住ReadOnlytrue手动模式由用户敲图形模式由生成函数写radioManual.CheckedChanged (s, e) UpdateEditorState(); radioGraph.CheckedChanged (s, e) UpdateEditorState(); void UpdateEditorState() { bool graph radioGraph.Checked; txtGCode.ReadOnly graph; // 图形模式下文本框只读 txtGCode.Text graph ? GCodeBuilder.Build(new CuttingParams()) // 由参数生成 : G90 G21\nG00 X0 Y0; }这里的要点是手动模式和图形模式的数据流不一样手动模式跳过 CuttingParams直接进解析器图形模式则必须经过实体类防止用户在界面上改了个参数、生成结果却还是旧值。界面主题也要顺手统一给根容器设置 BackColor子控件不会自动继承我一般写一个递归方法遍历 Control.Controls统一设置字体和颜色比逐个控件手改省事得多。3. G代码解析器实现从行文本到切割路径的坐标换算3.1 行级解析与坐标模态解析器拿到的输入是整段文本首先要按行切再对每一行做单词级处理。G 代码坐标是模态的上一行的 X100 在下一行不写 X 时就继续生效所以不能一行一行独立翻译必须持有一个当前状态对象。标准流程是先去掉注释分号后或者括号内的内容再 Trim 后按空格切分逐个单词更新状态public class MotionState { public int G { get; set; } // 当前运动组指令0/1/2/3 public double X, Y, Z; // 当前绝对坐标 public double I, J; // 圆弧圆心相对起点的增量 public double R; // 圆弧半径 public double F; // 进给速度mm/min public int PauseMs; // G04 暂停时间 } static void ParseLine(string raw, MotionState st) { raw raw.ToUpperInvariant(); int c1 raw.IndexOf(;); int c2 raw.IndexOf((); int cut c1 0 ? c1 : c2; if (cut 0) raw raw.Substring(0, cut); string[] parts raw.Split(new[] { }, StringSplitOptions.RemoveEmptyEntries); foreach (string p in parts) { if (p.Length 2) continue; char code p[0]; if (!double.TryParse(p.Substring(1), out double v)) continue; switch (code) { case G: st.G (int)v; break; case X: st.X v; break; case Y: st.Y v; break; case Z: st.Z v; break; case I: st.I v; break; case J: st.J v; break; case R: st.R v; break; case F: st.F v; break; } } }逻辑说明解析器故意不区分 G90/G91默认按绝对坐标处理因为火焰切割的 CAM 后处理基本都输出 G90如果程序里出现 G91X 和 Y 就要累加而不是赋值。ParseLine 只负责把文本状态搬到对象状态之后的路径生成和预览都只读 MotionState。F 在这里被保留生成轨迹时用来估算切割时间和判断是否超过机床速度上限。3.2 圆弧指令的圆心计算与方向判定G02/G03 的格式有两种一种给 I/J圆心相对起点的增量偏移一种只给 R圆弧半径。预览时要把两种格式都换算成圆心坐标否则画出来的圆弧和实际切割轨迹对不上。常见做法是 I/J 优先只有 R 时用几何关系解算圆心——弦的两个端点已知圆心必然在弦的中垂线上再结合顺逆方向选定唯一解static bool TryGetCenter(double sx, double sy, double ex, double ey, double i, double j, double r, bool cw, out double cx, out double cy) { if (r 0 (i ! 0 || j ! 0)) { cx sx i; cy sy j; // I/J 是相对起点的偏移 return true; } double dx ex - sx, dy ey - sy; double chord Math.Sqrt(dx * dx dy * dy); if (chord Math.Abs(r * 2) 1e-6) { cx 0; cy 0; return false; } double mx (sx ex) / 2, my (sy ey) / 2; // 弦中点 double h Math.Sqrt(r * r - (chord / 2) * (chord / 2)); // 圆心到弦的距离 double nx -dy / chord, ny dx / chord; // 中垂线单位向量 if (cw) { nx -nx; ny -ny; } cx mx nx * h; cy my ny * h; return true; }这段代码的要点在最后两行中垂线有两个方向G02 顺时针和 G03 逆时针要取不同的圆心。chord |r|×2 时表示给出的三个点构不成圆弧常见原因是 I/J 写错或者坐标精度丢失解析到这个情况我一般直接停下报错而不是用近似值往下切——火焰切割一旦圆弧偏差叠加后面的图形全都会错位。提示G02/G03 是否合法不能只看数值拿实际路径画到界面上看一遍最可靠不少圆弧过切问题都出在 I/J 符号取反整圆被画成劣弧。3.3 预热等待与 F 速度的边界处理火焰切割和铣削最大的差异在切割不是连续的每一圈烧穿之前要先预热这个动作用 G04 暂停来实现参数通常是 P 或 X单位毫秒部分控制器用秒需要按硬件手册换算。解析器要把 G04 单独识别成一个等待节点不能当成普通运动指令否则生成的轨迹里会多出一段莫名其妙的直线case G when v 4: st.G 4; // 接下来读取 P/X 字段 break; case P when st.G 4: st.PauseMs (int)v; // P1000 预热或穿孔等待 1000ms break;速度 F 在火焰切割里也要单独限幅不是照抄 CAM 值就能用。6mm 碳钢板配 1.6mm 割嘴速度一般压在 450~600mm/min板厚到 30mm 以上速度要降到 300mm/min 以内同时提高预热时间。解析器里我会把 F 值先暂存到了串口下发那一步统一做过限幅处理而不是在解析时就改掉这样界面上还能看到原始设定值。4. 切割路径预览与 UI 刷新GDI 渲染和跨线程卡顿处理4.1 GDI 轨迹预览坐标系映射不要逐点换算预览窗口的作用是让操作员在下发前看到路径。GDI 绘制时最容易犯的错是拿到每个坐标点都做一次机床坐标到屏幕坐标的公式换算路径一长就卡。正确做法是用 ScaleTransform 和 TranslateTransform 把 Graphics 的坐标系整体搬过去之后的 DrawPath 全在机床坐标里做using System.Drawing.Drawing2D; void DrawPath(Graphics g, Rectangle area, ListMotionSegment segs) { g.SmoothingMode SmoothingMode.AntiAlias; double minX segs.Min(s Math.Min(s.SX, s.EX)); double maxX segs.Max(s Math.Max(s.SX, s.EX)); double minY segs.Min(s Math.Min(s.SY, s.EY)); double maxY segs.Max(s Math.Max(s.SY, s.EY)); double scale Math.Min(area.Width / (maxX - minX), area.Height / (maxY - minY)); g.ScaleTransform((float)scale, (float)scale); g.TranslateTransform((float)(area.X - minX), (float)(area.Y - minY)); // 之后的绘制全部使用机床坐标值 }参数说明ScaleTransform 的 scale 是两个方向分别取包围盒到显示区的适配比例这样 1mm 的圆和 500mm 的方料在屏幕上不会变形。TranslateTransform 把包围盒原点搬进区域内部。预览代码里X 轴是机床横移、Y 轴是纵移Z 轴只显示数值不参与平面绘制。4.2 循环数据采集和 UI 刷新卡顿的来源与修复切割机上有一组常驻传感器在轮询割炬高度、预热压力、切割压力、火焰状态这类循环数据采集如果直接放在 UI 线程里跑任何一次 ReadLine 等待都会让界面冻结。SerialPort.DataReceived 事件本身就在后台线程触发事件里碰控件是 WinForms 里最常见的崩溃来源。常见做法是后台收数据前台定时取快照private volatile SensorSnapshot _latest; private void OnDataReceived(object sender, SerialDataReceivedEventArgs e) { try { string line _serialPort.ReadLine(); _latest SensorParser.Parse(line); // 只更新共享快照 } catch (TimeoutException) { /* 读取超时不抛到 UI */ } } private void timerUi_Tick(object sender, EventArgs e) { var snap _latest; lblTorchHeight.Text snap.Height.ToString(0.0); lblOxyPressure.Text snap.Pressure.ToString(0.00); }逻辑说明_latest 用 volatile 修饰配合快照对象整体赋值保证 UI 线程读到的是一份完整数据而不是拼了一半的状态。timerUi_Tick 里只做控件赋值不做任何串口读写。如果采用 System.Windows.Forms.Timer它天然跑在 UI 线程所以它只适合做这种轻量刷新千万别在里面做 Thread.Sleep 或者 ReadLine。方案线程适用场景System.Windows.Forms.TimerUI 线程高频控件刷新、动画System.Threading.Timer线程池数据采集、串口轮询async/await Task.Delay上下文延续发送序列、避免阻塞采集循环我建议直接用 Threading.Timer 或者 Task.Run 循环界面刷新保持 100ms 间隔就够了不需要 10ms 一次PLC 和人眼都响应不过来。火焰切割的预热阶段压力会缓慢上升刷新太频繁反而看不清趋势。4.3 上千行路径的重绘性能处理切割图形复杂时解析结果可能有两三千个线段每次 Paint 都重新 AddLine 会拖垮界面。我会把解析结果预先编译成 GraphicsPath参数改完只重建一次PictureBox 的 DoubleBuffered 打开重绘交给系统合并_path?.Dispose(); _path new GraphicsPath(); foreach (var seg in _segments) { if (seg.Type SegmentType.Line) _path.AddLine((float)seg.SX, (float)seg.SY, (float)seg.EX, (float)seg.EY); else _path.AddArc((float)seg.Rect.X, (float)seg.Rect.Y, (float)seg.Rect.W, (float)seg.Rect.H, (float)seg.StartAngle, (float)seg.SweepAngle); } pictureBox.Invalidate(); // 合并且标记重绘不等同步说明AddArc 需要的是圆弧外接矩形和起止角度3.2 节算出的圆心、半径可以换算成 RectInvalidate 的作用是标记区域为脏最终由消息循环统一绘制这样连续修改参数时不会每改一次就全量重画一次。WinForms 界面主题的统一也可以在这个环节做预览区用深色背景配合亮色路径线条参数区保持浅色把各自的 BackColor 收敛到两个自定义 UserControl 上重构时只动控件根节点。5. 串口下发与工艺参数补偿割缝、预热、速度上限的联调技巧5.1 逐行发送与 ACK 确认不要一口气 Write下位机控制器一般带缓冲但上位机把整段 G 代码一次性 Write 出去老控制器的缓冲会溢出丢行。常见做法是逐行发送、等控制器回 ok 再发下一行超时就重发void SendLineBlocking(string line, int timeoutMs 1000) { _serialPort.WriteLine(line); string ack _serialPort.ReadLine(); if (!ack.Trim().Equals(ok, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException($第 {_sentCount} 行未确认{line}); _sentCount; }说明ReadLine 阻塞等待期间 UI 不能卡所以这个方法要放在后台任务里跑完成一行就把行号回调到界面。串口参数我一般先试 115200-8-N-1老设备认不出再降到 9600火焰切割控制器对 G00 和 G01 的答复时序可能不一样ACK 超时要按运动指令单独调。5.2 割缝补偿、预热时间与速度上限的联调割缝宽度直接决定成品尺寸。板厚 6mm 时火焰割缝大约在 1.5~2mm切 100mm 的圆半径方向就得把坐标系整体偏置一刀宽的一半。支持 G41/G42 的系统直接发补偿指令老系统没有刀补就得在解析器的偏置阶段算好沿运动方向的法向把起终点各平移一个 kerf/2。G41 是轮廓左侧偏置G42 是右侧偏置方向错了切出来的零件整体小一圈或大一圈。预热和速度要放在一起调板厚增加时预热时间从 1s 起步往上加速度从 600mm/min 往下减。给出一个简单的限幅函数double ClampCutSpeed(CuttingParams p) { // 经验值6mm 板约 500mm/min板厚每增加 10mm 降 20% double limit 500 * Math.Pow(0.8, (p.PlateThickness - 6) / 10.0); return Math.Min(p.CutSpeed, Math.Max(80, limit)); }参数说明这个公式只是下限保护实际速度还要看割嘴型号和氧气纯度特点是保证 F 值不会超过板厚对应的经验上限。限幅结果要回写到下发队列界面上保留原始值方便对照。5.3 以笔代炬的离线验证最实用的验证方式不是直接点火切割而是把割炬夹持器换成记号笔在纸面上完整走一遍轨迹。重点看三处图形起点与终点是否闭合、圆弧衔接处有没有过切尖角、割缝补偿的方向是不是落在废料侧。走完之后把串口调试窗口记录的发送行号和源文件行号做一次比对发现某段轨迹不对直接查那一段对应的解析输出定位是坐标系换算问题还是补偿方向取反通常几分钟就能解决。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价