You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中GRBL兼容文本转G-Code切割路径优化问询

文本转GRBL G-Code切割路径优化方案

问题描述

使用C#的GraphicsPath将文本转换为GRBL支持的G-code时,出现字母被切半或未完成的情况,核心问题在于原代码仅遍历PathPoints并全部输出G1直线指令,忽略了GraphicsPath中路径段的类型逻辑。

原代码

private void ExportButton_Click(object sender, EventArgs e)
{
    // Step 1: Retrieve the displayed text
    string textToConvert = displayLabel.Text;
    if (string.IsNullOrWhiteSpace(displayLabel.Text))
    {
        MessageBox.Show("Text box is empty. Please try again.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    else
    {
        // Step 2: Convert the text to outlines curves
        GraphicsPath textPath = new GraphicsPath();
        textPath.AddString(textToConvert, displayLabel.Font.FontFamily, (int)displayLabel.Font.Style, displayLabel.Font.Size, new Point(0, 0), StringFormat.GenericTypographic);

        // Step 4: Generate G-code instructions
        const float scaleToMM = 5.0f; // Convert pixels to millimeters
        string gcodeFilePath = "output.gcode";

        using (StreamWriter writer = new StreamWriter(gcodeFilePath))
        {
            writer.WriteLine("G90"); // Use absolute coordinates mode
            writer.WriteLine("G17");
            writer.WriteLine("F200");
            writer.WriteLine("M3 S50");

            // Move to the starting point at (0,0)
            writer.WriteLine("G0 X0 Y0");
            writer.WriteLine("G1 X0 Y0");

            PointF lastMoveToPoint = PointF.Empty;
            PointF lastLetterEndPoint = PointF.Empty;

            foreach (PointF point in textPath.PathPoints)
            {
                writer.WriteLine($"G1 X{point.X * scaleToMM:F2} Y{point.Y * scaleToMM:F2}");

                lastMoveToPoint = point;
                lastLetterEndPoint = point;
            }

            // Return to the origin point at (0,0) after finishing the word
            writer.WriteLine("G1 X0 Y0");
            writer.WriteLine("G0 X0 Y0");
            writer.WriteLine("M5");
        }

        MessageBox.Show("G-code export complete.");
    }
}

优化方案

问题根源

GraphicsPath.AddString生成的路径包含**起始点(MoveTo)、直线段(LineTo)、三次贝塞尔曲线(Bezier)**等不同类型的路径段,原代码未区分这些类型,导致路径逻辑混乱:

  • 不同字母的起始点未抬刀移动,直接画直线导致跨字母切割
  • 贝塞尔曲线被当作直线段处理,导致曲线变形或路径断裂

核心修改点

  1. 结合PathTypes数组判断每个点的路径类型,分别处理
  2. 加入字母间的抬刀/落刀逻辑,避免多余切割
  3. 将贝塞尔曲线拆解为小直线段(GRBL不原生支持贝塞尔指令)

优化后代码

private void ExportButton_Click(object sender, EventArgs e)
{
    string textToConvert = displayLabel.Text;
    if (string.IsNullOrWhiteSpace(textToConvert))
    {
        MessageBox.Show("Text box is empty. Please try again.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // 生成文本轮廓路径
    GraphicsPath textPath = new GraphicsPath();
    textPath.AddString(textToConvert, displayLabel.Font.FontFamily, (int)displayLabel.Font.Style, displayLabel.Font.Size, new Point(0, 0), StringFormat.GenericTypographic);

    const float scaleToMM = 5.0f;
    string gcodeFilePath = "output.gcode";

    using (StreamWriter writer = new StreamWriter(gcodeFilePath))
    {
        // GRBL初始化指令
        writer.WriteLine("G90");       // 绝对坐标模式
        writer.WriteLine("G17");       // XY平面
        writer.WriteLine("F200");      // 进给速度
        writer.WriteLine("M3 S50");    // 主轴启动
        writer.WriteLine("G0 Z1");     // 初始抬刀(根据机器调整Z值)

        PointF[] pathPoints = textPath.PathPoints;
        byte[] pathTypes = textPath.PathTypes;

        int i = 0;
        while (i < pathPoints.Length)
        {
            PathPointType pointType = (PathPointType)pathTypes[i];
            float x = pathPoints[i].X * scaleToMM;
            float y = pathPoints[i].Y * scaleToMM;

            // 处理路径起始点(新字母/新轮廓的起点)
            if ((pointType & PathPointType.Start) == PathPointType.Start)
            {
                // 抬刀快速移动到起点,再落刀
                writer.WriteLine($"G0 X{x:F2} Y{y:F2}");
                writer.WriteLine("G1 Z0");
                i++;
            }
            // 处理直线段
            else if ((pointType & PathPointType.Line) == PathPointType.Line)
            {
                writer.WriteLine($"G1 X{x:F2} Y{y:F2}");
                i++;
            }
            // 处理三次贝塞尔曲线(拆解为小直线段)
            else if ((pointType & PathPointType.Bezier3) == PathPointType.Bezier3)
            {
                // 贝塞尔曲线需要4个点:起点(i-1)、三个控制点(i, i+1, i+2)
                PointF p0 = pathPoints[i-1];
                PointF p1 = pathPoints[i];
                PointF p2 = pathPoints[i+1];
                PointF p3 = pathPoints[i+2];

                // 拆解为10段直线,可根据精度调整段数
                int segments = 10;
                for (float t = 0; t <= 1; t += 1.0f / segments)
                {
                    // 贝塞尔曲线点计算公式
                    float px = (float)(Math.Pow(1-t,3)*p0.X + 3*Math.Pow(1-t,2)*t*p1.X + 3*(1-t)*Math.Pow(t,2)*p2.X + Math.Pow(t,3)*p3.X);
                    float py = (float)(Math.Pow(1-t,3)*p0.Y + 3*Math.Pow(1-t,2)*t*p1.Y + 3*(1-t)*Math.Pow(t,2)*p2.Y + Math.Pow(t,3)*p3.Y);
                    float gx = px * scaleToMM;
                    float gy = py * scaleToMM;
                    writer.WriteLine($"G1 X{gx:F2} Y{gy:F2}");
                }

                // 跳过后续三个控制点
                i += 3;
            }
            // 其他类型点直接跳过(如PathPointType.CloseSubpath)
            else
            {
                i++;
            }
        }

        // 结束后抬刀回到原点,停止主轴
        writer.WriteLine("G0 Z1");
        writer.WriteLine("G0 X0 Y0");
        writer.WriteLine("M5");
    }

    MessageBox.Show("G-code export complete.");
}

额外注意事项

  • 根据你的机器参数调整Z轴抬刀/落刀值(示例中Z1为抬刀高度,Z0为切割高度)
  • 贝塞尔曲线的拆解段数可按需调整:段数越多,曲线越平滑,但G-code文件越大
  • 若需要修正Y轴方向(GraphicsPath的Y轴向下,GRBL通常向上),可在计算Y坐标时取反:y = (displayLabel.Height - pathPoints[i].Y) * scaleToMM
  • 验证scaleToMM比例,确保像素到毫米的转换符合实际需求

内容的提问来源于stack exchange,提问作者tymol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:54:56