diff --git a/OpenNest.IO/NestWriter.cs b/OpenNest.IO/NestWriter.cs index 29692cc..2715bec 100644 --- a/OpenNest.IO/NestWriter.cs +++ b/OpenNest.IO/NestWriter.cs @@ -305,6 +305,15 @@ namespace OpenNest.IO var writer = new StreamWriter(stream); writer.AutoFlush = true; + // Emit variable definitions before G-code + foreach (var v in program.Variables.Values) + { + var line = $"{v.Name} = {v.Expression}"; + if (v.Inline) line += " inline"; + if (v.Global) line += " global"; + writer.WriteLine(line); + } + writer.WriteLine(program.Mode == Mode.Absolute ? "G90" : "G91"); for (var i = 0; i < drawing.Program.Length; ++i) @@ -316,6 +325,13 @@ namespace OpenNest.IO stream.Position = 0; } + private string FormatCoord(double value, string axis, Dictionary variableRefs) + { + if (variableRefs != null && variableRefs.TryGetValue(axis, out var varName)) + return $"${varName}"; + return System.Math.Round(value, OutputPrecision).ToString(CoordinateFormat); + } + private string GetCodeString(ICode code) { switch (code.Type) @@ -324,16 +340,16 @@ namespace OpenNest.IO { var sb = new StringBuilder(); var arcMove = (ArcMove)code; + var refs = arcMove.VariableRefs; - var x = System.Math.Round(arcMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat); - var y = System.Math.Round(arcMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat); - var i = System.Math.Round(arcMove.CenterPoint.X, OutputPrecision).ToString(CoordinateFormat); - var j = System.Math.Round(arcMove.CenterPoint.Y, OutputPrecision).ToString(CoordinateFormat); + var x = FormatCoord(arcMove.EndPoint.X, "X", refs); + var y = FormatCoord(arcMove.EndPoint.Y, "Y", refs); + var i = FormatCoord(arcMove.CenterPoint.X, "I", refs); + var j = FormatCoord(arcMove.CenterPoint.Y, "J", refs); - if (arcMove.Rotation == RotationType.CW) - sb.Append(string.Format("G02X{0}Y{1}I{2}J{3}", x, y, i, j)); - else - sb.Append(string.Format("G03X{0}Y{1}I{2}J{3}", x, y, i, j)); + sb.Append(arcMove.Rotation == RotationType.CW + ? $"G02X{x}Y{y}I{i}J{j}" + : $"G03X{x}Y{y}I{i}J{j}"); if (arcMove.Layer != LayerType.Cut) sb.Append(GetLayerString(arcMove.Layer)); @@ -354,10 +370,9 @@ namespace OpenNest.IO { var sb = new StringBuilder(); var linearMove = (LinearMove)code; + var refs = linearMove.VariableRefs; - sb.Append(string.Format("G01X{0}Y{1}", - System.Math.Round(linearMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat), - System.Math.Round(linearMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat))); + sb.Append($"G01X{FormatCoord(linearMove.EndPoint.X, "X", refs)}Y{FormatCoord(linearMove.EndPoint.Y, "Y", refs)}"); if (linearMove.Layer != LayerType.Cut) sb.Append(GetLayerString(linearMove.Layer)); @@ -371,15 +386,16 @@ namespace OpenNest.IO case CodeType.RapidMove: { var rapidMove = (RapidMove)code; + var refs = rapidMove.VariableRefs; - return string.Format("G00X{0}Y{1}", - System.Math.Round(rapidMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat), - System.Math.Round(rapidMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat)); + return $"G00X{FormatCoord(rapidMove.EndPoint.X, "X", refs)}Y{FormatCoord(rapidMove.EndPoint.Y, "Y", refs)}"; } case CodeType.SetFeedrate: { var setFeedrate = (Feedrate)code; + if (setFeedrate.VariableRef != null) + return $"F${setFeedrate.VariableRef}"; return "F" + setFeedrate.Value; } diff --git a/OpenNest.Tests/IO/NestWriterVariableTests.cs b/OpenNest.Tests/IO/NestWriterVariableTests.cs new file mode 100644 index 0000000..e13d756 --- /dev/null +++ b/OpenNest.Tests/IO/NestWriterVariableTests.cs @@ -0,0 +1,91 @@ +using System.IO; +using System.Linq; +using System.Text; +using OpenNest.CNC; +using OpenNest.Geometry; +using OpenNest.IO; + +namespace OpenNest.Tests.IO; + +public class NestWriterVariableTests +{ + [Fact] + public void RoundTrip_VariableDefinitions_Preserved() + { + var nest = CreateNestWithVariableProgram( + "width = 48.0 global\ndiameter = 0.3\nG90\nG01X$widthY$diameter"); + + var loaded = RoundTrip(nest); + var pgm = loaded.Drawings.First().Program; + + Assert.Equal(2, pgm.Variables.Count); + Assert.Equal(48.0, pgm.Variables["width"].Value); + Assert.True(pgm.Variables["width"].Global); + Assert.Equal(0.3, pgm.Variables["diameter"].Value); + Assert.False(pgm.Variables["diameter"].Global); + } + + [Fact] + public void RoundTrip_VariableRefs_Preserved() + { + var nest = CreateNestWithVariableProgram( + "width = 48.0\nG90\nG01X$widthY0"); + + var loaded = RoundTrip(nest); + var pgm = loaded.Drawings.First().Program; + + var linear = (LinearMove)pgm.Codes[0]; + Assert.Equal(48.0, linear.EndPoint.X); + Assert.NotNull(linear.VariableRefs); + Assert.Equal("width", linear.VariableRefs["X"]); + } + + [Fact] + public void RoundTrip_InlineFlag_Preserved() + { + var nest = CreateNestWithVariableProgram( + "kerf = 0.06 inline\nG90\nG01X1Y0"); + + var loaded = RoundTrip(nest); + var pgm = loaded.Drawings.First().Program; + + Assert.True(pgm.Variables["kerf"].Inline); + } + + [Fact] + public void RoundTrip_NoVariables_WorksAsNormal() + { + var nest = CreateNestWithVariableProgram("G90\nG01X1Y2"); + + var loaded = RoundTrip(nest); + var pgm = loaded.Drawings.First().Program; + + Assert.Empty(pgm.Variables); + var linear = (LinearMove)pgm.Codes[0]; + Assert.Equal(1.0, linear.EndPoint.X); + } + + private static Nest CreateNestWithVariableProgram(string gcode) + { + var stream = new MemoryStream(Encoding.UTF8.GetBytes(gcode)); + var reader = new ProgramReader(stream); + var program = reader.Read(); + reader.Close(); + + var drawing = new Drawing("TestPart", program); + var nest = new Nest { Name = "Test" }; + nest.Drawings.Add(drawing); + var plate = new Plate(new Size(100, 100)); + plate.Parts.Add(new Part(drawing, new Vector(0, 0))); + nest.Plates.Add(plate); + return nest; + } + + private static Nest RoundTrip(Nest nest) + { + var ms = new MemoryStream(); + new NestWriter(nest).Write(ms); + ms.Position = 0; + return new NestReader(ms).Read(); + } +}