diff --git a/OpenNest.Posts.Cincinnati/CincinnatiFeatureWriter.cs b/OpenNest.Posts.Cincinnati/CincinnatiFeatureWriter.cs
index 18ff323..c2777a6 100644
--- a/OpenNest.Posts.Cincinnati/CincinnatiFeatureWriter.cs
+++ b/OpenNest.Posts.Cincinnati/CincinnatiFeatureWriter.cs
@@ -40,6 +40,18 @@ public sealed class FeatureContext
/// The drawing ID for the current part, used to look up user variable mappings.
///
public int DrawingId { get; set; }
+
+ ///
+ /// True if this feature is a cut-off line. Used to substitute plate-edge
+ /// coordinates with sheet width/length variables.
+ ///
+ public bool IsCutOff { get; set; }
+
+ /// Plate width (Y extent for vertical cutoffs).
+ public double PlateWidth { get; set; }
+
+ /// Plate length (X extent for horizontal cutoffs).
+ public double PlateLength { get; set; }
}
///
@@ -74,7 +86,7 @@ public sealed class CincinnatiFeatureWriter
var piercePoint = FindPiercePoint(ctx.Codes);
// 1. Rapid to pierce point (with line number if configured)
- WriteRapidToPierce(writer, ctx.FeatureNumber, piercePoint, offset);
+ WriteRapidToPierce(writer, ctx, piercePoint, offset);
// 2. Part name comment on first feature of each part
if (ctx.IsFirstFeatureOfPart && !string.IsNullOrEmpty(ctx.PartName))
@@ -195,11 +207,14 @@ public sealed class CincinnatiFeatureWriter
///
/// Formats a coordinate value, using a #number variable reference if the motion
/// has a VariableRef for this axis and the variable is mapped (non-inline).
+ /// For cut-off features, plate-edge coordinates are substituted with
+ /// the sheet width/length variables.
/// Inline variables fall through to literal formatting.
///
private string FormatCoordWithVars(double value, string axis,
Dictionary variableRefs, FeatureContext ctx)
{
+ // User-defined variable references take priority
if (variableRefs != null
&& variableRefs.TryGetValue(axis, out var varName)
&& ctx.UserVariableMapping != null
@@ -208,9 +223,33 @@ public sealed class CincinnatiFeatureWriter
return $"#{varNum}";
}
+ // Cut-off plate-edge substitution
+ if (ctx.IsCutOff)
+ {
+ var sheetVar = MatchCutOffSheetVariable(value, axis, ctx);
+ if (sheetVar != null)
+ return sheetVar;
+ }
+
return _fmt.FormatCoord(value);
}
+ ///
+ /// For cut-off coordinates, checks if the value matches a plate edge dimension
+ /// and returns the sheet variable reference (e.g., "#110") if so.
+ ///
+ private string MatchCutOffSheetVariable(double value, string axis, FeatureContext ctx)
+ {
+ // Vertical cutoffs travel along Y — the Y endpoint at the plate edge = sheet width
+ // Horizontal cutoffs travel along X — the X endpoint at the plate edge = sheet length
+ if (axis == "Y" && Tolerance.IsEqualTo(value, ctx.PlateWidth))
+ return $"#{_config.SheetWidthVariable}";
+ if (axis == "X" && Tolerance.IsEqualTo(value, ctx.PlateLength))
+ return $"#{_config.SheetLengthVariable}";
+
+ return null;
+ }
+
private Vector FindPiercePoint(List codes)
{
foreach (var code in codes)
@@ -229,14 +268,16 @@ public sealed class CincinnatiFeatureWriter
return Vector.Zero;
}
- private void WriteRapidToPierce(TextWriter writer, int featureNumber, Vector piercePoint, Vector offset)
+ private void WriteRapidToPierce(TextWriter writer, FeatureContext ctx, Vector piercePoint, Vector offset)
{
var sb = new StringBuilder();
if (_config.UseLineNumbers)
- sb.Append($"N{featureNumber} ");
+ sb.Append($"N{ctx.FeatureNumber} ");
- sb.Append($"G0 X{_fmt.FormatCoord(piercePoint.X + offset.X)} Y{_fmt.FormatCoord(piercePoint.Y + offset.Y)}");
+ var xCoord = FormatCoordWithVars(piercePoint.X + offset.X, "X", null, ctx);
+ var yCoord = FormatCoordWithVars(piercePoint.Y + offset.Y, "Y", null, ctx);
+ sb.Append($"G0 X{xCoord} Y{yCoord}");
writer.WriteLine(sb.ToString());
}
diff --git a/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs b/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs
index 87b5449..63228e4 100644
--- a/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs
+++ b/OpenNest.Posts.Cincinnati/CincinnatiSheetWriter.cs
@@ -89,9 +89,9 @@ public sealed class CincinnatiSheetWriter
// 4. Emit parts
if (partSubprograms != null)
- WritePartsWithSubprograms(w, allParts, cutLibrary, etchLibrary, sheetDiagonal, partSubprograms, userVarMapping);
+ WritePartsWithSubprograms(w, allParts, cutLibrary, etchLibrary, sheetDiagonal, width, length, partSubprograms, userVarMapping);
else
- WritePartsInline(w, allParts, cutLibrary, etchLibrary, sheetDiagonal, userVarMapping);
+ WritePartsInline(w, allParts, cutLibrary, etchLibrary, sheetDiagonal, width, length, userVarMapping);
// 5. Footer
w.WriteLine("M42");
@@ -105,6 +105,7 @@ public sealed class CincinnatiSheetWriter
private void WritePartsWithSubprograms(TextWriter w, List allParts,
string cutLibrary, string etchLibrary, double sheetDiagonal,
+ double plateWidth, double plateLength,
Dictionary<(int, long), int> partSubprograms,
Dictionary<(int drawingId, string varName), int> userVarMapping)
{
@@ -158,7 +159,10 @@ public sealed class CincinnatiSheetWriter
SheetDiagonal = sheetDiagonal,
PartLocation = part.Location,
UserVariableMapping = userVarMapping,
- DrawingId = part.BaseDrawing.Id
+ DrawingId = part.BaseDrawing.Id,
+ IsCutOff = part.BaseDrawing.IsCutOff,
+ PlateWidth = plateWidth,
+ PlateLength = plateLength
};
_featureWriter.Write(w, ctx);
@@ -207,6 +211,7 @@ public sealed class CincinnatiSheetWriter
private void WritePartsInline(TextWriter w, List allParts,
string cutLibrary, string etchLibrary, double sheetDiagonal,
+ double plateWidth, double plateLength,
Dictionary<(int drawingId, string varName), int> userVarMapping)
{
// Split and classify features, ordering etch before cut per part
@@ -249,7 +254,10 @@ public sealed class CincinnatiSheetWriter
SheetDiagonal = sheetDiagonal,
PartLocation = part.Location,
UserVariableMapping = userVarMapping,
- DrawingId = part.BaseDrawing.Id
+ DrawingId = part.BaseDrawing.Id,
+ IsCutOff = part.BaseDrawing.IsCutOff,
+ PlateWidth = plateWidth,
+ PlateLength = plateLength
};
_featureWriter.Write(w, ctx);
diff --git a/OpenNest.Tests/Cincinnati/UserVariablePostTests.cs b/OpenNest.Tests/Cincinnati/UserVariablePostTests.cs
index cb72170..5c5db2e 100644
--- a/OpenNest.Tests/Cincinnati/UserVariablePostTests.cs
+++ b/OpenNest.Tests/Cincinnati/UserVariablePostTests.cs
@@ -108,6 +108,71 @@ public class UserVariablePostTests
Assert.Contains("#300=48", output);
}
+ [Fact]
+ public void CutOff_VerticalCut_UsesSheetWidthVariable()
+ {
+ // Create a plate with a vertical cutoff
+ var config = new CincinnatiPostConfig { SheetWidthVariable = 110, SheetLengthVariable = 111 };
+ var nest = new Nest { Name = "Test" };
+ var plate = new Plate(new Size(48, 96));
+
+ // Add a simple part so the plate isn't empty
+ var partPgm = new Program();
+ partPgm.Codes.Add(new RapidMove(0, 0));
+ partPgm.Codes.Add(new LinearMove(10, 0));
+ partPgm.Codes.Add(new LinearMove(10, 10));
+ partPgm.Codes.Add(new LinearMove(0, 10));
+ partPgm.Codes.Add(new LinearMove(0, 0));
+ var drawing = new Drawing("Part1", partPgm);
+ nest.Drawings.Add(drawing);
+ plate.Parts.Add(new Part(drawing, new Vector(0, 0)));
+
+ // Add a vertical cutoff that goes full width (Y=0 to Y=48)
+ var cutoff = new CutOff(new Vector(20, 0), CutOffAxis.Vertical);
+ plate.CutOffs.Add(cutoff);
+ plate.RegenerateCutOffs(new CutOffSettings());
+
+ nest.Plates.Add(plate);
+
+ var post = new CincinnatiPostProcessor(config);
+ var output = PostToString(post, nest);
+
+ // The cutoff line end at Y=48 (sheet width) should use #110
+ Assert.Contains("Y#110", output);
+ }
+
+ [Fact]
+ public void CutOff_SegmentedCut_OnlyEdgeUsesVariable()
+ {
+ // Create a plate with a part in the middle and a vertical cutoff
+ var config = new CincinnatiPostConfig { SheetWidthVariable = 110 };
+ var nest = new Nest { Name = "Test" };
+ var plate = new Plate(new Size(48, 96));
+
+ // Part in the middle — cutoff will be segmented around it
+ var partPgm = new Program();
+ partPgm.Codes.Add(new RapidMove(0, 0));
+ partPgm.Codes.Add(new LinearMove(10, 0));
+ partPgm.Codes.Add(new LinearMove(10, 10));
+ partPgm.Codes.Add(new LinearMove(0, 10));
+ partPgm.Codes.Add(new LinearMove(0, 0));
+ var drawing = new Drawing("Part1", partPgm);
+ nest.Drawings.Add(drawing);
+ plate.Parts.Add(new Part(drawing, new Vector(15, 20))); // Part at Y=20-30, should create gap
+
+ var cutoff = new CutOff(new Vector(20, 0), CutOffAxis.Vertical);
+ plate.CutOffs.Add(cutoff);
+ plate.RegenerateCutOffs(new CutOffSettings());
+
+ nest.Plates.Add(plate);
+
+ var post = new CincinnatiPostProcessor(config);
+ var output = PostToString(post, nest);
+
+ // The last segment endpoint at Y=48 should use #110
+ Assert.Contains("Y#110", output);
+ }
+
private static string PostNestWithVariables(string gcode, CincinnatiPostConfig config = null)
{
var program = ParseProgram(gcode);