diff --git a/OpenNest.Posts.Cincinnati/CincinnatiPostProcessor.cs b/OpenNest.Posts.Cincinnati/CincinnatiPostProcessor.cs new file mode 100644 index 0000000..d5e6a82 --- /dev/null +++ b/OpenNest.Posts.Cincinnati/CincinnatiPostProcessor.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace OpenNest.Posts.Cincinnati +{ + public sealed class CincinnatiPostProcessor : IPostProcessor + { + public string Name => "Cincinnati CL-707"; + public string Author => "OpenNest"; + public string Description => "Cincinnati CL-707/CL-800/CL-900/CL-940/CLX family"; + + public CincinnatiPostConfig Config { get; } + + public CincinnatiPostProcessor(CincinnatiPostConfig config) + { + Config = config; + } + + public void Post(Nest nest, Stream outputStream) + { + // 1. Create variable manager and register standard variables + var vars = CreateVariableManager(); + + // 2. Filter to non-empty plates + var plates = nest.Plates + .Where(p => p.Parts.Count > 0) + .ToList(); + + // 3. Create writers + var preamble = new CincinnatiPreambleWriter(Config); + var sheetWriter = new CincinnatiSheetWriter(Config, vars); + + // 4. Build material description from first plate + var material = plates.FirstOrDefault()?.Material; + var materialDesc = material != null + ? $"{material.Name}{(string.IsNullOrEmpty(material.Grade) ? "" : $", {material.Grade}")}" + : ""; + + // 5. Write to stream + using var writer = new StreamWriter(outputStream, Encoding.UTF8, 1024, leaveOpen: true); + + // Main program + preamble.WriteMainProgram(writer, nest.Name ?? "NEST", materialDesc, plates.Count); + + // Variable declaration subprogram + preamble.WriteVariableDeclaration(writer, vars); + + // Sheet subprograms + for (var i = 0; i < plates.Count; i++) + { + var sheetIndex = i + 1; + var subNumber = Config.SheetSubprogramStart + i; + sheetWriter.Write(writer, plates[i], nest.Name ?? "NEST", sheetIndex, subNumber); + } + + writer.Flush(); + } + + public void Post(Nest nest, string outputFile) + { + using var fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write); + Post(nest, fs); + } + + private ProgramVariableManager CreateVariableManager() + { + var vars = new ProgramVariableManager(); + vars.GetOrCreate("ProcessFeedrate", 148); // Set by G89, no expression + vars.GetOrCreate("LeadInFeedrate", 126, $"[#148*{Config.LeadInFeedratePercent}]"); + vars.GetOrCreate("LeadInArcLine2Feedrate", 127, $"[#148*{Config.LeadInArcLine2FeedratePercent}]"); + vars.GetOrCreate("CircleFeedrate", 128, Config.CircleFeedrateMultiplier.ToString("0.#")); + return vars; + } + } +} diff --git a/OpenNest.Tests/Cincinnati/CincinnatiPostProcessorTests.cs b/OpenNest.Tests/Cincinnati/CincinnatiPostProcessorTests.cs new file mode 100644 index 0000000..1bf8100 --- /dev/null +++ b/OpenNest.Tests/Cincinnati/CincinnatiPostProcessorTests.cs @@ -0,0 +1,122 @@ +using System.IO; +using System.Text; +using OpenNest.CNC; +using OpenNest.Geometry; +using OpenNest.Posts.Cincinnati; + +namespace OpenNest.Tests.Cincinnati; + +public class CincinnatiPostProcessorTests +{ + [Fact] + public void Post_ProducesOutput_ForSinglePlateNest() + { + var nest = CreateTestNest(); + var config = new CincinnatiPostConfig + { + ConfigurationName = "CL940", + DefaultLibraryFile = "MS135N2PANEL.lib", + PostedAccuracy = 4 + }; + var post = new CincinnatiPostProcessor(config); + + using var ms = new MemoryStream(); + post.Post(nest, ms); + + var output = Encoding.UTF8.GetString(ms.ToArray()); + + // Main program elements + Assert.Contains("( NEST TestNest )", output); + Assert.Contains("( CONFIGURATION - CL940 )", output); + Assert.Contains("G20", output); + Assert.Contains("M30 (END OF MAIN)", output); + + // Variable declaration + Assert.Contains(":100", output); + Assert.Contains("#126=", output); + + // Sheet subprogram + Assert.Contains(":101", output); + Assert.Contains("( Sheet 1 )", output); + Assert.Contains("G84", output); + Assert.Contains("M99", output); + } + + [Fact] + public void Post_ImplementsIPostProcessor() + { + var post = new CincinnatiPostProcessor(new CincinnatiPostConfig()); + IPostProcessor pp = post; + + Assert.Equal("Cincinnati CL-707", pp.Name); + Assert.Equal("OpenNest", pp.Author); + } + + [Fact] + public void Post_SkipsEmptyPlates() + { + var nest = new Nest("TestNest"); + nest.Plates.Add(new Plate(48, 96)); // empty plate + var plate2 = new Plate(48, 96); + plate2.Parts.Add(new Part(new Drawing("Part1", CreateSquareProgram()))); + nest.Plates.Add(plate2); + + var config = new CincinnatiPostConfig { PostedAccuracy = 4 }; + var post = new CincinnatiPostProcessor(config); + + using var ms = new MemoryStream(); + post.Post(nest, ms); + + var output = Encoding.UTF8.GetString(ms.ToArray()); + + // Should only have one sheet subprogram call in main + Assert.Contains("N1M98 P101 (SHEET 1)", output); + Assert.DoesNotContain("SHEET 2", output); + } + + [Fact] + public void Post_ToFile_CreatesFile() + { + var nest = CreateTestNest(); + var config = new CincinnatiPostConfig { PostedAccuracy = 4 }; + var post = new CincinnatiPostProcessor(config); + var tempFile = Path.GetTempFileName() + ".CNC"; + + try + { + post.Post(nest, tempFile); + Assert.True(File.Exists(tempFile)); + var content = File.ReadAllText(tempFile); + Assert.Contains("M30", content); + } + finally + { + if (File.Exists(tempFile)) + File.Delete(tempFile); + } + } + + private static Nest CreateTestNest() + { + var nest = new Nest("TestNest"); + var drawing = new Drawing("Square", CreateSquareProgram()); + nest.Drawings.Add(drawing); + + var plate = new Plate(48.0, 96.0); + plate.Parts.Add(new Part(drawing, new Vector(10, 10))); + nest.Plates.Add(plate); + + return nest; + } + + private static Program CreateSquareProgram() + { + var pgm = new Program(); + pgm.Codes.Add(new RapidMove(0, 0)); + pgm.Codes.Add(new LinearMove(2, 0)); + pgm.Codes.Add(new LinearMove(2, 2)); + pgm.Codes.Add(new LinearMove(0, 2)); + pgm.Codes.Add(new LinearMove(0, 0)); + return pgm; + } +}