feat: serialize variable definitions and \$references in NestWriter
Emit variable definitions before G-code in program text entries and use \$varName syntax for coordinate fields that have VariableRefs, so programs round-trip through NestWriter → NestReader without losing variable information. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+30
-14
@@ -305,6 +305,15 @@ namespace OpenNest.IO
|
|||||||
var writer = new StreamWriter(stream);
|
var writer = new StreamWriter(stream);
|
||||||
writer.AutoFlush = true;
|
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");
|
writer.WriteLine(program.Mode == Mode.Absolute ? "G90" : "G91");
|
||||||
|
|
||||||
for (var i = 0; i < drawing.Program.Length; ++i)
|
for (var i = 0; i < drawing.Program.Length; ++i)
|
||||||
@@ -316,6 +325,13 @@ namespace OpenNest.IO
|
|||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string FormatCoord(double value, string axis, Dictionary<string, string> 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)
|
private string GetCodeString(ICode code)
|
||||||
{
|
{
|
||||||
switch (code.Type)
|
switch (code.Type)
|
||||||
@@ -324,16 +340,16 @@ namespace OpenNest.IO
|
|||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
var arcMove = (ArcMove)code;
|
var arcMove = (ArcMove)code;
|
||||||
|
var refs = arcMove.VariableRefs;
|
||||||
|
|
||||||
var x = System.Math.Round(arcMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat);
|
var x = FormatCoord(arcMove.EndPoint.X, "X", refs);
|
||||||
var y = System.Math.Round(arcMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat);
|
var y = FormatCoord(arcMove.EndPoint.Y, "Y", refs);
|
||||||
var i = System.Math.Round(arcMove.CenterPoint.X, OutputPrecision).ToString(CoordinateFormat);
|
var i = FormatCoord(arcMove.CenterPoint.X, "I", refs);
|
||||||
var j = System.Math.Round(arcMove.CenterPoint.Y, OutputPrecision).ToString(CoordinateFormat);
|
var j = FormatCoord(arcMove.CenterPoint.Y, "J", refs);
|
||||||
|
|
||||||
if (arcMove.Rotation == RotationType.CW)
|
sb.Append(arcMove.Rotation == RotationType.CW
|
||||||
sb.Append(string.Format("G02X{0}Y{1}I{2}J{3}", x, y, i, j));
|
? $"G02X{x}Y{y}I{i}J{j}"
|
||||||
else
|
: $"G03X{x}Y{y}I{i}J{j}");
|
||||||
sb.Append(string.Format("G03X{0}Y{1}I{2}J{3}", x, y, i, j));
|
|
||||||
|
|
||||||
if (arcMove.Layer != LayerType.Cut)
|
if (arcMove.Layer != LayerType.Cut)
|
||||||
sb.Append(GetLayerString(arcMove.Layer));
|
sb.Append(GetLayerString(arcMove.Layer));
|
||||||
@@ -354,10 +370,9 @@ namespace OpenNest.IO
|
|||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
var linearMove = (LinearMove)code;
|
var linearMove = (LinearMove)code;
|
||||||
|
var refs = linearMove.VariableRefs;
|
||||||
|
|
||||||
sb.Append(string.Format("G01X{0}Y{1}",
|
sb.Append($"G01X{FormatCoord(linearMove.EndPoint.X, "X", refs)}Y{FormatCoord(linearMove.EndPoint.Y, "Y", refs)}");
|
||||||
System.Math.Round(linearMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat),
|
|
||||||
System.Math.Round(linearMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat)));
|
|
||||||
|
|
||||||
if (linearMove.Layer != LayerType.Cut)
|
if (linearMove.Layer != LayerType.Cut)
|
||||||
sb.Append(GetLayerString(linearMove.Layer));
|
sb.Append(GetLayerString(linearMove.Layer));
|
||||||
@@ -371,15 +386,16 @@ namespace OpenNest.IO
|
|||||||
case CodeType.RapidMove:
|
case CodeType.RapidMove:
|
||||||
{
|
{
|
||||||
var rapidMove = (RapidMove)code;
|
var rapidMove = (RapidMove)code;
|
||||||
|
var refs = rapidMove.VariableRefs;
|
||||||
|
|
||||||
return string.Format("G00X{0}Y{1}",
|
return $"G00X{FormatCoord(rapidMove.EndPoint.X, "X", refs)}Y{FormatCoord(rapidMove.EndPoint.Y, "Y", refs)}";
|
||||||
System.Math.Round(rapidMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat),
|
|
||||||
System.Math.Round(rapidMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case CodeType.SetFeedrate:
|
case CodeType.SetFeedrate:
|
||||||
{
|
{
|
||||||
var setFeedrate = (Feedrate)code;
|
var setFeedrate = (Feedrate)code;
|
||||||
|
if (setFeedrate.VariableRef != null)
|
||||||
|
return $"F${setFeedrate.VariableRef}";
|
||||||
return "F" + setFeedrate.Value;
|
return "F" + setFeedrate.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user