feat: RegenerateCutOffs uses geometry-based perimeter cache

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 22:44:15 -04:00
parent 4287c5fa46
commit 17fc9c6cab
2 changed files with 33 additions and 1 deletions

View File

@@ -112,10 +112,12 @@ namespace OpenNest
Parts.RemoveAt(i);
}
var cache = BuildPerimeterCache(this);
// Regenerate and materialize each cut-off
foreach (var cutoff in CutOffs)
{
cutoff.Regenerate(this, settings);
cutoff.Regenerate(this, settings, cache);
if (cutoff.Drawing.Program.Codes.Count == 0)
continue;

View File

@@ -357,6 +357,36 @@ public class CutOffGeometryTests
Assert.NotNull(cache[part]);
}
[Fact]
public void RegenerateCutOffs_UsesGeometryExclusions()
{
// Circle radius=10 at origin. Vertical cut at X=2.
// With geometry: tighter exclusion than BB.
var drawing = new Drawing("circ", MakeCircle(10));
var plate = new Plate(100, 100);
var part = Part.CreateAtOrigin(drawing);
plate.Parts.Add(part);
var cutoff = new CutOff(new Vector(2, 0), CutOffAxis.Vertical);
plate.CutOffs.Add(cutoff);
plate.RegenerateCutOffs(new CutOffSettings { PartClearance = 0 });
// Find the materialized cut-off part
var cutPart = plate.Parts.First(p => p.BaseDrawing.IsCutOff);
var totalCutLength = 0.0;
for (var i = 0; i < cutPart.BaseDrawing.Program.Codes.Count - 1; i += 2)
{
if (cutPart.BaseDrawing.Program.Codes[i] is RapidMove rapid &&
cutPart.BaseDrawing.Program.Codes[i + 1] is LinearMove linear)
{
totalCutLength += System.Math.Abs(rapid.EndPoint.Y - linear.EndPoint.Y);
}
}
// BB would give 80 (100 - 20). Geometry should give more.
Assert.True(totalCutLength > 80, $"RegenerateCutOffs should use geometry. Got {totalCutLength:F2}");
}
[Fact]
public void ShapeProfile_SelectsLargestShapeAsPerimeter()
{