diff --git a/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs b/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs
new file mode 100644
index 0000000..0f280b8
--- /dev/null
+++ b/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using OpenNest.CNC;
+using OpenNest.Geometry;
+
+namespace OpenNest.Posts.Cincinnati;
+
+///
+/// Emits one Cincinnati-format sheet subprogram per plate.
+/// Splits each part's codes at RapidMove boundaries to handle multi-contour parts.
+///
+public sealed class CincinnatiSheetWriter
+{
+ private readonly CincinnatiPostConfig _config;
+ private readonly ProgramVariableManager _vars;
+ private readonly CoordinateFormatter _fmt;
+ private readonly CincinnatiFeatureWriter _featureWriter;
+
+ public CincinnatiSheetWriter(CincinnatiPostConfig config, ProgramVariableManager vars)
+ {
+ _config = config;
+ _vars = vars;
+ _fmt = new CoordinateFormatter(config.PostedAccuracy);
+ _featureWriter = new CincinnatiFeatureWriter(config);
+ }
+
+ ///
+ /// Writes a complete sheet subprogram for the given plate.
+ ///
+ public void Write(TextWriter w, Plate plate, string nestName, int sheetIndex, int subNumber)
+ {
+ if (plate.Parts.Count == 0)
+ return;
+
+ var width = plate.Size.Width;
+ var length = plate.Size.Length;
+ var sheetDiagonal = System.Math.Sqrt(width * width + length * length);
+ var libraryFile = _config.DefaultLibraryFile ?? "";
+ var varDeclSub = _config.VariableDeclarationSubprogram;
+ var partCount = plate.Parts.Count(p => !p.BaseDrawing.IsCutOff);
+
+ // 1. Sheet header
+ w.WriteLine("(*****************************************************)");
+ w.WriteLine($"( START OF {nestName}.{sheetIndex:D3} )");
+ w.WriteLine($":{subNumber}");
+ w.WriteLine($"( Sheet {sheetIndex} )");
+ w.WriteLine($"( Layout {sheetIndex} )");
+ w.WriteLine($"( SHEET NAME = {_fmt.FormatCoord(length)} X {_fmt.FormatCoord(width)} )");
+ w.WriteLine($"( Total parts on sheet = {partCount} )");
+ w.WriteLine($"#{_config.SheetWidthVariable}={_fmt.FormatCoord(width)}(SHEET WIDTH FOR CUTOFFS)");
+ w.WriteLine($"#{_config.SheetLengthVariable}={_fmt.FormatCoord(length)}(SHEET LENGTH FOR CUTOFFS)");
+
+ // 2. Coordinate setup
+ w.WriteLine("M42");
+ w.WriteLine("N10000");
+ w.WriteLine("G92X#5021Y#5022");
+ if (!string.IsNullOrEmpty(libraryFile))
+ w.WriteLine($"G89 P {libraryFile}");
+ w.WriteLine($"M98 P{varDeclSub} (Variable Declaration)");
+ w.WriteLine("G90");
+ w.WriteLine("M47(CPT)");
+ if (!string.IsNullOrEmpty(libraryFile))
+ w.WriteLine($"G89 P {libraryFile}");
+ w.WriteLine("GOTO1( Goto Feature )");
+
+ // 3. Order parts: non-cutoff sorted by Bottom then Left, cutoffs last
+ var nonCutoffParts = plate.Parts
+ .Where(p => !p.BaseDrawing.IsCutOff)
+ .OrderBy(p => p.Bottom)
+ .ThenBy(p => p.Left)
+ .ToList();
+
+ var cutoffParts = plate.Parts
+ .Where(p => p.BaseDrawing.IsCutOff)
+ .ToList();
+
+ var allParts = nonCutoffParts.Concat(cutoffParts).ToList();
+
+ // 4. Multi-contour splitting
+ var features = new List<(Part part, List codes)>();
+ foreach (var part in allParts)
+ {
+ List current = null;
+ foreach (var code in part.Program.Codes)
+ {
+ if (code is RapidMove)
+ {
+ if (current != null)
+ features.Add((part, current));
+ current = new List { code };
+ }
+ else
+ {
+ current ??= new List();
+ current.Add(code);
+ }
+ }
+ if (current != null && current.Count > 0)
+ features.Add((part, current));
+ }
+
+ // 5. Emit features
+ var lastPartName = "";
+ for (var i = 0; i < features.Count; i++)
+ {
+ var (part, codes) = features[i];
+ var partName = part.BaseDrawing.Name;
+ var isFirstFeatureOfPart = partName != lastPartName;
+ var isSafetyHeadraise = partName != lastPartName && lastPartName != "";
+ var isLastFeature = i == features.Count - 1;
+
+ // Feature numbering: first = FeatureLineNumberStart, then 1002, 1003, etc.
+ var featureNumber = i == 0
+ ? _config.FeatureLineNumberStart
+ : 1000 + i + 1;
+
+ // Compute cut distance for this feature
+ var cutDistance = ComputeCutDistance(codes);
+
+ var ctx = new FeatureContext
+ {
+ Codes = codes,
+ FeatureNumber = featureNumber,
+ PartName = partName,
+ IsFirstFeatureOfPart = isFirstFeatureOfPart,
+ IsLastFeatureOnSheet = isLastFeature,
+ IsSafetyHeadraise = isSafetyHeadraise,
+ IsExteriorFeature = false,
+ LibraryFile = libraryFile,
+ CutDistance = cutDistance,
+ SheetDiagonal = sheetDiagonal
+ };
+
+ _featureWriter.Write(w, ctx);
+ lastPartName = partName;
+ }
+
+ // 6. Footer
+ w.WriteLine("M42");
+ w.WriteLine("G0X0Y0");
+ if (_config.PalletExchange != PalletMode.None)
+ w.WriteLine($"N{sheetIndex + 1}M50");
+ w.WriteLine($"M99(END OF {nestName}.{sheetIndex:D3})");
+ }
+
+ private static double ComputeCutDistance(List codes)
+ {
+ var distance = 0.0;
+ var currentPos = Vector.Zero;
+
+ foreach (var code in codes)
+ {
+ if (code is RapidMove rapid)
+ {
+ currentPos = rapid.EndPoint;
+ }
+ else if (code is LinearMove linear)
+ {
+ distance += currentPos.DistanceTo(linear.EndPoint);
+ currentPos = linear.EndPoint;
+ }
+ else if (code is ArcMove arc)
+ {
+ distance += currentPos.DistanceTo(arc.EndPoint);
+ currentPos = arc.EndPoint;
+ }
+ }
+
+ return distance;
+ }
+}
diff --git a/OpenNest.Tests/Cincinnati/CincinnatiSheetWriterTests.cs b/OpenNest.Tests/Cincinnati/CincinnatiSheetWriterTests.cs
new file mode 100644
index 0000000..a74b718
--- /dev/null
+++ b/OpenNest.Tests/Cincinnati/CincinnatiSheetWriterTests.cs
@@ -0,0 +1,117 @@
+using System.IO;
+using System.Linq;
+using System.Text;
+using OpenNest.CNC;
+using OpenNest.Geometry;
+using OpenNest.Posts.Cincinnati;
+
+namespace OpenNest.Tests.Cincinnati;
+
+public class CincinnatiSheetWriterTests
+{
+ [Fact]
+ public void WriteSheet_EmitsSheetHeader()
+ {
+ var config = new CincinnatiPostConfig
+ {
+ DefaultLibraryFile = "MS135N2PANEL.lib",
+ PostedAccuracy = 4
+ };
+ var plate = new Plate(48.0, 96.0);
+ plate.Parts.Add(new Part(new Drawing("TestPart", CreateSimpleProgram())));
+
+ var sb = new StringBuilder();
+ using var sw = new StringWriter(sb);
+ var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
+
+ sheetWriter.Write(sw, plate, "TestNest", 1, 101);
+
+ var output = sb.ToString();
+ Assert.Contains(":101", output);
+ Assert.Contains("( Sheet 1 )", output);
+ Assert.Contains("#110=", output);
+ Assert.Contains("#111=", output);
+ Assert.Contains("G92X#5021Y#5022", output);
+ Assert.Contains("M99", output);
+ }
+
+ [Fact]
+ public void WriteSheet_EmitsReturnToOriginAndPalletExchange()
+ {
+ var config = new CincinnatiPostConfig
+ {
+ PalletExchange = PalletMode.EndOfSheet,
+ PostedAccuracy = 4
+ };
+ var plate = new Plate(48.0, 96.0);
+ plate.Parts.Add(new Part(new Drawing("TestPart", CreateSimpleProgram())));
+
+ var sb = new StringBuilder();
+ using var sw = new StringWriter(sb);
+ var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
+
+ sheetWriter.Write(sw, plate, "TestNest", 1, 101);
+
+ var output = sb.ToString();
+ Assert.Contains("M42", output);
+ Assert.Contains("G0X0Y0", output);
+ Assert.Contains("M50", output);
+ }
+
+ [Fact]
+ public void WriteSheet_SkipsEmptyPlate()
+ {
+ var config = new CincinnatiPostConfig { PostedAccuracy = 4 };
+ var plate = new Plate(48.0, 96.0);
+
+ var sb = new StringBuilder();
+ using var sw = new StringWriter(sb);
+ var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
+
+ sheetWriter.Write(sw, plate, "TestNest", 1, 101);
+
+ Assert.Equal("", sb.ToString());
+ }
+
+ [Fact]
+ public void WriteSheet_SplitsMultiContourParts()
+ {
+ var config = new CincinnatiPostConfig { PostedAccuracy = 4 };
+ var pgm = new Program();
+ // First contour (hole)
+ pgm.Codes.Add(new RapidMove(1, 1));
+ pgm.Codes.Add(new LinearMove(2, 1));
+ pgm.Codes.Add(new LinearMove(2, 2));
+ pgm.Codes.Add(new LinearMove(1, 1));
+ // Second contour (exterior)
+ pgm.Codes.Add(new RapidMove(0, 0));
+ pgm.Codes.Add(new LinearMove(5, 0));
+ pgm.Codes.Add(new LinearMove(5, 5));
+ pgm.Codes.Add(new LinearMove(0, 0));
+
+ var plate = new Plate(48.0, 96.0);
+ plate.Parts.Add(new Part(new Drawing("MultiContour", pgm)));
+
+ var sb = new StringBuilder();
+ using var sw = new StringWriter(sb);
+ var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
+
+ sheetWriter.Write(sw, plate, "TestNest", 1, 101);
+
+ var output = sb.ToString();
+ // Should have two G84 pierce commands (one per contour)
+ var g84Count = output.Split('\n').Count(l => l.Trim() == "G84");
+ Assert.Equal(2, g84Count);
+ }
+
+ private static Program CreateSimpleProgram()
+ {
+ var pgm = new Program();
+ pgm.Codes.Add(new RapidMove(0, 0));
+ pgm.Codes.Add(new LinearMove(1, 0));
+ pgm.Codes.Add(new LinearMove(1, 1));
+ pgm.Codes.Add(new LinearMove(0, 1));
+ pgm.Codes.Add(new LinearMove(0, 0));
+ return pgm;
+ }
+}