diff --git a/OpenNest.IO/NestReader.cs b/OpenNest.IO/NestReader.cs index 8458561..1ee5899 100644 --- a/OpenNest.IO/NestReader.cs +++ b/OpenNest.IO/NestReader.cs @@ -71,10 +71,68 @@ namespace OpenNest.IO var reader = new ProgramReader(memStream); programs[i] = reader.Read(); + + // Read sub-programs if present + var subsEntry = zipArchive.GetEntry($"programs/program-{i}-subs"); + if (subsEntry != null) + { + using var subsStream = subsEntry.Open(); + ReadSubPrograms(programs[i], subsStream); + } } return programs; } + private static void ReadSubPrograms(Program parent, Stream stream) + { + using var reader = new StreamReader(stream); + var currentId = -1; + var lines = new List(); + + string line; + while ((line = reader.ReadLine()) != null) + { + var trimmed = line.Trim(); + + if (trimmed.StartsWith(":") && int.TryParse(trimmed.Substring(1), out var id)) + { + // Flush previous sub-program + if (currentId >= 0 && lines.Count > 0) + parent.SubPrograms[currentId] = ParseSubProgram(lines); + + currentId = id; + lines.Clear(); + } + else if (trimmed == "M99") + { + if (currentId >= 0 && lines.Count > 0) + parent.SubPrograms[currentId] = ParseSubProgram(lines); + + currentId = -1; + lines.Clear(); + } + else + { + lines.Add(trimmed); + } + } + + // Wire up SubProgramCall.Program references + foreach (var code in parent.Codes) + { + if (code is SubProgramCall call && parent.SubPrograms.TryGetValue(call.Id, out var sub)) + call.Program = sub; + } + } + + private static Program ParseSubProgram(List lines) + { + var text = string.Join("\n", lines); + var memStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(text)); + var reader = new ProgramReader(memStream); + return reader.Read(); + } + private Dictionary entities, HashSet suppressed)> ReadEntitySets(int count) { var result = new Dictionary, HashSet)>(); diff --git a/OpenNest.IO/NestWriter.cs b/OpenNest.IO/NestWriter.cs index fffe57d..c12b44c 100644 --- a/OpenNest.IO/NestWriter.cs +++ b/OpenNest.IO/NestWriter.cs @@ -308,8 +308,32 @@ namespace OpenNest.IO WriteDrawing(stream, kvp.Value); var entry = zipArchive.CreateEntry(name); - using var entryStream = entry.Open(); - stream.CopyTo(entryStream); + using (var entryStream = entry.Open()) + { + stream.CopyTo(entryStream); + } + + // Write sub-programs if present + if (kvp.Value.Program.SubPrograms.Count > 0) + WriteSubPrograms(zipArchive, kvp.Key, kvp.Value.Program.SubPrograms); + } + } + + private void WriteSubPrograms(ZipArchive zipArchive, int drawingId, Dictionary subPrograms) + { + var entry = zipArchive.CreateEntry($"programs/program-{drawingId}-subs"); + using var entryStream = entry.Open(); + using var writer = new StreamWriter(entryStream, Encoding.UTF8); + + foreach (var kvp in subPrograms.OrderBy(k => k.Key)) + { + writer.WriteLine($":{kvp.Key}"); + writer.WriteLine(kvp.Value.Mode == Mode.Absolute ? "G90" : "G91"); + + foreach (var code in kvp.Value.Codes) + writer.WriteLine(GetCodeString(code)); + + writer.WriteLine("M99"); } } @@ -448,7 +472,9 @@ namespace OpenNest.IO case CodeType.SubProgramCall: { var subProgramCall = (SubProgramCall)code; - break; + var x = System.Math.Round(subProgramCall.Offset.X, OutputPrecision).ToString(CoordinateFormat); + var y = System.Math.Round(subProgramCall.Offset.Y, OutputPrecision).ToString(CoordinateFormat); + return $"G65P{subProgramCall.Id}X{x}Y{y}"; } } diff --git a/OpenNest.IO/ProgramReader.cs b/OpenNest.IO/ProgramReader.cs index 5df8200..ae2a944 100644 --- a/OpenNest.IO/ProgramReader.cs +++ b/OpenNest.IO/ProgramReader.cs @@ -374,6 +374,8 @@ namespace OpenNest.IO { var p = 0; var r = 0.0; + var x = 0.0; + var y = 0.0; while (section == CodeSection.SubProgram) { @@ -395,13 +397,26 @@ namespace OpenNest.IO r = double.Parse(code.Value); break; + case 'X': + x = double.Parse(code.Value); + break; + + case 'Y': + y = double.Parse(code.Value); + break; + default: section = CodeSection.Unknown; break; } } - program.Codes.Add(new SubProgramCall() { Id = p, Rotation = r }); + program.Codes.Add(new SubProgramCall + { + Id = p, + Rotation = r, + Offset = new Geometry.Vector(x, y) + }); } private Code GetNextCode() diff --git a/OpenNest.Tests/IO/SubProgramSerializationTests.cs b/OpenNest.Tests/IO/SubProgramSerializationTests.cs new file mode 100644 index 0000000..184987b --- /dev/null +++ b/OpenNest.Tests/IO/SubProgramSerializationTests.cs @@ -0,0 +1,75 @@ +using OpenNest.CNC; +using OpenNest.Geometry; +using OpenNest.IO; + +namespace OpenNest.Tests.IO; + +public class SubProgramSerializationTests +{ + [Fact] + public void NestWriter_WritesSubProgramCall_WithOffset() + { + var nest = CreateNestWithHoleSubProgram(); + + using var stream = new MemoryStream(); + var writer = new NestWriter(nest); + writer.Write(stream); + stream.Position = 0; + + var reader = new NestReader(stream); + var loaded = reader.Read(); + + var drawing = loaded.Drawings.First(); + var calls = drawing.Program.Codes.OfType().ToList(); + Assert.Single(calls); + Assert.Equal(5, calls[0].Offset.X, 1); + Assert.Equal(5, calls[0].Offset.Y, 1); + } + + [Fact] + public void NestWriter_WritesSubPrograms_AndRestoresOnLoad() + { + var nest = CreateNestWithHoleSubProgram(); + + using var stream = new MemoryStream(); + var writer = new NestWriter(nest); + writer.Write(stream); + stream.Position = 0; + + var reader = new NestReader(stream); + var loaded = reader.Read(); + + var drawing = loaded.Drawings.First(); + Assert.True(drawing.Program.SubPrograms.Count > 0); + + var call = drawing.Program.Codes.OfType().First(); + Assert.True(drawing.Program.SubPrograms.ContainsKey(call.Id)); + } + + private static Nest CreateNestWithHoleSubProgram() + { + var sub = new Program(Mode.Incremental); + sub.Codes.Add(new LinearMove(0.1, 0) { Layer = LayerType.Leadin }); + sub.Codes.Add(new ArcMove(new Vector(0, 0), new Vector(-0.5, 0), RotationType.CW)); + + var pgm = new Program(Mode.Absolute); + pgm.SubPrograms[42] = sub; + pgm.Codes.Add(new SubProgramCall { Id = 42, Program = sub, Offset = new Vector(5, 5) }); + // Add perimeter so the drawing has non-zero geometry + pgm.Codes.Add(new RapidMove(0, 0)); + pgm.Codes.Add(new LinearMove(10, 0)); + pgm.Codes.Add(new LinearMove(10, 10)); + pgm.Codes.Add(new LinearMove(0, 10)); + pgm.Codes.Add(new LinearMove(0, 0)); + + var drawing = new Drawing("TestPart") { Program = pgm }; + var nest = new Nest(); + nest.Drawings.Add(drawing); + + var plate = new Plate { Size = new Size(48, 96) }; + plate.Parts.Add(new Part(drawing)); + nest.Plates.Add(plate); + + return nest; + } +}