fix: preserve leading rapid in programs to prevent missing contour segment
The CAD converter and BOM import were stripping the leading RapidMove after normalizing program coordinates to origin. This left programs starting with a LinearMove, causing the post-processor to use that endpoint as the pierce point — making the first contour edge zero-length and losing the closing segment (e.g. the bottom line on curved parts). Root cause: CadConverterForm.GetDrawings(), OnSplitClicked(), and BomImportForm all called pgm.Codes.RemoveAt(0) after offsetting the rapid to origin. The rapid at (0,0) is a harmless no-op that marks the contour start point for downstream processing. Also adds EnsureLeadingRapid() safety net in the Cincinnati post for existing nest files that already have the rapid stripped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,26 @@ public sealed class CincinnatiPartSubprogramWriter
|
||||
w.WriteLine($"M99 (END OF {drawingName})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the program has no leading rapid, inserts a synthetic rapid at the
|
||||
/// last motion endpoint (the contour return point). This ensures the feature
|
||||
/// writer knows the true pierce location and preserves the first contour segment.
|
||||
/// </summary>
|
||||
internal static void EnsureLeadingRapid(Program pgm)
|
||||
{
|
||||
if (pgm.Codes.Count == 0 || pgm.Codes[0] is RapidMove)
|
||||
return;
|
||||
|
||||
for (var i = pgm.Codes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (pgm.Codes[i] is Motion lastMotion)
|
||||
{
|
||||
pgm.Codes.Insert(0, new RapidMove(lastMotion.EndPoint));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sub-program key for matching parts to their sub-programs.
|
||||
/// </summary>
|
||||
@@ -103,6 +123,13 @@ public sealed class CincinnatiPartSubprogramWriter
|
||||
var bbox = pgm.BoundingBox();
|
||||
pgm.Offset(-bbox.Location.X, -bbox.Location.Y);
|
||||
|
||||
// If the program has no leading rapid, the feature writer
|
||||
// will use the first motion endpoint as the pierce point,
|
||||
// losing the first contour segment. Insert a synthetic rapid
|
||||
// at the contour's return point (last motion endpoint) so
|
||||
// the full contour is preserved.
|
||||
EnsureLeadingRapid(pgm);
|
||||
|
||||
entries.Add((subNum, part.BaseDrawing.Name, pgm));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,27 @@ public static class FeatureUtils
|
||||
public static List<(List<ICode> codes, bool isEtch)> SplitAndClassify(Part part)
|
||||
{
|
||||
part.Program.Mode = Mode.Absolute;
|
||||
return ClassifyAndOrder(SplitByRapids(part.Program.Codes));
|
||||
var codes = part.Program.Codes;
|
||||
|
||||
// If no leading rapid, the first contour segment would be lost because
|
||||
// the feature writer pierces at the first motion endpoint. Insert a
|
||||
// synthetic rapid at the contour's return point to preserve closure.
|
||||
if (codes.Count > 0 && codes[0] is not RapidMove)
|
||||
{
|
||||
for (var i = codes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (codes[i] is Motion lastMotion)
|
||||
{
|
||||
var withRapid = new List<ICode>(codes.Count + 1);
|
||||
withRapid.Add(new RapidMove(lastMotion.EndPoint));
|
||||
withRapid.AddRange(codes);
|
||||
codes = withRapid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ClassifyAndOrder(SplitByRapids(codes));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -388,6 +388,57 @@ public class CincinnatiPostProcessorTests
|
||||
Assert.Equal(300, deserialized.PartSubprogramStart);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Post_ClosedContourWithoutRapid_OutputsAllSegments()
|
||||
{
|
||||
// Reproduce bug: a closed contour with no leading rapid loses its
|
||||
// first segment in the CNC output because the feature writer uses
|
||||
// the first LinearMove endpoint as the pierce point.
|
||||
var pgm = new Program(Mode.Incremental);
|
||||
pgm.Codes.Add(new LinearMove(0, 2)); // (0,0) → (0,2)
|
||||
pgm.Codes.Add(new LinearMove(2, 0)); // (0,2) → (2,2)
|
||||
pgm.Codes.Add(new LinearMove(0, -2)); // (2,2) → (2,0)
|
||||
pgm.Codes.Add(new LinearMove(-2, 0)); // (2,0) → (0,0)
|
||||
|
||||
var drawing = new Drawing("ClosedSquare", pgm);
|
||||
var nest = new Nest("TestClosure");
|
||||
nest.Drawings.Add(drawing);
|
||||
var plate = new Plate(24, 24);
|
||||
plate.Parts.Add(new Part(drawing, new Vector(1, 1)));
|
||||
nest.Plates.Add(plate);
|
||||
|
||||
var config = new CincinnatiPostConfig
|
||||
{
|
||||
UsePartSubprograms = true,
|
||||
PostedAccuracy = 4
|
||||
};
|
||||
var post = new CincinnatiPostProcessor(config);
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
post.Post(nest, ms);
|
||||
var output = Encoding.UTF8.GetString(ms.ToArray());
|
||||
|
||||
// The subprogram should contain all 4 segments of the square.
|
||||
// Without the fix, the first segment (0,0)→(0,2) was lost
|
||||
// because the feature writer pierced at (0,2) making it zero-length.
|
||||
var lines = output.Split('\n').Select(l => l.Trim()).ToArray();
|
||||
|
||||
// Count G1 moves in the subprogram (should be 4 for a square)
|
||||
var subStart = Array.FindIndex(lines, l => l.StartsWith(":200") || l.StartsWith(":300"));
|
||||
Assert.True(subStart >= 0, "Expected a part subprogram");
|
||||
|
||||
var g1Count = 0;
|
||||
for (var i = subStart; i < lines.Length; i++)
|
||||
{
|
||||
if (lines[i].StartsWith("M99"))
|
||||
break;
|
||||
if (lines[i].Contains("G1 "))
|
||||
g1Count++;
|
||||
}
|
||||
|
||||
Assert.Equal(4, g1Count);
|
||||
}
|
||||
|
||||
private static Nest CreateTestNest()
|
||||
{
|
||||
var nest = new Nest("TestNest");
|
||||
|
||||
@@ -432,7 +432,6 @@ namespace OpenNest.Forms
|
||||
var rapid = (RapidMove)pgm[0];
|
||||
drawing.Source.Offset = rapid.EndPoint;
|
||||
pgm.Offset(-rapid.EndPoint);
|
||||
pgm.Codes.RemoveAt(0);
|
||||
}
|
||||
|
||||
drawing.Program = pgm;
|
||||
|
||||
@@ -362,7 +362,6 @@ namespace OpenNest.Forms
|
||||
var rapid = (RapidMove)pgm[0];
|
||||
originOffset = rapid.EndPoint;
|
||||
pgm.Offset(-originOffset);
|
||||
pgm.Codes.RemoveAt(0);
|
||||
}
|
||||
|
||||
var drawing = new Drawing(item.Name, pgm);
|
||||
@@ -660,7 +659,8 @@ namespace OpenNest.Forms
|
||||
var rapid = (RapidMove)firstCode;
|
||||
drawing.Source.Offset = rapid.EndPoint;
|
||||
pgm.Offset(-rapid.EndPoint);
|
||||
pgm.Codes.RemoveAt(0);
|
||||
// Keep the rapid (now at origin) — it marks the contour
|
||||
// start and is needed by the post for correct pierce placement.
|
||||
}
|
||||
|
||||
if (item == CurrentItem && programEditor.IsDirty && programEditor.Program != null)
|
||||
|
||||
Reference in New Issue
Block a user