feat: cutoff coordinates use sheet width/length variables in Cincinnati post
Cutoff features now substitute plate-edge coordinates with #SheetWidthVariable and #SheetLengthVariable references. Vertical cutoffs at Y=plate_width emit Y#110, horizontal cutoffs at X=plate_length emit X#111. Segmented cutoffs only substitute the edge coordinate, interior segment endpoints stay literal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,18 @@ public sealed class FeatureContext
|
||||
/// The drawing ID for the current part, used to look up user variable mappings.
|
||||
/// </summary>
|
||||
public int DrawingId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if this feature is a cut-off line. Used to substitute plate-edge
|
||||
/// coordinates with sheet width/length variables.
|
||||
/// </summary>
|
||||
public bool IsCutOff { get; set; }
|
||||
|
||||
/// <summary>Plate width (Y extent for vertical cutoffs).</summary>
|
||||
public double PlateWidth { get; set; }
|
||||
|
||||
/// <summary>Plate length (X extent for horizontal cutoffs).</summary>
|
||||
public double PlateLength { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private string FormatCoordWithVars(double value, string axis,
|
||||
Dictionary<string, string> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For cut-off coordinates, checks if the value matches a plate edge dimension
|
||||
/// and returns the sheet variable reference (e.g., "#110") if so.
|
||||
/// </summary>
|
||||
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<ICode> 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());
|
||||
}
|
||||
|
||||
@@ -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<Part> 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<Part> 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user