diff --git a/OpenNest.Posts.Cincinnati/CincinnatiFeatureWriter.cs b/OpenNest.Posts.Cincinnati/CincinnatiFeatureWriter.cs
index ca5bbde..f2885cb 100644
--- a/OpenNest.Posts.Cincinnati/CincinnatiFeatureWriter.cs
+++ b/OpenNest.Posts.Cincinnati/CincinnatiFeatureWriter.cs
@@ -23,6 +23,12 @@ public sealed class FeatureContext
public string LibraryFile { get; set; } = "";
public double CutDistance { get; set; }
public double SheetDiagonal { get; set; }
+
+ ///
+ /// Part location on the plate. Added to all output X/Y coordinates
+ /// so part-relative programs become plate-absolute under G90.
+ ///
+ public Vector PartLocation { get; set; } = Vector.Zero;
}
///
@@ -51,12 +57,13 @@ public sealed class CincinnatiFeatureWriter
var currentPos = Vector.Zero;
var lastFeedVar = "";
var kerfEmitted = false;
+ var offset = ctx.PartLocation;
// Find the pierce point from the first rapid move
var piercePoint = FindPiercePoint(ctx.Codes);
// 1. Rapid to pierce point (with line number if configured)
- WriteRapidToPierce(writer, ctx.FeatureNumber, piercePoint);
+ WriteRapidToPierce(writer, ctx.FeatureNumber, piercePoint, offset);
// 2. Part name comment on first feature of each part
if (ctx.IsFirstFeatureOfPart && !string.IsNullOrEmpty(ctx.PartName))
@@ -105,7 +112,7 @@ public sealed class CincinnatiFeatureWriter
kerfEmitted = true;
}
- sb.Append($"G1 X{_fmt.FormatCoord(linear.EndPoint.X)} Y{_fmt.FormatCoord(linear.EndPoint.Y)}");
+ sb.Append($"G1 X{_fmt.FormatCoord(linear.EndPoint.X + offset.X)} Y{_fmt.FormatCoord(linear.EndPoint.Y + offset.Y)}");
// Feedrate — etch always uses process feedrate
var feedVar = ctx.IsEtch ? "#148" : GetLinearFeedVariable(linear.Layer);
@@ -131,7 +138,7 @@ public sealed class CincinnatiFeatureWriter
// G2 = CW, G3 = CCW
var gCode = arc.Rotation == RotationType.CW ? "G2" : "G3";
- sb.Append($"{gCode} X{_fmt.FormatCoord(arc.EndPoint.X)} Y{_fmt.FormatCoord(arc.EndPoint.Y)}");
+ sb.Append($"{gCode} X{_fmt.FormatCoord(arc.EndPoint.X + offset.X)} Y{_fmt.FormatCoord(arc.EndPoint.Y + offset.Y)}");
// Convert absolute center to incremental I/J
var i = arc.CenterPoint.X - currentPos.X;
@@ -188,14 +195,14 @@ public sealed class CincinnatiFeatureWriter
return Vector.Zero;
}
- private void WriteRapidToPierce(TextWriter writer, int featureNumber, Vector piercePoint)
+ private void WriteRapidToPierce(TextWriter writer, int featureNumber, Vector piercePoint, Vector offset)
{
var sb = new StringBuilder();
if (_config.UseLineNumbers)
sb.Append($"N{featureNumber} ");
- sb.Append($"G0 X{_fmt.FormatCoord(piercePoint.X)} Y{_fmt.FormatCoord(piercePoint.Y)}");
+ sb.Append($"G0 X{_fmt.FormatCoord(piercePoint.X + offset.X)} Y{_fmt.FormatCoord(piercePoint.Y + offset.Y)}");
writer.WriteLine(sb.ToString());
}
diff --git a/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs b/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs
index 084f876..6a18ef2 100644
--- a/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs
+++ b/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs
@@ -153,7 +153,8 @@ public sealed class CincinnatiSheetWriter
IsEtch = isEtch,
LibraryFile = isEtch ? etchLibrary : cutLibrary,
CutDistance = cutDistance,
- SheetDiagonal = sheetDiagonal
+ SheetDiagonal = sheetDiagonal,
+ PartLocation = part.Location
};
_featureWriter.Write(w, ctx);
@@ -240,7 +241,8 @@ public sealed class CincinnatiSheetWriter
IsEtch = isEtch,
LibraryFile = isEtch ? etchLibrary : cutLibrary,
CutDistance = cutDistance,
- SheetDiagonal = sheetDiagonal
+ SheetDiagonal = sheetDiagonal,
+ PartLocation = part.Location
};
_featureWriter.Write(w, ctx);
diff --git a/OpenNest.Tests/Cincinnati/CincinnatiSheetWriterTests.cs b/OpenNest.Tests/Cincinnati/CincinnatiSheetWriterTests.cs
index 3fdc8b0..d13279a 100644
--- a/OpenNest.Tests/Cincinnati/CincinnatiSheetWriterTests.cs
+++ b/OpenNest.Tests/Cincinnati/CincinnatiSheetWriterTests.cs
@@ -280,6 +280,67 @@ public class CincinnatiSheetWriterTests
Assert.False(FeatureUtils.IsEtch(codes));
}
+ [Fact]
+ public void WriteSheet_InlineCoordinates_AreAbsoluteOnPlate()
+ {
+ var config = new CincinnatiPostConfig { PostedAccuracy = 4 };
+ // Part program is at origin: (0,0) to (2,0) to (2,2) to (0,2) to (0,0)
+ 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));
+
+ var plate = new Plate(48.0, 96.0);
+ // Place part at (10.5, 5.25) on the plate to produce non-integer coordinates
+ plate.Parts.Add(new Part(new Drawing("Square", pgm), new Vector(10.5, 5.25)));
+
+ 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();
+ // Under G90, coordinates must be plate-absolute (part coords + part location)
+ Assert.Contains("G0 X10.5 Y5.25", output); // rapid to pierce
+ Assert.Contains("G1 X12.5 Y5.25", output); // (2,0) + (10.5,5.25)
+ Assert.Contains("G1 X12.5 Y7.25", output); // (2,2) + (10.5,5.25)
+ Assert.Contains("G1 X10.5 Y7.25", output); // (0,2) + (10.5,5.25)
+ Assert.Contains("G1 X10.5 Y5.25", output); // (0,0) + (10.5,5.25)
+ }
+
+ [Fact]
+ public void WriteSheet_TwoPartsAtDifferentLocations_HaveDistinctAbsoluteCoords()
+ {
+ var config = new CincinnatiPostConfig { PostedAccuracy = 4 };
+ 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, 0));
+
+ var drawing = new Drawing("Tri", pgm);
+ var plate = new Plate(48.0, 96.0);
+ plate.Parts.Add(new Part(drawing, new Vector(5.5, 3.25)));
+ plate.Parts.Add(new Part(drawing, new Vector(20.5, 10.25)));
+
+ 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();
+ // First part at (5.5, 3.25)
+ Assert.Contains("G0 X5.5 Y3.25", output);
+ Assert.Contains("G1 X6.5 Y3.25", output);
+ // Second part at (20.5, 10.25)
+ Assert.Contains("G0 X20.5 Y10.25", output);
+ Assert.Contains("G1 X21.5 Y10.25", output);
+ }
+
private static Program CreateSimpleProgram()
{
var pgm = new Program();