Skip to content

Commit

Permalink
serious gcode parser improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
martin2250 committed Dec 24, 2018
1 parent f559415 commit d80ba35
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 44 deletions.
7 changes: 6 additions & 1 deletion OpenCNCPilot/GCode/GCodeCommands/Line.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
using OpenCNCPilot.Util;
using System;
using System.Collections.Generic;
using System.Linq;

namespace OpenCNCPilot.GCode.GCodeCommands
{
class Line : Motion
{
public bool Rapid;
// PositionValid[i] is true if the corresponding coordinate of the end position was defined in the file.
// eg. for a file with "G0 Z15" as the first line, X and Y would still be false
public bool[] PositionValid = new bool[] { false, false, false };

public override double Length
{
Expand All @@ -23,7 +27,7 @@ public override Vector3 Interpolate(double ratio)

public override IEnumerable<Motion> Split(double length)
{
if (Rapid) //don't split up rapid motions
if (Rapid || PositionValid.Any(isValid => !isValid)) //don't split up rapid or not fully defined motions
{
yield return this;
yield break;
Expand All @@ -44,6 +48,7 @@ public override IEnumerable<Motion> Split(double length)
immediate.Start = lastEnd;
immediate.End = end;
immediate.Feed = Feed;
immediate.PositionValid = new bool[] { true, true, true };

yield return immediate;

Expand Down
68 changes: 41 additions & 27 deletions OpenCNCPilot/GCode/GCodeFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,34 +55,34 @@ private GCodeFile(List<Command> toolpath)
Vector3 min = Vector3.MaxValue, max = Vector3.MinValue;
Vector3 minfeed = Vector3.MaxValue, maxfeed = Vector3.MinValue;

foreach (Motion m in Enumerable.Concat(Toolpath.OfType<Line>(), Toolpath.OfType<Arc>().SelectMany(a => a.Split(0.1))))
foreach (Command c in Toolpath)
{
TravelDistance += m.Length;
if (c is Line)
{
Line l = (Line)c;
if (l.PositionValid.Any(isValid => !isValid))
continue;
}

if (m is Line && !((Line)m).Rapid && ((Line)m).Feed > 0.0)
TotalTime += TimeSpan.FromMinutes(m.Length / m.Feed);
if (c is Motion)
{
ContainsMotion = true;

ContainsMotion = true;
Motion m = (Motion)c;

for (int i = 0; i < 3; i++)
{
if (m.End[i] > max[i])
max[i] = m.End[i];
TravelDistance += m.Length;

if (m.End[i] < min[i])
min[i] = m.End[i];
}
if (m is Line && !((Line)m).Rapid && ((Line)m).Feed > 0.0)
TotalTime += TimeSpan.FromMinutes(m.Length / m.Feed);

if (m is Line && (m as Line).Rapid)
continue;
min = Vector3.ElementwiseMin(min, m.End);
max = Vector3.ElementwiseMax(max, m.End);

for (int i = 0; i < 3; i++)
{
if (m.End[i] > maxfeed[i])
maxfeed[i] = m.End[i];
if (m is Line && (m as Line).Rapid)
continue;

if (m.End[i] < minfeed[i])
minfeed[i] = m.End[i];
minfeed = Vector3.ElementwiseMin(minfeed, m.End);
maxfeed = Vector3.ElementwiseMax(maxfeed, m.End);
}
}

Expand All @@ -98,7 +98,6 @@ private GCodeFile(List<Command> toolpath)

Size = size;


MaxFeed = maxfeed;
MinFeed = minfeed;
Vector3 sizefeed = MaxFeed - MinFeed;
Expand Down Expand Up @@ -169,6 +168,7 @@ public void GetModel(LinesVisual3D line, LinesVisual3D rapid, LinesVisual3D arc)
linePoints.Add(l.Start.ToPoint3D());
linePoints.Add(l.End.ToPoint3D());
}

continue;
}

Expand All @@ -189,7 +189,7 @@ public void GetModel(LinesVisual3D line, LinesVisual3D rapid, LinesVisual3D arc)
arc.Points = arcPoints;

sw.Stop();
Console.WriteLine("Generating the Toolpath Model took {0} ms", sw.ElapsedMilliseconds);
Console.WriteLine("Generating the toolpath model took {0} ms", sw.ElapsedMilliseconds);
}

public List<string> GetGCode()
Expand Down Expand Up @@ -221,11 +221,11 @@ public List<string> GetGCode()

string code = l.Rapid ? "G0" : "G1";

if (State.Position.X != l.End.X)
if (State.Position.X != l.End.X && l.PositionValid[0])
code += string.Format(nfi, " X{0:0.###}", l.End.X);
if (State.Position.Y != l.End.Y)
if (State.Position.Y != l.End.Y && l.PositionValid[1])
code += string.Format(nfi, " Y{0:0.###}", l.End.Y);
if (State.Position.Z != l.End.Z)
if (State.Position.Z != l.End.Z && l.PositionValid[2])
code += string.Format(nfi, " Z{0:0.###}", l.End.Z);

GCode.Add(code);
Expand Down Expand Up @@ -351,6 +351,7 @@ public GCodeFile ArcsToLines(double length)
l.End = segment.End;
l.Feed = segment.Feed;
l.Rapid = false;
l.PositionValid = new bool[] { true, true, true };
newFile.Add(l);
}
}
Expand Down Expand Up @@ -379,7 +380,19 @@ public GCodeFile ApplyHeightMap(HeightMap map)
{
Arc a = m as Arc;
if (a.Plane != ArcPlane.XY)
throw new Exception("GCode contains arcs in YZ or XZ plane (G18/19), can't apply HeightMap. Use Arcs to Lines if you really need this.");
throw new Exception("GCode contains arcs in YZ or XZ plane (G18/19), can't apply height map. Use 'Arcs to Lines' if you really need this.");
}

if (m is Line)
{
Line l = (Line)m;

// do not split up or modify any lines that are rapid or not fully defined
if (l.PositionValid.Any(isValid => !isValid) || l.Rapid)
{
newToolPath.Add(l);
continue;
}
}

foreach (Motion subMotion in m.Split(segmentLength))
Expand Down Expand Up @@ -418,7 +431,7 @@ public GCodeFile RotateCW()

// would be possible, but I'm too lazy to implement this properly
if (oldArc.Plane != ArcPlane.XY)
throw new Exception("GCode contains arcs in YZ or XZ plane (G18/19), can't rotate gcode. Use Arcs to Lines if you really need this.");
throw new Exception("GCode contains arcs in YZ or XZ plane (G18/19), can't rotate gcode. Use 'Arcs to Lines' if you really need this.");

newArc.Direction = oldArc.Direction;
newArc.Plane = oldArc.Plane;
Expand All @@ -431,6 +444,7 @@ public GCodeFile RotateCW()
Line oldLine = (Line)oldMotion;
Line newLine = new Line();
newLine.Rapid = oldLine.Rapid;
newLine.PositionValid = oldLine.PositionValid;
newMotion = newLine;
}
else
Expand Down
53 changes: 37 additions & 16 deletions OpenCNCPilot/GCode/GCodeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public enum ParseUnit
class ParserState
{
public Vector3 Position;
public bool[] PositionValid; // true if the position for this coordinate was previously specified in absolute terms, to prevent the start point of (0, 0, 0) to influence the output file
public ArcPlane Plane;
public double Feed;
public ParseDistanceMode DistanceMode;
Expand All @@ -32,7 +33,8 @@ class ParserState

public ParserState()
{
Position = new Vector3();
Position = Vector3.MinValue;
PositionValid = new bool[] { false, false, false };
Plane = ArcPlane.XY;
Feed = 0;
DistanceMode = ParseDistanceMode.Absolute;
Expand Down Expand Up @@ -100,7 +102,7 @@ public static void Parse(IEnumerable<string> file)

sw.Stop();

Console.WriteLine("Parsing the GCode File took {0} ms", sw.ElapsedMilliseconds);
Console.WriteLine("parsing the G code file took {0} ms", sw.ElapsedMilliseconds);
}

static string CleanupLine(string line, int lineNumber)
Expand Down Expand Up @@ -152,7 +154,7 @@ static void Parse(string line, int lineNumber)

if (!ValidWords.Contains(Words[i].Command))
{
Warnings.Add($"ignoring unknown word (letter): \"{Words[i]}\" in line {lineNumber}");
Warnings.Add($"ignoring unknown word (letter): \"{Words[i]}\". (line {lineNumber})");
Words.RemoveAt(i--);
continue;
}
Expand All @@ -174,7 +176,7 @@ static void Parse(string line, int lineNumber)
int param = (int)Words[i].Parameter;

if (param != Words[i].Parameter || param < 0)
throw new ParseException("MCode can only have integer parameters", lineNumber);
throw new ParseException("M code can only have positive integer parameters", lineNumber);

Commands.Add(new MCode() { Code = param });

Expand All @@ -188,7 +190,7 @@ static void Parse(string line, int lineNumber)
double param = Words[i].Parameter;

if (param < 0)
Warnings.Add($"Spindle Speed must be positive in line {lineNumber}");
Warnings.Add($"spindle speed must be positive. (line {lineNumber})");

Commands.Add(new Spindle() { Speed = Math.Abs(param) });

Expand Down Expand Up @@ -270,7 +272,7 @@ static void Parse(string line, int lineNumber)
if (Words.Count >= 2 && Words[i + 1].Command == 'P')
{
if (Words[i + 1].Parameter < 0)
Warnings.Add($"Dwell time must be positive in line {lineNumber}");
Warnings.Add($"dwell time must be positive. (line {lineNumber})");

Commands.Add(new Dwell() { Seconds = Math.Abs(Words[i + 1].Parameter) });
Words.RemoveAt(i + 1);
Expand All @@ -280,7 +282,7 @@ static void Parse(string line, int lineNumber)
}
}

Warnings.Add($"ignoring unknown command G{param} in line {lineNumber}");
Warnings.Add($"ignoring unknown command G{param}. (line {lineNumber})");
Words.RemoveAt(i--);
#endregion
}
Expand All @@ -299,12 +301,22 @@ static void Parse(string line, int lineNumber)
}

if (MotionMode < 0)
throw new ParseException("No Motion Mode active", lineNumber);
throw new ParseException("no motion mode active", lineNumber);

double UnitMultiplier = (State.Unit == ParseUnit.Metric) ? 1 : 25.4;

Vector3 EndPos = State.Position;

if (State.DistanceMode == ParseDistanceMode.Incremental && State.PositionValid.Any(isValid => !isValid))
{
throw new ParseException("incremental motion is only allowed after an absolute position has been established (eg. with \"G90 G0 X0 Y0 Z5\")", lineNumber);
}

if ((MotionMode == 2 || MotionMode == 3) && State.PositionValid.Any(isValid => !isValid))
{
throw new ParseException("arcs (G2/G3) are only allowed after an absolute position has been established (eg. with \"G90 G0 X0 Y0 Z5\")", lineNumber);
}

#region FindEndPos
{
int Incremental = (State.DistanceMode == ParseDistanceMode.Incremental) ? 1 : 0;
Expand All @@ -315,6 +327,7 @@ static void Parse(string line, int lineNumber)
continue;
EndPos.X = Words[i].Parameter * UnitMultiplier + Incremental * EndPos.X;
Words.RemoveAt(i);
State.PositionValid[0] = true;
break;
}

Expand All @@ -324,6 +337,7 @@ static void Parse(string line, int lineNumber)
continue;
EndPos.Y = Words[i].Parameter * UnitMultiplier + Incremental * EndPos.Y;
Words.RemoveAt(i);
State.PositionValid[1] = true;
break;
}

Expand All @@ -333,26 +347,33 @@ static void Parse(string line, int lineNumber)
continue;
EndPos.Z = Words[i].Parameter * UnitMultiplier + Incremental * EndPos.Z;
Words.RemoveAt(i);
State.PositionValid[2] = true;
break;
}
}
#endregion

if (MotionMode != 0 && State.Feed <= 0)
{
throw new ParseException("Feed Rate Undefined", lineNumber);
throw new ParseException("feed rate undefined", lineNumber);
}

if (MotionMode == 1 && State.PositionValid.Any(isValid => !isValid))
{
Warnings.Add($"a feed move is used before an absolute position is established, height maps will not be applied to this motion. (line {lineNumber})");
}

if (MotionMode <= 1)
{
if (Words.Count > 0)
Warnings.Add($"Motion Command must be last in line (ignoring unused Words {string.Join(" ", Words)} in Block) in line {lineNumber}");
Warnings.Add($"motion command must be last in line (ignoring unused words {string.Join(" ", Words)} in block). (line {lineNumber})");

Line motion = new Line();
motion.Start = State.Position;
motion.End = EndPos;
motion.Feed = State.Feed;
motion.Rapid = MotionMode == 0;
State.PositionValid.CopyTo(motion.PositionValid, 0);

Commands.Add(motion);
State.Position = EndPos;
Expand Down Expand Up @@ -394,7 +415,7 @@ static void Parse(string line, int lineNumber)
U = Words[i].Parameter * UnitMultiplier + ArcIncremental * State.Position.X;
break;
case ArcPlane.YZ:
throw new ParseException("Current Plane is YZ, I word is invalid", lineNumber);
throw new ParseException("current plane is YZ, I word is invalid", lineNumber);
case ArcPlane.ZX:
V = Words[i].Parameter * UnitMultiplier + ArcIncremental * State.Position.X;
break;
Expand All @@ -419,7 +440,7 @@ static void Parse(string line, int lineNumber)
U = Words[i].Parameter * UnitMultiplier + ArcIncremental * State.Position.Y;
break;
case ArcPlane.ZX:
throw new ParseException("Current Plane is ZX, J word is invalid", lineNumber);
throw new ParseException("current plane is ZX, J word is invalid", lineNumber);
}

IJKused = true;
Expand All @@ -435,7 +456,7 @@ static void Parse(string line, int lineNumber)
switch (State.Plane)
{
case ArcPlane.XY:
throw new ParseException("Current Plane is XY, K word is invalid", lineNumber);
throw new ParseException("current plane is XY, K word is invalid", lineNumber);
case ArcPlane.YZ:
V = Words[i].Parameter * UnitMultiplier + ArcIncremental * State.Position.Z;
break;
Expand All @@ -458,15 +479,15 @@ static void Parse(string line, int lineNumber)
continue;

if (IJKused)
throw new ParseException("Both IJK and R notation used", lineNumber);
throw new ParseException("both IJK and R notation used", lineNumber);

if (State.Position == EndPos)
throw new ParseException("arcs in R-notation must have non-coincident start and end points", lineNumber);

double Radius = Words[i].Parameter * UnitMultiplier;

if (Radius == 0)
throw new ParseException("Radius can't be zero", lineNumber);
throw new ParseException("radius can't be zero", lineNumber);

double A, B;

Expand Down Expand Up @@ -512,7 +533,7 @@ static void Parse(string line, int lineNumber)
#endregion

if (Words.Count > 0)
Warnings.Add($"Motion Command must be last in line (ignoring unused Words {string.Join(" ", Words)} in Block) in line {lineNumber}");
Warnings.Add($"motion command must be last in line (ignoring unused words {string.Join(" ", Words)} in block). (line {lineNumber})");

Arc arc = new Arc();
arc.Start = State.Position;
Expand Down
Loading

0 comments on commit d80ba35

Please sign in to comment.