Parser for G-code for laser cutting machine

20 Views Asked by At

I am writing the code to parse and render g-code for a laser machine. But while rendering the code, it doesn't seem to understand the difference between a line, a circle and an arc although my logic seems to be correct. here is a small sample of the g-code I am trying to parse and render.

G-code

%
O2034
GOTO90000
G100A500.200B500.200 C1500.000 D3000.000
N90000
M98 P9101
GOTO#598
(PART 1)
(CONTOUR 1)
N1 M05
#599=1
E1
E101
G00 X300.000 Y250.000
M101
M102
G41
G03 X300.000 Y250.000 I-50.000 J0.000
M103
(CONTOUR 2)
N2 M05
#599=2
E1
E101
G00 X10.200 Y-0.200
M101
M102
G01 X10.000 Y-0.200
G02 X-0.200 Y10.000 I0.000 J10.200
G01 X-0.200 Y10.200
M103
(CONTOUR 3)
N3 M05
#599=3
E1
E101
M28 G00 X-0.200 Y489.800
M101
M102
G01 X-0.200 Y490.000
G02 X10.000 Y500.200 I10.200 J0.000
G01 X490.000 Y500.200
G02 X500.200 Y490.000 I0.000 J-10.200
G01 X500.200 Y10.000
G02 X490.000 Y-0.200 I-10.200 J0.000
G01 X489.800 Y-0.200
M103
N10G14
G5.1Q0
M103
M41
(G32L0)
G69
M98P9102
M30
this is the code in C# I wrote

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace SIL_HMI
{
    public enum MeasurementUnit
    {
        Pixels,
        Millimeters
    }
    class GcodeParser
    {
        private MeasurementUnit unitOfMeasurement;
        private List<GCodePoint> toolpathPoints;
        private const float PixelsPerMmforRendering = 1.0f;
        private PointF currentPosition;
        private Regex gCodeRegex;
        private PictureBox nCodeRenderBox;
        private TextBox gcodeDisplayBox;
        private ProductionPage productionPage;
        private float controlX;
        private float controlY;
        private float currentI;
        private float currentJ;
        public GcodeParser(PictureBox pictureBox, MeasurementUnit unitOfMeasurement, ProductionPage productionPage)
        {
            toolpathPoints = new List<GCodePoint>();
            InitializeGCodeRegex();
            currentPosition = new PointF(0, 0);
            nCodeRenderBox = pictureBox;
            this.unitOfMeasurement = unitOfMeasurement;
            this.productionPage = productionPage;
            gcodeDisplayBox = productionPage.GcodeDisplayBox;
        }
        private void InitializeGCodeRegex()
        {
            string mCommandPattern = @"^M\d+\s*([^\r\n]*)";
            string nCommandPattern = @"^N\d+\s*([^\r\n]*)";
            string gCommandPattern = @"^G(?:54|90|00|01|02|03)\s*([^\r\n]*)";
            string xCoordinatePattern = @"[Xx]([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)";
            string yCoordinatePattern = @"[Yy]([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)";
            string iCoordinatePattern = @"[Ii]([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)";
            string jCoordinatePattern = @"[Jj]([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)";
            string fullPattern = $"{mCommandPattern}|{nCommandPattern}|{gCommandPattern}|{xCoordinatePattern}|{yCoordinatePattern}|{iCoordinatePattern}|{jCoordinatePattern}";
            gCodeRegex = new Regex(fullPattern, RegexOptions.Multiline);
        }
        public string FilterGCodeForRendering(string gCode)
        {
            StringBuilder filteredGCode = new StringBuilder();
            string[] lines = gCode.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string line in lines)
            {
                string upperCaseLine = line.ToUpper();
                if (upperCaseLine.Contains("G00") ||
                    upperCaseLine.Contains("G01") ||
                    upperCaseLine.Contains("G02") ||
                    upperCaseLine.Contains("G03"))
                {
                    string processedLine = Regex.Replace(line, @"^N\d+\s*", "");                    
                    filteredGCode.AppendLine(processedLine);                    
                }
            }
            gcodeDisplayBox.Text = filteredGCode.ToString();
            return filteredGCode.ToString();
        }
        class GCodePoint
        {
            public PointF Point { get; set; }
            public string GCodeCommand { get; set; }
        }
        public void GcodeParse(string gCode, MeasurementUnit unitOfMeasurement)
        {
            try
            {
                toolpathPoints.Clear();
                string filteredGCode = FilterGCodeForRendering(gCode);
                MatchCollection matches = gCodeRegex.Matches(filteredGCode);
                currentPosition = new PointF(0, 0);
                int index = 0;
                foreach (Match match in matches)
                {
                    string line = match.Groups[0].Value;
                    string command = match.Value.ToUpper();
                    if (command.StartsWith("N"))
                    {
                        HandleNLine(line);
                    }
                    else if (command.StartsWith("G00"))
                    {
                        HandleRapidMovement(match, unitOfMeasurement, index);
                    }
                    else if (command.StartsWith("G01"))
                    {
                        HandleLinearMovement(match, unitOfMeasurement, index);
                    }
                    else if (command.StartsWith("G02") || command.StartsWith("G03"))
                    {
                        HandleCircularMovement(match, unitOfMeasurement, index);
                    }
                    index++;
                }
                Console.WriteLine($"Parsed {toolpathPoints.Count} points.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error during parsing: {ex.Message}");
            }
        }
        private void HandleNLine(string line)
        {
            Console.WriteLine($"{line}");
        }
        private void HandleRapidMovement(Match match, MeasurementUnit unitOfMeasurement, int index)
        {
            float x = ExtractCoordinate(match, 'X', currentPosition.X);
            float y = ExtractCoordinate(match, 'Y', currentPosition.Y);
            if (unitOfMeasurement == MeasurementUnit.Millimeters)
            {
                x *= PixelsPerMmforRendering;
                y *= PixelsPerMmforRendering;
            }
            string command = match.Value.ToUpper();
            toolpathPoints.Add(new GCodePoint { Point = new PointF(x, y), GCodeCommand = command });
            Console.WriteLine($"Parsed Rapid movement: X={x}, Y={y}");
            currentPosition = new PointF(x, y);
        }
        private void HandleLinearMovement(Match match, MeasurementUnit unitOfMeasurement, int index)
        {
            float x = ExtractCoordinate(match, 'X', currentPosition.X);
            float y = ExtractCoordinate(match, 'Y', currentPosition.Y);
            if (unitOfMeasurement == MeasurementUnit.Millimeters)
            {
                x *= PixelsPerMmforRendering;
                y *= PixelsPerMmforRendering;
            }
            string command = match.Value.ToUpper();
            toolpathPoints.Add(new GCodePoint { Point = new PointF(x, y), GCodeCommand = command });
            Console.WriteLine($"Parsed linear movement: X={x}, Y={y}");
            currentPosition = new PointF(x, y);
        }
        private void HandleCircularMovement(Match match, MeasurementUnit unitOfMeasurement, int index)
        {
            float x = ExtractCoordinate(match, 'X', currentPosition.X);
            float y = ExtractCoordinate(match, 'Y', currentPosition.Y);
            currentI = ExtractRadius(match, "I", 0);
            currentJ = ExtractRadius(match, "J", 0);
            if (unitOfMeasurement == MeasurementUnit.Millimeters)
            {
                x *= PixelsPerMmforRendering;
                y *= PixelsPerMmforRendering;
                currentI *= PixelsPerMmforRendering;
                currentJ *= PixelsPerMmforRendering;
            }
            float cx = x + currentI;
            float cy = y + currentJ;
            float radius = (float)Math.Sqrt(currentI * currentI + currentJ * currentJ);            
            if (toolpathPoints.Count > 0)
            {                
                int previousIndex = (index - 1 + toolpathPoints.Count) % toolpathPoints.Count;
                int nextIndex = (index + 1) % toolpathPoints.Count;
                bool isRapidBefore = IsRapidMovement(toolpathPoints[previousIndex].GCodeCommand);
                bool isRapidAfter = IsRapidMovement(toolpathPoints[nextIndex].GCodeCommand);                
                if (isRapidBefore && isRapidAfter)
                {
                    float startAngle = 0.0f;
                    float sweepAngle = 360.0f;
                    toolpathPoints.Add(new GCodePoint { Point = new PointF(cx, cy), GCodeCommand = match.Value.ToUpper() });
                    toolpathPoints.Add(new GCodePoint { Point = new PointF(cx + radius, cy), GCodeCommand = match.Value.ToUpper() });
                }
                else
                {                    
                    PointF startPoint = toolpathPoints[previousIndex].Point;
                    PointF endPoint = toolpathPoints[nextIndex].Point;                    
                    float startAngle = (float)Math.Atan2(startPoint.Y - cy, startPoint.X - cx) * (180 / (float)Math.PI);
                    float endAngle = (float)Math.Atan2(endPoint.Y - cy, endPoint.X - cx) * (180 / (float)Math.PI);                    
                    toolpathPoints.Add(new GCodePoint { Point = startPoint, GCodeCommand = match.Value.ToUpper() });
                    toolpathPoints.Add(new GCodePoint { Point = endPoint, GCodeCommand = match.Value.ToUpper() });
                }
            }            
            Console.WriteLine($"Parsed circular movement: X={x}, Y={y}, I={currentI}, J={currentJ}, CenterX={cx}, CenterY={cy}, Radius={radius}");
            currentPosition = new PointF(x, y);
        }
        private float ExtractCoordinate(Match match, char axis, float defaultValue)
        {
            string coordinatePattern = $@"{axis}([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)";
            Match coordinateMatch = Regex.Match(match.Groups[0].Value, coordinatePattern);
            if (coordinateMatch.Success)
            {
                return float.Parse(coordinateMatch.Groups[1].Value);
            }
            return defaultValue;
        }
        private float ExtractRadius(Match match, string radius, float defaultValue)
        {
            string coordinatePattern = $@"{radius}([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)";
            Match coordinateMatch = Regex.Match(match.Groups[0].Value, coordinatePattern);
            if (coordinateMatch.Success)
            {
                return float.Parse(coordinateMatch.Groups[1].Value);
            }
            return defaultValue;
        }        
        private bool IsRapidMovement(string gCodeCommand)
        {
            return gCodeCommand.StartsWith("G00", StringComparison.OrdinalIgnoreCase);
        }
        private bool IsCircularMovement(int currentIndex, List<GCodePoint> points)
        {
            return points[currentIndex].Point.Equals(points[(currentIndex + 2) % points.Count].Point);
        }        
        public void RenderToolpath()
        {
            try
            {
                Bitmap toolpathBitmap = new Bitmap(nCodeRenderBox.Width, nCodeRenderBox.Height);
                using (Graphics g = Graphics.FromImage(toolpathBitmap))
                {
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    for (int index = 0; index < toolpathPoints.Count; index++)
                    {
                        try
                        {
                            GCodePoint startPointInfo = toolpathPoints[index];
                            GCodePoint endPointInfo = toolpathPoints[(index + 1) % toolpathPoints.Count];
                            PointF startPoint = startPointInfo.Point;
                            PointF endPoint = endPointInfo.Point;
                            string startGCodeCommand = startPointInfo.GCodeCommand;
                            if (startPoint != endPoint)
                            {
                                if (IsCircularMovement(index, toolpathPoints))
                                {                                    
                                    bool isRapidBefore = IsRapidMovement(toolpathPoints[(index - 1 + toolpathPoints.Count) % toolpathPoints.Count].GCodeCommand);
                                    bool isRapidAfter = IsRapidMovement(toolpathPoints[(index + 2) % toolpathPoints.Count].GCodeCommand);
                                    float startAngle = isRapidBefore && isRapidAfter ? 0 : 0.000f;
                                    float sweepAngle = isRapidBefore && isRapidAfter ? 0 : 360.000f;
                                    if (isRapidBefore && isRapidAfter)
                                    {                                        
                                        float cx = startPoint.X + currentI;
                                        float cy = startPoint.Y + currentJ;
                                        float radius = (float)Math.Sqrt(currentI * currentI + currentJ * currentJ);
                                        Color penColor = IsRapidMovement(startGCodeCommand) ? Color.Green : Color.Red;
                                        using (Pen pen = new Pen(penColor, 2))
                                        {
                                            g.DrawArc(pen, cx, cy, 2 * radius, 2 * radius, startAngle, sweepAngle);
                                        }
                                    }
                                    else
                                    {                                        
                                        float cx = startPoint.X + currentI;
                                        float cy = startPoint.Y + currentJ;
                                        float radius = (float)Math.Sqrt(currentI * currentI + currentJ * currentJ);
                                        Color penColor = IsRapidMovement(startGCodeCommand) ? Color.Green : Color.Red;
                                        using (Pen pen = new Pen(penColor, 2))
                                        {
                                            g.DrawArc(pen, cx, cy, 2 * radius, 2 * radius, startAngle, sweepAngle);
                                        }
                                    }
                                }
                                else
                                {                                    
                                    Color penColor = IsRapidMovement(startGCodeCommand) ? Color.Green : Color.Red;
                                    using (Pen pen = new Pen(penColor, 2))
                                    {
                                        g.DrawLine(pen, startPoint, endPoint);
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"Error processing index {index}: {ex.Message}");
                        }
                    }
                    nCodeRenderBox.Image = toolpathBitmap;
                    nCodeRenderBox.Invalidate();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error during rendering: {ex.Message}");
            }
        }
    }
}

When running the code, it is rendering even the circular movement as lines (in most of the cases).

0

There are 0 best solutions below